@barnum/barnum 0.0.0-main-e8b82cff → 0.0.0-main-00082d0f
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/win-x64/barnum.exe +0 -0
- package/package.json +1 -1
- package/src/ast.ts +23 -28
- package/src/bind.ts +5 -10
- package/src/builtins.ts +2 -2
- package/src/handler.ts +20 -3
- package/src/recursive.ts +112 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
package/src/ast.ts
CHANGED
|
@@ -134,29 +134,23 @@ export type MergeTuple<TTuple> = TTuple extends unknown[]
|
|
|
134
134
|
* An action with tracked input/output types. Phantom fields enforce invariance
|
|
135
135
|
* and are never set at runtime — they exist only for the TypeScript compiler.
|
|
136
136
|
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* Out: __phantom_out (covariant) + __phantom_out_check (contravariant) → invariant
|
|
137
|
+
* Each type variable gets a contravariant + covariant field pair:
|
|
138
|
+
* In: __in (contravariant) + __in_co (covariant) → invariant
|
|
139
|
+
* Out: __out (covariant) + __out_contra (contravariant) → invariant
|
|
141
140
|
*
|
|
142
141
|
* This ensures exact type matching at every pipeline connection point.
|
|
143
142
|
* Data crosses serialization boundaries to handlers in arbitrary languages
|
|
144
143
|
* (Rust, Python, etc.), so extra/missing fields are runtime errors.
|
|
145
|
-
*
|
|
146
|
-
* __in also enables config() to reject workflows that expect input
|
|
147
|
-
* (the contravariant __phantom_in makes never the most permissive input,
|
|
148
|
-
* so the covariant __in is needed for the entry point check).
|
|
149
|
-
*
|
|
150
144
|
*/
|
|
151
145
|
export type TypedAction<
|
|
152
146
|
In = unknown,
|
|
153
147
|
Out = unknown,
|
|
154
148
|
Refs extends string = never,
|
|
155
149
|
> = Action & {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
150
|
+
__in?: (input: In) => void;
|
|
151
|
+
__in_co?: In;
|
|
152
|
+
__out?: () => Out;
|
|
153
|
+
__out_contra?: (output: Out) => void;
|
|
160
154
|
__refs?: { _brand: Refs };
|
|
161
155
|
/** Chain this action with another. `a.then(b)` ≡ `chain(a, b)`. */
|
|
162
156
|
then<TNext, TRefs2 extends string = never>(
|
|
@@ -260,8 +254,8 @@ export type TypedAction<
|
|
|
260
254
|
* as TypedAction but without methods.
|
|
261
255
|
*
|
|
262
256
|
* Invariance: Both In and Out are invariant, matching TypedAction:
|
|
263
|
-
* In:
|
|
264
|
-
* Out:
|
|
257
|
+
* In: __in (contravariant) + __in_co (covariant) → invariant
|
|
258
|
+
* Out: __out (covariant) + __out_contra (contravariant) → invariant
|
|
265
259
|
*
|
|
266
260
|
* Why no methods: TypedAction's methods (then, branch, etc.) participate in
|
|
267
261
|
* TS assignability checks in complex, recursive ways that interfere with
|
|
@@ -278,20 +272,20 @@ export type Pipeable<
|
|
|
278
272
|
Out = unknown,
|
|
279
273
|
Refs extends string = never,
|
|
280
274
|
> = Action & {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
275
|
+
__in?: (input: In) => void;
|
|
276
|
+
__in_co?: In;
|
|
277
|
+
__out?: () => Out;
|
|
278
|
+
__out_contra?: (output: Out) => void;
|
|
285
279
|
__refs?: { _brand: Refs };
|
|
286
280
|
};
|
|
287
281
|
|
|
288
282
|
/**
|
|
289
283
|
* Contravariant-only input checking for branch case handler positions.
|
|
290
284
|
*
|
|
291
|
-
* Omits
|
|
285
|
+
* Omits __in_co (covariant input) and __out_contra (contravariant output)
|
|
292
286
|
* compared to TypedAction/Pipeable. This gives:
|
|
293
|
-
* In: contravariant only (via
|
|
294
|
-
* Out: covariant only (via
|
|
287
|
+
* In: contravariant only (via __in)
|
|
288
|
+
* Out: covariant only (via __out)
|
|
295
289
|
*
|
|
296
290
|
* Why contravariant input: a handler that accepts `unknown` (like drop)
|
|
297
291
|
* can handle any variant. (input: unknown) => void is assignable to
|
|
@@ -299,7 +293,7 @@ export type Pipeable<
|
|
|
299
293
|
*
|
|
300
294
|
* Why covariant output: the constraint doesn't restrict output types —
|
|
301
295
|
* they're inferred from the actual case handlers via ExtractOutput.
|
|
302
|
-
* TypedAction's invariant
|
|
296
|
+
* TypedAction's invariant __out_contra with Out=unknown would
|
|
303
297
|
* reject any handler with a specific output type, so we omit it.
|
|
304
298
|
*
|
|
305
299
|
* TypedAction is assignable to CaseHandler because CaseHandler only
|
|
@@ -310,8 +304,8 @@ type CaseHandler<
|
|
|
310
304
|
TOut = unknown,
|
|
311
305
|
TRefs extends string = never,
|
|
312
306
|
> = Action & {
|
|
313
|
-
|
|
314
|
-
|
|
307
|
+
__in?: (input: TIn) => void;
|
|
308
|
+
__out?: () => TOut;
|
|
315
309
|
__refs?: { _brand: TRefs };
|
|
316
310
|
};
|
|
317
311
|
|
|
@@ -629,10 +623,10 @@ export function typedAction<
|
|
|
629
623
|
*
|
|
630
624
|
* Uses direct phantom field extraction (not full TypedAction matching) to
|
|
631
625
|
* avoid the `TypedAction<any, any, any>` constraint which fails for In=never
|
|
632
|
-
* due to
|
|
626
|
+
* due to __in contravariance.
|
|
633
627
|
*/
|
|
634
628
|
export type ExtractInput<T> = T extends {
|
|
635
|
-
|
|
629
|
+
__in?: (input: infer In) => void;
|
|
636
630
|
}
|
|
637
631
|
? In
|
|
638
632
|
: never;
|
|
@@ -642,7 +636,7 @@ export type ExtractInput<T> = T extends {
|
|
|
642
636
|
*
|
|
643
637
|
* Uses direct phantom field extraction to avoid constraint issues.
|
|
644
638
|
*/
|
|
645
|
-
export type ExtractOutput<T> = T extends {
|
|
639
|
+
export type ExtractOutput<T> = T extends { __out?: () => infer Out }
|
|
646
640
|
? Out
|
|
647
641
|
: never;
|
|
648
642
|
|
|
@@ -654,6 +648,7 @@ export { pipe } from "./pipe.js";
|
|
|
654
648
|
export { chain } from "./chain.js";
|
|
655
649
|
export { all } from "./all.js";
|
|
656
650
|
export { bind, bindInput, type VarRef, type InferVarRefs } from "./bind.js";
|
|
651
|
+
export { defineRecursiveFunctions } from "./recursive.js";
|
|
657
652
|
export { resetEffectIdCounter } from "./effect-id.js";
|
|
658
653
|
import {
|
|
659
654
|
allocateRestartHandlerId,
|
package/src/bind.ts
CHANGED
|
@@ -41,7 +41,7 @@ function createVarRef<TValue>(
|
|
|
41
41
|
*
|
|
42
42
|
* Constraint is `Action[]` (not `Pipeable<any, any>[]`) because
|
|
43
43
|
* `TypedAction<never, X>` (e.g. from `constant()`) fails the invariant
|
|
44
|
-
* `
|
|
44
|
+
* `__in` check against `Pipeable<any, any>` on the 9-variant
|
|
45
45
|
* Action union. Using raw `Action[]` avoids the phantom field
|
|
46
46
|
* assignability issue while `ExtractOutput` still extracts the correct
|
|
47
47
|
* output type from the phantom fields on the concrete types.
|
|
@@ -118,17 +118,12 @@ function readVar(n: number): Action {
|
|
|
118
118
|
*/
|
|
119
119
|
/**
|
|
120
120
|
* Constraint for the body callback return type. Only requires the output
|
|
121
|
-
* phantom fields — omits `
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* This is necessary because `TypedAction<never, X>` is not assignable to
|
|
125
|
-
* `Pipeable<any, X>`: the contravariant `__phantom_in` field check fails
|
|
126
|
-
* since `(input: never) => void` is not assignable to `(input: any) => void`
|
|
127
|
-
* when distributed across the 9-variant Action union.
|
|
121
|
+
* phantom fields — omits `__in` and `__in_co` so that body actions with
|
|
122
|
+
* `In = never` (e.g. pipelines starting from a VarRef) are assignable.
|
|
128
123
|
*/
|
|
129
124
|
type BodyResult<TOut> = Action & {
|
|
130
|
-
|
|
131
|
-
|
|
125
|
+
__out?: () => TOut;
|
|
126
|
+
__out_contra?: (output: TOut) => void;
|
|
132
127
|
};
|
|
133
128
|
|
|
134
129
|
export function bind<TBindings extends Action[], TOut>(
|
package/src/builtins.ts
CHANGED
|
@@ -686,8 +686,8 @@ export const Result = {
|
|
|
686
686
|
*/
|
|
687
687
|
unwrapOr<TValue, TError>(
|
|
688
688
|
defaultAction: Action & {
|
|
689
|
-
|
|
690
|
-
|
|
689
|
+
__in?: (input: TError) => void;
|
|
690
|
+
__out?: () => TValue;
|
|
691
691
|
},
|
|
692
692
|
): TypedAction<ResultT<TValue, TError>, TValue> {
|
|
693
693
|
return typedAction(resultBranch(IDENTITY, defaultAction as Action));
|
package/src/handler.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fileURLToPath } from "node:url";
|
|
2
|
+
import type { JSONSchema7 } from "json-schema";
|
|
2
3
|
import type { z } from "zod";
|
|
3
4
|
import { type TypedAction, typedAction } from "./ast.js";
|
|
4
5
|
import { zodToCheckedJsonSchema } from "./schema.js";
|
|
@@ -182,12 +183,28 @@ export function createHandlerWithConfig(
|
|
|
182
183
|
const filePath = getCallerFilePath();
|
|
183
184
|
const funcName = exportName ?? "default";
|
|
184
185
|
|
|
185
|
-
|
|
186
|
+
// The invoke receives [value, config] from All(Identity, Constant(config)).
|
|
187
|
+
// Build a tuple schema manually — the Rust engine doesn't support draft-07
|
|
188
|
+
// array-form `items` for tuples, so use `prefixItems` (2020-12 style).
|
|
189
|
+
const valueSchema = definition.inputValidator
|
|
186
190
|
? zodToCheckedJsonSchema(
|
|
187
191
|
definition.inputValidator,
|
|
188
192
|
`${filePath}:${funcName} input`,
|
|
189
193
|
)
|
|
190
|
-
:
|
|
194
|
+
: {};
|
|
195
|
+
const configSchema = definition.stepConfigValidator
|
|
196
|
+
? zodToCheckedJsonSchema(
|
|
197
|
+
definition.stepConfigValidator,
|
|
198
|
+
`${filePath}:${funcName} stepConfig`,
|
|
199
|
+
)
|
|
200
|
+
: {};
|
|
201
|
+
const inputSchema: JSONSchema7 = {
|
|
202
|
+
type: "array",
|
|
203
|
+
prefixItems: [valueSchema, configSchema],
|
|
204
|
+
items: false,
|
|
205
|
+
minItems: 2,
|
|
206
|
+
maxItems: 2,
|
|
207
|
+
} as JSONSchema7;
|
|
191
208
|
const outputSchema = definition.outputValidator
|
|
192
209
|
? zodToCheckedJsonSchema(
|
|
193
210
|
definition.outputValidator,
|
|
@@ -209,7 +226,7 @@ export function createHandlerWithConfig(
|
|
|
209
226
|
kind: "TypeScript",
|
|
210
227
|
module: filePath,
|
|
211
228
|
func: funcName,
|
|
212
|
-
|
|
229
|
+
input_schema: inputSchema,
|
|
213
230
|
...(outputSchema && { output_schema: outputSchema }),
|
|
214
231
|
},
|
|
215
232
|
});
|
package/src/recursive.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Action,
|
|
3
|
+
type Pipeable,
|
|
4
|
+
type TypedAction,
|
|
5
|
+
typedAction,
|
|
6
|
+
branch,
|
|
7
|
+
} from "./ast.js";
|
|
8
|
+
import { all } from "./all.js";
|
|
9
|
+
import { chain } from "./chain.js";
|
|
10
|
+
import {
|
|
11
|
+
constant,
|
|
12
|
+
identity,
|
|
13
|
+
extractField,
|
|
14
|
+
extractIndex,
|
|
15
|
+
tag,
|
|
16
|
+
} from "./builtins.js";
|
|
17
|
+
import { allocateResumeHandlerId } from "./effect-id.js";
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Types
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
type FunctionDef = [input: unknown, output: unknown];
|
|
24
|
+
|
|
25
|
+
type FunctionRefs<TDefs extends FunctionDef[]> = {
|
|
26
|
+
[K in keyof TDefs]: TypedAction<TDefs[K][0], TDefs[K][1]>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Constraint for the entry-point callback return type. Only requires the
|
|
31
|
+
* output phantom fields — omits __in and __in_co so that actions with
|
|
32
|
+
* In = never (e.g. pipelines starting from a call token) are assignable.
|
|
33
|
+
*/
|
|
34
|
+
type BodyResult<TOut> = Action & {
|
|
35
|
+
__out?: () => TOut;
|
|
36
|
+
__out_contra?: (output: TOut) => void;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
+
const UNUSED_STATE: any = undefined;
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// defineRecursiveFunctions
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Define mutually recursive functions that can call each other.
|
|
48
|
+
*
|
|
49
|
+
* The type parameter is an array of [In, Out] tuples — one per function.
|
|
50
|
+
* TypeScript can't infer these from circular definitions, so they must be
|
|
51
|
+
* explicit.
|
|
52
|
+
*
|
|
53
|
+
* Returns a curried combinator: the first callback defines function bodies,
|
|
54
|
+
* the second receives the same call tokens and returns the workflow entry
|
|
55
|
+
* point.
|
|
56
|
+
*
|
|
57
|
+
* Desugars to a ResumeHandle with a Branch-based handler. Each call token
|
|
58
|
+
* is Chain(Tag("CallN"), ResumePerform(id)). The handler dispatches to the
|
|
59
|
+
* correct function body by tag. The caller's pipeline is preserved as a
|
|
60
|
+
* ResumePerformFrame across each call.
|
|
61
|
+
*/
|
|
62
|
+
export function defineRecursiveFunctions<TDefs extends FunctionDef[]>(
|
|
63
|
+
bodiesFn: (...fns: FunctionRefs<TDefs>) => {
|
|
64
|
+
[K in keyof TDefs]: Pipeable<TDefs[K][0], TDefs[K][1]>;
|
|
65
|
+
},
|
|
66
|
+
): <TOut>(
|
|
67
|
+
entryFn: (...fns: FunctionRefs<TDefs>) => BodyResult<TOut>,
|
|
68
|
+
) => TypedAction<any, TOut> {
|
|
69
|
+
const resumeHandlerId = allocateResumeHandlerId();
|
|
70
|
+
|
|
71
|
+
const resumePerform: Action = {
|
|
72
|
+
kind: "ResumePerform",
|
|
73
|
+
resume_handler_id: resumeHandlerId,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Call tokens: Chain(Tag("CallN"), ResumePerform(resumeHandlerId))
|
|
77
|
+
const fnCount = bodiesFn.length;
|
|
78
|
+
const callTokens = Array.from({ length: fnCount }, (_, i) =>
|
|
79
|
+
typedAction(chain(tag(`Call${i}`), resumePerform as any) as Action),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Get function body ASTs
|
|
83
|
+
const bodyActions = bodiesFn(
|
|
84
|
+
...(callTokens as FunctionRefs<TDefs>),
|
|
85
|
+
) as Action[];
|
|
86
|
+
|
|
87
|
+
// Branch cases: CallN → ExtractField("value") → bodyN
|
|
88
|
+
const cases: Record<string, Action> = {};
|
|
89
|
+
for (let i = 0; i < bodyActions.length; i++) {
|
|
90
|
+
cases[`Call${i}`] = chain(
|
|
91
|
+
extractField("value"),
|
|
92
|
+
bodyActions[i] as any,
|
|
93
|
+
) as Action;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Return curried entry-point combinator
|
|
97
|
+
return <TOut>(entryFn: (...fns: FunctionRefs<TDefs>) => BodyResult<TOut>) => {
|
|
98
|
+
const userBody = entryFn(...(callTokens as FunctionRefs<TDefs>)) as Action;
|
|
99
|
+
|
|
100
|
+
return typedAction<any, TOut>(
|
|
101
|
+
chain(all(identity, constant(UNUSED_STATE)), {
|
|
102
|
+
kind: "ResumeHandle",
|
|
103
|
+
resume_handler_id: resumeHandlerId,
|
|
104
|
+
body: chain(extractIndex(0), userBody as any) as Action,
|
|
105
|
+
handler: all(
|
|
106
|
+
chain(extractIndex(0), branch(cases) as any),
|
|
107
|
+
constant(UNUSED_STATE),
|
|
108
|
+
) as Action,
|
|
109
|
+
} as Action) as Action,
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
}
|