@stackables/bridge 1.2.0 → 1.4.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.
Files changed (40) hide show
  1. package/build/{ExecutionTree.d.ts → src/ExecutionTree.d.ts} +1 -0
  2. package/build/src/ExecutionTree.d.ts.map +1 -0
  3. package/build/{ExecutionTree.js → src/ExecutionTree.js} +12 -2
  4. package/build/{bridge-format.d.ts → src/bridge-format.d.ts} +1 -10
  5. package/build/src/bridge-format.d.ts.map +1 -0
  6. package/build/{bridge-format.js → src/bridge-format.js} +680 -220
  7. package/build/{bridge-transform.d.ts → src/bridge-transform.d.ts} +1 -0
  8. package/build/src/bridge-transform.d.ts.map +1 -0
  9. package/build/{index.d.ts → src/index.d.ts} +1 -0
  10. package/build/src/index.d.ts.map +1 -0
  11. package/build/{tools → src/tools}/find-object.d.ts +1 -0
  12. package/build/src/tools/find-object.d.ts.map +1 -0
  13. package/build/{tools → src/tools}/http-call.d.ts +1 -0
  14. package/build/src/tools/http-call.d.ts.map +1 -0
  15. package/build/{tools → src/tools}/index.d.ts +1 -0
  16. package/build/src/tools/index.d.ts.map +1 -0
  17. package/build/{tools → src/tools}/lower-case.d.ts +1 -0
  18. package/build/src/tools/lower-case.d.ts.map +1 -0
  19. package/build/{tools → src/tools}/pick-first.d.ts +1 -0
  20. package/build/src/tools/pick-first.d.ts.map +1 -0
  21. package/build/{tools → src/tools}/to-array.d.ts +1 -0
  22. package/build/src/tools/to-array.d.ts.map +1 -0
  23. package/build/{tools → src/tools}/upper-case.d.ts +1 -0
  24. package/build/src/tools/upper-case.d.ts.map +1 -0
  25. package/build/{types.d.ts → src/types.d.ts} +49 -1
  26. package/build/src/types.d.ts.map +1 -0
  27. package/build/tsconfig.tsbuildinfo +1 -0
  28. package/package.json +9 -6
  29. package/LICENSE +0 -21
  30. package/README.md +0 -361
  31. /package/build/{bridge-transform.js → src/bridge-transform.js} +0 -0
  32. /package/build/{index.js → src/index.js} +0 -0
  33. /package/build/{tools → src/tools}/find-object.js +0 -0
  34. /package/build/{tools → src/tools}/http-call.js +0 -0
  35. /package/build/{tools → src/tools}/index.js +0 -0
  36. /package/build/{tools → src/tools}/lower-case.js +0 -0
  37. /package/build/{tools → src/tools}/pick-first.js +0 -0
  38. /package/build/{tools → src/tools}/to-array.js +0 -0
  39. /package/build/{tools → src/tools}/upper-case.js +0 -0
  40. /package/build/{types.js → src/types.js} +0 -0
package/README.md DELETED
@@ -1,361 +0,0 @@
1
- # The Bridge
2
-
3
- **Declarative dataflow for GraphQL.**
4
- Wire data between APIs, tools, and fields using `.bridge` files—no resolvers, no codegen, no plumbing.
5
-
6
- ```bash
7
- npm install @stackables/bridge
8
- ```
9
-
10
- > **Developer Preview**
11
- > The Bridge v1.x is a public preview and is not recommended for production use.
12
- > - Stability: Breaking changes to the .bridge language and TypeScript API will occur frequently.
13
- > - Versioning: We follow strict SemVer starting from v2.0.0.
14
- >
15
- > Feedback: We are actively looking for use cases. Please share yours in our GitHub Discussions.
16
-
17
- ---
18
-
19
- ## The Idea
20
-
21
- Most GraphQL backends are just plumbing: take input, call an API, rename fields, and return. **The Bridge** turns that manual labor into a declarative graph of intent.
22
-
23
- The engine resolves **backwards from demand**: when a GraphQL query requests `results[0].lat`, the engine traces the wire back to the `position.lat` of a specific API response. Only the data required to satisfy the query is ever fetched or executed.
24
-
25
- ### What it is (and isn't)
26
-
27
- The Bridge is a **Smart Mapping Outgoing Proxy**, not a replacement for your application logic.
28
-
29
- * **Use it to:** Morph external API shapes, enforce single exit points for security, and swap providers (e.g., SendGrid to Postmark) without changing app code.
30
- * **Don't use it for:** Complex business logic or database transactions. Keep the "intelligence" in your Tools; keep the "connectivity" in your Bridge.
31
-
32
- ---
33
-
34
- ## The Language
35
-
36
- ### 1. Const Blocks (`const`)
37
-
38
- Named JSON values reusable across tools and bridges. Avoids repetition for fallback payloads, defaults, and config fragments.
39
-
40
- ```hcl
41
- const fallbackGeo = { "lat": 0, "lon": 0 }
42
- const defaultCurrency = "EUR"
43
- const maxRetries = 3
44
- ```
45
-
46
- Access const values in bridges or tools via `with const as c`, then reference as `c.<name>.<path>`.
47
-
48
- ### 2. Extend Blocks (`extend`)
49
-
50
- Defines the "Where" and the "How." Takes a function (or parent tool) and configures i, giving it a new name.
51
-
52
- ```hcl
53
- extend <source> as <name> {
54
- [with context] # Injects GraphQL context (auth, secrets, etc.)
55
- [on error = <json_fallback>] # Fallback value if tool fails
56
- [on error <- <source>] # Pull fallback from context/tool
57
-
58
- <param> = <value> # Constant/Default value
59
- <param> <- <source> # Dynamic wire
60
- }
61
- ```
62
-
63
- When `<source>` is a function name (e.g. `httpCall`), a new tool is created.
64
- When `<source>` is an existing tool name, the new tool inherits its configuration.
65
-
66
- ### 3. Bridge Blocks (`bridge`)
67
-
68
- The resolver logic connecting GraphQL schema fields to your tools.
69
-
70
- ```hcl
71
- bridge <Type.field> {
72
- with <tool> [as <alias>]
73
- with input [as <i>]
74
-
75
- # Field Mapping
76
- <field> = <json> # Constant value
77
- <field> <- <source> # Standard Pull (lazy)
78
- <field> <-! <source> # Forced Push (eager/side-effect)
79
-
80
- # Pipe chain (tool transformation)
81
- <field> <- handle:source # Route source through tool handle
82
-
83
- # Fallbacks
84
- <field> <- <source> || <alt> || <alt> # Null-coalesce: use alt if source is null
85
- <field> <- <source> ?? <fallback> # Error-fallback: use fallback if chain throws
86
-
87
- # Array Mapping
88
- <field>[] <- <source>[]
89
- .<sub_field> <- .<sub_src> # Relative scoping
90
- }
91
- ```
92
-
93
- ---
94
-
95
- ## Key Features
96
-
97
- ### Resiliency
98
-
99
- Each layer handles a different failure mode. They compose freely.
100
-
101
- #### Layer 1 — Tool `on error` (execution errors)
102
-
103
- Declared inside the `extend` block. Catches any exception thrown by the tool's `fn(input)`. All tools that `extend` this tool inherit the fallback.
104
-
105
- ```hcl
106
- extend httpCall as geo {
107
- baseUrl = "https://nominatim.openstreetmap.org"
108
- method = GET
109
- path = /search
110
- on error = { "lat": 0, "lon": 0 } # tool-level default
111
- }
112
- ```
113
-
114
- #### Layer 2 — Wire `||` (null / absent values)
115
-
116
- Fires when a source resolves **successfully but returns `null` or `undefined`**. The fallback can be a JSON literal or another source expression (handle path or pipe chain). Multiple `||` alternatives chain left-to-right like `COALESCE`.
117
-
118
- ```hcl
119
- # JSON literal fallback
120
- lat <- geo.lat || 0.0
121
-
122
- # Alternative source fallback
123
- label <- api.label || backup.label || "unknown"
124
-
125
- # Pipe chain as alternative
126
- textPart <- i.textBody || convert:i.htmlBody || "empty"
127
- ```
128
-
129
- #### Layer 3 — Wire `??` (errors and exceptions)
130
-
131
- Fires when the **entire resolution chain throws** (network failure, tool down, dependency error). Does not fire on null values — that's `||`'s job. The fallback can be a JSON literal or a source/pipe expression (evaluated lazily, only when the error fires).
132
-
133
- ```hcl
134
- # JSON literal error fallback
135
- lat <- geo.lat ?? 0.0
136
-
137
- # Error fallback pulls from another source
138
- label <- api.label ?? errorHandler:i.fallbackMsg
139
- ```
140
-
141
- #### Full COALESCE — composing all three layers
142
-
143
- `||` and `??` compose into a Postgres-style `COALESCE` with an error guard at the end:
144
-
145
- ```hcl
146
- # label <- A || B || C || "literal" ?? errorSource
147
- label <- api.label || tool:api.backup.label || "unknown" ?? tool:const.errorString
148
-
149
- # Evaluation order:
150
- # api.label non-null → use it immediately
151
- # api.label null → try toolIfNeeded(api.backup.label)
152
- # that null → "unknown" (|| json literal always succeeds)
153
- # any source throws → toolIfNeeded(const.errorString) (?? fires last)
154
- ```
155
-
156
- Multiple `||` sources desugar to **parallel wires** — all sources are evaluated concurrently and the first that resolves to a non-null value wins. Cheaper/faster sources (like `input` fields) naturally win without any priority hints.
157
-
158
- ### Forced Wires (`<-!`)
159
-
160
- By default, the engine is **lazy**. Use `<-!` to force execution regardless of demand—perfect for side-effects like analytics, audit logging, or cache warming.
161
-
162
- ```hcl
163
- bridge Mutation.updateUser {
164
- with audit.logger as log
165
-
166
- # 'log' runs even if the client doesn't query the 'status' field
167
- status <-! log:i.changeData
168
- }
169
- ```
170
-
171
- ### The Pipe Operator (`:`)
172
-
173
- Routes data right-to-left through one or more tool handles: `dest <- handle:source`.
174
-
175
- ```hcl
176
- # i.rawData → normalize → transform → result
177
- result <- transform:normalize:i.rawData
178
- ```
179
-
180
- Full example with a tool that has 2 input parameters:
181
-
182
- ```hcl
183
- extend currencyConverter as convert {
184
- currency = EUR # default currency
185
- }
186
-
187
- bridge Query.price {
188
- with convert as c
189
- with input as i
190
-
191
- c.currency <- i.currency # overrides the default per request
192
-
193
- # Safe to use repeatedly — each is an independent tool call
194
- itemPrice <- c:i.itemPrice
195
- totalPrice <- c:i.totalPrice
196
- }
197
- ```
198
-
199
- ---
200
-
201
- ## Syntax Reference
202
-
203
- | Operator | Type | Behavior |
204
- | --- | --- | --- |
205
- | **`=`** | Constant | Sets a static value. |
206
- | **`<-`** | Wire | Pulls data from a source at runtime. |
207
- | **`<-!`** | Force | Eagerly schedules a tool (for side-effects). |
208
- | **`:`** | Pipe | Chains data through tools right-to-left. |
209
- | **`\|\|`** | Null-coalesce | Next alternative if current source is `null`/`undefined`. Fires on absent values, not errors. |
210
- | **`??`** | Error-fallback | Alternative used when the resolution chain **throws**. Fires on errors, not null values. |
211
- | **`on error`** | Tool Fallback | Returns a default if the tool's `fn(input)` throws. |
212
- | **`extend`** | Tool Definition | Configures a function or extends a parent tool. |
213
- | **`const`** | Named Value | Declares reusable JSON constants. |
214
- | **`[] <- []`** | Map | Iterates over arrays to create nested wire contexts. |
215
-
216
- ---
217
-
218
- ## Usage
219
-
220
- ### 1. Basic Setup
221
-
222
- The Bridge wraps your existing GraphQL schema, handling the `resolve` functions automatically.
223
-
224
- ```typescript
225
- import { createSchema, createYoga } from "graphql-yoga";
226
- import { bridgeTransform, parseBridge } from "@stackables/bridge";
227
-
228
- const schema = bridgeTransform(
229
- createSchema({ typeDefs }),
230
- parseBridge(bridgeFileText)
231
- );
232
-
233
- const yoga = createYoga({
234
- schema,
235
- context: () => ({
236
- api: { key: process.env.API_KEY },
237
- }),
238
- });
239
-
240
- ```
241
-
242
- ### 2. Custom Tools
243
-
244
- ```typescript
245
- const schema = bridgeTransform(createSchema({ typeDefs }), instructions, {
246
- tools: {
247
- toCents: ({ in: dollars }) => ({ cents: dollars * 100 }),
248
- },
249
- });
250
-
251
- ```
252
-
253
- ---
254
-
255
- ## Built-in Tools
256
-
257
- The Bridge ships with built-in tools under the `std` namespace, always available by default. All tools (including `httpCall`) live under `std` and can be referenced with or without the `std.` prefix.
258
-
259
- | Tool | Input | Output | Description |
260
- | --- | --- | --- | --- |
261
- | `httpCall` | `{ baseUrl, method?, path?, headers?, cache?, ...fields }` | JSON response | REST API caller. GET fields → query params; POST/PUT/PATCH/DELETE → JSON body. `cache` = TTL in seconds (0 = off). |
262
- | `upperCase` | `{ in: string }` | `string` | Converts `in` to UPPER CASE. |
263
- | `lowerCase` | `{ in: string }` | `string` | Converts `in` to lower case. |
264
- | `findObject` | `{ in: any[], ...criteria }` | `object \| undefined` | Finds the first object in `in` where all criteria match. |
265
- | `pickFirst` | `{ in: any[], strict?: bool }` | `any` | Returns the first array element. With `strict = true`, throws if the array is empty or has more than one item. |
266
- | `toArray` | `{ in: any }` | `any[]` | Wraps a single value in an array. Returns as-is if already an array. |
267
-
268
- ### Using Built-in Tools
269
-
270
- **No `extend` block needed** for pipe-like tools — reference them with the `std.` prefix in the `with` header:
271
-
272
- ```hcl
273
- bridge Query.format {
274
- with std.upperCase as up
275
- with std.lowerCase as lo
276
- with input as i
277
-
278
- upper <- up:i.text
279
- lower <- lo:i.text
280
- }
281
- ```
282
-
283
- Use an `extend` block when you need to configure defaults:
284
-
285
- ```hcl
286
- extend std.pickFirst as pf {
287
- strict = true
288
- }
289
-
290
- bridge Query.onlyResult {
291
- with pf
292
- with someApi as api
293
- with input as i
294
-
295
- value <- pf:api.items
296
- }
297
- ```
298
-
299
- ### Adding Custom Tools
300
-
301
- The `std` namespace is always included automatically. Just add your own tools — no need to spread `builtinTools`:
302
-
303
- ```typescript
304
- import { bridgeTransform } from "@stackables/bridge";
305
-
306
- const schema = bridgeTransform(createSchema({ typeDefs }), instructions, {
307
- tools: {
308
- myCustomTool: (input) => ({ result: input.value * 2 }),
309
- },
310
- });
311
- // std.upperCase, std.lowerCase, etc. are still available
312
- ```
313
-
314
- To override a `std` tool, replace the namespace (shallow merge):
315
-
316
- ```typescript
317
- import { bridgeTransform, std } from "@stackables/bridge";
318
-
319
- const schema = bridgeTransform(createSchema({ typeDefs }), instructions, {
320
- tools: {
321
- std: { ...std, upperCase: myCustomUpperCase },
322
- },
323
- });
324
- ```
325
-
326
- ### Response Caching
327
-
328
- Add `cache = <seconds>` to any `httpCall` tool to enable TTL-based response caching. Identical requests (same method + URL + params) return the cached result without hitting the network.
329
-
330
- ```hcl
331
- extend httpCall as geo {
332
- cache = 300 # cache for 5 minutes
333
- baseUrl = "https://nominatim.openstreetmap.org"
334
- method = GET
335
- path = /search
336
- }
337
- ```
338
-
339
- The default is an in-memory store. For Redis or other backends, pass a custom `CacheStore` to `createHttpCall`:
340
-
341
- ```typescript
342
- import { createHttpCall, std } from "@stackables/bridge";
343
- import type { CacheStore } from "@stackables/bridge";
344
-
345
- const redisCache: CacheStore = {
346
- async get(key) { return redis.get(key).then(v => v ? JSON.parse(v) : undefined); },
347
- async set(key, value, ttl) { await redis.set(key, JSON.stringify(value), "EX", ttl); },
348
- };
349
-
350
- bridgeTransform(schema, instructions, {
351
- tools: { std: { ...std, httpCall: createHttpCall(fetch, redisCache) } },
352
- });
353
- ```
354
-
355
- ---
356
-
357
- ## Why The Bridge?
358
-
359
- * **No Resolver Sprawl:** Stop writing identical `fetch` and `map` logic
360
- * **Provider Agnostic:** Swap implementations (e.g., SendGrid vs Postmark) at the request level
361
- * **Edge-Ready:** Small footprint; works in Node, Bun, and Cloudflare Workers
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes