@barnum/barnum 0.3.0 → 0.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.
Files changed (109) hide show
  1. package/artifacts/linux-arm64/barnum +0 -0
  2. package/artifacts/linux-x64/barnum +0 -0
  3. package/artifacts/macos-arm64/barnum +0 -0
  4. package/artifacts/macos-x64/barnum +0 -0
  5. package/artifacts/win-x64/barnum.exe +0 -0
  6. package/dist/all.d.ts +41 -10
  7. package/dist/all.d.ts.map +1 -0
  8. package/dist/all.js +1 -1
  9. package/dist/ast.d.ts +199 -98
  10. package/dist/ast.d.ts.map +1 -0
  11. package/dist/ast.js +271 -233
  12. package/dist/bind.d.ts +9 -12
  13. package/dist/bind.d.ts.map +1 -0
  14. package/dist/bind.js +14 -51
  15. package/dist/builtins/array.d.ts +36 -0
  16. package/dist/builtins/array.d.ts.map +1 -0
  17. package/dist/builtins/array.js +93 -0
  18. package/dist/builtins/index.d.ts +6 -0
  19. package/dist/builtins/index.d.ts.map +1 -0
  20. package/dist/builtins/index.js +5 -0
  21. package/dist/builtins/scalar.d.ts +12 -0
  22. package/dist/builtins/scalar.d.ts.map +1 -0
  23. package/dist/builtins/scalar.js +41 -0
  24. package/dist/builtins/struct.d.ts +25 -0
  25. package/dist/builtins/struct.d.ts.map +1 -0
  26. package/dist/builtins/struct.js +67 -0
  27. package/dist/builtins/tagged-union.d.ts +54 -0
  28. package/dist/builtins/tagged-union.d.ts.map +1 -0
  29. package/dist/builtins/tagged-union.js +81 -0
  30. package/dist/builtins/with-resource.d.ts +23 -0
  31. package/dist/builtins/with-resource.d.ts.map +1 -0
  32. package/dist/builtins/with-resource.js +35 -0
  33. package/dist/chain.d.ts +1 -0
  34. package/dist/chain.d.ts.map +1 -0
  35. package/dist/chain.js +3 -3
  36. package/dist/effect-id.d.ts +1 -0
  37. package/dist/effect-id.d.ts.map +1 -0
  38. package/dist/handler.d.ts +7 -6
  39. package/dist/handler.d.ts.map +1 -0
  40. package/dist/handler.js +5 -21
  41. package/dist/index.d.ts +10 -6
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +4 -2
  44. package/dist/iterator.d.ts +32 -0
  45. package/dist/iterator.d.ts.map +1 -0
  46. package/dist/iterator.js +123 -0
  47. package/dist/option.d.ts +74 -0
  48. package/dist/option.d.ts.map +1 -0
  49. package/dist/option.js +141 -0
  50. package/dist/pipe.d.ts +11 -10
  51. package/dist/pipe.d.ts.map +1 -0
  52. package/dist/pipe.js +5 -4
  53. package/dist/race.d.ts +5 -4
  54. package/dist/race.d.ts.map +1 -0
  55. package/dist/race.js +17 -42
  56. package/dist/recursive.d.ts +9 -3
  57. package/dist/recursive.d.ts.map +1 -0
  58. package/dist/recursive.js +18 -13
  59. package/dist/result.d.ts +50 -0
  60. package/dist/result.d.ts.map +1 -0
  61. package/dist/result.js +117 -0
  62. package/dist/run.d.ts +9 -2
  63. package/dist/run.d.ts.map +1 -0
  64. package/dist/run.js +37 -20
  65. package/dist/runtime.d.ts +6 -0
  66. package/dist/runtime.d.ts.map +1 -0
  67. package/dist/runtime.js +7 -0
  68. package/dist/schema.d.ts +1 -0
  69. package/dist/schema.d.ts.map +1 -0
  70. package/dist/schemas.d.ts +5 -0
  71. package/dist/schemas.d.ts.map +1 -0
  72. package/dist/schemas.js +13 -0
  73. package/dist/try-catch.d.ts +2 -1
  74. package/dist/try-catch.d.ts.map +1 -0
  75. package/dist/try-catch.js +10 -9
  76. package/dist/values.d.ts +6 -0
  77. package/dist/values.d.ts.map +1 -0
  78. package/dist/values.js +12 -0
  79. package/dist/worker.d.ts +5 -1
  80. package/dist/worker.d.ts.map +1 -0
  81. package/dist/worker.js +15 -3
  82. package/package.json +8 -6
  83. package/src/all.ts +118 -74
  84. package/src/ast.ts +773 -350
  85. package/src/bind.ts +32 -62
  86. package/src/builtins/array.ts +121 -0
  87. package/src/builtins/index.ts +17 -0
  88. package/src/builtins/scalar.ts +49 -0
  89. package/src/builtins/struct.ts +111 -0
  90. package/src/builtins/tagged-union.ts +142 -0
  91. package/src/builtins/with-resource.ts +69 -0
  92. package/src/chain.ts +4 -4
  93. package/src/handler.ts +12 -28
  94. package/src/index.ts +24 -17
  95. package/src/iterator.ts +243 -0
  96. package/src/option.ts +199 -0
  97. package/src/pipe.ts +123 -78
  98. package/src/race.ts +41 -51
  99. package/src/recursive.ts +44 -27
  100. package/src/result.ts +168 -0
  101. package/src/run.ts +53 -25
  102. package/src/runtime.ts +16 -0
  103. package/src/schemas.ts +21 -0
  104. package/src/try-catch.ts +14 -10
  105. package/src/values.ts +21 -0
  106. package/src/worker.ts +17 -2
  107. package/dist/builtins.d.ts +0 -257
  108. package/dist/builtins.js +0 -600
  109. package/src/builtins.ts +0 -804
package/src/ast.ts CHANGED
@@ -1,4 +1,34 @@
1
1
  import type { JSONSchema7 } from "json-schema";
2
+ import { chain } from "./chain.js";
3
+ import {
4
+ constant,
5
+ drop,
6
+ extractPrefix,
7
+ flatten as flattenBuiltin,
8
+ getField,
9
+ getIndex,
10
+ identity,
11
+ panic,
12
+ pick,
13
+ splitFirst,
14
+ splitLast,
15
+ tag,
16
+ wrapInField,
17
+ asOption as asOptionStandalone,
18
+ } from "./builtins/index.js";
19
+ import { Option } from "./option.js";
20
+ import { Result } from "./result.js";
21
+ // Lazy import — iterator.ts imports from ast.ts, but these are only called inside
22
+ // methods (after all modules load), so the circular reference is safe at runtime.
23
+ import { Iterator as IteratorNs } from "./iterator.js";
24
+ // Lazy import — bind.ts imports from ast.ts, but these are only called inside
25
+ // methods (after all modules load), so the circular reference is safe at runtime.
26
+ import {
27
+ bind as bindStandalone,
28
+ bindInput as bindInputStandalone,
29
+ type VarRef,
30
+ type InferVarRefs,
31
+ } from "./bind.js";
2
32
 
3
33
  // ---------------------------------------------------------------------------
4
34
  // Serializable Types — mirror the Rust AST in barnum_ast
@@ -88,20 +118,32 @@ export type BuiltinKind =
88
118
  | { kind: "Constant"; value: unknown }
89
119
  | { kind: "Identity" }
90
120
  | { kind: "Drop" }
91
- | { kind: "Tag"; value: string }
92
121
  | { kind: "Merge" }
93
122
  | { kind: "Flatten" }
94
- | { kind: "ExtractField"; value: string }
95
- | { kind: "ExtractIndex"; value: number }
96
- | { kind: "Pick"; value: string[] }
123
+ | { kind: "GetField"; field: string }
124
+ | { kind: "GetIndex"; index: number }
97
125
  | { kind: "CollectSome" }
98
- | { kind: "Sleep"; value: number };
126
+ // Future: Add WrapInArray builtin (T → [T]). Currently done via all(identity()) which
127
+ // works but routes through the All executor for a trivial operation.
128
+ | { kind: "AsOption" }
129
+ | { kind: "SplitFirst" }
130
+ | { kind: "SplitLast" }
131
+ | { kind: "WrapInField"; field: string }
132
+ | { kind: "Sleep"; ms: number }
133
+ | { kind: "Panic"; message: string }
134
+ | { kind: "ExtractPrefix" }
135
+ | { kind: "Slice"; start: number; end?: number };
99
136
 
100
137
  /**
101
- * When TIn is `never` (handler ignores input), produce `any` so the
102
- * combinator/pipe can sit in any pipeline position.
138
+ * When T is `never` or `void` (handler ignores input / recur doesn't
139
+ * thread state), produce `any` so the combinator can sit in any
140
+ * pipeline position.
103
141
  */
104
- export type PipeIn<T> = [T] extends [never] ? any : T;
142
+ export type PipeIn<T> = [T] extends [never]
143
+ ? any
144
+ : [T] extends [void]
145
+ ? any
146
+ : T;
105
147
 
106
148
  // ---------------------------------------------------------------------------
107
149
  // Config
@@ -132,34 +174,26 @@ export type MergeTuple<TTuple> = TTuple extends unknown[]
132
174
  // ---------------------------------------------------------------------------
133
175
 
134
176
  /**
135
- * An action with tracked input/output types. Phantom fields enforce invariance
177
+ * An action with tracked input/output types. Phantom fields enforce variance
136
178
  * and are never set at runtime — they exist only for the TypeScript compiler.
137
179
  *
138
- * Each type variable gets a contravariant + covariant field pair:
139
180
  * In: __in (contravariant) + __in_co (covariant) → invariant
140
- * Out: __out (covariant) + __out_contra (contravariant) → invariant
181
+ * Out: __out (covariant only)
141
182
  *
142
- * This ensures exact type matching at every pipeline connection point.
183
+ * Input invariance ensures exact type matching at pipeline connection points.
143
184
  * Data crosses serialization boundaries to handlers in arbitrary languages
144
185
  * (Rust, Python, etc.), so extra/missing fields are runtime errors.
186
+ *
187
+ * Output covariance is safe — a step producing Dog where Animal is expected
188
+ * downstream works. `never` (throwError, recur, done) is assignable to any
189
+ * output slot via standard subtyping.
145
190
  */
146
- export type TypedAction<
147
- In = unknown,
148
- Out = unknown,
149
- Refs extends string = never,
150
- > = Action & {
191
+ export type TypedAction<In = unknown, Out = unknown> = Action & {
151
192
  __in?: (input: In) => void;
152
193
  __in_co?: In;
153
194
  __out?: () => Out;
154
- __out_contra?: (output: Out) => void;
155
- __refs?: { _brand: Refs };
156
195
  /** Chain this action with another. `a.then(b)` ≡ `chain(a, b)`. */
157
- then<TNext>(next: Pipeable<Out, TNext>): TypedAction<In, TNext, Refs>;
158
- /** Apply an action to each element of an array output. `a.forEach(b)` ≡ `a.then(forEach(b))`. */
159
- forEach<TIn, TElement, TNext, TRefs extends string>(
160
- this: TypedAction<TIn, TElement[], TRefs>,
161
- action: Pipeable<TElement, TNext>,
162
- ): TypedAction<TIn, TNext[], TRefs>;
196
+ then<TNext>(next: Pipeable<Out, TNext>): TypedAction<In, TNext>;
163
197
  /** Dispatch on a tagged union output. Auto-unwraps `value` before each case handler. */
164
198
  branch<
165
199
  TCases extends {
@@ -167,91 +201,281 @@ export type TypedAction<
167
201
  },
168
202
  >(
169
203
  cases: [BranchKeys<Out>] extends [never] ? never : TCases,
170
- ): TypedAction<In, ExtractOutput<TCases[keyof TCases & string]>, Refs>;
171
- /** Flatten a nested array output. `a.flatten()` `pipe(a, flatten())`. */
172
- flatten(): TypedAction<
173
- In,
174
- Out extends (infer TElement)[][] ? TElement[] : Out,
175
- Refs
176
- >;
204
+ ): TypedAction<In, ExtractOutput<TCases[keyof TCases & string]>>;
205
+ /** Flatten one level of array nesting. `TElement[][] TElement[]` */
206
+ flatten<TIn, TElement>(
207
+ this: TypedAction<TIn, TElement[][]>,
208
+ ): TypedAction<TIn, TElement[]>;
177
209
  /** Discard output. `a.drop()` ≡ `pipe(a, drop)`. */
178
- drop(): TypedAction<In, never, Refs>;
210
+ drop(): TypedAction<In, void>;
179
211
  /** Wrap output as a tagged union member. Requires full variant map TDef so __def is carried. */
180
- tag<TDef extends Record<string, unknown>, TKind extends keyof TDef & string>(
212
+ tag<
213
+ TEnumName extends string,
214
+ TDef extends Record<string, unknown>,
215
+ TKind extends keyof TDef & string,
216
+ >(
181
217
  kind: TKind,
182
- ): TypedAction<In, TaggedUnion<TDef>, Refs>;
183
- /** Extract a field from the output object. `a.get("name")` `pipe(a, extractField("name"))`. */
184
- get<TField extends keyof Out & string>(
218
+ enumName: TEnumName,
219
+ ): TypedAction<In, TaggedUnion<TEnumName, TDef>>;
220
+ /** Wrap output as `Option.Some`. `T → Option<T>` */
221
+ some(): TypedAction<In, Option<Out>>;
222
+ /** Wrap output as `Result.Ok`. `T → Result<T, never>` */
223
+ ok(): TypedAction<In, Result<Out, never>>;
224
+ /** Wrap output as `Result.Err`. `T → Result<never, T>` */
225
+ err(): TypedAction<In, Result<never, Out>>;
226
+ /** Extract a field from the output object. `a.getField("name")` ≡ `pipe(a, getField("name"))`. */
227
+ getField<TField extends keyof Out & string>(
185
228
  field: TField,
186
- ): TypedAction<In, Out[TField], Refs>;
187
- /**
188
- * Run this sub-pipeline, then merge its output back into the original input.
189
- * `pipe(extractField("x"), transform).augment()` takes `In`, runs the
190
- * sub-pipeline to get `Out`, and returns `In & Out`.
191
- *
192
- * Unlike the standalone `augment()` function, the postfix form has access
193
- * to `In` so the intersection types correctly.
194
- */
195
- augment(): TypedAction<In, In & Out, Refs>;
196
- /** Merge a tuple of objects into a single object. `a.merge()` ≡ `pipe(a, merge())`. */
197
- merge(): TypedAction<In, MergeTuple<Out>, Refs>;
229
+ ): TypedAction<In, Out[TField]>;
230
+ /** Extract an element from the output array by index. Returns Option. */
231
+ getIndex<TIn, TTuple extends unknown[], TIndex extends number>(
232
+ this: TypedAction<TIn, TTuple>,
233
+ index: TIndex,
234
+ ): TypedAction<TIn, Option<TTuple[TIndex]>>;
235
+ /** Wrap output in an object under a field name. `a.wrapInField("foo")` ≡ `pipe(a, wrapInField("foo"))`. */
236
+ wrapInField<TField extends string>(
237
+ field: TField,
238
+ ): TypedAction<In, Record<TField, Out>>;
198
239
  /** Select fields from the output. `a.pick("x", "y")` ≡ `pipe(a, pick("x", "y"))`. */
199
240
  pick<TKeys extends (keyof Out & string)[]>(
200
241
  ...keys: TKeys
201
- ): TypedAction<In, Pick<Out, TKeys[number]>, Refs>;
242
+ ): TypedAction<In, Pick<Out, TKeys[number]>>;
243
+ /** Head/tail decomposition for Iterator. `Iterator<T> → Option<[T, Iterator<T>]>` */
244
+ splitFirst<TIn, TElement>(
245
+ this: TypedAction<TIn, Iterator<TElement>>,
246
+ ): TypedAction<TIn, Option<[TElement, Iterator<TElement>]>>;
247
+ /** Head/tail decomposition. Only callable when Out is TElement[]. */
248
+ splitFirst<TIn, TElement>(
249
+ this: TypedAction<TIn, TElement[]>,
250
+ ): TypedAction<TIn, Option<[TElement, TElement[]]>>;
251
+ /** Init/last decomposition for Iterator. `Iterator<T> → Option<[Iterator<T>, T]>` */
252
+ splitLast<TIn, TElement>(
253
+ this: TypedAction<TIn, Iterator<TElement>>,
254
+ ): TypedAction<TIn, Option<[Iterator<TElement>, TElement]>>;
255
+ /** Init/last decomposition. Only callable when Out is TElement[]. */
256
+ splitLast<TIn, TElement>(
257
+ this: TypedAction<TIn, TElement[]>,
258
+ ): TypedAction<TIn, Option<[TElement[], TElement]>>;
202
259
  /**
203
- * Transform the Some value inside an Option output. Only callable when
204
- * Out is Option<T>. Uses `this` parameter constraint to gate availability.
260
+ * Transform the inner value. Dispatches: Option.map, Result.map.
205
261
  */
206
- mapOption<TIn, T, U, TRefs extends string>(
207
- this: TypedAction<TIn, Option<T>, TRefs>,
262
+ map<TIn, T, U>(
263
+ this: TypedAction<TIn, Option<T>>,
208
264
  action: Pipeable<T, U>,
209
- ): TypedAction<TIn, Option<U>, TRefs>;
265
+ ): TypedAction<TIn, Option<U>>;
266
+ map<TIn, TValue, TOut, TError>(
267
+ this: TypedAction<TIn, Result<TValue, TError>>,
268
+ action: Pipeable<TValue, TOut>,
269
+ ): TypedAction<TIn, Result<TOut, TError>>;
270
+ /** Transform each element in Iterator. `Iterator<T> → Iterator<U>` */
271
+ map<TIn, TElement, TOut>(
272
+ this: TypedAction<TIn, Iterator<TElement>>,
273
+ action: Pipeable<TElement, TOut>,
274
+ ): TypedAction<TIn, Iterator<TOut>>;
210
275
  /**
211
276
  * Transform the Err value of a Result output.
212
277
  * `Result<TValue, TError> → Result<TValue, TErrorOut>`
213
- *
214
- * Only callable when Out is Result<TValue, TError>.
215
278
  */
216
279
  mapErr<TIn, TValue, TError, TErrorOut>(
217
- this: TypedAction<TIn, Result<TValue, TError>, any>,
280
+ this: TypedAction<TIn, Result<TValue, TError>>,
218
281
  action: Pipeable<TError, TErrorOut>,
219
- ): TypedAction<TIn, Result<TValue, TErrorOut>, Refs>;
282
+ ): TypedAction<TIn, Result<TValue, TErrorOut>>;
220
283
  /**
221
- * Unwrap a Result output. If Ok, pass through the value. If Err, apply
222
- * the default action. Only callable when Out is Result<TValue, TError>.
284
+ * Unwrap or panic. Dispatches: Option.unwrap, Result.unwrap.
223
285
  *
224
- * The `this` constraint provides TValue from context, so throw tokens
225
- * (Out=never) work without explicit type parameters:
226
- * `handler.unwrapOr(throwError)`
286
+ * Option: If Some, pass through value. If None, panic.
287
+ * Result: If Ok, pass through value. If Err, panic.
288
+ */
289
+ unwrap<TIn, TValue>(
290
+ this: TypedAction<TIn, Option<TValue>>,
291
+ ): TypedAction<TIn, TValue>;
292
+ unwrap<TIn, TValue, TError>(
293
+ this: TypedAction<TIn, Result<TValue, TError>>,
294
+ ): TypedAction<TIn, TValue>;
295
+
296
+ /**
297
+ * Unwrap a union output. Dispatches: Option.unwrapOr, Result.unwrapOr.
227
298
  *
228
- * Uses CaseHandler for defaultAction (covariant output only) so that
229
- * `TypedAction<TError, never>` is assignable to `CaseHandler<TError, TValue>`.
299
+ * Option: If Some, pass through value. If None, apply default.
300
+ * Result: If Ok, pass through value. If Err, apply default.
230
301
  *
231
- * Refs position uses `any` in the `this` constraint to avoid TS
232
- * falling back to the constraint bound `string` when Refs = never.
233
- * The return type uses the enclosing TypedAction's `Refs` directly.
302
+ * Covariant output makes throw tokens (Out=never) work:
303
+ * `handler.unwrapOr(throwError)`
234
304
  */
305
+ unwrapOr<TIn, TValue>(
306
+ this: TypedAction<TIn, Option<TValue>>,
307
+ defaultAction: Pipeable<void, TValue>,
308
+ ): TypedAction<TIn, TValue>;
235
309
  unwrapOr<TIn, TValue, TError>(
236
- this: TypedAction<TIn, Result<TValue, TError>, any>,
237
- defaultAction: CaseHandler<TError, TValue>,
238
- ): TypedAction<TIn, TValue, Refs>;
310
+ this: TypedAction<TIn, Result<TValue, TError>>,
311
+ defaultAction: Pipeable<TError, TValue>,
312
+ ): TypedAction<TIn, TValue>;
313
+
314
+ /** Monadic bind. Option: `Option<T> → Option<U>`. Result: `Result<T,E> → Result<U,E>`. */
315
+ andThen<TIn, TValue, TOut>(
316
+ this: TypedAction<TIn, Option<TValue>>,
317
+ action: Pipeable<TValue, Option<TOut>>,
318
+ ): TypedAction<TIn, Option<TOut>>;
319
+ andThen<TIn, TValue, TOut, TError>(
320
+ this: TypedAction<TIn, Result<TValue, TError>>,
321
+ action: Pipeable<TValue, Result<TOut, TError>>,
322
+ ): TypedAction<TIn, Result<TOut, TError>>;
323
+
324
+ /** Conditional keep. If Some, apply predicate. If None, stay None. */
325
+ filter<TIn, TValue>(
326
+ this: TypedAction<TIn, Option<TValue>>,
327
+ predicate: Pipeable<TValue, Option<TValue>>,
328
+ ): TypedAction<TIn, Option<TValue>>;
329
+ /** Keep elements where predicate returns true. `Iterator<T> → Iterator<T>` */
330
+ filter<TIn, TElement>(
331
+ this: TypedAction<TIn, Iterator<TElement>>,
332
+ predicate: Pipeable<TElement, boolean>,
333
+ ): TypedAction<TIn, Iterator<TElement>>;
334
+
335
+ /** Test if the value is Some. `Option<T> → boolean` */
336
+ isSome<TIn, TValue>(
337
+ this: TypedAction<TIn, Option<TValue>>,
338
+ ): TypedAction<TIn, boolean>;
339
+
340
+ /** Test if the value is None. `Option<T> → boolean` */
341
+ isNone<TIn, TValue>(
342
+ this: TypedAction<TIn, Option<TValue>>,
343
+ ): TypedAction<TIn, boolean>;
344
+
345
+ /** Collect Some values from an array, discarding Nones. `Option<T>[] → T[]` */
346
+ collect<TIn, TValue>(
347
+ this: TypedAction<TIn, Option<TValue>[]>,
348
+ ): TypedAction<TIn, TValue[]>;
349
+ /** Unwrap Iterator to array. `Iterator<T> → T[]` */
350
+ collect<TIn, TElement>(
351
+ this: TypedAction<TIn, Iterator<TElement>>,
352
+ ): TypedAction<TIn, TElement[]>;
353
+
354
+ /** Fallback on Err. `Result<T,E> → Result<T,F>` */
355
+ or<TIn, TValue, TError, TErrorOut>(
356
+ this: TypedAction<TIn, Result<TValue, TError>>,
357
+ fallback: Pipeable<TError, Result<TValue, TErrorOut>>,
358
+ ): TypedAction<TIn, Result<TValue, TErrorOut>>;
359
+
360
+ /** Convert Ok to Some, Err to None. `Result<T,E> → Option<T>` */
361
+ asOkOption<TIn, TValue, TError>(
362
+ this: TypedAction<TIn, Result<TValue, TError>>,
363
+ ): TypedAction<TIn, Option<TValue>>;
364
+
365
+ /** Convert Err to Some, Ok to None. `Result<T,E> → Option<E>` */
366
+ asErrOption<TIn, TValue, TError>(
367
+ this: TypedAction<TIn, Result<TValue, TError>>,
368
+ ): TypedAction<TIn, Option<TError>>;
369
+
370
+ /** Convert boolean to Option<void>. `boolean → Option<void>` */
371
+ asOption<TIn>(
372
+ this: TypedAction<TIn, boolean>,
373
+ ): TypedAction<TIn, Option<void>>;
374
+
375
+ /** Test if the value is Ok. `Result<T,E> → boolean` */
376
+ isOk<TIn, TValue, TError>(
377
+ this: TypedAction<TIn, Result<TValue, TError>>,
378
+ ): TypedAction<TIn, boolean>;
379
+
380
+ /** Test if the value is Err. `Result<T,E> → boolean` */
381
+ isErr<TIn, TValue, TError>(
382
+ this: TypedAction<TIn, Result<TValue, TError>>,
383
+ ): TypedAction<TIn, boolean>;
384
+
385
+ /** Swap nesting. `Option<Result<T,E>> → Result<Option<T>,E>` or `Result<Option<T>,E> → Option<Result<T,E>>`. */
386
+ transpose<TIn, TValue, TError>(
387
+ this: TypedAction<TIn, Option<Result<TValue, TError>>>,
388
+ ): TypedAction<TIn, Result<Option<TValue>, TError>>;
389
+ transpose<TIn, TValue, TError>(
390
+ this: TypedAction<TIn, Result<Option<TValue>, TError>>,
391
+ ): TypedAction<TIn, Option<Result<TValue, TError>>>;
392
+
393
+ // --- Iterator methods ---
394
+
395
+ /** Enter Iterator from Option. `Option<T> → Iterator<T>` */
396
+ iterate<TIn, TElement>(
397
+ this: TypedAction<TIn, Option<TElement>>,
398
+ ): TypedAction<TIn, Iterator<TElement>>;
399
+ /** Enter Iterator from Result. `Result<T,E> → Iterator<T>` */
400
+ iterate<TIn, TElement, TError>(
401
+ this: TypedAction<TIn, Result<TElement, TError>>,
402
+ ): TypedAction<TIn, Iterator<TElement>>;
403
+ /** Enter Iterator from array. `T[] → Iterator<T>` */
404
+ iterate<TIn, TElement>(
405
+ this: TypedAction<TIn, TElement[]>,
406
+ ): TypedAction<TIn, Iterator<TElement>>;
407
+
408
+ /** Flat-map each element. `f` returns Iterator. `Iterator<T> → Iterator<U>` */
409
+ flatMap<TIn, TElement, TOut>(
410
+ this: TypedAction<TIn, Iterator<TElement>>,
411
+ action: Pipeable<TElement, Iterator<TOut>>,
412
+ ): TypedAction<TIn, Iterator<TOut>>;
413
+ /** Flat-map each element. `f` returns Option. `Iterator<T> → Iterator<U>` */
414
+ flatMap<TIn, TElement, TOut>(
415
+ this: TypedAction<TIn, Iterator<TElement>>,
416
+ action: Pipeable<TElement, Option<TOut>>,
417
+ ): TypedAction<TIn, Iterator<TOut>>;
418
+ /** Flat-map each element. `f` returns Result. `Iterator<T> → Iterator<U>` */
419
+ flatMap<TIn, TElement, TOut, TError>(
420
+ this: TypedAction<TIn, Iterator<TElement>>,
421
+ action: Pipeable<TElement, Result<TOut, TError>>,
422
+ ): TypedAction<TIn, Iterator<TOut>>;
423
+ /** Flat-map each element. `f` returns array. `Iterator<T> → Iterator<U>` */
424
+ flatMap<TIn, TElement, TOut>(
425
+ this: TypedAction<TIn, Iterator<TElement>>,
426
+ action: Pipeable<TElement, TOut[]>,
427
+ ): TypedAction<TIn, Iterator<TOut>>;
428
+
429
+ /** Fold elements with accumulator. `Iterator<T> → TAcc` */
430
+ fold<TIn, TElement, TAcc>(
431
+ this: TypedAction<TIn, Iterator<TElement>>,
432
+ init: Pipeable<void, TAcc>,
433
+ body: Pipeable<[TAcc, TElement], TAcc>,
434
+ ): TypedAction<TIn, TAcc>;
435
+
436
+ /** Check if iterator is empty. `Iterator<T> → boolean` */
437
+ isEmpty<TIn, TElement>(
438
+ this: TypedAction<TIn, Iterator<TElement>>,
439
+ ): TypedAction<TIn, boolean>;
440
+
441
+ /** Slice elements from start to end. `Iterator<T> → Iterator<T>` */
442
+ slice<TIn, TElement>(
443
+ this: TypedAction<TIn, Iterator<TElement>>,
444
+ start: number,
445
+ end?: number,
446
+ ): TypedAction<TIn, Iterator<TElement>>;
447
+
448
+ /** First n elements. `Iterator<T> → Iterator<T>` */
449
+ take<TIn, TElement>(
450
+ this: TypedAction<TIn, Iterator<TElement>>,
451
+ n: number,
452
+ ): TypedAction<TIn, Iterator<TElement>>;
453
+
454
+ /** Drop first n elements. `Iterator<T> → Iterator<T>` */
455
+ skip<TIn, TElement>(
456
+ this: TypedAction<TIn, Iterator<TElement>>,
457
+ n: number,
458
+ ): TypedAction<TIn, Iterator<TElement>>;
459
+
460
+ /** Bind concurrent values as VarRefs available throughout the body. */
461
+ bind<TBindings extends Action[], TOut>(
462
+ bindings: [...TBindings],
463
+ body: (vars: InferVarRefs<TBindings>) => Action & { __out?: () => TOut },
464
+ ): TypedAction<In, TOut>;
465
+ /** Capture the pipeline input as a VarRef. */
466
+ bindInput<TOut>(
467
+ body: (input: VarRef<Out>) => Action & { __out?: () => TOut },
468
+ ): TypedAction<In, TOut>;
239
469
  };
240
470
 
241
471
  /**
242
- * Parameter type for pipe and combinators. Contains the same phantom fields
243
- * as TypedAction but without methods.
244
- *
245
- * Invariance: Both In and Out are invariant, matching TypedAction:
246
- * In: __in (contravariant) + __in_co (covariant) → invariant
247
- * Out: __out (covariant) + __out_contra (contravariant) → invariant
472
+ * Parameter type for pipe and combinators. Same phantom fields as TypedAction
473
+ * but without methods.
248
474
  *
249
475
  * Why no methods: TypedAction's methods (then, branch, etc.) participate in
250
476
  * TS assignability checks in complex, recursive ways that interfere with
251
477
  * generic inference in pipe overloads. Pipeable strips methods so that only
252
- * phantom fields drive inference — predictable covariant/contravariant
253
- * resolution, with invariance enforced when TS checks candidates from
254
- * both sides of a connection.
478
+ * phantom fields drive inference.
255
479
  *
256
480
  * TypedAction (with methods) is assignable to Pipeable because Pipeable
257
481
  * only requires a subset of properties.
@@ -260,25 +484,30 @@ export type Pipeable<In = unknown, Out = unknown> = Action & {
260
484
  __in?: (input: In) => void;
261
485
  __in_co?: In;
262
486
  __out?: () => Out;
263
- __out_contra?: (output: Out) => void;
264
487
  };
265
488
 
266
489
  /**
267
- * Contravariant-only input checking for branch case handler positions.
490
+ * Strip phantom types from a Pipeable, returning a plain Action.
491
+ *
492
+ * Replaces `x as Action` casts throughout the codebase. The constraint
493
+ * ensures the argument is structurally a Pipeable — unlike a bare cast,
494
+ * `toAction(123)` is a type error.
495
+ */
496
+ export function toAction<TIn, TOut>(pipeable: Pipeable<TIn, TOut>): Action {
497
+ return pipeable;
498
+ }
499
+
500
+ /**
501
+ * Contravariant input + covariant output for branch case handler positions.
268
502
  *
269
- * Omits __in_co (covariant input) and __out_contra (contravariant output)
270
- * compared to TypedAction/Pipeable. This gives:
503
+ * Omits __in_co (covariant input) compared to Pipeable. This gives:
271
504
  * In: contravariant only (via __in)
272
505
  * Out: covariant only (via __out)
273
506
  *
274
507
  * Why contravariant input: a handler that accepts `unknown` (like drop)
275
508
  * can handle any variant. (input: unknown) => void is assignable to
276
509
  * (input: HasErrors) => void because HasErrors extends unknown.
277
- *
278
- * Why covariant output: the constraint doesn't restrict output types —
279
- * they're inferred from the actual case handlers via ExtractOutput.
280
- * TypedAction's invariant __out_contra with Out=unknown would
281
- * reject any handler with a specific output type, so we omit it.
510
+ * Pipeable's invariant input (__in_co) would reject this.
282
511
  *
283
512
  * TypedAction is assignable to CaseHandler because CaseHandler only
284
513
  * requires a subset of TypedAction's phantom fields.
@@ -298,6 +527,11 @@ type CaseHandler<TIn = unknown, TOut = unknown> = Action & {
298
527
  * field enables `.branch()` to decompose the union via simple indexing
299
528
  * (`keyof ExtractDef<Out>` and `ExtractDef<Out>[K]`) instead of
300
529
  * conditional types (`KindOf<Out>` and `Extract<Out, { kind: K }>`).
530
+ *
531
+ * **Void → null mapping:** Variants with `void` payload (e.g. `{ None: void }`)
532
+ * become `{ kind: "None"; value: null }` at runtime. This is handled by
533
+ * `VoidToNull` below — `void` has no runtime representation in JSON, so it
534
+ * serializes as `null`. Use `z.null()` in Zod schemas for void variants.
301
535
  */
302
536
  // 0 extends 1 & T detects `any` — preserve as-is to avoid collapsing.
303
537
  type VoidToNull<T> = 0 extends 1 & T
@@ -308,9 +542,12 @@ type VoidToNull<T> = 0 extends 1 & T
308
542
  ? null
309
543
  : T;
310
544
 
311
- export type TaggedUnion<TDef extends Record<string, unknown>> = {
545
+ export type TaggedUnion<
546
+ TEnumName extends string,
547
+ TDef extends Record<string, unknown>,
548
+ > = {
312
549
  [K in keyof TDef & string]: {
313
- kind: K;
550
+ kind: `${TEnumName}.${K}`;
314
551
  value: VoidToNull<TDef[K]>;
315
552
  __def?: TDef;
316
553
  };
@@ -324,36 +561,52 @@ export type ExtractDef<T> = T extends { __def?: infer D } ? D : never;
324
561
  // ---------------------------------------------------------------------------
325
562
 
326
563
  export type OptionDef<T> = { Some: T; None: void };
327
- export type Option<T> = TaggedUnion<OptionDef<T>>;
564
+ export type Option<T> = TaggedUnion<"Option", OptionDef<T>>;
328
565
 
329
566
  // ---------------------------------------------------------------------------
330
567
  // Result<TValue, TError> — standard success/error type
331
568
  // ---------------------------------------------------------------------------
332
569
 
333
570
  export type ResultDef<TValue, TError> = { Ok: TValue; Err: TError };
334
- export type Result<TValue, TError> = TaggedUnion<ResultDef<TValue, TError>>;
571
+ export type Result<TValue, TError> = TaggedUnion<
572
+ "Result",
573
+ ResultDef<TValue, TError>
574
+ >;
575
+
576
+ // ---------------------------------------------------------------------------
577
+ // Iterator<T> — sequence wrapper (single-variant TaggedUnion)
578
+ // ---------------------------------------------------------------------------
579
+
580
+ export type IteratorDef<TElement> = { Iterator: TElement[] };
581
+ export type Iterator<TElement> = TaggedUnion<"Iterator", IteratorDef<TElement>>;
335
582
 
336
583
  /** Extract all `kind` string literals from a discriminated union. */
337
584
  type KindOf<T> = T extends { kind: infer K extends string } ? K : never;
338
585
 
586
+ /** Strip a `"Prefix."` namespace from a dotted kind string. `"Nat.Zero"` → `"Zero"`. */
587
+ type StripKindPrefix<K extends string> = K extends `${string}.${infer Bare}`
588
+ ? Bare
589
+ : K;
590
+
339
591
  /** Extract the `value` field from a `{ kind, value }` variant. Falls back to T if no `value` field. */
340
592
  type UnwrapVariant<T> = T extends { value: infer V } ? V : T;
341
593
 
342
594
  /**
343
595
  * Branch case keys: prefer ExtractDef (simple keyof indexing) when the
344
- * output carries __def. Falls back to KindOf (conditional type) for
345
- * outputs without __def.
596
+ * output carries __def. Falls back to KindOf with prefix stripping for
597
+ * outputs without __def (namespaced kinds like "Nat.Zero" → "Zero").
346
598
  */
347
599
  type BranchKeys<Out> = [ExtractDef<Out>] extends [never]
348
- ? KindOf<Out>
600
+ ? StripKindPrefix<KindOf<Out>>
349
601
  : keyof ExtractDef<Out> & string;
350
602
 
351
603
  /**
352
604
  * Branch case payload: prefer ExtractDef[K] (simple indexing) when available.
353
- * Falls back to UnwrapVariant<Extract<Out, { kind: K }>> for outputs without __def.
605
+ * Falls back to UnwrapVariant<Extract<Out, { kind: ... }>> for outputs without __def.
606
+ * In the fallback, matches namespaced kinds (`"Prefix.K"`) against the bare key K.
354
607
  */
355
608
  type BranchPayload<Out, K extends string> = [ExtractDef<Out>] extends [never]
356
- ? UnwrapVariant<Extract<Out, { kind: K }>>
609
+ ? UnwrapVariant<Extract<Out, { kind: K } | { kind: `${string}.${K}` }>>
357
610
  : K extends keyof ExtractDef<Out>
358
611
  ? VoidToNull<ExtractDef<Out>[K]>
359
612
  : never;
@@ -363,228 +616,422 @@ type BranchPayload<Out, K extends string> = [ExtractDef<Out>] extends [never]
363
616
  // ---------------------------------------------------------------------------
364
617
 
365
618
  // Shared implementations (one closure, not per-instance)
366
- function thenMethod<TIn, TOut, TRefs extends string, TNext>(
367
- this: TypedAction<TIn, TOut, TRefs>,
619
+ function thenMethod<TIn, TOut, TNext>(
620
+ this: TypedAction<TIn, TOut>,
368
621
  next: Pipeable<TOut, TNext>,
369
- ): TypedAction<TIn, TNext, TRefs> {
370
- return typedAction({ kind: "Chain", first: this, rest: next as Action });
371
- }
372
-
373
- function forEachMethod(this: TypedAction, action: Action): TypedAction {
374
- return typedAction({
375
- kind: "Chain",
376
- first: this,
377
- rest: { kind: "ForEach", action },
378
- });
622
+ ): TypedAction<TIn, TNext> {
623
+ return chain(this, next);
379
624
  }
380
625
 
381
626
  function branchMethod(
382
627
  this: TypedAction,
383
628
  cases: Record<string, Action>,
384
629
  ): TypedAction {
385
- return typedAction({
386
- kind: "Chain",
387
- first: this,
388
- rest: { kind: "Branch", cases: unwrapBranchCases(cases) },
389
- });
630
+ return chain(toAction(this), toAction(branch(cases)));
390
631
  }
391
632
 
392
633
  function flattenMethod(this: TypedAction): TypedAction {
393
- return typedAction({
394
- kind: "Chain",
395
- first: this,
396
- rest: {
397
- kind: "Invoke",
398
- handler: { kind: "Builtin", builtin: { kind: "Flatten" } },
399
- },
400
- });
634
+ return chain(toAction(this), toAction(flattenBuiltin()));
401
635
  }
402
636
 
403
637
  function dropMethod(this: TypedAction): TypedAction {
404
- return typedAction({
405
- kind: "Chain",
406
- first: this,
407
- rest: {
408
- kind: "Invoke",
409
- handler: { kind: "Builtin", builtin: { kind: "Drop" } },
410
- },
411
- });
638
+ return chain(toAction(this), toAction(drop));
412
639
  }
413
640
 
414
- function tagMethod(this: TypedAction, kind: string): TypedAction {
415
- return typedAction({
416
- kind: "Chain",
417
- first: this,
418
- rest: {
419
- kind: "Invoke",
420
- handler: { kind: "Builtin", builtin: { kind: "Tag", value: kind } },
421
- },
422
- });
641
+ function tagMethod(
642
+ this: TypedAction,
643
+ kind: string,
644
+ enumName: string,
645
+ ): TypedAction {
646
+ return chain(toAction(this), toAction(tag(kind, enumName)));
423
647
  }
424
648
 
425
- function getMethod(this: TypedAction, field: string): TypedAction {
426
- return typedAction({
427
- kind: "Chain",
428
- first: this,
429
- rest: {
430
- kind: "Invoke",
431
- handler: {
432
- kind: "Builtin",
433
- builtin: { kind: "ExtractField", value: field },
434
- },
435
- },
436
- });
649
+ function someMethod(this: TypedAction): TypedAction {
650
+ return chain(toAction(this), toAction(Option.some()));
437
651
  }
438
652
 
439
- function augmentMethod(this: TypedAction): TypedAction {
440
- // Construct: All(this, identity) → Merge
441
- // "this" is the sub-pipeline. augment() wraps it so the original input
442
- // flows through identity alongside the sub-pipeline, then merges the results.
443
- return typedAction({
444
- kind: "Chain",
445
- first: {
446
- kind: "All",
447
- actions: [
448
- this as Action,
449
- {
450
- kind: "Invoke",
451
- handler: { kind: "Builtin", builtin: { kind: "Identity" } },
452
- },
453
- ],
454
- },
455
- rest: {
456
- kind: "Invoke",
457
- handler: { kind: "Builtin", builtin: { kind: "Merge" } },
458
- },
459
- });
653
+ function okMethod(this: TypedAction): TypedAction {
654
+ return chain(toAction(this), toAction(Result.ok()));
460
655
  }
461
656
 
462
- function mergeMethod(this: TypedAction): TypedAction {
463
- return typedAction({
464
- kind: "Chain",
465
- first: this,
466
- rest: {
467
- kind: "Invoke",
468
- handler: { kind: "Builtin", builtin: { kind: "Merge" } },
469
- },
470
- });
657
+ function errMethod(this: TypedAction): TypedAction {
658
+ return chain(toAction(this), toAction(Result.err()));
659
+ }
660
+
661
+ function getFieldMethod(this: TypedAction, field: string): TypedAction {
662
+ return chain(toAction(this), toAction(getField(field)));
663
+ }
664
+
665
+ function getIndexMethod(this: TypedAction, index: number): TypedAction {
666
+ return chain(toAction(this), toAction(getIndex(index)));
667
+ }
668
+
669
+ function wrapInFieldMethod(this: TypedAction, field: string): TypedAction {
670
+ return chain(toAction(this), toAction(wrapInField(field)));
471
671
  }
472
672
 
473
673
  function pickMethod(this: TypedAction, ...keys: string[]): TypedAction {
474
- return typedAction({
475
- kind: "Chain",
476
- first: this,
477
- rest: {
478
- kind: "Invoke",
479
- handler: { kind: "Builtin", builtin: { kind: "Pick", value: keys } },
480
- },
481
- });
674
+ return chain(toAction(this), toAction(pick(...keys)));
482
675
  }
483
676
 
484
- function mapOptionMethod(this: TypedAction, action: Action): TypedAction {
485
- // Desugars to: self.then(branch({ Some: pipe(action, tag("Some")), None: tag("None") }))
486
- // But branch auto-unwraps value, so:
487
- // Some case: receives T, runs action, wraps as Some
488
- // None case: receives void, wraps as None
489
- return typedAction({
490
- kind: "Chain",
491
- first: this,
492
- rest: {
493
- kind: "Branch",
494
- cases: unwrapBranchCases({
495
- Some: {
496
- kind: "Chain",
497
- first: action,
498
- rest: {
499
- kind: "Invoke",
500
- handler: {
501
- kind: "Builtin",
502
- builtin: { kind: "Tag", value: "Some" },
503
- },
504
- },
505
- },
506
- None: {
507
- kind: "Invoke",
508
- handler: { kind: "Builtin", builtin: { kind: "Tag", value: "None" } },
509
- },
677
+ function splitFirstMethod(this: TypedAction): TypedAction {
678
+ return chain(
679
+ toAction(this),
680
+ toAction(
681
+ branchFamily({
682
+ Iterator: IteratorNs.splitFirst(),
683
+ Array: splitFirst(),
510
684
  }),
511
- },
512
- });
685
+ ),
686
+ );
513
687
  }
514
688
 
515
- function mapErrMethod(this: TypedAction, action: Action): TypedAction {
516
- // Desugars to: self.then(branch({ Ok: tag("Ok"), Err: pipe(action, tag("Err")) }))
517
- return typedAction({
518
- kind: "Chain",
519
- first: this,
520
- rest: {
521
- kind: "Branch",
522
- cases: unwrapBranchCases({
523
- Ok: {
524
- kind: "Invoke",
525
- handler: { kind: "Builtin", builtin: { kind: "Tag", value: "Ok" } },
526
- },
527
- Err: {
528
- kind: "Chain",
529
- first: action,
530
- rest: {
531
- kind: "Invoke",
532
- handler: {
533
- kind: "Builtin",
534
- builtin: { kind: "Tag", value: "Err" },
535
- },
536
- },
537
- },
689
+ function splitLastMethod(this: TypedAction): TypedAction {
690
+ return chain(
691
+ toAction(this),
692
+ toAction(
693
+ branchFamily({
694
+ Iterator: IteratorNs.splitLast(),
695
+ Array: splitLast(),
538
696
  }),
539
- },
540
- });
697
+ ),
698
+ );
699
+ }
700
+
701
+ // --- Shared postfix methods (Option + Result) — dispatch via branchFamily ---
702
+
703
+ function mapMethod(this: TypedAction, action: Action): TypedAction {
704
+ return chain(
705
+ toAction(this),
706
+ toAction(
707
+ branchFamily({
708
+ Result: branch({
709
+ Ok: chain(toAction(action), toAction(Result.ok())),
710
+ Err: Result.err(),
711
+ }),
712
+ Option: branch({
713
+ Some: chain(toAction(action), toAction(Option.some())),
714
+ None: Option.none(),
715
+ }),
716
+ Iterator: IteratorNs.map(action),
717
+ }),
718
+ ),
719
+ );
720
+ }
721
+
722
+ function unwrapMethod(this: TypedAction): TypedAction {
723
+ return chain(
724
+ toAction(this),
725
+ toAction(
726
+ branchFamily({
727
+ Result: branch({ Ok: identity(), Err: panic("called unwrap on Err") }),
728
+ Option: branch({
729
+ Some: identity(),
730
+ None: panic("called unwrap on None"),
731
+ }),
732
+ }),
733
+ ),
734
+ );
541
735
  }
542
736
 
543
737
  function unwrapOrMethod(this: TypedAction, defaultAction: Action): TypedAction {
544
- // Desugars to: self.then(branch({ Ok: identity(), Err: defaultAction }))
545
- return typedAction({
546
- kind: "Chain",
547
- first: this,
548
- rest: {
549
- kind: "Branch",
550
- cases: unwrapBranchCases({
551
- Ok: {
552
- kind: "Invoke",
553
- handler: { kind: "Builtin", builtin: { kind: "Identity" } },
554
- },
555
- Err: defaultAction,
738
+ return chain(
739
+ toAction(this),
740
+ toAction(
741
+ branchFamily({
742
+ Result: branch({ Ok: identity(), Err: defaultAction }),
743
+ Option: branch({ Some: identity(), None: defaultAction }),
556
744
  }),
557
- },
558
- });
745
+ ),
746
+ );
747
+ }
748
+
749
+ function andThenMethod(this: TypedAction, action: Action): TypedAction {
750
+ return chain(
751
+ toAction(this),
752
+ toAction(
753
+ branchFamily({
754
+ Result: branch({ Ok: action, Err: Result.err() }),
755
+ Option: branch({ Some: action, None: Option.none() }),
756
+ }),
757
+ ),
758
+ );
759
+ }
760
+
761
+ function transposeMethod(this: TypedAction): TypedAction {
762
+ return chain(
763
+ toAction(this),
764
+ toAction(
765
+ branchFamily({
766
+ Option: branch({
767
+ Some: branch({
768
+ Ok: chain(toAction(Option.some()), toAction(Result.ok())),
769
+ Err: Result.err(),
770
+ }),
771
+ None: chain(
772
+ toAction(chain(toAction(drop), toAction(Option.none()))),
773
+ toAction(Result.ok()),
774
+ ),
775
+ }),
776
+ Result: branch({
777
+ Ok: branch({
778
+ Some: chain(toAction(Result.ok()), toAction(Option.some())),
779
+ None: chain(toAction(drop), toAction(Option.none())),
780
+ }),
781
+ Err: chain(toAction(Result.err()), toAction(Option.some())),
782
+ }),
783
+ }),
784
+ ),
785
+ );
786
+ }
787
+
788
+ // --- Result-only postfix methods ---
789
+
790
+ function mapErrMethod(this: TypedAction, action: Action): TypedAction {
791
+ return chain(
792
+ toAction(this),
793
+ toAction(
794
+ branch({
795
+ Ok: Result.ok(),
796
+ Err: chain(toAction(action), toAction(Result.err())),
797
+ }),
798
+ ),
799
+ );
800
+ }
801
+
802
+ function orMethod(this: TypedAction, fallback: Action): TypedAction {
803
+ return chain(
804
+ toAction(this),
805
+ toAction(
806
+ branch({
807
+ Ok: Result.ok(),
808
+ Err: fallback,
809
+ }),
810
+ ),
811
+ );
812
+ }
813
+
814
+ function asOkOptionMethod(this: TypedAction): TypedAction {
815
+ return chain(
816
+ toAction(this),
817
+ toAction(
818
+ branch({
819
+ Ok: Option.some(),
820
+ Err: chain(toAction(drop), toAction(Option.none())),
821
+ }),
822
+ ),
823
+ );
824
+ }
825
+
826
+ function asErrOptionMethod(this: TypedAction): TypedAction {
827
+ return chain(
828
+ toAction(this),
829
+ toAction(
830
+ branch({
831
+ Ok: chain(toAction(drop), toAction(Option.none())),
832
+ Err: Option.some(),
833
+ }),
834
+ ),
835
+ );
836
+ }
837
+
838
+ function isOkMethod(this: TypedAction): TypedAction {
839
+ return chain(
840
+ toAction(this),
841
+ toAction(
842
+ branch({
843
+ Ok: constant(true),
844
+ Err: constant(false),
845
+ }),
846
+ ),
847
+ );
848
+ }
849
+
850
+ function isErrMethod(this: TypedAction): TypedAction {
851
+ return chain(
852
+ toAction(this),
853
+ toAction(
854
+ branch({
855
+ Ok: constant(false),
856
+ Err: constant(true),
857
+ }),
858
+ ),
859
+ );
860
+ }
861
+
862
+ // --- Option-only postfix methods ---
863
+
864
+ function filterMethod(this: TypedAction, predicate: Action): TypedAction {
865
+ return chain(
866
+ toAction(this),
867
+ toAction(
868
+ branchFamily({
869
+ Option: branch({
870
+ Some: predicate,
871
+ None: Option.none(),
872
+ }),
873
+ Iterator: IteratorNs.filter(predicate),
874
+ }),
875
+ ),
876
+ );
877
+ }
878
+
879
+ function isSomeMethod(this: TypedAction): TypedAction {
880
+ return chain(
881
+ toAction(this),
882
+ toAction(
883
+ branch({
884
+ Some: constant(true),
885
+ None: constant(false),
886
+ }),
887
+ ),
888
+ );
889
+ }
890
+
891
+ function isNoneMethod(this: TypedAction): TypedAction {
892
+ return chain(
893
+ toAction(this),
894
+ toAction(
895
+ branch({
896
+ Some: constant(false),
897
+ None: constant(true),
898
+ }),
899
+ ),
900
+ );
901
+ }
902
+
903
+ function asOptionMethod(this: TypedAction): TypedAction {
904
+ return chain(toAction(this), toAction(asOptionStandalone()));
905
+ }
906
+
907
+ // --- Iterator postfix methods ---
908
+
909
+ function iterateMethod(this: TypedAction): TypedAction {
910
+ return chain(
911
+ toAction(this),
912
+ toAction(
913
+ branchFamily({
914
+ Option: IteratorNs.fromOption(),
915
+ Result: IteratorNs.fromResult(),
916
+ Array: IteratorNs.fromArray(),
917
+ }),
918
+ ),
919
+ );
920
+ }
921
+
922
+ function flatMapMethod(this: TypedAction, action: Action): TypedAction {
923
+ return chain(toAction(this), toAction(IteratorNs.flatMap(action)));
924
+ }
925
+
926
+ function collectMethod(this: TypedAction): TypedAction {
927
+ return chain(
928
+ toAction(this),
929
+ toAction(
930
+ branchFamily({
931
+ Array: Option.collect(),
932
+ Iterator: IteratorNs.collect(),
933
+ }),
934
+ ),
935
+ );
936
+ }
937
+
938
+ function foldMethod(
939
+ this: TypedAction,
940
+ init: Action,
941
+ body: Action,
942
+ ): TypedAction {
943
+ return chain(toAction(this), toAction(IteratorNs.fold(init, body)));
944
+ }
945
+
946
+ function isEmptyMethod(this: TypedAction): TypedAction {
947
+ return chain(toAction(this), toAction(IteratorNs.isEmpty()));
948
+ }
949
+
950
+ function sliceMethod(
951
+ this: TypedAction,
952
+ start: number,
953
+ end?: number,
954
+ ): TypedAction {
955
+ return chain(toAction(this), toAction(IteratorNs.slice(start, end)));
956
+ }
957
+
958
+ function takeMethod(this: TypedAction, n: number): TypedAction {
959
+ return chain(toAction(this), toAction(IteratorNs.take(n)));
960
+ }
961
+
962
+ function skipMethod(this: TypedAction, n: number): TypedAction {
963
+ return chain(toAction(this), toAction(IteratorNs.skip(n)));
964
+ }
965
+
966
+ function bindMethod(
967
+ this: TypedAction,
968
+ bindings: Action[],
969
+ body: (vars: any) => Action,
970
+ ): TypedAction {
971
+ return chain(toAction(this), toAction(bindStandalone(bindings, body)));
972
+ }
973
+
974
+ function bindInputMethod(
975
+ this: TypedAction,
976
+ body: (input: any) => Action,
977
+ ): TypedAction {
978
+ return chain(toAction(this), toAction(bindInputStandalone(body)));
559
979
  }
560
980
 
561
981
  /**
562
982
  * Attach `.then()` and `.forEach()` methods to a plain Action object.
563
983
  * Methods are non-enumerable: invisible to JSON.stringify and toEqual.
564
984
  */
565
- export function typedAction<
566
- In = unknown,
567
- Out = unknown,
568
- Refs extends string = never,
569
- >(action: Action): TypedAction<In, Out, Refs> {
985
+ export function typedAction<In = unknown, Out = unknown>(
986
+ action: Action,
987
+ ): TypedAction<In, Out> {
570
988
  if (!("then" in action)) {
571
989
  Object.defineProperties(action, {
572
990
  then: { value: thenMethod, configurable: true },
573
- forEach: { value: forEachMethod, configurable: true },
991
+
574
992
  branch: { value: branchMethod, configurable: true },
575
993
  flatten: { value: flattenMethod, configurable: true },
576
994
  drop: { value: dropMethod, configurable: true },
577
995
  tag: { value: tagMethod, configurable: true },
578
- get: { value: getMethod, configurable: true },
579
- augment: { value: augmentMethod, configurable: true },
580
- merge: { value: mergeMethod, configurable: true },
996
+ some: { value: someMethod, configurable: true },
997
+ ok: { value: okMethod, configurable: true },
998
+ err: { value: errMethod, configurable: true },
999
+ getField: { value: getFieldMethod, configurable: true },
1000
+ getIndex: { value: getIndexMethod, configurable: true },
1001
+ wrapInField: { value: wrapInFieldMethod, configurable: true },
1002
+
581
1003
  pick: { value: pickMethod, configurable: true },
582
- mapOption: { value: mapOptionMethod, configurable: true },
1004
+ splitFirst: { value: splitFirstMethod, configurable: true },
1005
+ splitLast: { value: splitLastMethod, configurable: true },
1006
+ map: { value: mapMethod, configurable: true },
583
1007
  mapErr: { value: mapErrMethod, configurable: true },
1008
+ unwrap: { value: unwrapMethod, configurable: true },
584
1009
  unwrapOr: { value: unwrapOrMethod, configurable: true },
1010
+ andThen: { value: andThenMethod, configurable: true },
1011
+ filter: { value: filterMethod, configurable: true },
1012
+ isSome: { value: isSomeMethod, configurable: true },
1013
+ isNone: { value: isNoneMethod, configurable: true },
1014
+ asOption: { value: asOptionMethod, configurable: true },
1015
+ collect: { value: collectMethod, configurable: true },
1016
+ fold: { value: foldMethod, configurable: true },
1017
+ isEmpty: { value: isEmptyMethod, configurable: true },
1018
+ slice: { value: sliceMethod, configurable: true },
1019
+ take: { value: takeMethod, configurable: true },
1020
+ skip: { value: skipMethod, configurable: true },
1021
+ or: { value: orMethod, configurable: true },
1022
+ iterate: { value: iterateMethod, configurable: true },
1023
+ flatMap: { value: flatMapMethod, configurable: true },
1024
+
1025
+ asOkOption: { value: asOkOptionMethod, configurable: true },
1026
+ asErrOption: { value: asErrOptionMethod, configurable: true },
1027
+ isOk: { value: isOkMethod, configurable: true },
1028
+ isErr: { value: isErrMethod, configurable: true },
1029
+ transpose: { value: transposeMethod, configurable: true },
1030
+ bind: { value: bindMethod, configurable: true },
1031
+ bindInput: { value: bindInputMethod, configurable: true },
585
1032
  });
586
1033
  }
587
- return action as TypedAction<In, Out, Refs>;
1034
+ return action as TypedAction<In, Out>;
588
1035
  }
589
1036
 
590
1037
  // ---------------------------------------------------------------------------
@@ -595,7 +1042,7 @@ export function typedAction<
595
1042
  * Extract the input type from a TypedAction.
596
1043
  *
597
1044
  * Uses direct phantom field extraction (not full TypedAction matching) to
598
- * avoid the `TypedAction<any, any, any>` constraint which fails for In=never
1045
+ * avoid a full `TypedAction<any, any>` constraint which fails for In=never
599
1046
  * due to __in contravariance.
600
1047
  */
601
1048
  export type ExtractInput<T> = T extends {
@@ -634,11 +1081,11 @@ export { race, sleep, withTimeout } from "./race.js";
634
1081
  export function forEach<In, Out>(
635
1082
  action: Pipeable<In, Out>,
636
1083
  ): TypedAction<In[], Out[]> {
637
- return typedAction({ kind: "ForEach", action: action as Action });
1084
+ return typedAction({ kind: "ForEach", action: toAction(action) });
638
1085
  }
639
1086
 
640
1087
  /**
641
- * Insert ExtractField("value") before each case handler in a branch.
1088
+ * Insert GetField("value") before each case handler in a branch.
642
1089
  * This implements auto-unwrapping: the engine dispatches on `kind`, then
643
1090
  * extracts `value` before passing to the handler. Case handlers receive
644
1091
  * the payload directly, not the full `{ kind, value }` variant.
@@ -648,17 +1095,9 @@ function unwrapBranchCases(
648
1095
  ): Record<string, Action> {
649
1096
  const unwrapped: Record<string, Action> = {};
650
1097
  for (const key of Object.keys(cases)) {
651
- unwrapped[key] = {
652
- kind: "Chain",
653
- first: {
654
- kind: "Invoke",
655
- handler: {
656
- kind: "Builtin",
657
- builtin: { kind: "ExtractField", value: "value" },
658
- },
659
- },
660
- rest: cases[key],
661
- };
1098
+ unwrapped[key] = toAction(
1099
+ chain(toAction(getField("value")), toAction(cases[key])),
1100
+ );
662
1101
  }
663
1102
  return unwrapped;
664
1103
  }
@@ -689,39 +1128,32 @@ export function branch<TCases extends Record<string, Action>>(
689
1128
  return typedAction({ kind: "Branch", cases: unwrapBranchCases(cases) });
690
1129
  }
691
1130
 
1131
+ /**
1132
+ * Two-level dispatch: extract the enum prefix from a tagged value's `kind`,
1133
+ * then branch on that prefix. Used by postfix methods (`.map()`, `.unwrapOr()`,
1134
+ * etc.) to dispatch across union families (Option, Result) without runtime
1135
+ * metadata.
1136
+ *
1137
+ * `branchFamily({ Result: ..., Option: ... })` ≡ `chain(extractPrefix(), branch(cases))`
1138
+ */
1139
+ export function branchFamily(cases: Record<string, Action>): TypedAction {
1140
+ return typedAction({
1141
+ kind: "Chain",
1142
+ first: toAction(extractPrefix()),
1143
+ rest: toAction(branch(cases)),
1144
+ });
1145
+ }
1146
+
692
1147
  type LoopResultDef<TContinue, TBreak> = {
693
1148
  Continue: TContinue;
694
1149
  Break: TBreak;
695
1150
  };
696
1151
 
697
1152
  export type LoopResult<TContinue, TBreak> = TaggedUnion<
1153
+ "LoopResult",
698
1154
  LoopResultDef<TContinue, TBreak>
699
1155
  >;
700
1156
 
701
- // ---------------------------------------------------------------------------
702
- // Shared AST constants for control flow compilation
703
- // ---------------------------------------------------------------------------
704
-
705
- const EXTRACT_PAYLOAD: Action = {
706
- kind: "Invoke",
707
- handler: { kind: "Builtin", builtin: { kind: "ExtractIndex", value: 0 } },
708
- };
709
-
710
- const TAG_CONTINUE: Action = {
711
- kind: "Invoke",
712
- handler: { kind: "Builtin", builtin: { kind: "Tag", value: "Continue" } },
713
- };
714
-
715
- export const TAG_BREAK: Action = {
716
- kind: "Invoke",
717
- handler: { kind: "Builtin", builtin: { kind: "Tag", value: "Break" } },
718
- };
719
-
720
- export const IDENTITY: Action = {
721
- kind: "Invoke",
722
- handler: { kind: "Builtin", builtin: { kind: "Identity" } },
723
- };
724
-
725
1157
  // ---------------------------------------------------------------------------
726
1158
  // recur — restart body primitive
727
1159
  // ---------------------------------------------------------------------------
@@ -733,9 +1165,9 @@ export const IDENTITY: Action = {
733
1165
  * If the body completes normally → output is TOut.
734
1166
  * If restart fires → body re-executes with the restarted value.
735
1167
  *
736
- * Compiled form: `RestartHandle(id, ExtractIndex(0), body)`
1168
+ * Compiled form: `RestartHandle(id, GetIndex(0), body)`
737
1169
  */
738
- export function recur<TIn = never, TOut = any>(
1170
+ export function recur<TIn = void, TOut = any>(
739
1171
  bodyFn: (restart: TypedAction<TIn, never>) => Pipeable<TIn, TOut>,
740
1172
  ): TypedAction<PipeIn<TIn>, TOut> {
741
1173
  const restartHandlerId = allocateRestartHandlerId();
@@ -745,13 +1177,13 @@ export function recur<TIn = never, TOut = any>(
745
1177
  restart_handler_id: restartHandlerId,
746
1178
  });
747
1179
 
748
- const body = bodyFn(restartAction) as Action;
1180
+ const body = toAction(bodyFn(restartAction));
749
1181
 
750
1182
  return typedAction({
751
1183
  kind: "RestartHandle",
752
1184
  restart_handler_id: restartHandlerId,
753
1185
  body,
754
- handler: EXTRACT_PAYLOAD,
1186
+ handler: toAction(getIndex(0).unwrap()),
755
1187
  });
756
1188
  }
757
1189
 
@@ -771,23 +1203,26 @@ export function recur<TIn = never, TOut = any>(
771
1203
  * a Branch. earlyReturn tags with Break and performs — the handler restarts
772
1204
  * the body, Branch takes the Break path, and the value exits.
773
1205
  */
774
- export function earlyReturn<TEarlyReturn = never, TIn = any, TOut = any>(
1206
+ export function earlyReturn<TEarlyReturn = void, TIn = any, TOut = any>(
775
1207
  bodyFn: (
776
1208
  earlyReturn: TypedAction<TEarlyReturn, never>,
777
1209
  ) => Pipeable<TIn, TOut>,
778
1210
  ): TypedAction<TIn, TEarlyReturn | TOut> {
779
1211
  const restartHandlerId = allocateRestartHandlerId();
780
1212
 
781
- const earlyReturnAction = typedAction<TEarlyReturn, never>({
782
- kind: "Chain",
783
- first: TAG_BREAK,
784
- rest: { kind: "RestartPerform", restart_handler_id: restartHandlerId },
785
- });
1213
+ const earlyReturnAction = typedAction<TEarlyReturn, never>(
1214
+ toAction(
1215
+ chain(toAction(tag("Break", "LoopResult")), {
1216
+ kind: "RestartPerform",
1217
+ restart_handler_id: restartHandlerId,
1218
+ }),
1219
+ ),
1220
+ );
786
1221
 
787
- const body = bodyFn(earlyReturnAction) as Action;
1222
+ const body = toAction(bodyFn(earlyReturnAction));
788
1223
 
789
1224
  return typedAction(
790
- buildRestartBranchAction(restartHandlerId, body, IDENTITY),
1225
+ buildRestartBranchAction(restartHandlerId, body, toAction(identity())),
791
1226
  );
792
1227
  }
793
1228
 
@@ -797,7 +1232,7 @@ export function earlyReturn<TEarlyReturn = never, TIn = any, TOut = any>(
797
1232
 
798
1233
  /**
799
1234
  * Build the restart+branch compiled form:
800
- * `Chain(Tag("Continue"), RestartHandle(id, ExtractIndex(0), Branch({ Continue: continueArm, Break: breakArm })))`
1235
+ * `Chain(Tag("Continue"), RestartHandle(id, GetIndex(0), Branch({ Continue: continueArm, Break: breakArm })))`
801
1236
  *
802
1237
  * Input is tagged Continue so the Branch enters the continueArm on first execution.
803
1238
  * Continue tag → restart → re-enters continueArm. Break tag → restart → runs breakArm, exits `RestartHandle`.
@@ -809,22 +1244,14 @@ export function buildRestartBranchAction(
809
1244
  continueArm: Action,
810
1245
  breakArm: Action,
811
1246
  ): Action {
812
- return {
813
- kind: "Chain",
814
- first: TAG_CONTINUE,
815
- rest: {
1247
+ return toAction(
1248
+ chain(toAction(tag("Continue", "LoopResult")), {
816
1249
  kind: "RestartHandle",
817
1250
  restart_handler_id: restartHandlerId,
818
- body: {
819
- kind: "Branch",
820
- cases: unwrapBranchCases({
821
- Continue: continueArm,
822
- Break: breakArm,
823
- }),
824
- },
825
- handler: EXTRACT_PAYLOAD,
826
- },
827
- };
1251
+ body: toAction(branch({ Continue: continueArm, Break: breakArm })),
1252
+ handler: toAction(getIndex(0).unwrap()),
1253
+ }),
1254
+ );
828
1255
  }
829
1256
 
830
1257
  /**
@@ -836,12 +1263,12 @@ export function buildRestartBranchAction(
836
1263
  *
837
1264
  * Compiles to `RestartHandle`/`RestartPerform`/Branch — same effect substrate as tryCatch and earlyReturn.
838
1265
  */
839
- export function loop<TBreak = never, TIn = never>(
1266
+ export function loop<TBreak = void, TRecur = void>(
840
1267
  bodyFn: (
841
- recur: TypedAction<TIn, never>,
1268
+ recur: TypedAction<TRecur, never>,
842
1269
  done: TypedAction<VoidToNull<TBreak>, never>,
843
- ) => Pipeable<TIn, never>,
844
- ): TypedAction<PipeIn<TIn>, VoidToNull<TBreak>> {
1270
+ ) => Pipeable<TRecur, never>,
1271
+ ): TypedAction<PipeIn<TRecur>, VoidToNull<TBreak>> {
845
1272
  const restartHandlerId = allocateRestartHandlerId();
846
1273
 
847
1274
  const perform: Action = {
@@ -849,22 +1276,18 @@ export function loop<TBreak = never, TIn = never>(
849
1276
  restart_handler_id: restartHandlerId,
850
1277
  };
851
1278
 
852
- const recurAction = typedAction<TIn, never>({
853
- kind: "Chain",
854
- first: TAG_CONTINUE,
855
- rest: perform,
856
- });
1279
+ const recurAction = typedAction<TRecur, never>(
1280
+ toAction(chain(toAction(tag("Continue", "LoopResult")), toAction(perform))),
1281
+ );
857
1282
 
858
- const doneAction = typedAction<VoidToNull<TBreak>, never>({
859
- kind: "Chain",
860
- first: TAG_BREAK,
861
- rest: perform,
862
- });
1283
+ const doneAction = typedAction<VoidToNull<TBreak>, never>(
1284
+ toAction(chain(toAction(tag("Break", "LoopResult")), toAction(perform))),
1285
+ );
863
1286
 
864
- const body = bodyFn(recurAction, doneAction) as Action;
1287
+ const body = toAction(bodyFn(recurAction, doneAction));
865
1288
 
866
1289
  return typedAction(
867
- buildRestartBranchAction(restartHandlerId, body, IDENTITY),
1290
+ buildRestartBranchAction(restartHandlerId, body, toAction(identity())),
868
1291
  );
869
1292
  }
870
1293