@stackables/bridge 1.0.0
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/README.md +291 -0
- package/build/ExecutionTree.d.ts +50 -0
- package/build/ExecutionTree.js +403 -0
- package/build/bridge-format.d.ts +24 -0
- package/build/bridge-format.js +1056 -0
- package/build/bridge-transform.d.ts +15 -0
- package/build/bridge-transform.js +57 -0
- package/build/index.d.ts +5 -0
- package/build/index.js +3 -0
- package/build/tools/find-object.d.ts +4 -0
- package/build/tools/find-object.js +11 -0
- package/build/tools/http-call.d.ts +34 -0
- package/build/tools/http-call.js +116 -0
- package/build/tools/index.d.ts +43 -0
- package/build/tools/index.js +43 -0
- package/build/tools/lower-case.d.ts +3 -0
- package/build/tools/lower-case.js +3 -0
- package/build/tools/pick-first.d.ts +11 -0
- package/build/tools/pick-first.js +20 -0
- package/build/tools/to-array.d.ts +8 -0
- package/build/tools/to-array.js +8 -0
- package/build/tools/upper-case.d.ts +3 -0
- package/build/tools/upper-case.js +3 -0
- package/build/types.d.ts +218 -0
- package/build/types.js +2 -0
- package/package.json +44 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type GraphQLSchema } from "graphql";
|
|
2
|
+
import type { Instruction, ToolMap } from "./types.js";
|
|
3
|
+
export type BridgeOptions = {
|
|
4
|
+
/** Tool functions available to the engine.
|
|
5
|
+
* Supports namespaced nesting: `{ myNamespace: { myTool } }`.
|
|
6
|
+
* The built-in `std` namespace and `httpCall` are always included;
|
|
7
|
+
* user tools are merged on top (shallow). */
|
|
8
|
+
tools?: ToolMap;
|
|
9
|
+
/** Optional function to reshape/restrict the GQL context before it reaches bridge files.
|
|
10
|
+
* By default the full context is exposed via `with context`. */
|
|
11
|
+
contextMapper?: (context: any) => Record<string, any>;
|
|
12
|
+
};
|
|
13
|
+
/** Instructions can be a static array or a function that selects per-request */
|
|
14
|
+
export type InstructionSource = Instruction[] | ((context: any) => Instruction[]);
|
|
15
|
+
export declare function bridgeTransform(schema: GraphQLSchema, instructions: InstructionSource, options?: BridgeOptions): GraphQLSchema;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { MapperKind, mapSchema } from "@graphql-tools/utils";
|
|
2
|
+
import { GraphQLList, GraphQLNonNull, defaultFieldResolver, } from "graphql";
|
|
3
|
+
import { ExecutionTree } from "./ExecutionTree.js";
|
|
4
|
+
import { builtinTools } from "./tools/index.js";
|
|
5
|
+
import { SELF_MODULE } from "./types.js";
|
|
6
|
+
export function bridgeTransform(schema, instructions, options) {
|
|
7
|
+
const userTools = options?.tools;
|
|
8
|
+
const contextMapper = options?.contextMapper;
|
|
9
|
+
return mapSchema(schema, {
|
|
10
|
+
[MapperKind.OBJECT_FIELD]: (fieldConfig, fieldName, typeName) => {
|
|
11
|
+
let array = false;
|
|
12
|
+
if (fieldConfig.type instanceof GraphQLNonNull) {
|
|
13
|
+
if (fieldConfig.type.ofType instanceof GraphQLList) {
|
|
14
|
+
array = true;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (fieldConfig.type instanceof GraphQLList) {
|
|
18
|
+
array = true;
|
|
19
|
+
}
|
|
20
|
+
const trunk = { module: SELF_MODULE, type: typeName, field: fieldName };
|
|
21
|
+
const { resolve = defaultFieldResolver } = fieldConfig;
|
|
22
|
+
return {
|
|
23
|
+
...fieldConfig,
|
|
24
|
+
resolve: async function (source, args, context, info) {
|
|
25
|
+
// Start execution tree at query/mutation root
|
|
26
|
+
if (!source && !info.path.prev) {
|
|
27
|
+
const bridgeContext = contextMapper
|
|
28
|
+
? contextMapper(context)
|
|
29
|
+
: (context ?? {});
|
|
30
|
+
const activeInstructions = typeof instructions === "function"
|
|
31
|
+
? instructions(context)
|
|
32
|
+
: instructions;
|
|
33
|
+
// Always include builtinTools; user tools merge on top (shallow)
|
|
34
|
+
const allTools = {
|
|
35
|
+
...builtinTools,
|
|
36
|
+
...(userTools ?? {}),
|
|
37
|
+
};
|
|
38
|
+
source = new ExecutionTree(trunk, activeInstructions, allTools, bridgeContext);
|
|
39
|
+
}
|
|
40
|
+
if (source instanceof ExecutionTree &&
|
|
41
|
+
args &&
|
|
42
|
+
Object.keys(args).length > 0) {
|
|
43
|
+
source.push(args);
|
|
44
|
+
}
|
|
45
|
+
// Kick off forced wires (<-!) at the root entry point
|
|
46
|
+
if (source instanceof ExecutionTree && !info.path.prev) {
|
|
47
|
+
source.executeForced();
|
|
48
|
+
}
|
|
49
|
+
if (source instanceof ExecutionTree) {
|
|
50
|
+
return source.response(info.path, array);
|
|
51
|
+
}
|
|
52
|
+
return resolve(source, args, context, info);
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { parseBridge } from "./bridge-format.js";
|
|
2
|
+
export { bridgeTransform } from "./bridge-transform.js";
|
|
3
|
+
export type { BridgeOptions, InstructionSource } from "./bridge-transform.js";
|
|
4
|
+
export { builtinTools, std, createHttpCall } from "./tools/index.js";
|
|
5
|
+
export type { CacheStore, ConstDef, Instruction, ToolCallFn, ToolDef, ToolMap } from "./types.js";
|
package/build/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CacheStore, ToolCallFn } from "../types.js";
|
|
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 };
|
|
@@ -0,0 +1,116 @@
|
|
|
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) { return lru.get(key); },
|
|
7
|
+
set(key, value, ttlSeconds) {
|
|
8
|
+
if (ttlSeconds <= 0)
|
|
9
|
+
return;
|
|
10
|
+
lru.set(key, value, { ttl: ttlSeconds * 1000 });
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Parse TTL (in seconds) from HTTP response headers.
|
|
16
|
+
*
|
|
17
|
+
* Priority: `Cache-Control: s-maxage` > `Cache-Control: max-age` > `Expires`.
|
|
18
|
+
* Returns 0 if the response is uncacheable (no-store, no-cache, or no headers).
|
|
19
|
+
*/
|
|
20
|
+
function parseCacheTTL(response) {
|
|
21
|
+
const cc = response.headers.get("cache-control");
|
|
22
|
+
if (cc) {
|
|
23
|
+
if (/\bno-store\b/i.test(cc) || /\bno-cache\b/i.test(cc))
|
|
24
|
+
return 0;
|
|
25
|
+
const sMax = cc.match(/\bs-maxage\s*=\s*(\d+)\b/i);
|
|
26
|
+
if (sMax)
|
|
27
|
+
return Number(sMax[1]);
|
|
28
|
+
const max = cc.match(/\bmax-age\s*=\s*(\d+)\b/i);
|
|
29
|
+
if (max)
|
|
30
|
+
return Number(max[1]);
|
|
31
|
+
}
|
|
32
|
+
const expires = response.headers.get("expires");
|
|
33
|
+
if (expires) {
|
|
34
|
+
const delta = Math.floor((new Date(expires).getTime() - Date.now()) / 1000);
|
|
35
|
+
return delta > 0 ? delta : 0;
|
|
36
|
+
}
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create an httpCall tool function — the built-in REST API tool.
|
|
41
|
+
*
|
|
42
|
+
* Receives a fully-built input object from the engine and makes an HTTP call.
|
|
43
|
+
* The engine resolves all wires (from tool definition + bridge wires) before calling.
|
|
44
|
+
*
|
|
45
|
+
* Expected input shape:
|
|
46
|
+
* { baseUrl, method?, path?, headers?, cache?, ...shorthandFields }
|
|
47
|
+
*
|
|
48
|
+
* Routing rules:
|
|
49
|
+
* - GET: shorthand fields → query string parameters
|
|
50
|
+
* - POST/PUT/PATCH/DELETE: shorthand fields → JSON body
|
|
51
|
+
* - `headers` object passed as HTTP headers
|
|
52
|
+
* - `baseUrl` + `path` concatenated for the URL
|
|
53
|
+
*
|
|
54
|
+
* Cache modes:
|
|
55
|
+
* - `cache = "auto"` (default) — respect HTTP Cache-Control / Expires headers
|
|
56
|
+
* - `cache = 0` — disable caching entirely
|
|
57
|
+
* - `cache = <seconds>` — explicit TTL override, ignores response headers
|
|
58
|
+
*
|
|
59
|
+
* @param fetchFn - Fetch implementation (override for testing)
|
|
60
|
+
* @param cacheStore - Pluggable cache store (default: in-memory LRU, 1024 entries)
|
|
61
|
+
*/
|
|
62
|
+
export function createHttpCall(fetchFn = globalThis.fetch, cacheStore = createMemoryCache()) {
|
|
63
|
+
return async (input) => {
|
|
64
|
+
const { baseUrl = "", method = "GET", path = "", headers: inputHeaders = {}, cache: cacheMode = "auto", ...rest } = input;
|
|
65
|
+
// Build URL
|
|
66
|
+
const url = new URL(baseUrl + path);
|
|
67
|
+
// Collect headers
|
|
68
|
+
const headers = {};
|
|
69
|
+
for (const [key, value] of Object.entries(inputHeaders)) {
|
|
70
|
+
if (value != null)
|
|
71
|
+
headers[key] = String(value);
|
|
72
|
+
}
|
|
73
|
+
// GET: shorthand fields → query string
|
|
74
|
+
if (method === "GET") {
|
|
75
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
76
|
+
if (value != null) {
|
|
77
|
+
url.searchParams.set(key, String(value));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Non-GET: shorthand fields → JSON body
|
|
82
|
+
let body;
|
|
83
|
+
if (method !== "GET") {
|
|
84
|
+
const bodyObj = {};
|
|
85
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
86
|
+
if (value != null)
|
|
87
|
+
bodyObj[key] = value;
|
|
88
|
+
}
|
|
89
|
+
if (Object.keys(bodyObj).length > 0) {
|
|
90
|
+
body = JSON.stringify(bodyObj);
|
|
91
|
+
headers["Content-Type"] ??= "application/json";
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// cache = 0 → no caching at all
|
|
95
|
+
const mode = String(cacheMode);
|
|
96
|
+
if (mode === "0") {
|
|
97
|
+
const response = await fetchFn(url.toString(), { method, headers, body });
|
|
98
|
+
return response.json();
|
|
99
|
+
}
|
|
100
|
+
const cacheKey = method + " " + url.toString() + (body ?? "");
|
|
101
|
+
// Check cache before fetching
|
|
102
|
+
const cached = await cacheStore.get(cacheKey);
|
|
103
|
+
if (cached !== undefined)
|
|
104
|
+
return cached;
|
|
105
|
+
const response = await fetchFn(url.toString(), { method, headers, body });
|
|
106
|
+
const data = await response.json();
|
|
107
|
+
// Determine TTL
|
|
108
|
+
const ttl = mode === "auto" ? parseCacheTTL(response) : Number(mode);
|
|
109
|
+
if (ttl > 0) {
|
|
110
|
+
await cacheStore.set(cacheKey, data, ttl);
|
|
111
|
+
}
|
|
112
|
+
return data;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/** Exported for testing. */
|
|
116
|
+
export { parseCacheTTL };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { upperCase } from "./upper-case.js";
|
|
2
|
+
import { lowerCase } from "./lower-case.js";
|
|
3
|
+
import { findObject } from "./find-object.js";
|
|
4
|
+
import { pickFirst } from "./pick-first.js";
|
|
5
|
+
import { toArray } from "./to-array.js";
|
|
6
|
+
export declare const std: {
|
|
7
|
+
readonly httpCall: import("../types.js").ToolCallFn;
|
|
8
|
+
readonly upperCase: typeof upperCase;
|
|
9
|
+
readonly lowerCase: typeof lowerCase;
|
|
10
|
+
readonly findObject: typeof findObject;
|
|
11
|
+
readonly pickFirst: typeof pickFirst;
|
|
12
|
+
readonly toArray: typeof toArray;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Built-in tools bundle.
|
|
16
|
+
*
|
|
17
|
+
* Used as the base for `BridgeOptions.tools`. The `std` namespace is always
|
|
18
|
+
* included; user-provided tools are merged on top.
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { builtinTools } from "@stackables/bridge";
|
|
22
|
+
*
|
|
23
|
+
* bridgeTransform(schema, instructions, {
|
|
24
|
+
* tools: { myCustomTool } // std + httpCall are still available
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare const builtinTools: {
|
|
29
|
+
readonly std: {
|
|
30
|
+
readonly httpCall: import("../types.js").ToolCallFn;
|
|
31
|
+
readonly upperCase: typeof upperCase;
|
|
32
|
+
readonly lowerCase: typeof lowerCase;
|
|
33
|
+
readonly findObject: typeof findObject;
|
|
34
|
+
readonly pickFirst: typeof pickFirst;
|
|
35
|
+
readonly toArray: typeof toArray;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
export { createHttpCall } from "./http-call.js";
|
|
39
|
+
export { upperCase } from "./upper-case.js";
|
|
40
|
+
export { lowerCase } from "./lower-case.js";
|
|
41
|
+
export { findObject } from "./find-object.js";
|
|
42
|
+
export { pickFirst } from "./pick-first.js";
|
|
43
|
+
export { toArray } from "./to-array.js";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createHttpCall } from "./http-call.js";
|
|
2
|
+
import { upperCase } from "./upper-case.js";
|
|
3
|
+
import { lowerCase } from "./lower-case.js";
|
|
4
|
+
import { findObject } from "./find-object.js";
|
|
5
|
+
import { pickFirst } from "./pick-first.js";
|
|
6
|
+
import { toArray } from "./to-array.js";
|
|
7
|
+
/**
|
|
8
|
+
* Standard built-in tools — available under the `std` namespace.
|
|
9
|
+
*
|
|
10
|
+
* Referenced in `.bridge` files as `std.upperCase`, `std.pickFirst`, etc.
|
|
11
|
+
*/
|
|
12
|
+
const httpCallFn = createHttpCall();
|
|
13
|
+
export const std = {
|
|
14
|
+
httpCall: httpCallFn,
|
|
15
|
+
upperCase,
|
|
16
|
+
lowerCase,
|
|
17
|
+
findObject,
|
|
18
|
+
pickFirst,
|
|
19
|
+
toArray,
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Built-in tools bundle.
|
|
23
|
+
*
|
|
24
|
+
* Used as the base for `BridgeOptions.tools`. The `std` namespace is always
|
|
25
|
+
* included; user-provided tools are merged on top.
|
|
26
|
+
*
|
|
27
|
+
* ```ts
|
|
28
|
+
* import { builtinTools } from "@stackables/bridge";
|
|
29
|
+
*
|
|
30
|
+
* bridgeTransform(schema, instructions, {
|
|
31
|
+
* tools: { myCustomTool } // std + httpCall are still available
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export const builtinTools = {
|
|
36
|
+
std,
|
|
37
|
+
};
|
|
38
|
+
export { createHttpCall } from "./http-call.js";
|
|
39
|
+
export { upperCase } from "./upper-case.js";
|
|
40
|
+
export { lowerCase } from "./lower-case.js";
|
|
41
|
+
export { findObject } from "./find-object.js";
|
|
42
|
+
export { pickFirst } from "./pick-first.js";
|
|
43
|
+
export { toArray } from "./to-array.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the first element of the array in `opts.in`.
|
|
3
|
+
*
|
|
4
|
+
* By default silently returns `undefined` for empty arrays.
|
|
5
|
+
* Set `opts.strict` to `true` (or the string "true") to throw when
|
|
6
|
+
* the array is empty or contains more than one element.
|
|
7
|
+
*/
|
|
8
|
+
export declare function pickFirst(opts: {
|
|
9
|
+
in: any[];
|
|
10
|
+
strict?: boolean | string;
|
|
11
|
+
}): any;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the first element of the array in `opts.in`.
|
|
3
|
+
*
|
|
4
|
+
* By default silently returns `undefined` for empty arrays.
|
|
5
|
+
* Set `opts.strict` to `true` (or the string "true") to throw when
|
|
6
|
+
* the array is empty or contains more than one element.
|
|
7
|
+
*/
|
|
8
|
+
export function pickFirst(opts) {
|
|
9
|
+
const arr = opts.in;
|
|
10
|
+
const strict = opts.strict === true || opts.strict === "true";
|
|
11
|
+
if (strict) {
|
|
12
|
+
if (!Array.isArray(arr) || arr.length === 0) {
|
|
13
|
+
throw new Error("pickFirst: expected a non-empty array");
|
|
14
|
+
}
|
|
15
|
+
if (arr.length > 1) {
|
|
16
|
+
throw new Error(`pickFirst: expected exactly one element but got ${arr.length}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return Array.isArray(arr) ? arr[0] : undefined;
|
|
20
|
+
}
|
package/build/types.d.ts
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured node reference — identifies a specific data point in the execution graph.
|
|
3
|
+
*
|
|
4
|
+
* Every wire has a "from" and "to", each described by a NodeRef.
|
|
5
|
+
* The trunk (module + type + field + instance) identifies the node,
|
|
6
|
+
* while path drills into its data.
|
|
7
|
+
*/
|
|
8
|
+
export type NodeRef = {
|
|
9
|
+
/** Module identifier: "hereapi", "sendgrid", "zillow", or SELF_MODULE */
|
|
10
|
+
module: string;
|
|
11
|
+
/** GraphQL type ("Query" | "Mutation") or "Tools" for tool functions */
|
|
12
|
+
type: string;
|
|
13
|
+
/** Field or function name: "geocode", "search", "centsToUsd" */
|
|
14
|
+
field: string;
|
|
15
|
+
/** Instance number for tool calls (1, 2, ...) */
|
|
16
|
+
instance?: number;
|
|
17
|
+
/** References the current array element in a shadow tree (for per-element mapping) */
|
|
18
|
+
element?: boolean;
|
|
19
|
+
/** Path into the data: ["items", "0", "position", "lat"] */
|
|
20
|
+
path: string[];
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* A wire connects a data source (from) to a data sink (to).
|
|
24
|
+
* Execution is pull-based: when "to" is demanded, "from" is resolved.
|
|
25
|
+
*
|
|
26
|
+
* Constant wires (`=`) set a fixed value on the target.
|
|
27
|
+
* Pull wires (`<-`) resolve the source at runtime.
|
|
28
|
+
* Pipe wires (`pipe: true`) are generated by the `<- h1|h2|source` shorthand
|
|
29
|
+
* and route data through declared tool handles; the serializer collapses them
|
|
30
|
+
* back to pipe notation.
|
|
31
|
+
*/
|
|
32
|
+
export type Wire = {
|
|
33
|
+
from: NodeRef;
|
|
34
|
+
to: NodeRef;
|
|
35
|
+
pipe?: true;
|
|
36
|
+
force?: true;
|
|
37
|
+
fallback?: string;
|
|
38
|
+
} | {
|
|
39
|
+
value: string;
|
|
40
|
+
to: NodeRef;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Bridge definition — wires one GraphQL field to its data sources.
|
|
44
|
+
*/
|
|
45
|
+
export type Bridge = {
|
|
46
|
+
kind: "bridge";
|
|
47
|
+
/** GraphQL type: "Query" | "Mutation" */
|
|
48
|
+
type: string;
|
|
49
|
+
/** GraphQL field name */
|
|
50
|
+
field: string;
|
|
51
|
+
/** Declared data sources and their wire handles */
|
|
52
|
+
handles: HandleBinding[];
|
|
53
|
+
/** Connection wires */
|
|
54
|
+
wires: Wire[];
|
|
55
|
+
/**
|
|
56
|
+
* Pipe fork registry — one entry per pipe use.
|
|
57
|
+
* Maps the fork's trunk key to the originating handle name and the base
|
|
58
|
+
* trunk (used by the executor to inherit non-pipe bridge wires).
|
|
59
|
+
*/
|
|
60
|
+
pipeHandles?: Array<{
|
|
61
|
+
/** Unique trunk key for this fork: "module:type:field:instance" */
|
|
62
|
+
key: string;
|
|
63
|
+
/** The bridge handle name that was piped, e.g. "pt" or "convertToEur" */
|
|
64
|
+
handle: string;
|
|
65
|
+
/** Base trunk — regular (non-pipe) bridge wires targeting this trunk are
|
|
66
|
+
* applied to every fork before the fork-specific pipe wires. */
|
|
67
|
+
baseTrunk: {
|
|
68
|
+
module: string;
|
|
69
|
+
type: string;
|
|
70
|
+
field: string;
|
|
71
|
+
instance?: number;
|
|
72
|
+
};
|
|
73
|
+
}>;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* A handle binding — declares a named data source available in a bridge.
|
|
77
|
+
*
|
|
78
|
+
* Every wire reference in the bridge body must trace back to one of these.
|
|
79
|
+
*/
|
|
80
|
+
export type HandleBinding = {
|
|
81
|
+
handle: string;
|
|
82
|
+
kind: "tool";
|
|
83
|
+
name: string;
|
|
84
|
+
} | {
|
|
85
|
+
handle: string;
|
|
86
|
+
kind: "input";
|
|
87
|
+
} | {
|
|
88
|
+
handle: string;
|
|
89
|
+
kind: "context";
|
|
90
|
+
} | {
|
|
91
|
+
handle: string;
|
|
92
|
+
kind: "const";
|
|
93
|
+
};
|
|
94
|
+
/** Internal module identifier for the bridge's own trunk (input args + output fields) */
|
|
95
|
+
export declare const SELF_MODULE = "_";
|
|
96
|
+
/**
|
|
97
|
+
* Tool definition — a declared tool with wires, dependencies, and optional inheritance.
|
|
98
|
+
*
|
|
99
|
+
* Tool blocks define reusable, composable API call configurations:
|
|
100
|
+
* tool hereapi httpCall — root tool with function name
|
|
101
|
+
* tool hereapi.geocode extends hereapi — child inherits parent wires
|
|
102
|
+
*
|
|
103
|
+
* The engine resolves extends chains, merges wires, and calls the
|
|
104
|
+
* registered tool function with the fully-built input object.
|
|
105
|
+
*/
|
|
106
|
+
export type ToolDef = {
|
|
107
|
+
kind: "tool";
|
|
108
|
+
/** Tool name: "hereapi", "sendgrid.send", "authService" */
|
|
109
|
+
name: string;
|
|
110
|
+
/** Function name — looked up in the tools map. Omitted when extends is used. */
|
|
111
|
+
fn?: string;
|
|
112
|
+
/** Parent tool name — inherits fn, deps, and wires */
|
|
113
|
+
extends?: string;
|
|
114
|
+
/** Dependencies declared via `with` inside the tool block */
|
|
115
|
+
deps: ToolDep[];
|
|
116
|
+
/** Wires: constants (`=`) and pulls (`<-`) defining the tool's input */
|
|
117
|
+
wires: ToolWire[];
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* A dependency declared inside a tool block.
|
|
121
|
+
*
|
|
122
|
+
* with context — brings the full GraphQL context into scope
|
|
123
|
+
* with authService as auth — brings another tool's output into scope
|
|
124
|
+
*/
|
|
125
|
+
export type ToolDep = {
|
|
126
|
+
kind: "context";
|
|
127
|
+
handle: string;
|
|
128
|
+
} | {
|
|
129
|
+
kind: "tool";
|
|
130
|
+
handle: string;
|
|
131
|
+
tool: string;
|
|
132
|
+
} | {
|
|
133
|
+
kind: "const";
|
|
134
|
+
handle: string;
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* A wire in a tool block — either a constant value, a pull from a dependency,
|
|
138
|
+
* or an error fallback.
|
|
139
|
+
*
|
|
140
|
+
* Examples:
|
|
141
|
+
* baseUrl = "https://api.sendgrid.com/v3" → constant
|
|
142
|
+
* method = POST → constant (unquoted)
|
|
143
|
+
* headers.Authorization <- ctx.sendgrid.token → pull from context
|
|
144
|
+
* headers.Authorization <- auth.access_token → pull from tool dep
|
|
145
|
+
* on error = { "lat": 0, "lon": 0 } → constant fallback
|
|
146
|
+
* on error <- ctx.fallbacks.geo → pull fallback from context
|
|
147
|
+
*/
|
|
148
|
+
export type ToolWire = {
|
|
149
|
+
target: string;
|
|
150
|
+
kind: "constant";
|
|
151
|
+
value: string;
|
|
152
|
+
} | {
|
|
153
|
+
target: string;
|
|
154
|
+
kind: "pull";
|
|
155
|
+
source: string;
|
|
156
|
+
} | {
|
|
157
|
+
kind: "onError";
|
|
158
|
+
value: string;
|
|
159
|
+
} | {
|
|
160
|
+
kind: "onError";
|
|
161
|
+
source: string;
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Tool call function — the signature for registered tool functions.
|
|
165
|
+
*
|
|
166
|
+
* Receives a fully-built nested input object and returns the response.
|
|
167
|
+
* The engine builds the input from tool wires + bridge wires.
|
|
168
|
+
*
|
|
169
|
+
* Example (httpCall):
|
|
170
|
+
* input = { baseUrl: "https://...", method: "GET", path: "/geocode",
|
|
171
|
+
* headers: { apiKey: "..." }, q: "Berlin" }
|
|
172
|
+
*/
|
|
173
|
+
export type ToolCallFn = (input: Record<string, any>) => Promise<Record<string, any>>;
|
|
174
|
+
/**
|
|
175
|
+
* Recursive tool map — supports namespaced tools via nesting.
|
|
176
|
+
*
|
|
177
|
+
* Example:
|
|
178
|
+
* { std: { upperCase, lowerCase }, httpCall: createHttpCall(), myCompany: { myTool } }
|
|
179
|
+
*
|
|
180
|
+
* Lookup is dot-separated: "std.upperCase" → tools.std.upperCase
|
|
181
|
+
*/
|
|
182
|
+
export type ToolMap = {
|
|
183
|
+
[key: string]: ToolCallFn | ((...args: any[]) => any) | ToolMap;
|
|
184
|
+
};
|
|
185
|
+
/**
|
|
186
|
+
* Named constant definition — a reusable value defined in the bridge file.
|
|
187
|
+
*
|
|
188
|
+
* Constants are available in bridge blocks via `with const as c` and in tool
|
|
189
|
+
* blocks via `with const`. The engine collects all ConstDef instructions into
|
|
190
|
+
* a single namespace object keyed by name.
|
|
191
|
+
*
|
|
192
|
+
* Examples:
|
|
193
|
+
* const fallbackGeo = { "lat": 0, "lon": 0 }
|
|
194
|
+
* const defaultCurrency = "EUR"
|
|
195
|
+
*/
|
|
196
|
+
export type ConstDef = {
|
|
197
|
+
kind: "const";
|
|
198
|
+
/** Constant name — used as the key in the const namespace */
|
|
199
|
+
name: string;
|
|
200
|
+
/** Raw JSON string — parsed at runtime when accessed */
|
|
201
|
+
value: string;
|
|
202
|
+
};
|
|
203
|
+
/** Union of all instruction types */
|
|
204
|
+
export type Instruction = Bridge | ToolDef | ConstDef;
|
|
205
|
+
/**
|
|
206
|
+
* Pluggable cache store for httpCall.
|
|
207
|
+
*
|
|
208
|
+
* Default: in-memory Map with TTL eviction.
|
|
209
|
+
* Override: pass any key-value store (Redis, Memcached, etc.) to `createHttpCall`.
|
|
210
|
+
*
|
|
211
|
+
* ```ts
|
|
212
|
+
* const httpCall = createHttpCall(fetch, myRedisStore);
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
export type CacheStore = {
|
|
216
|
+
get(key: string): Promise<any | undefined> | any | undefined;
|
|
217
|
+
set(key: string, value: any, ttlSeconds: number): Promise<void> | void;
|
|
218
|
+
};
|
package/build/types.js
ADDED