@stackables/bridge 1.1.1 → 1.3.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/README.md +221 -59
- package/build/ExecutionTree.js +12 -2
- package/build/bridge-format.d.ts +0 -10
- package/build/bridge-format.js +721 -218
- package/build/types.d.ts +48 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -7,10 +7,11 @@ Wire data between APIs, tools, and fields using `.bridge` files—no resolvers,
|
|
|
7
7
|
npm install @stackables/bridge
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
> **Developer Preview**
|
|
10
|
+
> **Developer Preview**
|
|
11
11
|
> The Bridge v1.x is a public preview and is not recommended for production use.
|
|
12
|
-
>
|
|
13
|
-
>
|
|
12
|
+
>
|
|
13
|
+
> * Stability: Breaking changes to the .bridge language and TypeScript API will occur frequently.
|
|
14
|
+
> * Versioning: We follow strict SemVer starting from v2.0.0.
|
|
14
15
|
>
|
|
15
16
|
> Feedback: We are actively looking for use cases. Please share yours in our GitHub Discussions.
|
|
16
17
|
|
|
@@ -29,10 +30,36 @@ The Bridge is a **Smart Mapping Outgoing Proxy**, not a replacement for your app
|
|
|
29
30
|
* **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
31
|
* **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
|
+
### Wiring, not Programming
|
|
34
|
+
|
|
35
|
+
The Bridge is not a programming language. It is a Data Topology Language.
|
|
36
|
+
|
|
37
|
+
Unlike Python or JavaScript, where you write a list of instructions for a computer to execute in order, a .bridge file describes a static circuit. There is no "execution pointer" that moves from the top of the file to the bottom.
|
|
38
|
+
|
|
39
|
+
No Sequential Logic: Shuffling the lines inside a define or bridge block changes nothing. The engine doesn't "run" your file; it uses your file to understand how your GraphQL fields are physically wired to your tools.
|
|
40
|
+
|
|
41
|
+
Pull, Don't Push: In a normal language, you "push" data into variables. In The Bridge, the GraphQL query "pulls" data through the wires. If a client doesn't ask for a field, the wire is "dead"—no code runs, and no API is called.
|
|
42
|
+
|
|
43
|
+
Declarative Connections: When you write o.name <- api.name, you aren't commanding a copy operation; you are soldering a permanent link between two points in your graph.
|
|
44
|
+
|
|
45
|
+
**Don't think in scripts. Think in schematics.**
|
|
46
|
+
|
|
47
|
+
### Portability & Performance
|
|
48
|
+
|
|
49
|
+
While the reference engine is implemented in TypeScript, the Bridge language itself is a simple, high-level specification for data flow. Because it describes intent rather than execution, it is architecturally "runtime-blind." It can be interpreted by any high-performance engines written in Rust, Go, or C++ without changing a single line of your .bridge files.
|
|
50
|
+
|
|
32
51
|
---
|
|
33
52
|
|
|
34
53
|
## The Language
|
|
35
54
|
|
|
55
|
+
Every `.bridge` file must begin with a version declaration.
|
|
56
|
+
|
|
57
|
+
```hcl
|
|
58
|
+
version 1.4
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
This is the first non-blank, non-comment line. Everything else follows after it.
|
|
62
|
+
|
|
36
63
|
### 1. Const Blocks (`const`)
|
|
37
64
|
|
|
38
65
|
Named JSON values reusable across tools and bridges. Avoids repetition for fallback payloads, defaults, and config fragments.
|
|
@@ -45,69 +72,161 @@ const maxRetries = 3
|
|
|
45
72
|
|
|
46
73
|
Access const values in bridges or tools via `with const as c`, then reference as `c.<name>.<path>`.
|
|
47
74
|
|
|
48
|
-
### 2.
|
|
75
|
+
### 2. Tool Blocks (`tool ... from`)
|
|
49
76
|
|
|
50
|
-
Defines the "Where" and the "How." Takes a function (or parent tool) and configures
|
|
77
|
+
Defines the "Where" and the "How." Takes a function (or parent tool) and configures it, giving it a new name.
|
|
51
78
|
|
|
52
79
|
```hcl
|
|
53
|
-
|
|
80
|
+
tool <name> from <source> {
|
|
54
81
|
[with context] # Injects GraphQL context (auth, secrets, etc.)
|
|
55
82
|
[on error = <json_fallback>] # Fallback value if tool fails
|
|
56
83
|
[on error <- <source>] # Pull fallback from context/tool
|
|
57
|
-
|
|
58
|
-
<param> = <value> # Constant/Default value
|
|
59
|
-
<param> <- <source> # Dynamic wire
|
|
60
84
|
|
|
85
|
+
.<param> = <value> # Constant/Default value (dot = "this tool's param")
|
|
86
|
+
.<param> <- <source> # Dynamic wire
|
|
87
|
+
}
|
|
61
88
|
```
|
|
62
89
|
|
|
90
|
+
Param lines use a `.` prefix — the dot means "this tool's own field". `with` and `on error` lines do not use a dot; they are control flow, not param assignments.
|
|
91
|
+
|
|
63
92
|
When `<source>` is a function name (e.g. `httpCall`), a new tool is created.
|
|
64
93
|
When `<source>` is an existing tool name, the new tool inherits its configuration.
|
|
65
94
|
|
|
66
|
-
### 3.
|
|
95
|
+
### 3. Define Blocks (`define`)
|
|
96
|
+
|
|
97
|
+
Reusable named subgraphs — compose tools and wires into a pipeline, then invoke it from any bridge.
|
|
98
|
+
|
|
99
|
+
```hcl
|
|
100
|
+
define <name> {
|
|
101
|
+
with <tool> as <handle> # Tools used inside the pipeline
|
|
102
|
+
with input as <handle> # Inputs provided by the caller
|
|
103
|
+
with output as <handle> # Outputs returned to the caller
|
|
104
|
+
|
|
105
|
+
<handle>.<param> <- <source> # Wiring (same syntax as bridge)
|
|
106
|
+
<handle>.<param> = <value> # Constants
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Use a define in a bridge with `with <define> as <handle>`:
|
|
111
|
+
|
|
112
|
+
```hcl
|
|
113
|
+
define geocode {
|
|
114
|
+
with std.httpCall as geo
|
|
115
|
+
with input as i
|
|
116
|
+
with output as o
|
|
117
|
+
|
|
118
|
+
geo.baseUrl = "https://nominatim.openstreetmap.org"
|
|
119
|
+
geo.method = GET
|
|
120
|
+
geo.path = /search
|
|
121
|
+
geo.q <- i.city
|
|
122
|
+
o.lat <- geo[0].lat
|
|
123
|
+
o.lon <- geo[0].lon
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
bridge Query.location {
|
|
127
|
+
with geocode as g
|
|
128
|
+
with input as i
|
|
129
|
+
with output as o
|
|
130
|
+
|
|
131
|
+
g.city <- i.city
|
|
132
|
+
o.lat <- g.lat
|
|
133
|
+
o.lon <- g.lon
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Each invocation is fully isolated — calling the same define twice creates independent tool instances with no namespace collisions.
|
|
138
|
+
|
|
139
|
+
### 4. Bridge Blocks (`bridge`)
|
|
67
140
|
|
|
68
141
|
The resolver logic connecting GraphQL schema fields to your tools.
|
|
69
142
|
|
|
70
143
|
```hcl
|
|
71
|
-
bridge <Type.field>
|
|
144
|
+
bridge <Type.field> {
|
|
72
145
|
with <tool> [as <alias>]
|
|
73
|
-
with input
|
|
146
|
+
with input as i
|
|
147
|
+
with output as o
|
|
74
148
|
|
|
75
149
|
# Field Mapping
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
150
|
+
o.<field> = <json> # Constant output value
|
|
151
|
+
o.<field> <- <source> # Standard Pull (lazy)
|
|
152
|
+
o.<field> <-! <source> # Forced Push (eager/side-effect)
|
|
79
153
|
|
|
80
154
|
# Pipe chain (tool transformation)
|
|
81
|
-
|
|
155
|
+
o.<field> <- handle:source # Route source through tool handle
|
|
82
156
|
|
|
83
157
|
# Fallbacks
|
|
84
|
-
|
|
85
|
-
|
|
158
|
+
o.<field> <- <source> || <alt> || <alt> # Null-coalesce: use alt if source is null
|
|
159
|
+
o.<field> <- <source> ?? <fallback> # Error-fallback: use fallback if chain throws
|
|
160
|
+
|
|
161
|
+
# Array Mapping (brace block per element)
|
|
162
|
+
o.<field> <- <source>[] as <iter> {
|
|
163
|
+
.<sub_field> <- <iter>.<sub_src> # Element field via iterator
|
|
164
|
+
.<sub_field> = "constant" # Element constant
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
86
168
|
|
|
87
|
-
|
|
88
|
-
<field>[] <- <source>[]
|
|
89
|
-
.<sub_field> <- .<sub_src> # Relative scoping
|
|
169
|
+
Bridge can be fully implemented in the defined pipeline.
|
|
90
170
|
|
|
171
|
+
```
|
|
172
|
+
define namedOperation {
|
|
173
|
+
....
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
bridge <Type.field> with namedOperation
|
|
91
177
|
```
|
|
92
178
|
|
|
93
179
|
---
|
|
94
180
|
|
|
95
181
|
## Key Features
|
|
96
182
|
|
|
183
|
+
### Reserved Words
|
|
184
|
+
|
|
185
|
+
**Keywords** — cannot be used as tool names, handle aliases, or const names:
|
|
186
|
+
|
|
187
|
+
> `bridge` `with` `as` `from` `const` `tool` `version` `define`
|
|
188
|
+
|
|
189
|
+
**Source identifiers** — reserved for their specific role inside `bridge` and `tool` blocks:
|
|
190
|
+
|
|
191
|
+
> `input` `output` `context`
|
|
192
|
+
|
|
193
|
+
A parse error is thrown immediately if any of these appear where a user-defined name is expected.
|
|
194
|
+
|
|
195
|
+
### Scope Rules
|
|
196
|
+
|
|
197
|
+
Bridge uses explicit scoping. Any entity referenced inside a `bridge` or `tool` block must first be introduced into scope using a `with` clause.
|
|
198
|
+
|
|
199
|
+
This includes:
|
|
200
|
+
|
|
201
|
+
* `tools`
|
|
202
|
+
* `input`
|
|
203
|
+
* `output`
|
|
204
|
+
* `context`
|
|
205
|
+
* `tool aliases`
|
|
206
|
+
|
|
207
|
+
The `input` and `output` handles represents GraphQL field arguments and output type. They exists **only inside `bridge` blocks**.
|
|
208
|
+
|
|
209
|
+
Because `tool` blocks are evaluated before any GraphQL execution occurs, they cannot reference `input` or `output`.
|
|
210
|
+
|
|
211
|
+
> **Rule of thumb:**
|
|
212
|
+
> `tool ... from` defines tools, `bridge` executes the graph.
|
|
213
|
+
> Since `input` and `output` belong to GraphQL execution, they only exist inside bridges.
|
|
214
|
+
|
|
97
215
|
### Resiliency
|
|
98
216
|
|
|
99
217
|
Each layer handles a different failure mode. They compose freely.
|
|
100
218
|
|
|
101
219
|
#### Layer 1 — Tool `on error` (execution errors)
|
|
102
220
|
|
|
103
|
-
Declared inside the `
|
|
221
|
+
Declared inside the `tool` block. Catches any exception thrown by the tool's `fn(input)`. All tools that inherit from this tool inherit the fallback.
|
|
104
222
|
|
|
105
223
|
```hcl
|
|
106
|
-
|
|
107
|
-
baseUrl = "https://nominatim.openstreetmap.org"
|
|
108
|
-
method = GET
|
|
109
|
-
path = /search
|
|
224
|
+
tool geo from httpCall {
|
|
225
|
+
.baseUrl = "https://nominatim.openstreetmap.org"
|
|
226
|
+
.method = GET
|
|
227
|
+
.path = /search
|
|
110
228
|
on error = { "lat": 0, "lon": 0 } # tool-level default
|
|
229
|
+
}
|
|
111
230
|
```
|
|
112
231
|
|
|
113
232
|
#### Layer 2 — Wire `||` (null / absent values)
|
|
@@ -115,14 +234,16 @@ extend httpCall as geo
|
|
|
115
234
|
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`.
|
|
116
235
|
|
|
117
236
|
```hcl
|
|
237
|
+
with output as o
|
|
238
|
+
|
|
118
239
|
# JSON literal fallback
|
|
119
|
-
lat <- geo.lat || 0.0
|
|
240
|
+
o.lat <- geo.lat || 0.0
|
|
120
241
|
|
|
121
242
|
# Alternative source fallback
|
|
122
|
-
label <- api.label || backup.label || "unknown"
|
|
243
|
+
o.label <- api.label || backup.label || "unknown"
|
|
123
244
|
|
|
124
245
|
# Pipe chain as alternative
|
|
125
|
-
textPart <- i.textBody || convert:i.htmlBody || "empty"
|
|
246
|
+
o.textPart <- i.textBody || convert:i.htmlBody || "empty"
|
|
126
247
|
```
|
|
127
248
|
|
|
128
249
|
#### Layer 3 — Wire `??` (errors and exceptions)
|
|
@@ -130,11 +251,13 @@ textPart <- i.textBody || convert:i.htmlBody || "empty"
|
|
|
130
251
|
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).
|
|
131
252
|
|
|
132
253
|
```hcl
|
|
254
|
+
with output as o
|
|
255
|
+
|
|
133
256
|
# JSON literal error fallback
|
|
134
|
-
lat <- geo.lat ?? 0.0
|
|
257
|
+
o.lat <- geo.lat ?? 0.0
|
|
135
258
|
|
|
136
259
|
# Error fallback pulls from another source
|
|
137
|
-
label <- api.label ?? errorHandler:i.fallbackMsg
|
|
260
|
+
o.label <- api.label ?? errorHandler:i.fallbackMsg
|
|
138
261
|
```
|
|
139
262
|
|
|
140
263
|
#### Full COALESCE — composing all three layers
|
|
@@ -142,8 +265,10 @@ label <- api.label ?? errorHandler:i.fallbackMsg
|
|
|
142
265
|
`||` and `??` compose into a Postgres-style `COALESCE` with an error guard at the end:
|
|
143
266
|
|
|
144
267
|
```hcl
|
|
145
|
-
|
|
146
|
-
|
|
268
|
+
with output as o
|
|
269
|
+
|
|
270
|
+
# o.label <- A || B || C || "literal" ?? errorSource
|
|
271
|
+
o.label <- api.label || tool:api.backup.label || "unknown" ?? tool:const.errorString
|
|
147
272
|
|
|
148
273
|
# Evaluation order:
|
|
149
274
|
# api.label non-null → use it immediately
|
|
@@ -159,11 +284,14 @@ Multiple `||` sources desugar to **parallel wires** — all sources are evaluate
|
|
|
159
284
|
By default, the engine is **lazy**. Use `<-!` to force execution regardless of demand—perfect for side-effects like analytics, audit logging, or cache warming.
|
|
160
285
|
|
|
161
286
|
```hcl
|
|
162
|
-
bridge Mutation.updateUser
|
|
287
|
+
bridge Mutation.updateUser {
|
|
163
288
|
with audit.logger as log
|
|
289
|
+
with input as in
|
|
290
|
+
with output as out
|
|
164
291
|
|
|
165
292
|
# 'log' runs even if the client doesn't query the 'status' field
|
|
166
|
-
status <-! log:
|
|
293
|
+
out.status <-! log:in.changeData
|
|
294
|
+
}
|
|
167
295
|
```
|
|
168
296
|
|
|
169
297
|
### The Pipe Operator (`:`)
|
|
@@ -171,25 +299,52 @@ bridge Mutation.updateUser
|
|
|
171
299
|
Routes data right-to-left through one or more tool handles: `dest <- handle:source`.
|
|
172
300
|
|
|
173
301
|
```hcl
|
|
302
|
+
with output as o
|
|
303
|
+
|
|
174
304
|
# i.rawData → normalize → transform → result
|
|
175
|
-
result <- transform:normalize:i.rawData
|
|
305
|
+
o.result <- transform:normalize:i.rawData
|
|
176
306
|
```
|
|
177
307
|
|
|
178
308
|
Full example with a tool that has 2 input parameters:
|
|
179
309
|
|
|
180
310
|
```hcl
|
|
181
|
-
|
|
182
|
-
currency = EUR # default currency
|
|
311
|
+
tool convert from currencyConverter {
|
|
312
|
+
.currency = EUR # default currency
|
|
313
|
+
}
|
|
183
314
|
|
|
184
|
-
|
|
315
|
+
# example with pipe syntax
|
|
316
|
+
bridge Query.price {
|
|
185
317
|
with convert as c
|
|
186
318
|
with input as i
|
|
319
|
+
with output as o
|
|
320
|
+
|
|
321
|
+
c.currency <- i.currency # overrides the default per request
|
|
322
|
+
|
|
323
|
+
# Safe to use repeatedly — each is an independent tool call
|
|
324
|
+
o.itemPrice <- c:i.itemPrice
|
|
325
|
+
o.totalPrice <- c:i.totalPrice
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
# same without the pipe syntax
|
|
329
|
+
tool c1 from convert
|
|
330
|
+
tool c2 from convert
|
|
331
|
+
|
|
332
|
+
bridge Query.price {
|
|
333
|
+
with c1
|
|
334
|
+
with c2
|
|
335
|
+
with input as i
|
|
336
|
+
with output as o
|
|
337
|
+
|
|
338
|
+
c1.currency <- i.currency # overrides the default per request
|
|
339
|
+
c2.currency <- i.currency # overrides the default per request
|
|
187
340
|
|
|
188
|
-
|
|
341
|
+
c1.in <- i.itemPrice
|
|
342
|
+
c2.in <- i.totalPrice
|
|
189
343
|
|
|
190
|
-
# Safe to use repeatedly — each is an independent tool call
|
|
191
|
-
itemPrice <-
|
|
192
|
-
totalPrice <-
|
|
344
|
+
# Safe to use repeatedly — each is an independent tool call
|
|
345
|
+
o.itemPrice <- c1
|
|
346
|
+
o.totalPrice <- c2
|
|
347
|
+
}
|
|
193
348
|
```
|
|
194
349
|
|
|
195
350
|
---
|
|
@@ -205,9 +360,10 @@ totalPrice <- c:i.totalPrice
|
|
|
205
360
|
| **`\|\|`** | Null-coalesce | Next alternative if current source is `null`/`undefined`. Fires on absent values, not errors. |
|
|
206
361
|
| **`??`** | Error-fallback | Alternative used when the resolution chain **throws**. Fires on errors, not null values. |
|
|
207
362
|
| **`on error`** | Tool Fallback | Returns a default if the tool's `fn(input)` throws. |
|
|
208
|
-
| **`
|
|
363
|
+
| **`tool ... from`** | Tool Definition | Configures a function or inherits from a parent tool. |
|
|
364
|
+
| **`define`** | Reusable Subgraph | Declares a named pipeline template invocable from bridges. |
|
|
209
365
|
| **`const`** | Named Value | Declares reusable JSON constants. |
|
|
210
|
-
|
|
|
366
|
+
| **`<- src[] as i { }`** | Map | Iterates over source array; each element accessed via the named iterator `i`. `i.field` references the current element. `.field = "value"` sets an element constant. |
|
|
211
367
|
|
|
212
368
|
---
|
|
213
369
|
|
|
@@ -263,30 +419,35 @@ The Bridge ships with built-in tools under the `std` namespace, always available
|
|
|
263
419
|
|
|
264
420
|
### Using Built-in Tools
|
|
265
421
|
|
|
266
|
-
**No `
|
|
422
|
+
**No `tool` block needed** for pipe-like tools — reference them with the `std.` prefix in the `with` header:
|
|
267
423
|
|
|
268
424
|
```hcl
|
|
269
|
-
bridge Query.format
|
|
425
|
+
bridge Query.format {
|
|
270
426
|
with std.upperCase as up
|
|
271
427
|
with std.lowerCase as lo
|
|
272
428
|
with input as i
|
|
429
|
+
with output as o
|
|
273
430
|
|
|
274
|
-
upper <- up:i.text
|
|
275
|
-
lower <- lo:i.text
|
|
431
|
+
o.upper <- up:i.text
|
|
432
|
+
o.lower <- lo:i.text
|
|
433
|
+
}
|
|
276
434
|
```
|
|
277
435
|
|
|
278
|
-
Use
|
|
436
|
+
Use a `tool` block when you need to configure defaults:
|
|
279
437
|
|
|
280
438
|
```hcl
|
|
281
|
-
|
|
282
|
-
strict = true
|
|
439
|
+
tool pf from std.pickFirst {
|
|
440
|
+
.strict = true
|
|
441
|
+
}
|
|
283
442
|
|
|
284
|
-
bridge Query.onlyResult
|
|
443
|
+
bridge Query.onlyResult {
|
|
285
444
|
with pf
|
|
286
445
|
with someApi as api
|
|
287
446
|
with input as i
|
|
447
|
+
with output as o
|
|
288
448
|
|
|
289
|
-
value <- pf:api.items
|
|
449
|
+
o.value <- pf:api.items
|
|
450
|
+
}
|
|
290
451
|
```
|
|
291
452
|
|
|
292
453
|
### Adding Custom Tools
|
|
@@ -321,11 +482,12 @@ const schema = bridgeTransform(createSchema({ typeDefs }), instructions, {
|
|
|
321
482
|
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.
|
|
322
483
|
|
|
323
484
|
```hcl
|
|
324
|
-
|
|
325
|
-
cache = 300 # cache for 5 minutes
|
|
326
|
-
baseUrl = "https://nominatim.openstreetmap.org"
|
|
327
|
-
method = GET
|
|
328
|
-
path = /search
|
|
485
|
+
tool geo from httpCall {
|
|
486
|
+
.cache = 300 # cache for 5 minutes
|
|
487
|
+
.baseUrl = "https://nominatim.openstreetmap.org"
|
|
488
|
+
.method = GET
|
|
489
|
+
.path = /search
|
|
490
|
+
}
|
|
329
491
|
```
|
|
330
492
|
|
|
331
493
|
The default is an in-memory store. For Redis or other backends, pass a custom `CacheStore` to `createHttpCall`:
|
package/build/ExecutionTree.js
CHANGED
|
@@ -264,7 +264,12 @@ export class ExecutionTree {
|
|
|
264
264
|
return [w.to.path, value];
|
|
265
265
|
}));
|
|
266
266
|
for (const [path, value] of resolved) {
|
|
267
|
-
|
|
267
|
+
if (path.length === 0 && value != null && typeof value === "object") {
|
|
268
|
+
Object.assign(input, value);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
setNested(input, path, value);
|
|
272
|
+
}
|
|
268
273
|
}
|
|
269
274
|
// Call ToolDef-backed tool function
|
|
270
275
|
if (toolDef) {
|
|
@@ -289,6 +294,11 @@ export class ExecutionTree {
|
|
|
289
294
|
if (directFn) {
|
|
290
295
|
return directFn(input);
|
|
291
296
|
}
|
|
297
|
+
// Define pass-through: synthetic trunks created by define inlining
|
|
298
|
+
// act as data containers — bridge wires set their values, no tool needed.
|
|
299
|
+
if (target.module.startsWith("__define_")) {
|
|
300
|
+
return input;
|
|
301
|
+
}
|
|
292
302
|
throw new Error(`No tool found for "${toolName}"`);
|
|
293
303
|
})();
|
|
294
304
|
}
|
|
@@ -432,7 +442,7 @@ export class ExecutionTree {
|
|
|
432
442
|
// Strip numeric indices (array positions) from path for wire matching
|
|
433
443
|
const cleanPath = pathSegments.filter((p) => !/^\d+$/.test(p));
|
|
434
444
|
// Find wires whose target matches this trunk + path
|
|
435
|
-
const matches = this.bridge?.wires.filter((w) =>
|
|
445
|
+
const matches = this.bridge?.wires.filter((w) => (w.to.element ? !!this.parent : true) &&
|
|
436
446
|
sameTrunk(w.to, this.trunk) &&
|
|
437
447
|
pathEquals(w.to.path, cleanPath)) ?? [];
|
|
438
448
|
if (matches.length > 0) {
|
package/build/bridge-format.d.ts
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
1
|
import type { Instruction } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
* Parse .bridge text format into structured instructions.
|
|
4
|
-
*
|
|
5
|
-
* The .bridge format is a human-readable representation of connection wires.
|
|
6
|
-
* Multiple blocks are separated by `---`.
|
|
7
|
-
* Tool blocks define API tools, bridge blocks define wire mappings.
|
|
8
|
-
*
|
|
9
|
-
* @param text - Bridge definition text
|
|
10
|
-
* @returns Array of instructions (Bridge, ToolDef)
|
|
11
|
-
*/
|
|
12
2
|
export declare function parseBridge(text: string): Instruction[];
|
|
13
3
|
/**
|
|
14
4
|
* Parse a dot-separated path with optional array indices.
|