@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.
- package/build/{ExecutionTree.d.ts → src/ExecutionTree.d.ts} +1 -0
- package/build/src/ExecutionTree.d.ts.map +1 -0
- package/build/{ExecutionTree.js → src/ExecutionTree.js} +12 -2
- package/build/{bridge-format.d.ts → src/bridge-format.d.ts} +1 -10
- package/build/src/bridge-format.d.ts.map +1 -0
- package/build/{bridge-format.js → src/bridge-format.js} +680 -220
- package/build/{bridge-transform.d.ts → src/bridge-transform.d.ts} +1 -0
- package/build/src/bridge-transform.d.ts.map +1 -0
- package/build/{index.d.ts → src/index.d.ts} +1 -0
- package/build/src/index.d.ts.map +1 -0
- package/build/{tools → src/tools}/find-object.d.ts +1 -0
- package/build/src/tools/find-object.d.ts.map +1 -0
- package/build/{tools → src/tools}/http-call.d.ts +1 -0
- package/build/src/tools/http-call.d.ts.map +1 -0
- package/build/{tools → src/tools}/index.d.ts +1 -0
- package/build/src/tools/index.d.ts.map +1 -0
- package/build/{tools → src/tools}/lower-case.d.ts +1 -0
- package/build/src/tools/lower-case.d.ts.map +1 -0
- package/build/{tools → src/tools}/pick-first.d.ts +1 -0
- package/build/src/tools/pick-first.d.ts.map +1 -0
- package/build/{tools → src/tools}/to-array.d.ts +1 -0
- package/build/src/tools/to-array.d.ts.map +1 -0
- package/build/{tools → src/tools}/upper-case.d.ts +1 -0
- package/build/src/tools/upper-case.d.ts.map +1 -0
- package/build/{types.d.ts → src/types.d.ts} +49 -1
- package/build/src/types.d.ts.map +1 -0
- package/build/tsconfig.tsbuildinfo +1 -0
- package/package.json +9 -6
- package/LICENSE +0 -21
- package/README.md +0 -361
- /package/build/{bridge-transform.js → src/bridge-transform.js} +0 -0
- /package/build/{index.js → src/index.js} +0 -0
- /package/build/{tools → src/tools}/find-object.js +0 -0
- /package/build/{tools → src/tools}/http-call.js +0 -0
- /package/build/{tools → src/tools}/index.js +0 -0
- /package/build/{tools → src/tools}/lower-case.js +0 -0
- /package/build/{tools → src/tools}/pick-first.js +0 -0
- /package/build/{tools → src/tools}/to-array.js +0 -0
- /package/build/{tools → src/tools}/upper-case.js +0 -0
- /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
|
|
File without changes
|