@stackables/bridge-stdlib 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stackables
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @stackables/bridge-stdlib — Bridge standard library tools.
3
+ *
4
+ * Contains the `std` namespace tools (httpCall, string helpers, array helpers,
5
+ * audit, assert) that ship with Bridge. Referenced in `.bridge` files as
6
+ * `std.httpCall`, `std.str.toUpperCase`, etc.
7
+ *
8
+ * Separated from core so it can be versioned independently.
9
+ */
10
+ import { audit } from "./tools/audit.ts";
11
+ import * as arrays from "./tools/arrays.ts";
12
+ import * as strings from "./tools/strings.ts";
13
+ import { assert } from "./tools/assert.ts";
14
+ export declare const std: {
15
+ readonly str: typeof strings;
16
+ readonly arr: typeof arrays;
17
+ readonly audit: typeof audit;
18
+ readonly httpCall: import("@stackables/bridge-types").ToolCallFn;
19
+ readonly assert: typeof assert;
20
+ };
21
+ /**
22
+ * All known built-in tool names as "namespace.tool" strings.
23
+ *
24
+ * Useful for LSP/IDE autocomplete and diagnostics.
25
+ */
26
+ export declare const builtinToolNames: readonly string[];
27
+ export { createHttpCall } from "./tools/http-call.ts";
28
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC;AAC5C,OAAO,KAAK,OAAO,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAS3C,eAAO,MAAM,GAAG;;;;;;CAMN,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAE7C,CAAC;AAEF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
package/build/index.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @stackables/bridge-stdlib — Bridge standard library tools.
3
+ *
4
+ * Contains the `std` namespace tools (httpCall, string helpers, array helpers,
5
+ * audit, assert) that ship with Bridge. Referenced in `.bridge` files as
6
+ * `std.httpCall`, `std.str.toUpperCase`, etc.
7
+ *
8
+ * Separated from core so it can be versioned independently.
9
+ */
10
+ import { audit } from "./tools/audit.js";
11
+ import { createHttpCall } from "./tools/http-call.js";
12
+ import * as arrays from "./tools/arrays.js";
13
+ import * as strings from "./tools/strings.js";
14
+ import { assert } from "./tools/assert.js";
15
+ /**
16
+ * Standard built-in tools — available under the `std` namespace.
17
+ *
18
+ * Referenced in `.bridge` files as `std.str.toUpperCase`, `std.arr.first`, etc.
19
+ */
20
+ const httpCallFn = createHttpCall();
21
+ export const std = {
22
+ str: strings,
23
+ arr: arrays,
24
+ audit,
25
+ httpCall: httpCallFn,
26
+ assert,
27
+ };
28
+ /**
29
+ * All known built-in tool names as "namespace.tool" strings.
30
+ *
31
+ * Useful for LSP/IDE autocomplete and diagnostics.
32
+ */
33
+ export const builtinToolNames = Object.keys(std).map((k) => `std.${k}`);
34
+ export { createHttpCall } from "./tools/http-call.js";
@@ -0,0 +1,28 @@
1
+ export declare function filter(opts: {
2
+ in: any[];
3
+ [key: string]: any;
4
+ }): any[];
5
+ export declare function find(opts: {
6
+ in: any[];
7
+ [key: string]: any;
8
+ }): any;
9
+ /**
10
+ * Returns the first element of the array in `opts.in`.
11
+ *
12
+ * By default silently returns `undefined` for empty arrays.
13
+ * Set `opts.strict` to `true` (or the string "true") to throw when
14
+ * the array is empty or contains more than one element.
15
+ */
16
+ export declare function first(opts: {
17
+ in: any[];
18
+ strict?: boolean | string;
19
+ }): any;
20
+ /**
21
+ * Wraps a single value in an array.
22
+ *
23
+ * If `opts.in` is already an array it is returned as-is.
24
+ */
25
+ export declare function toArray(opts: {
26
+ in: any;
27
+ }): any[];
28
+ //# sourceMappingURL=arrays.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arrays.d.ts","sourceRoot":"","sources":["../../src/tools/arrays.ts"],"names":[],"mappings":"AAAA,wBAAgB,MAAM,CAAC,IAAI,EAAE;IAAE,EAAE,EAAE,GAAG,EAAE,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,SAU7D;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE;IAAE,EAAE,EAAE,GAAG,EAAE,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,OAU3D;AAED;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE;IAAE,EAAE,EAAE,GAAG,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;CAAE,OAgBnE;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE;IAAE,EAAE,EAAE,GAAG,CAAA;CAAE,SAExC"}
@@ -0,0 +1,50 @@
1
+ export function filter(opts) {
2
+ const { in: arr, ...criteria } = opts;
3
+ return arr.filter((obj) => {
4
+ for (const [key, value] of Object.entries(criteria)) {
5
+ if (obj[key] !== value) {
6
+ return false;
7
+ }
8
+ }
9
+ return true;
10
+ });
11
+ }
12
+ export function find(opts) {
13
+ const { in: arr, ...criteria } = opts;
14
+ return arr.find((obj) => {
15
+ for (const [key, value] of Object.entries(criteria)) {
16
+ if (obj[key] !== value) {
17
+ return false;
18
+ }
19
+ }
20
+ return true;
21
+ });
22
+ }
23
+ /**
24
+ * Returns the first element of the array in `opts.in`.
25
+ *
26
+ * By default silently returns `undefined` for empty arrays.
27
+ * Set `opts.strict` to `true` (or the string "true") to throw when
28
+ * the array is empty or contains more than one element.
29
+ */
30
+ export function first(opts) {
31
+ const arr = opts.in;
32
+ const strict = opts.strict === true || opts.strict === "true";
33
+ if (strict) {
34
+ if (!Array.isArray(arr) || arr.length === 0) {
35
+ throw new Error("pickFirst: expected a non-empty array");
36
+ }
37
+ if (arr.length > 1) {
38
+ throw new Error(`pickFirst: expected exactly one element but got ${arr.length}`);
39
+ }
40
+ }
41
+ return Array.isArray(arr) ? arr[0] : undefined;
42
+ }
43
+ /**
44
+ * Wraps a single value in an array.
45
+ *
46
+ * If `opts.in` is already an array it is returned as-is.
47
+ */
48
+ export function toArray(opts) {
49
+ return Array.isArray(opts.in) ? opts.in : [opts.in];
50
+ }
@@ -0,0 +1,5 @@
1
+ import type { ToolContext } from "@stackables/bridge-types";
2
+ export declare function assert(input: {
3
+ in: any;
4
+ }, _context?: ToolContext): any;
5
+ //# sourceMappingURL=assert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assert.d.ts","sourceRoot":"","sources":["../../src/tools/assert.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE5D,wBAAgB,MAAM,CAAC,KAAK,EAAE;IAAE,EAAE,EAAE,GAAG,CAAA;CAAE,EAAE,QAAQ,CAAC,EAAE,WAAW,OAKhE"}
@@ -0,0 +1,6 @@
1
+ export function assert(input, _context) {
2
+ if (!input.in) {
3
+ throw new Error("Assertion failed: input is falsy");
4
+ }
5
+ return input.in;
6
+ }
@@ -0,0 +1,36 @@
1
+ import type { ToolContext } from "@stackables/bridge-types";
2
+ /**
3
+ * Built-in audit tool — logs all inputs via the engine logger.
4
+ *
5
+ * Designed for use with `force` — wire any number of inputs,
6
+ * force the handle, and every key-value pair is logged.
7
+ *
8
+ * The logger comes from the engine's `ToolContext` (configured via
9
+ * `BridgeOptions.logger`). When no logger is configured the engine's
10
+ * default no-op logger applies — nothing is logged.
11
+ *
12
+ * Structured logging style: data object first, message tag last.
13
+ *
14
+ * The log level defaults to `info` but can be overridden via `level` input:
15
+ * ```bridge
16
+ * audit.level = "warn"
17
+ * ```
18
+ *
19
+ * ```bridge
20
+ * bridge Mutation.createOrder {
21
+ * with std.audit as audit
22
+ * with orderApi as api
23
+ * with input as i
24
+ * with output as o
25
+ *
26
+ * api.userId <- i.userId
27
+ * audit.action = "createOrder"
28
+ * audit.userId <- i.userId
29
+ * audit.orderId <- api.id
30
+ * force audit
31
+ * o.id <- api.id
32
+ * }
33
+ * ```
34
+ */
35
+ export declare function audit(input: Record<string, any>, context?: ToolContext): Record<string, any>;
36
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/tools/audit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,WAAW,uBAKtE"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Built-in audit tool — logs all inputs via the engine logger.
3
+ *
4
+ * Designed for use with `force` — wire any number of inputs,
5
+ * force the handle, and every key-value pair is logged.
6
+ *
7
+ * The logger comes from the engine's `ToolContext` (configured via
8
+ * `BridgeOptions.logger`). When no logger is configured the engine's
9
+ * default no-op logger applies — nothing is logged.
10
+ *
11
+ * Structured logging style: data object first, message tag last.
12
+ *
13
+ * The log level defaults to `info` but can be overridden via `level` input:
14
+ * ```bridge
15
+ * audit.level = "warn"
16
+ * ```
17
+ *
18
+ * ```bridge
19
+ * bridge Mutation.createOrder {
20
+ * with std.audit as audit
21
+ * with orderApi as api
22
+ * with input as i
23
+ * with output as o
24
+ *
25
+ * api.userId <- i.userId
26
+ * audit.action = "createOrder"
27
+ * audit.userId <- i.userId
28
+ * audit.orderId <- api.id
29
+ * force audit
30
+ * o.id <- api.id
31
+ * }
32
+ * ```
33
+ */
34
+ export function audit(input, context) {
35
+ const { level = "info", ...data } = input;
36
+ const log = context?.logger?.[level];
37
+ log?.(data, "[bridge:audit]");
38
+ return input;
39
+ }
@@ -0,0 +1,35 @@
1
+ import type { CacheStore, ToolCallFn } from "@stackables/bridge-types";
2
+ /**
3
+ * Parse TTL (in seconds) from HTTP response headers.
4
+ *
5
+ * Priority: `Cache-Control: s-maxage` > `Cache-Control: max-age` > `Expires`.
6
+ * Returns 0 if the response is uncacheable (no-store, no-cache, or no headers).
7
+ */
8
+ declare function parseCacheTTL(response: Response): number;
9
+ /**
10
+ * Create an httpCall tool function — the built-in REST API tool.
11
+ *
12
+ * Receives a fully-built input object from the engine and makes an HTTP call.
13
+ * The engine resolves all wires (from tool definition + bridge wires) before calling.
14
+ *
15
+ * Expected input shape:
16
+ * { baseUrl, method?, path?, headers?, cache?, ...shorthandFields }
17
+ *
18
+ * Routing rules:
19
+ * - GET: shorthand fields → query string parameters
20
+ * - POST/PUT/PATCH/DELETE: shorthand fields → JSON body
21
+ * - `headers` object passed as HTTP headers
22
+ * - `baseUrl` + `path` concatenated for the URL
23
+ *
24
+ * Cache modes:
25
+ * - `cache = "auto"` (default) — respect HTTP Cache-Control / Expires headers
26
+ * - `cache = 0` — disable caching entirely
27
+ * - `cache = <seconds>` — explicit TTL override, ignores response headers
28
+ *
29
+ * @param fetchFn - Fetch implementation (override for testing)
30
+ * @param cacheStore - Pluggable cache store (default: in-memory LRU, 1024 entries)
31
+ */
32
+ export declare function createHttpCall(fetchFn?: typeof fetch, cacheStore?: CacheStore): ToolCallFn;
33
+ /** Exported for testing. */
34
+ export { parseCacheTTL };
35
+ //# sourceMappingURL=http-call.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-call.d.ts","sourceRoot":"","sources":["../../src/tools/http-call.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAgBvE;;;;;GAKG;AACH,iBAAS,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAejD;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,cAAc,CAC5B,OAAO,GAAE,OAAO,KAAwB,EACxC,UAAU,GAAE,UAAgC,GAC3C,UAAU,CAkEZ;AAED,4BAA4B;AAC5B,OAAO,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,118 @@
1
+ import { LRUCache } from "lru-cache";
2
+ /** Default in-memory LRU cache with per-entry TTL. */
3
+ function createMemoryCache(maxEntries = 1024) {
4
+ const lru = new LRUCache({ max: maxEntries });
5
+ return {
6
+ get(key) {
7
+ return lru.get(key);
8
+ },
9
+ set(key, value, ttlSeconds) {
10
+ if (ttlSeconds <= 0)
11
+ return;
12
+ lru.set(key, value, { ttl: ttlSeconds * 1000 });
13
+ },
14
+ };
15
+ }
16
+ /**
17
+ * Parse TTL (in seconds) from HTTP response headers.
18
+ *
19
+ * Priority: `Cache-Control: s-maxage` > `Cache-Control: max-age` > `Expires`.
20
+ * Returns 0 if the response is uncacheable (no-store, no-cache, or no headers).
21
+ */
22
+ function parseCacheTTL(response) {
23
+ const cc = response.headers.get("cache-control");
24
+ if (cc) {
25
+ if (/\bno-store\b/i.test(cc) || /\bno-cache\b/i.test(cc))
26
+ return 0;
27
+ const sMax = cc.match(/\bs-maxage\s*=\s*(\d+)\b/i);
28
+ if (sMax)
29
+ return Number(sMax[1]);
30
+ const max = cc.match(/\bmax-age\s*=\s*(\d+)\b/i);
31
+ if (max)
32
+ return Number(max[1]);
33
+ }
34
+ const expires = response.headers.get("expires");
35
+ if (expires) {
36
+ const delta = Math.floor((new Date(expires).getTime() - Date.now()) / 1000);
37
+ return delta > 0 ? delta : 0;
38
+ }
39
+ return 0;
40
+ }
41
+ /**
42
+ * Create an httpCall tool function — the built-in REST API tool.
43
+ *
44
+ * Receives a fully-built input object from the engine and makes an HTTP call.
45
+ * The engine resolves all wires (from tool definition + bridge wires) before calling.
46
+ *
47
+ * Expected input shape:
48
+ * { baseUrl, method?, path?, headers?, cache?, ...shorthandFields }
49
+ *
50
+ * Routing rules:
51
+ * - GET: shorthand fields → query string parameters
52
+ * - POST/PUT/PATCH/DELETE: shorthand fields → JSON body
53
+ * - `headers` object passed as HTTP headers
54
+ * - `baseUrl` + `path` concatenated for the URL
55
+ *
56
+ * Cache modes:
57
+ * - `cache = "auto"` (default) — respect HTTP Cache-Control / Expires headers
58
+ * - `cache = 0` — disable caching entirely
59
+ * - `cache = <seconds>` — explicit TTL override, ignores response headers
60
+ *
61
+ * @param fetchFn - Fetch implementation (override for testing)
62
+ * @param cacheStore - Pluggable cache store (default: in-memory LRU, 1024 entries)
63
+ */
64
+ export function createHttpCall(fetchFn = globalThis.fetch, cacheStore = createMemoryCache()) {
65
+ return async (input) => {
66
+ const { baseUrl = "", method = "GET", path = "", headers: inputHeaders = {}, cache: cacheMode = "auto", ...rest } = input;
67
+ // Build URL
68
+ const url = new URL(baseUrl + path);
69
+ // Collect headers
70
+ const headers = {};
71
+ for (const [key, value] of Object.entries(inputHeaders)) {
72
+ if (value != null)
73
+ headers[key] = String(value);
74
+ }
75
+ // GET: shorthand fields → query string
76
+ if (method === "GET") {
77
+ for (const [key, value] of Object.entries(rest)) {
78
+ if (value != null) {
79
+ url.searchParams.set(key, String(value));
80
+ }
81
+ }
82
+ }
83
+ // Non-GET: shorthand fields → JSON body
84
+ let body;
85
+ if (method !== "GET") {
86
+ const bodyObj = {};
87
+ for (const [key, value] of Object.entries(rest)) {
88
+ if (value != null)
89
+ bodyObj[key] = value;
90
+ }
91
+ if (Object.keys(bodyObj).length > 0) {
92
+ body = JSON.stringify(bodyObj);
93
+ headers["Content-Type"] ??= "application/json";
94
+ }
95
+ }
96
+ // cache = 0 → no caching at all
97
+ const mode = String(cacheMode);
98
+ if (mode === "0") {
99
+ const response = await fetchFn(url.toString(), { method, headers, body });
100
+ return response.json();
101
+ }
102
+ const cacheKey = method + " " + url.toString() + (body ?? "");
103
+ // Check cache before fetching
104
+ const cached = await cacheStore.get(cacheKey);
105
+ if (cached !== undefined)
106
+ return cached;
107
+ const response = await fetchFn(url.toString(), { method, headers, body });
108
+ const data = (await response.json());
109
+ // Determine TTL
110
+ const ttl = mode === "auto" ? parseCacheTTL(response) : Number(mode);
111
+ if (ttl > 0) {
112
+ await cacheStore.set(cacheKey, data, ttl);
113
+ }
114
+ return data;
115
+ };
116
+ }
117
+ /** Exported for testing. */
118
+ export { parseCacheTTL };
@@ -0,0 +1,13 @@
1
+ export declare function toLowerCase(opts: {
2
+ in: string;
3
+ }): string;
4
+ export declare function toUpperCase(opts: {
5
+ in: string;
6
+ }): string;
7
+ export declare function trim(opts: {
8
+ in: string;
9
+ }): string;
10
+ export declare function length(opts: {
11
+ in: string;
12
+ }): number;
13
+ //# sourceMappingURL=strings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/tools/strings.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,UAE/C;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,UAE/C;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,UAExC;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,UAE1C"}
@@ -0,0 +1,12 @@
1
+ export function toLowerCase(opts) {
2
+ return opts.in?.toLowerCase();
3
+ }
4
+ export function toUpperCase(opts) {
5
+ return opts.in?.toUpperCase();
6
+ }
7
+ export function trim(opts) {
8
+ return opts.in?.trim();
9
+ }
10
+ export function length(opts) {
11
+ return opts.in?.length;
12
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@stackables/bridge-stdlib",
3
+ "version": "0.0.1",
4
+ "description": "Bridge standard library — httpCall, string, array, audit, and assert tools",
5
+ "main": "./build/index.js",
6
+ "type": "module",
7
+ "types": "./build/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "source": "./src/index.ts",
11
+ "import": "./build/index.js",
12
+ "types": "./build/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "build",
17
+ "README.md"
18
+ ],
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/stackables/bridge.git"
22
+ },
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "lru-cache": "^11.2.6",
26
+ "@stackables/bridge-types": "0.0.1"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^25.3.2",
30
+ "typescript": "^5.9.3"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "scripts": {
36
+ "build": "tsc -p tsconfig.json",
37
+ "test": "node --experimental-transform-types --conditions source --test test/*.test.ts"
38
+ }
39
+ }