diff --git a/.gitignore b/.gitignore index 0338461..e834e1a 100644 --- a/.gitignore +++ b/.gitignore @@ -38,8 +38,12 @@ erl_crash.dump /Manifest.toml # ReScript -/lib/bs/ +/lib/ /.bsb.lock +.merlin + +# npm (fallback only - prefer Deno) +package-lock.json # Python (SaltStack only) __pycache__/ diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..2d3ade3 --- /dev/null +++ b/deno.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://deno.land/x/deno/cli/schemas/config-file.v1.json", + "tasks": { + "build": "deno run -A npm:rescript@11.1.4 build", + "clean": "deno run -A npm:rescript@11.1.4 clean", + "test": "deno test --allow-read tests/", + "check": "deno task build && deno task test" + }, + "imports": { + "rescript": "npm:rescript@11.1.4" + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a7c1702 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "bridge-web-rescript", + "version": "0.1.0", + "type": "module", + "scripts": { + "build": "rescript build", + "clean": "rescript clean", + "test": "node --test tests/bridge_test.mjs", + "check": "npm run build && npm run test" + }, + "devDependencies": { + "rescript": "^11.1.4" + } +} diff --git a/rescript.json b/rescript.json new file mode 100644 index 0000000..551d821 --- /dev/null +++ b/rescript.json @@ -0,0 +1,20 @@ +{ + "name": "bridge-web-rescript", + "sources": [ + { + "dir": "src", + "subdirs": true + } + ], + "package-specs": [ + { + "module": "esmodule", + "in-source": false + } + ], + "suffix": ".mjs", + "bs-dependencies": [], + "warnings": { + "number": "+A-48-42" + } +} diff --git a/src/Bridge.res b/src/Bridge.res new file mode 100644 index 0000000..1514084 --- /dev/null +++ b/src/Bridge.res @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT OR AGPL-3.0-or-later +// SPDX-FileCopyrightText: 2024 Hyperpolymath + +/** + * Bridge - A minimal input → output transformation module. + * Demonstrates the core pattern: receive input, transform, emit output. + */ + +/** Result type for bridge operations */ +type bridgeResult<'a> = Ok('a) | Error(string) + +/** Bridge configuration */ +type config = { + name: string, + version: string, +} + +/** Default configuration */ +let defaultConfig: config = { + name: "bridge", + version: "0.1.0", +} + +/** Transform input string to output with bridge metadata */ +let transform = (input: string): string => { + `[bridge] ${input}` +} + +/** Transform with result wrapper for error handling */ +let transformSafe = (input: string): bridgeResult => { + if input == "" { + Error("Input cannot be empty") + } else { + Ok(transform(input)) + } +} + +/** Compose two transformations */ +let compose = (f: string => string, g: string => string): (string => string) => { + (input: string) => g(f(input)) +} + +/** Identity transform - returns input unchanged */ +let identity = (input: string): string => input + +/** Uppercase transform - binds to JavaScript String.toUpperCase */ +@send external toUpperCase: string => string = "toUpperCase" +let uppercase = (input: string): string => input->toUpperCase + +/** Prefix transform factory */ +let prefix = (pre: string): (string => string) => { + (input: string) => `${pre}${input}` +} + +/** Suffix transform factory */ +let suffix = (suf: string): (string => string) => { + (input: string) => `${input}${suf}` +} + +/** Get bridge info */ +let info = (config: config): string => { + `${config.name} v${config.version}` +} diff --git a/tests/bridge_test.mjs b/tests/bridge_test.mjs new file mode 100644 index 0000000..5d6d40f --- /dev/null +++ b/tests/bridge_test.mjs @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT OR AGPL-3.0-or-later +// SPDX-FileCopyrightText: 2024 Hyperpolymath + +/** + * Bridge module tests - verifies input → output transformations + */ + +import { test, describe } from "node:test"; +import { strictEqual, deepStrictEqual } from "node:assert"; +import { + defaultConfig, + transform, + transformSafe, + compose, + identity, + uppercase, + prefix, + suffix, + info, +} from "../lib/es6/src/Bridge.mjs"; + +describe("Bridge", () => { + describe("transform", () => { + test("adds bridge prefix to input", () => { + strictEqual(transform("hello"), "[bridge] hello"); + }); + + test("handles empty string", () => { + strictEqual(transform(""), "[bridge] "); + }); + }); + + describe("transformSafe", () => { + test("returns Ok for valid input", () => { + const result = transformSafe("hello"); + deepStrictEqual(result, { TAG: "Ok", _0: "[bridge] hello" }); + }); + + test("returns Error for empty input", () => { + const result = transformSafe(""); + deepStrictEqual(result, { TAG: "Error", _0: "Input cannot be empty" }); + }); + }); + + describe("compose", () => { + test("composes two functions left-to-right", () => { + const prefixHello = prefix("hello-"); + const suffixWorld = suffix("-world"); + const composed = compose(prefixHello, suffixWorld); + strictEqual(composed("test"), "hello-test-world"); + }); + }); + + describe("identity", () => { + test("returns input unchanged", () => { + strictEqual(identity("test"), "test"); + }); + }); + + describe("uppercase", () => { + test("converts string to uppercase", () => { + strictEqual(uppercase("hello"), "HELLO"); + }); + }); + + describe("prefix", () => { + test("creates a function that adds prefix", () => { + const addPrefix = prefix("pre-"); + strictEqual(addPrefix("test"), "pre-test"); + }); + }); + + describe("suffix", () => { + test("creates a function that adds suffix", () => { + const addSuffix = suffix("-suf"); + strictEqual(addSuffix("test"), "test-suf"); + }); + }); + + describe("info", () => { + test("returns formatted config info", () => { + strictEqual(info(defaultConfig), "bridge v0.1.0"); + }); + + test("works with custom config", () => { + const customConfig = { name: "custom", version: "1.0.0" }; + strictEqual(info(customConfig), "custom v1.0.0"); + }); + }); + + describe("defaultConfig", () => { + test("has correct default values", () => { + deepStrictEqual(defaultConfig, { name: "bridge", version: "0.1.0" }); + }); + }); +});