@barnum/barnum 0.2.3 → 0.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/artifacts/linux-arm64/barnum +0 -0
- package/artifacts/linux-x64/barnum +0 -0
- package/artifacts/macos-arm64/barnum +0 -0
- package/artifacts/macos-x64/barnum +0 -0
- package/artifacts/win-x64/barnum.exe +0 -0
- package/cli.cjs +33 -0
- package/dist/all.d.ts +12 -0
- package/dist/all.js +8 -0
- package/dist/ast.d.ts +375 -0
- package/dist/ast.js +381 -0
- package/dist/bind.d.ts +62 -0
- package/dist/bind.js +106 -0
- package/dist/builtins.d.ts +257 -0
- package/dist/builtins.js +600 -0
- package/dist/chain.d.ts +2 -0
- package/dist/chain.js +8 -0
- package/dist/effect-id.d.ts +14 -0
- package/dist/effect-id.js +16 -0
- package/dist/handler.d.ts +50 -0
- package/dist/handler.js +146 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +5 -0
- package/dist/pipe.d.ts +11 -0
- package/dist/pipe.js +11 -0
- package/dist/race.d.ts +53 -0
- package/dist/race.js +141 -0
- package/dist/recursive.d.ts +34 -0
- package/dist/recursive.js +53 -0
- package/dist/run.d.ts +7 -0
- package/dist/run.js +143 -0
- package/dist/schema.d.ts +8 -0
- package/dist/schema.js +95 -0
- package/dist/try-catch.d.ts +23 -0
- package/dist/try-catch.js +36 -0
- package/dist/worker.d.ts +11 -0
- package/dist/worker.js +46 -0
- package/package.json +40 -16
- package/src/all.ts +89 -0
- package/src/ast.ts +878 -0
- package/src/bind.ts +192 -0
- package/src/builtins.ts +804 -0
- package/src/chain.ts +17 -0
- package/src/effect-id.ts +30 -0
- package/src/handler.ts +279 -0
- package/src/index.ts +30 -0
- package/src/pipe.ts +93 -0
- package/src/race.ts +183 -0
- package/src/recursive.ts +112 -0
- package/src/run.ts +181 -0
- package/src/schema.ts +118 -0
- package/src/try-catch.ts +53 -0
- package/src/worker.ts +56 -0
- package/README.md +0 -19
- package/barnum-config-schema.json +0 -408
- package/cli.js +0 -20
- package/index.js +0 -23
package/dist/ast.js
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// typedAction — attach .then() and .forEach() as non-enumerable methods
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Shared implementations (one closure, not per-instance)
|
|
5
|
+
function thenMethod(next) {
|
|
6
|
+
return typedAction({ kind: "Chain", first: this, rest: next });
|
|
7
|
+
}
|
|
8
|
+
function forEachMethod(action) {
|
|
9
|
+
return typedAction({
|
|
10
|
+
kind: "Chain",
|
|
11
|
+
first: this,
|
|
12
|
+
rest: { kind: "ForEach", action },
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function branchMethod(cases) {
|
|
16
|
+
return typedAction({
|
|
17
|
+
kind: "Chain",
|
|
18
|
+
first: this,
|
|
19
|
+
rest: { kind: "Branch", cases: unwrapBranchCases(cases) },
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function flattenMethod() {
|
|
23
|
+
return typedAction({
|
|
24
|
+
kind: "Chain",
|
|
25
|
+
first: this,
|
|
26
|
+
rest: {
|
|
27
|
+
kind: "Invoke",
|
|
28
|
+
handler: { kind: "Builtin", builtin: { kind: "Flatten" } },
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function dropMethod() {
|
|
33
|
+
return typedAction({
|
|
34
|
+
kind: "Chain",
|
|
35
|
+
first: this,
|
|
36
|
+
rest: {
|
|
37
|
+
kind: "Invoke",
|
|
38
|
+
handler: { kind: "Builtin", builtin: { kind: "Drop" } },
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function tagMethod(kind) {
|
|
43
|
+
return typedAction({
|
|
44
|
+
kind: "Chain",
|
|
45
|
+
first: this,
|
|
46
|
+
rest: {
|
|
47
|
+
kind: "Invoke",
|
|
48
|
+
handler: { kind: "Builtin", builtin: { kind: "Tag", value: kind } },
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function getMethod(field) {
|
|
53
|
+
return typedAction({
|
|
54
|
+
kind: "Chain",
|
|
55
|
+
first: this,
|
|
56
|
+
rest: {
|
|
57
|
+
kind: "Invoke",
|
|
58
|
+
handler: {
|
|
59
|
+
kind: "Builtin",
|
|
60
|
+
builtin: { kind: "ExtractField", value: field },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function augmentMethod() {
|
|
66
|
+
// Construct: All(this, identity) → Merge
|
|
67
|
+
// "this" is the sub-pipeline. augment() wraps it so the original input
|
|
68
|
+
// flows through identity alongside the sub-pipeline, then merges the results.
|
|
69
|
+
return typedAction({
|
|
70
|
+
kind: "Chain",
|
|
71
|
+
first: {
|
|
72
|
+
kind: "All",
|
|
73
|
+
actions: [
|
|
74
|
+
this,
|
|
75
|
+
{
|
|
76
|
+
kind: "Invoke",
|
|
77
|
+
handler: { kind: "Builtin", builtin: { kind: "Identity" } },
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
rest: {
|
|
82
|
+
kind: "Invoke",
|
|
83
|
+
handler: { kind: "Builtin", builtin: { kind: "Merge" } },
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function mergeMethod() {
|
|
88
|
+
return typedAction({
|
|
89
|
+
kind: "Chain",
|
|
90
|
+
first: this,
|
|
91
|
+
rest: {
|
|
92
|
+
kind: "Invoke",
|
|
93
|
+
handler: { kind: "Builtin", builtin: { kind: "Merge" } },
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function pickMethod(...keys) {
|
|
98
|
+
return typedAction({
|
|
99
|
+
kind: "Chain",
|
|
100
|
+
first: this,
|
|
101
|
+
rest: {
|
|
102
|
+
kind: "Invoke",
|
|
103
|
+
handler: { kind: "Builtin", builtin: { kind: "Pick", value: keys } },
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function mapOptionMethod(action) {
|
|
108
|
+
// Desugars to: self.then(branch({ Some: pipe(action, tag("Some")), None: tag("None") }))
|
|
109
|
+
// But branch auto-unwraps value, so:
|
|
110
|
+
// Some case: receives T, runs action, wraps as Some
|
|
111
|
+
// None case: receives void, wraps as None
|
|
112
|
+
return typedAction({
|
|
113
|
+
kind: "Chain",
|
|
114
|
+
first: this,
|
|
115
|
+
rest: {
|
|
116
|
+
kind: "Branch",
|
|
117
|
+
cases: unwrapBranchCases({
|
|
118
|
+
Some: {
|
|
119
|
+
kind: "Chain",
|
|
120
|
+
first: action,
|
|
121
|
+
rest: {
|
|
122
|
+
kind: "Invoke",
|
|
123
|
+
handler: {
|
|
124
|
+
kind: "Builtin",
|
|
125
|
+
builtin: { kind: "Tag", value: "Some" },
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
None: {
|
|
130
|
+
kind: "Invoke",
|
|
131
|
+
handler: { kind: "Builtin", builtin: { kind: "Tag", value: "None" } },
|
|
132
|
+
},
|
|
133
|
+
}),
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function mapErrMethod(action) {
|
|
138
|
+
// Desugars to: self.then(branch({ Ok: tag("Ok"), Err: pipe(action, tag("Err")) }))
|
|
139
|
+
return typedAction({
|
|
140
|
+
kind: "Chain",
|
|
141
|
+
first: this,
|
|
142
|
+
rest: {
|
|
143
|
+
kind: "Branch",
|
|
144
|
+
cases: unwrapBranchCases({
|
|
145
|
+
Ok: {
|
|
146
|
+
kind: "Invoke",
|
|
147
|
+
handler: { kind: "Builtin", builtin: { kind: "Tag", value: "Ok" } },
|
|
148
|
+
},
|
|
149
|
+
Err: {
|
|
150
|
+
kind: "Chain",
|
|
151
|
+
first: action,
|
|
152
|
+
rest: {
|
|
153
|
+
kind: "Invoke",
|
|
154
|
+
handler: {
|
|
155
|
+
kind: "Builtin",
|
|
156
|
+
builtin: { kind: "Tag", value: "Err" },
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function unwrapOrMethod(defaultAction) {
|
|
165
|
+
// Desugars to: self.then(branch({ Ok: identity(), Err: defaultAction }))
|
|
166
|
+
return typedAction({
|
|
167
|
+
kind: "Chain",
|
|
168
|
+
first: this,
|
|
169
|
+
rest: {
|
|
170
|
+
kind: "Branch",
|
|
171
|
+
cases: unwrapBranchCases({
|
|
172
|
+
Ok: {
|
|
173
|
+
kind: "Invoke",
|
|
174
|
+
handler: { kind: "Builtin", builtin: { kind: "Identity" } },
|
|
175
|
+
},
|
|
176
|
+
Err: defaultAction,
|
|
177
|
+
}),
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Attach `.then()` and `.forEach()` methods to a plain Action object.
|
|
183
|
+
* Methods are non-enumerable: invisible to JSON.stringify and toEqual.
|
|
184
|
+
*/
|
|
185
|
+
export function typedAction(action) {
|
|
186
|
+
if (!("then" in action)) {
|
|
187
|
+
Object.defineProperties(action, {
|
|
188
|
+
then: { value: thenMethod, configurable: true },
|
|
189
|
+
forEach: { value: forEachMethod, configurable: true },
|
|
190
|
+
branch: { value: branchMethod, configurable: true },
|
|
191
|
+
flatten: { value: flattenMethod, configurable: true },
|
|
192
|
+
drop: { value: dropMethod, configurable: true },
|
|
193
|
+
tag: { value: tagMethod, configurable: true },
|
|
194
|
+
get: { value: getMethod, configurable: true },
|
|
195
|
+
augment: { value: augmentMethod, configurable: true },
|
|
196
|
+
merge: { value: mergeMethod, configurable: true },
|
|
197
|
+
pick: { value: pickMethod, configurable: true },
|
|
198
|
+
mapOption: { value: mapOptionMethod, configurable: true },
|
|
199
|
+
mapErr: { value: mapErrMethod, configurable: true },
|
|
200
|
+
unwrapOr: { value: unwrapOrMethod, configurable: true },
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return action;
|
|
204
|
+
}
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
// Combinators
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
export { pipe } from "./pipe.js";
|
|
209
|
+
export { chain } from "./chain.js";
|
|
210
|
+
export { all } from "./all.js";
|
|
211
|
+
export { bind, bindInput } from "./bind.js";
|
|
212
|
+
export { defineRecursiveFunctions } from "./recursive.js";
|
|
213
|
+
export { resetEffectIdCounter } from "./effect-id.js";
|
|
214
|
+
import { allocateRestartHandlerId, } from "./effect-id.js";
|
|
215
|
+
export { tryCatch } from "./try-catch.js";
|
|
216
|
+
export { race, sleep, withTimeout } from "./race.js";
|
|
217
|
+
export function forEach(action) {
|
|
218
|
+
return typedAction({ kind: "ForEach", action: action });
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Insert ExtractField("value") before each case handler in a branch.
|
|
222
|
+
* This implements auto-unwrapping: the engine dispatches on `kind`, then
|
|
223
|
+
* extracts `value` before passing to the handler. Case handlers receive
|
|
224
|
+
* the payload directly, not the full `{ kind, value }` variant.
|
|
225
|
+
*/
|
|
226
|
+
function unwrapBranchCases(cases) {
|
|
227
|
+
const unwrapped = {};
|
|
228
|
+
for (const key of Object.keys(cases)) {
|
|
229
|
+
unwrapped[key] = {
|
|
230
|
+
kind: "Chain",
|
|
231
|
+
first: {
|
|
232
|
+
kind: "Invoke",
|
|
233
|
+
handler: {
|
|
234
|
+
kind: "Builtin",
|
|
235
|
+
builtin: { kind: "ExtractField", value: "value" },
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
rest: cases[key],
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return unwrapped;
|
|
242
|
+
}
|
|
243
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
244
|
+
export function branch(cases) {
|
|
245
|
+
return typedAction({ kind: "Branch", cases: unwrapBranchCases(cases) });
|
|
246
|
+
}
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
// Shared AST constants for control flow compilation
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
const EXTRACT_PAYLOAD = {
|
|
251
|
+
kind: "Invoke",
|
|
252
|
+
handler: { kind: "Builtin", builtin: { kind: "ExtractIndex", value: 0 } },
|
|
253
|
+
};
|
|
254
|
+
const TAG_CONTINUE = {
|
|
255
|
+
kind: "Invoke",
|
|
256
|
+
handler: { kind: "Builtin", builtin: { kind: "Tag", value: "Continue" } },
|
|
257
|
+
};
|
|
258
|
+
export const TAG_BREAK = {
|
|
259
|
+
kind: "Invoke",
|
|
260
|
+
handler: { kind: "Builtin", builtin: { kind: "Tag", value: "Break" } },
|
|
261
|
+
};
|
|
262
|
+
export const IDENTITY = {
|
|
263
|
+
kind: "Invoke",
|
|
264
|
+
handler: { kind: "Builtin", builtin: { kind: "Identity" } },
|
|
265
|
+
};
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
// recur — restart body primitive
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
/**
|
|
270
|
+
* Restartable scope. The body callback receives `restart`, a TypedAction that
|
|
271
|
+
* re-executes the body from the beginning with a new input value.
|
|
272
|
+
*
|
|
273
|
+
* If the body completes normally → output is TOut.
|
|
274
|
+
* If restart fires → body re-executes with the restarted value.
|
|
275
|
+
*
|
|
276
|
+
* Compiled form: `RestartHandle(id, ExtractIndex(0), body)`
|
|
277
|
+
*/
|
|
278
|
+
export function recur(bodyFn) {
|
|
279
|
+
const restartHandlerId = allocateRestartHandlerId();
|
|
280
|
+
const restartAction = typedAction({
|
|
281
|
+
kind: "RestartPerform",
|
|
282
|
+
restart_handler_id: restartHandlerId,
|
|
283
|
+
});
|
|
284
|
+
const body = bodyFn(restartAction);
|
|
285
|
+
return typedAction({
|
|
286
|
+
kind: "RestartHandle",
|
|
287
|
+
restart_handler_id: restartHandlerId,
|
|
288
|
+
body,
|
|
289
|
+
handler: EXTRACT_PAYLOAD,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
// ---------------------------------------------------------------------------
|
|
293
|
+
// earlyReturn — exit scope primitive (built on restart + Branch)
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
/**
|
|
296
|
+
* Early return scope. The body callback receives `earlyReturn`, a TypedAction
|
|
297
|
+
* that exits the scope immediately with the returned value.
|
|
298
|
+
*
|
|
299
|
+
* If the body completes normally → output is TOut.
|
|
300
|
+
* If earlyReturn fires → output is TEarlyReturn.
|
|
301
|
+
* Combined output: TEarlyReturn | TOut.
|
|
302
|
+
*
|
|
303
|
+
* Built on the restart mechanism: input is tagged Continue, body runs inside
|
|
304
|
+
* a Branch. earlyReturn tags with Break and performs — the handler restarts
|
|
305
|
+
* the body, Branch takes the Break path, and the value exits.
|
|
306
|
+
*/
|
|
307
|
+
export function earlyReturn(bodyFn) {
|
|
308
|
+
const restartHandlerId = allocateRestartHandlerId();
|
|
309
|
+
const earlyReturnAction = typedAction({
|
|
310
|
+
kind: "Chain",
|
|
311
|
+
first: TAG_BREAK,
|
|
312
|
+
rest: { kind: "RestartPerform", restart_handler_id: restartHandlerId },
|
|
313
|
+
});
|
|
314
|
+
const body = bodyFn(earlyReturnAction);
|
|
315
|
+
return typedAction(buildRestartBranchAction(restartHandlerId, body, IDENTITY));
|
|
316
|
+
}
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
// loop — iterative restart with break
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
/**
|
|
321
|
+
* Build the restart+branch compiled form:
|
|
322
|
+
* `Chain(Tag("Continue"), RestartHandle(id, ExtractIndex(0), Branch({ Continue: continueArm, Break: breakArm })))`
|
|
323
|
+
*
|
|
324
|
+
* Input is tagged Continue so the Branch enters the continueArm on first execution.
|
|
325
|
+
* Continue tag → restart → re-enters continueArm. Break tag → restart → runs breakArm, exits `RestartHandle`.
|
|
326
|
+
*
|
|
327
|
+
* Used by earlyReturn, loop, tryCatch, and race.
|
|
328
|
+
*/
|
|
329
|
+
export function buildRestartBranchAction(restartHandlerId, continueArm, breakArm) {
|
|
330
|
+
return {
|
|
331
|
+
kind: "Chain",
|
|
332
|
+
first: TAG_CONTINUE,
|
|
333
|
+
rest: {
|
|
334
|
+
kind: "RestartHandle",
|
|
335
|
+
restart_handler_id: restartHandlerId,
|
|
336
|
+
body: {
|
|
337
|
+
kind: "Branch",
|
|
338
|
+
cases: unwrapBranchCases({
|
|
339
|
+
Continue: continueArm,
|
|
340
|
+
Break: breakArm,
|
|
341
|
+
}),
|
|
342
|
+
},
|
|
343
|
+
handler: EXTRACT_PAYLOAD,
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Iterative loop. The body callback receives `recur` and `done`:
|
|
349
|
+
* - `recur`: restart the loop with a new input
|
|
350
|
+
* - `done`: exit the loop with the break value
|
|
351
|
+
*
|
|
352
|
+
* Both are TypedAction values (not functions), consistent with throwError in tryCatch.
|
|
353
|
+
*
|
|
354
|
+
* Compiles to `RestartHandle`/`RestartPerform`/Branch — same effect substrate as tryCatch and earlyReturn.
|
|
355
|
+
*/
|
|
356
|
+
export function loop(bodyFn) {
|
|
357
|
+
const restartHandlerId = allocateRestartHandlerId();
|
|
358
|
+
const perform = {
|
|
359
|
+
kind: "RestartPerform",
|
|
360
|
+
restart_handler_id: restartHandlerId,
|
|
361
|
+
};
|
|
362
|
+
const recurAction = typedAction({
|
|
363
|
+
kind: "Chain",
|
|
364
|
+
first: TAG_CONTINUE,
|
|
365
|
+
rest: perform,
|
|
366
|
+
});
|
|
367
|
+
const doneAction = typedAction({
|
|
368
|
+
kind: "Chain",
|
|
369
|
+
first: TAG_BREAK,
|
|
370
|
+
rest: perform,
|
|
371
|
+
});
|
|
372
|
+
const body = bodyFn(recurAction, doneAction);
|
|
373
|
+
return typedAction(buildRestartBranchAction(restartHandlerId, body, IDENTITY));
|
|
374
|
+
}
|
|
375
|
+
// ---------------------------------------------------------------------------
|
|
376
|
+
// Config builders
|
|
377
|
+
// ---------------------------------------------------------------------------
|
|
378
|
+
/** Simple config factory. */
|
|
379
|
+
export function config(workflow) {
|
|
380
|
+
return { workflow };
|
|
381
|
+
}
|
package/dist/bind.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type Action, type ExtractInput, type ExtractOutput, type TypedAction } from "./ast.js";
|
|
2
|
+
/**
|
|
3
|
+
* A typed reference to a bound value. Output is `TValue`.
|
|
4
|
+
*
|
|
5
|
+
* Use `.then()` (not `pipe()`) when chaining a VarRef into a generic
|
|
6
|
+
* action like `pick` or `extractField` — pipe overloads can't infer
|
|
7
|
+
* the generic's type parameter from the VarRef's output.
|
|
8
|
+
*/
|
|
9
|
+
export type VarRef<TValue> = TypedAction<never, TValue>;
|
|
10
|
+
/**
|
|
11
|
+
* Maps each binding's output type to a VarRef. TypeScript resolves
|
|
12
|
+
* ExtractOutput from each binding expression.
|
|
13
|
+
*
|
|
14
|
+
* Constraint is `Action[]` (not `Pipeable<any, any>[]`) because
|
|
15
|
+
* `TypedAction<never, X>` (e.g. from `constant()`) fails the invariant
|
|
16
|
+
* `__in` check against `Pipeable<any, any>` on the 9-variant
|
|
17
|
+
* Action union. Using raw `Action[]` avoids the phantom field
|
|
18
|
+
* assignability issue while `ExtractOutput` still extracts the correct
|
|
19
|
+
* output type from the phantom fields on the concrete types.
|
|
20
|
+
*/
|
|
21
|
+
export type InferVarRefs<TBindings extends Action[]> = {
|
|
22
|
+
[K in keyof TBindings]: VarRef<ExtractOutput<TBindings[K]>>;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Bind concurrent values as VarRefs available throughout the body.
|
|
26
|
+
*
|
|
27
|
+
* All bindings are actions (Pipeable) evaluated concurrently with the
|
|
28
|
+
* pipeline input. The body callback receives an array of VarRefs,
|
|
29
|
+
* one per binding.
|
|
30
|
+
*
|
|
31
|
+
* Compiles to:
|
|
32
|
+
* Chain(
|
|
33
|
+
* All(...bindings, Identity),
|
|
34
|
+
* ResumeHandle(r0, readVar(0),
|
|
35
|
+
* ResumeHandle(r1, readVar(1),
|
|
36
|
+
* Chain(ExtractIndex(N), body)
|
|
37
|
+
* )
|
|
38
|
+
* )
|
|
39
|
+
* )
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* Constraint for the body callback return type. Only requires the output
|
|
43
|
+
* phantom fields — omits `__in` and `__in_co` so that body actions with
|
|
44
|
+
* `In = never` (e.g. pipelines starting from a VarRef) are assignable.
|
|
45
|
+
*/
|
|
46
|
+
type BodyResult<TOut> = Action & {
|
|
47
|
+
__out?: () => TOut;
|
|
48
|
+
__out_contra?: (output: TOut) => void;
|
|
49
|
+
};
|
|
50
|
+
export declare function bind<TBindings extends Action[], TOut>(bindings: [...TBindings], body: (vars: InferVarRefs<TBindings>) => BodyResult<TOut>): TypedAction<ExtractInput<TBindings[number]>, TOut>;
|
|
51
|
+
/**
|
|
52
|
+
* Convenience wrapper for the common pattern of capturing the pipeline
|
|
53
|
+
* input as a VarRef. The body's pipeline input is `never` — the input
|
|
54
|
+
* is dropped, so the body must access it through the VarRef.
|
|
55
|
+
*
|
|
56
|
+
* Sugar for: `bind([identity()], ([input]) => pipe(drop, body(input)))`
|
|
57
|
+
*
|
|
58
|
+
* TOut defaults to `any` so callers can specify just TIn:
|
|
59
|
+
* bindInput<FileEntry>((entry) => ...)
|
|
60
|
+
*/
|
|
61
|
+
export declare function bindInput<TIn, TOut = any>(body: (input: VarRef<TIn>) => BodyResult<TOut>): TypedAction<TIn, TOut>;
|
|
62
|
+
export {};
|
package/dist/bind.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { typedAction, } from "./ast.js";
|
|
2
|
+
import { identity, drop } from "./builtins.js";
|
|
3
|
+
import { allocateResumeHandlerId } from "./effect-id.js";
|
|
4
|
+
import { pipe } from "./pipe.js";
|
|
5
|
+
function createVarRef(resumeHandlerId) {
|
|
6
|
+
return typedAction({
|
|
7
|
+
kind: "ResumePerform",
|
|
8
|
+
resume_handler_id: resumeHandlerId,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// readVar — handler DAG for the nth binding
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
/**
|
|
15
|
+
* Returns an action that extracts the nth value from the ResumeHandle's
|
|
16
|
+
* state tuple and passes state through unchanged. When a ResumePerform
|
|
17
|
+
* fires, the engine calls the handler with `[payload, state]`. For bind,
|
|
18
|
+
* `state` (index 1) is the full All output tuple. The handler produces
|
|
19
|
+
* `[state[n], state]` — value is state[n], new_state is state (unchanged).
|
|
20
|
+
*
|
|
21
|
+
* Expanded AST: All(Chain(ExtractIndex(1), ExtractIndex(n)), ExtractIndex(1))
|
|
22
|
+
*/
|
|
23
|
+
function readVar(n) {
|
|
24
|
+
return {
|
|
25
|
+
kind: "All",
|
|
26
|
+
actions: [
|
|
27
|
+
{
|
|
28
|
+
kind: "Chain",
|
|
29
|
+
first: {
|
|
30
|
+
kind: "Invoke",
|
|
31
|
+
handler: {
|
|
32
|
+
kind: "Builtin",
|
|
33
|
+
builtin: { kind: "ExtractIndex", value: 1 },
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
rest: {
|
|
37
|
+
kind: "Invoke",
|
|
38
|
+
handler: {
|
|
39
|
+
kind: "Builtin",
|
|
40
|
+
builtin: { kind: "ExtractIndex", value: n },
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
kind: "Invoke",
|
|
46
|
+
handler: {
|
|
47
|
+
kind: "Builtin",
|
|
48
|
+
builtin: { kind: "ExtractIndex", value: 1 },
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function bind(bindings, body) {
|
|
55
|
+
// 1. Gensym one resumeHandlerId per binding.
|
|
56
|
+
const resumeHandlerIds = bindings.map(() => allocateResumeHandlerId());
|
|
57
|
+
// 2. Create VarRefs (ResumePerform nodes) for each binding.
|
|
58
|
+
const varRefs = resumeHandlerIds.map((id) => createVarRef(id));
|
|
59
|
+
// 3. Invoke the body callback with the VarRefs.
|
|
60
|
+
const bodyAction = body(varRefs);
|
|
61
|
+
// 4. Build nested Handles from inside out.
|
|
62
|
+
// Innermost: extract pipeline_input (last All element) → user body
|
|
63
|
+
const pipelineInputIndex = bindings.length;
|
|
64
|
+
let inner = {
|
|
65
|
+
kind: "Chain",
|
|
66
|
+
first: {
|
|
67
|
+
kind: "Invoke",
|
|
68
|
+
handler: {
|
|
69
|
+
kind: "Builtin",
|
|
70
|
+
builtin: { kind: "ExtractIndex", value: pipelineInputIndex },
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
rest: bodyAction,
|
|
74
|
+
};
|
|
75
|
+
for (let i = resumeHandlerIds.length - 1; i >= 0; i--) {
|
|
76
|
+
inner = {
|
|
77
|
+
kind: "ResumeHandle",
|
|
78
|
+
resume_handler_id: resumeHandlerIds[i],
|
|
79
|
+
handler: readVar(i),
|
|
80
|
+
body: inner,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// 5. All(...bindings, identity()) → nested Handles
|
|
84
|
+
const allActions = [...bindings.map((b) => b), identity];
|
|
85
|
+
return typedAction({
|
|
86
|
+
kind: "Chain",
|
|
87
|
+
first: { kind: "All", actions: allActions },
|
|
88
|
+
rest: inner,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// bindInput — bind the pipeline input
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
/**
|
|
95
|
+
* Convenience wrapper for the common pattern of capturing the pipeline
|
|
96
|
+
* input as a VarRef. The body's pipeline input is `never` — the input
|
|
97
|
+
* is dropped, so the body must access it through the VarRef.
|
|
98
|
+
*
|
|
99
|
+
* Sugar for: `bind([identity()], ([input]) => pipe(drop, body(input)))`
|
|
100
|
+
*
|
|
101
|
+
* TOut defaults to `any` so callers can specify just TIn:
|
|
102
|
+
* bindInput<FileEntry>((entry) => ...)
|
|
103
|
+
*/
|
|
104
|
+
export function bindInput(body) {
|
|
105
|
+
return bind([identity], ([input]) => pipe(drop, body(input)));
|
|
106
|
+
}
|