@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 +21 -0
- package/build/index.d.ts +28 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +34 -0
- package/build/tools/arrays.d.ts +28 -0
- package/build/tools/arrays.d.ts.map +1 -0
- package/build/tools/arrays.js +50 -0
- package/build/tools/assert.d.ts +5 -0
- package/build/tools/assert.d.ts.map +1 -0
- package/build/tools/assert.js +6 -0
- package/build/tools/audit.d.ts +36 -0
- package/build/tools/audit.d.ts.map +1 -0
- package/build/tools/audit.js +39 -0
- package/build/tools/http-call.d.ts +35 -0
- package/build/tools/http-call.d.ts.map +1 -0
- package/build/tools/http-call.js +118 -0
- package/build/tools/strings.d.ts +13 -0
- package/build/tools/strings.d.ts.map +1 -0
- package/build/tools/strings.js +12 -0
- package/package.json +39 -0
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.
|
package/build/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
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,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
|
+
}
|