@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.
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barnum/barnum",
3
- "version": "0.0.0-main-e8b82cff",
3
+ "version": "0.0.0-main-00082d0f",
4
4
  "description": "Barnum workflow engine",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
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
- * Invariance is enforced through paired covariant/contravariant phantom fields:
138
- *
139
- * In: __phantom_in (contravariant) + __in (covariant) → invariant
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
- __phantom_in?: (input: In) => void;
157
- __phantom_out?: () => Out;
158
- __phantom_out_check?: (output: Out) => void;
159
- __in?: In;
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: __phantom_in (contravariant) + __in (covariant) → invariant
264
- * Out: __phantom_out (covariant) + __phantom_out_check (contravariant) → invariant
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
- __phantom_in?: (input: In) => void;
282
- __phantom_out?: () => Out;
283
- __phantom_out_check?: (output: Out) => void;
284
- __in?: In;
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 __in (covariant input) and __phantom_out_check (contravariant output)
285
+ * Omits __in_co (covariant input) and __out_contra (contravariant output)
292
286
  * compared to TypedAction/Pipeable. This gives:
293
- * In: contravariant only (via __phantom_in)
294
- * Out: covariant only (via __phantom_out)
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 __phantom_out_check with Out=unknown would
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
- __phantom_in?: (input: TIn) => void;
314
- __phantom_out?: () => TOut;
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 __phantom_in contravariance.
626
+ * due to __in contravariance.
633
627
  */
634
628
  export type ExtractInput<T> = T extends {
635
- __phantom_in?: (input: infer In) => void;
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 { __phantom_out?: () => infer Out }
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
- * `__phantom_in` check against `Pipeable<any, any>` on the 9-variant
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 `__phantom_in` and `__in` so that body actions
122
- * with `In = never` (e.g. pipelines starting from a VarRef) are assignable.
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
- __phantom_out?: () => TOut;
131
- __phantom_out_check?: (output: TOut) => void;
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
- __phantom_in?: (input: TError) => void;
690
- __phantom_out?: () => TValue;
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
- const inputSchema = definition.inputValidator
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
- : undefined;
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
- ...(inputSchema && { input_schema: inputSchema }),
229
+ input_schema: inputSchema,
213
230
  ...(outputSchema && { output_schema: outputSchema }),
214
231
  },
215
232
  });
@@ -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
+ }