@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/builtins.ts DELETED
@@ -1,804 +0,0 @@
1
- import {
2
- type Action,
3
- type Option as OptionT,
4
- type Pipeable,
5
- type Result as ResultT,
6
- type TaggedUnion,
7
- type TypedAction,
8
- typedAction,
9
- } from "./ast.js";
10
- import { chain } from "./chain.js";
11
-
12
- /**
13
- * Typed combinators for structural data transformations.
14
- *
15
- * All builtins emit `{ kind: "Builtin", builtin: { kind: ... } }` handler
16
- * kinds. The Rust scheduler executes them inline (no subprocess).
17
- */
18
-
19
- // ---------------------------------------------------------------------------
20
- // Constant — produce a fixed value (takes no pipeline input)
21
- // ---------------------------------------------------------------------------
22
-
23
- export function constant<TValue>(value: TValue): TypedAction<any, TValue> {
24
- return typedAction({
25
- kind: "Invoke",
26
- handler: { kind: "Builtin", builtin: { kind: "Constant", value } },
27
- });
28
- }
29
-
30
- // ---------------------------------------------------------------------------
31
- // Identity — pass input through unchanged
32
- // ---------------------------------------------------------------------------
33
-
34
- export const identity: TypedAction<any, any> = typedAction({
35
- kind: "Invoke",
36
- handler: { kind: "Builtin", builtin: { kind: "Identity" } },
37
- });
38
-
39
- // ---------------------------------------------------------------------------
40
- // Drop — discard pipeline value
41
- // ---------------------------------------------------------------------------
42
-
43
- export const drop: TypedAction<any, never> = typedAction({
44
- kind: "Invoke",
45
- handler: { kind: "Builtin", builtin: { kind: "Drop" } },
46
- });
47
-
48
- // ---------------------------------------------------------------------------
49
- // Tag — wrap input as a tagged union variant
50
- // ---------------------------------------------------------------------------
51
-
52
- /**
53
- * Wrap input as a tagged union member. Requires the full variant map TDef
54
- * so the output type carries __def for branch decomposition.
55
- *
56
- * Usage: tag<{ Ok: string; Err: number }, "Ok">("Ok")
57
- * input: string → output: TaggedUnion<{ Ok: string; Err: number }>
58
- */
59
- export function tag<
60
- TDef extends Record<string, unknown>,
61
- TKind extends keyof TDef & string,
62
- >(kind: TKind): TypedAction<TDef[TKind], TaggedUnion<TDef>> {
63
- return typedAction({
64
- kind: "Invoke",
65
- handler: { kind: "Builtin", builtin: { kind: "Tag", value: kind } },
66
- });
67
- }
68
-
69
- // ---------------------------------------------------------------------------
70
- // Merge — merge a tuple of objects into a single object
71
- // ---------------------------------------------------------------------------
72
-
73
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
74
- type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (
75
- x: infer I,
76
- ) => void
77
- ? I
78
- : never;
79
-
80
- export function merge<
81
- TObjects extends Record<string, unknown>[],
82
- >(): TypedAction<TObjects, UnionToIntersection<TObjects[number]>> {
83
- return typedAction({
84
- kind: "Invoke",
85
- handler: { kind: "Builtin", builtin: { kind: "Merge" } },
86
- });
87
- }
88
-
89
- // ---------------------------------------------------------------------------
90
- // Flatten — flatten a nested array one level
91
- // ---------------------------------------------------------------------------
92
-
93
- export function flatten<TElement>(): TypedAction<TElement[][], TElement[]> {
94
- return typedAction({
95
- kind: "Invoke",
96
- handler: { kind: "Builtin", builtin: { kind: "Flatten" } },
97
- });
98
- }
99
-
100
- // ---------------------------------------------------------------------------
101
- // ExtractField — extract a single field from an object
102
- // ---------------------------------------------------------------------------
103
-
104
- export function extractField<
105
- TObj extends Record<string, unknown>,
106
- TField extends keyof TObj & string,
107
- >(field: TField): TypedAction<TObj, TObj[TField]> {
108
- return typedAction({
109
- kind: "Invoke",
110
- handler: {
111
- kind: "Builtin",
112
- builtin: { kind: "ExtractField", value: field },
113
- },
114
- });
115
- }
116
-
117
- // ---------------------------------------------------------------------------
118
- // ExtractIndex — extract a single element from an array by index
119
- // ---------------------------------------------------------------------------
120
-
121
- export function extractIndex<TTuple extends unknown[], TIndex extends number>(
122
- index: TIndex,
123
- ): TypedAction<TTuple, TTuple[TIndex]> {
124
- return typedAction({
125
- kind: "Invoke",
126
- handler: {
127
- kind: "Builtin",
128
- builtin: { kind: "ExtractIndex", value: index },
129
- },
130
- });
131
- }
132
-
133
- // ---------------------------------------------------------------------------
134
- // Pick — select named fields from an object
135
- // ---------------------------------------------------------------------------
136
-
137
- export function pick<
138
- TObj extends Record<string, unknown>,
139
- TKeys extends (keyof TObj & string)[],
140
- >(...keys: TKeys): TypedAction<TObj, Pick<TObj, TKeys[number]>> {
141
- return typedAction({
142
- kind: "Invoke",
143
- handler: { kind: "Builtin", builtin: { kind: "Pick", value: keys } },
144
- });
145
- }
146
-
147
- // ---------------------------------------------------------------------------
148
- // DropResult — run an action for side effects, discard its output
149
- // ---------------------------------------------------------------------------
150
-
151
- export function dropResult<TInput, TOutput>(
152
- action: Pipeable<TInput, TOutput>,
153
- ): TypedAction<TInput, never> {
154
- // Build AST directly — chain inference fails when drop's TValue
155
- // isn't constrained by context (resolves to unknown ≠ TOutput).
156
- return typedAction({
157
- kind: "Chain",
158
- first: action as Action,
159
- rest: {
160
- kind: "Invoke",
161
- handler: { kind: "Builtin", builtin: { kind: "Drop" } },
162
- },
163
- });
164
- }
165
-
166
- // ---------------------------------------------------------------------------
167
- // WithResource — RAII-style create/action/dispose
168
- // ---------------------------------------------------------------------------
169
-
170
- /**
171
- * RAII-style resource management combinator.
172
- *
173
- * Runs `create` to acquire a resource, then merges the resource with the
174
- * original input into a flat object (`TResource & TIn`) for the action.
175
- * After the action completes, `dispose` receives the resource for cleanup.
176
- * The overall combinator returns the action's output.
177
- *
178
- * ```
179
- * TIn → create → TResource
180
- * → merge(TResource, TIn) → TResource & TIn
181
- * → action(TResource & TIn) → TOut
182
- * → dispose(TResource) → (discarded)
183
- * → TOut
184
- * ```
185
- */
186
- export function withResource<
187
- TIn extends Record<string, unknown>,
188
- TResource extends Record<string, unknown>,
189
- TOut,
190
- TDisposeOut = unknown,
191
- >({
192
- create,
193
- action,
194
- dispose,
195
- }: {
196
- create: Pipeable<TIn, TResource>;
197
- action: Pipeable<TResource & TIn, TOut>;
198
- dispose: Pipeable<TResource, TDisposeOut>;
199
- }): TypedAction<TIn, TOut> {
200
- const mergeBuiltin: Action = {
201
- kind: "Invoke",
202
- handler: { kind: "Builtin", builtin: { kind: "Merge" } },
203
- };
204
-
205
- // Step 1: all(create, identity) → [TResource, TIn] → merge → TResource & TIn
206
- const acquireAndMerge = chain(
207
- typedAction<TIn, [TResource, TIn]>({
208
- kind: "All",
209
- actions: [create as Action, identity as Action],
210
- }),
211
- typedAction<[TResource, TIn], TResource & TIn>(mergeBuiltin),
212
- );
213
-
214
- // Step 2: all(action, identity) → [TOut, TResource & TIn]
215
- // Keep merged object so dispose can access resource fields.
216
- const actionAndKeepMerged = typedAction<
217
- TResource & TIn,
218
- [TOut, TResource & TIn]
219
- >({
220
- kind: "All",
221
- actions: [action as Action, identity as Action],
222
- });
223
-
224
- // Step 3: all(extractIndex(0), chain(extractIndex(1), dispose)) → [TOut, unknown]
225
- const disposeAndKeepResult = typedAction<
226
- [TOut, TResource & TIn],
227
- [TOut, unknown]
228
- >({
229
- kind: "All",
230
- actions: [
231
- extractIndex<[TOut, TResource & TIn], 0>(0) as Action,
232
- chain(
233
- extractIndex<[TOut, TResource & TIn], 1>(1),
234
- dispose as Pipeable<TResource & TIn, unknown>,
235
- ) as Action,
236
- ],
237
- });
238
-
239
- // Step 4: extractIndex(0) → TOut
240
- return chain(
241
- chain(chain(acquireAndMerge, actionAndKeepMerged), disposeAndKeepResult),
242
- extractIndex<[TOut, unknown], 0>(0),
243
- ) as TypedAction<TIn, TOut>;
244
- }
245
-
246
- // ---------------------------------------------------------------------------
247
- // Augment — run a transform, merge its output back into the original input
248
- // ---------------------------------------------------------------------------
249
-
250
- /**
251
- * Run `action` on the input, then merge the action's output fields back
252
- * into the original input object. The action must accept exactly `TInput`.
253
- * Use `pick` inside the action's pipe if the inner handler needs a subset.
254
- *
255
- * Example:
256
- * augment(pipe(pick("file"), migrate))
257
- * // { file, outputPath } → { file, outputPath, content, migrated }
258
- */
259
- export function augment<
260
- TInput extends Record<string, unknown>,
261
- TOutput extends Record<string, unknown>,
262
- >(action: Pipeable<TInput, TOutput>): TypedAction<TInput, TInput & TOutput> {
263
- // Build AST directly — chain inference fails because [TOutput, TInput]
264
- // doesn't match merge()'s Record<string, unknown>[] with invariance.
265
- return typedAction({
266
- kind: "Chain",
267
- first: {
268
- kind: "All",
269
- actions: [action as Action, identity as Action],
270
- },
271
- rest: {
272
- kind: "Invoke",
273
- handler: { kind: "Builtin", builtin: { kind: "Merge" } },
274
- },
275
- });
276
- }
277
-
278
- // ---------------------------------------------------------------------------
279
- // Tap — run an action for side effects, preserve original input
280
- // ---------------------------------------------------------------------------
281
-
282
- /**
283
- * Run `action` on the input for its side effects, then discard the action's
284
- * output and return the original input unchanged. The action must accept
285
- * exactly `TInput`. Use `pick` inside the action's pipe if the inner
286
- * handler needs a subset.
287
- *
288
- * Constraint: input must be an object (uses augment internally, which
289
- * relies on all + merge).
290
- *
291
- * Example:
292
- * pipe(tap(pipe(pick("worktreePath", "description"), implement)), createPR)
293
- */
294
- export function tap<TInput extends Record<string, unknown>>(
295
- action: Pipeable<TInput, any>,
296
- ): TypedAction<TInput, TInput> {
297
- // Build AST directly — internal plumbing (action → constant → augment)
298
- // can't go through typed chain/augment with invariant phantom fields.
299
- // tap: all(chain(action, constant({})), identity()) → merge
300
- return typedAction({
301
- kind: "Chain",
302
- first: {
303
- kind: "All",
304
- actions: [
305
- {
306
- kind: "Chain",
307
- first: action as Action,
308
- rest: {
309
- kind: "Invoke",
310
- handler: {
311
- kind: "Builtin",
312
- builtin: { kind: "Constant", value: {} },
313
- },
314
- },
315
- },
316
- {
317
- kind: "Invoke",
318
- handler: { kind: "Builtin", builtin: { kind: "Identity" } },
319
- },
320
- ],
321
- },
322
- rest: {
323
- kind: "Invoke",
324
- handler: { kind: "Builtin", builtin: { kind: "Merge" } },
325
- },
326
- });
327
- }
328
-
329
- // ---------------------------------------------------------------------------
330
- // Range — produce an integer array [start, start+1, ..., end-1]
331
- // ---------------------------------------------------------------------------
332
-
333
- export function range(start: number, end: number): TypedAction<any, number[]> {
334
- const result: number[] = [];
335
- for (let i = start; i < end; i++) {
336
- result.push(i);
337
- }
338
- return typedAction({
339
- kind: "Invoke",
340
- handler: { kind: "Builtin", builtin: { kind: "Constant", value: result } },
341
- });
342
- }
343
-
344
- // ---------------------------------------------------------------------------
345
- // Option namespace — combinators for Option<T> tagged unions
346
- // ---------------------------------------------------------------------------
347
-
348
- // Shared AST fragments for Option desugaring
349
- const TAG_SOME: Action = {
350
- kind: "Invoke",
351
- handler: { kind: "Builtin", builtin: { kind: "Tag", value: "Some" } },
352
- };
353
- const TAG_NONE: Action = {
354
- kind: "Invoke",
355
- handler: { kind: "Builtin", builtin: { kind: "Tag", value: "None" } },
356
- };
357
- const EXTRACT_VALUE: Action = {
358
- kind: "Invoke",
359
- handler: {
360
- kind: "Builtin",
361
- builtin: { kind: "ExtractField", value: "value" },
362
- },
363
- };
364
- const DROP: Action = {
365
- kind: "Invoke",
366
- handler: { kind: "Builtin", builtin: { kind: "Drop" } },
367
- };
368
- const IDENTITY: Action = {
369
- kind: "Invoke",
370
- handler: { kind: "Builtin", builtin: { kind: "Identity" } },
371
- };
372
-
373
- /** Wrap branch cases with ExtractField("value") auto-unwrapping. */
374
- function optionBranch(someCaseBody: Action, noneCaseBody: Action): Action {
375
- return {
376
- kind: "Branch",
377
- cases: {
378
- Some: { kind: "Chain", first: EXTRACT_VALUE, rest: someCaseBody },
379
- None: { kind: "Chain", first: EXTRACT_VALUE, rest: noneCaseBody },
380
- },
381
- };
382
- }
383
-
384
- /**
385
- * Option namespace. All combinators produce TypedAction AST nodes that
386
- * desugar to branch + existing builtins, except collect which uses the
387
- * CollectSome builtin.
388
- */
389
- export const Option = {
390
- /**
391
- * Wrap a value as Some. `T → Option<T>`
392
- *
393
- * Equivalent to `tag<OptionDef<T>, "Some">("Some")`.
394
- */
395
- some<T>(): TypedAction<T, OptionT<T>> {
396
- return typedAction(TAG_SOME);
397
- },
398
-
399
- /**
400
- * Produce a None. `never → Option<T>`
401
- *
402
- * Chain after `.drop()` to discard the current value first.
403
- * Equivalent to `tag<OptionDef<T>, "None">("None")`.
404
- */
405
- none<T>(): TypedAction<never, OptionT<T>> {
406
- return typedAction(TAG_NONE);
407
- },
408
-
409
- /**
410
- * Transform the Some value. `Option<T> → Option<U>`
411
- *
412
- * Desugars to: `branch({ Some: pipe(action, tag("Some")), None: tag("None") })`
413
- */
414
- map<T, U>(action: Pipeable<T, U>): TypedAction<OptionT<T>, OptionT<U>> {
415
- return typedAction(
416
- optionBranch(
417
- { kind: "Chain", first: action as Action, rest: TAG_SOME },
418
- TAG_NONE,
419
- ),
420
- );
421
- },
422
-
423
- /**
424
- * Monadic bind (flatMap). If Some, pass the value to action which
425
- * returns Option<U>. If None, stay None. `Option<T> → Option<U>`
426
- *
427
- * This is the most fundamental combinator — map, flatten, and filter
428
- * are all derivable from andThen + constructors.
429
- *
430
- * Desugars to: `branch({ Some: action, None: tag("None") })`
431
- */
432
- andThen<T, U>(
433
- action: Pipeable<T, OptionT<U>>,
434
- ): TypedAction<OptionT<T>, OptionT<U>> {
435
- return typedAction(optionBranch(action as Action, TAG_NONE));
436
- },
437
-
438
- /**
439
- * Extract the Some value or produce a default from an action.
440
- * `Option<T> → T`
441
- *
442
- * The defaultAction takes no meaningful input (never) and must produce T.
443
- * Use `Option.unwrapOr(constant("fallback"))`.
444
- *
445
- * The None branch drops its void payload before calling defaultAction,
446
- * matching Rust's `unwrap_or_else(|| default)` where the closure takes
447
- * no arguments.
448
- *
449
- * Desugars to: `branch({ Some: identity(), None: pipe(drop(), defaultAction) })`
450
- */
451
- unwrapOr<T>(defaultAction: Pipeable<never, T>): TypedAction<OptionT<T>, T> {
452
- return typedAction({
453
- kind: "Branch",
454
- cases: {
455
- Some: { kind: "Chain", first: EXTRACT_VALUE, rest: IDENTITY },
456
- None: {
457
- kind: "Chain",
458
- first: EXTRACT_VALUE,
459
- rest: { kind: "Chain", first: DROP, rest: defaultAction as Action },
460
- },
461
- },
462
- });
463
- },
464
-
465
- /**
466
- * Unwrap a nested Option. `Option<Option<T>> → Option<T>`
467
- *
468
- * Desugars to: `branch({ Some: identity(), None: tag("None") })`
469
- */
470
- flatten<T>(): TypedAction<OptionT<OptionT<T>>, OptionT<T>> {
471
- return typedAction(optionBranch(IDENTITY, TAG_NONE));
472
- },
473
-
474
- /**
475
- * Conditional keep. If Some, pass value to predicate which returns
476
- * Option<T> (some() to keep, none() to discard). If None, stay None.
477
- * `Option<T> → Option<T>`
478
- *
479
- * This has the same signature and desugaring as andThen with T=U.
480
- * Named "filter" for readability when the intent is filtering.
481
- *
482
- * Desugars to: `branch({ Some: predicate, None: tag("None") })`
483
- */
484
- filter<T>(
485
- predicate: Pipeable<T, OptionT<T>>,
486
- ): TypedAction<OptionT<T>, OptionT<T>> {
487
- return typedAction(optionBranch(predicate as Action, TAG_NONE));
488
- },
489
-
490
- /**
491
- * Collect Some values from an array, discarding Nones.
492
- * `Option<T>[] → T[]`
493
- *
494
- * This is a builtin handler (CollectSome) — it can't be expressed
495
- * as a composition of existing AST nodes because it requires
496
- * array-level filtering logic.
497
- */
498
- collect<T = any>(): TypedAction<OptionT<T>[], T[]> {
499
- return typedAction({
500
- kind: "Invoke",
501
- handler: { kind: "Builtin", builtin: { kind: "CollectSome" } },
502
- });
503
- },
504
-
505
- /**
506
- * Test if the value is Some. `Option<T> → boolean`
507
- *
508
- * Rarely useful — branch on Some/None directly instead.
509
- *
510
- * Desugars to: `branch({ Some: pipe(drop(), constant(true)), None: pipe(drop(), constant(false)) })`
511
- */
512
- isSome<T>(): TypedAction<OptionT<T>, boolean> {
513
- const constTrue: Action = {
514
- kind: "Invoke",
515
- handler: { kind: "Builtin", builtin: { kind: "Constant", value: true } },
516
- };
517
- const constFalse: Action = {
518
- kind: "Invoke",
519
- handler: { kind: "Builtin", builtin: { kind: "Constant", value: false } },
520
- };
521
- return typedAction(
522
- optionBranch(
523
- { kind: "Chain", first: DROP, rest: constTrue },
524
- { kind: "Chain", first: DROP, rest: constFalse },
525
- ),
526
- );
527
- },
528
-
529
- /**
530
- * Test if the value is None. `Option<T> → boolean`
531
- *
532
- * Rarely useful — branch on Some/None directly instead.
533
- *
534
- * Desugars to: `branch({ Some: pipe(drop(), constant(false)), None: pipe(drop(), constant(true)) })`
535
- */
536
- isNone<T>(): TypedAction<OptionT<T>, boolean> {
537
- const constTrue: Action = {
538
- kind: "Invoke",
539
- handler: { kind: "Builtin", builtin: { kind: "Constant", value: true } },
540
- };
541
- const constFalse: Action = {
542
- kind: "Invoke",
543
- handler: { kind: "Builtin", builtin: { kind: "Constant", value: false } },
544
- };
545
- return typedAction(
546
- optionBranch(
547
- { kind: "Chain", first: DROP, rest: constFalse },
548
- { kind: "Chain", first: DROP, rest: constTrue },
549
- ),
550
- );
551
- },
552
- } as const;
553
-
554
- // ---------------------------------------------------------------------------
555
- // Result namespace — combinators for Result<TValue, TError> tagged unions
556
- // ---------------------------------------------------------------------------
557
-
558
- // Shared AST fragments for Result desugaring
559
- const TAG_OK: Action = {
560
- kind: "Invoke",
561
- handler: { kind: "Builtin", builtin: { kind: "Tag", value: "Ok" } },
562
- };
563
- const TAG_ERR: Action = {
564
- kind: "Invoke",
565
- handler: { kind: "Builtin", builtin: { kind: "Tag", value: "Err" } },
566
- };
567
-
568
- /** Wrap branch cases with ExtractField("value") auto-unwrapping. */
569
- function resultBranch(okCaseBody: Action, errCaseBody: Action): Action {
570
- return {
571
- kind: "Branch",
572
- cases: {
573
- Ok: { kind: "Chain", first: EXTRACT_VALUE, rest: okCaseBody },
574
- Err: { kind: "Chain", first: EXTRACT_VALUE, rest: errCaseBody },
575
- },
576
- };
577
- }
578
-
579
- /**
580
- * Result namespace. All combinators produce TypedAction AST nodes that
581
- * desugar to branch + existing builtins.
582
- */
583
- export const Result = {
584
- /**
585
- * Wrap a value as Ok. `TValue → Result<TValue, TError>`
586
- */
587
- ok<TValue, TError>(): TypedAction<TValue, ResultT<TValue, TError>> {
588
- return typedAction(TAG_OK);
589
- },
590
-
591
- /**
592
- * Wrap a value as Err. `TError → Result<TValue, TError>`
593
- */
594
- err<TValue, TError>(): TypedAction<TError, ResultT<TValue, TError>> {
595
- return typedAction(TAG_ERR);
596
- },
597
-
598
- /**
599
- * Transform the Ok value. `Result<TValue, TError> → Result<TOut, TError>`
600
- *
601
- * Desugars to: `branch({ Ok: pipe(action, tag("Ok")), Err: tag("Err") })`
602
- */
603
- map<TValue, TOut, TError>(
604
- action: Pipeable<TValue, TOut>,
605
- ): TypedAction<ResultT<TValue, TError>, ResultT<TOut, TError>> {
606
- return typedAction(
607
- resultBranch(
608
- { kind: "Chain", first: action as Action, rest: TAG_OK },
609
- TAG_ERR,
610
- ),
611
- );
612
- },
613
-
614
- /**
615
- * Transform the Err value. `Result<TValue, TError> → Result<TValue, TErrorOut>`
616
- *
617
- * Desugars to: `branch({ Ok: tag("Ok"), Err: pipe(action, tag("Err")) })`
618
- */
619
- mapErr<TValue, TError, TErrorOut>(
620
- action: Pipeable<TError, TErrorOut>,
621
- ): TypedAction<ResultT<TValue, TError>, ResultT<TValue, TErrorOut>> {
622
- return typedAction(
623
- resultBranch(TAG_OK, {
624
- kind: "Chain",
625
- first: action as Action,
626
- rest: TAG_ERR,
627
- }),
628
- );
629
- },
630
-
631
- /**
632
- * Monadic bind (flatMap) for Ok. If Ok, pass value to action which
633
- * returns Result<TOut, TError>. If Err, propagate.
634
- *
635
- * Desugars to: `branch({ Ok: action, Err: tag("Err") })`
636
- */
637
- andThen<TValue, TOut, TError>(
638
- action: Pipeable<TValue, ResultT<TOut, TError>>,
639
- ): TypedAction<ResultT<TValue, TError>, ResultT<TOut, TError>> {
640
- return typedAction(resultBranch(action as Action, TAG_ERR));
641
- },
642
-
643
- /**
644
- * Fallback on Err. If Ok, keep it. If Err, pass error to fallback
645
- * which returns a new Result.
646
- *
647
- * Desugars to: `branch({ Ok: tag("Ok"), Err: fallback })`
648
- */
649
- or<TValue, TError, TErrorOut>(
650
- fallback: Pipeable<TError, ResultT<TValue, TErrorOut>>,
651
- ): TypedAction<ResultT<TValue, TError>, ResultT<TValue, TErrorOut>> {
652
- return typedAction(resultBranch(TAG_OK, fallback as Action));
653
- },
654
-
655
- /**
656
- * Replace Ok value with another Result. If Ok, discard value and
657
- * return other. If Err, propagate.
658
- *
659
- * Desugars to: `branch({ Ok: pipe(drop(), other), Err: tag("Err") })`
660
- */
661
- and<TValue, TOut, TError>(
662
- other: Pipeable<never, ResultT<TOut, TError>>,
663
- ): TypedAction<ResultT<TValue, TError>, ResultT<TOut, TError>> {
664
- return typedAction(
665
- resultBranch(
666
- { kind: "Chain", first: DROP, rest: other as Action },
667
- TAG_ERR,
668
- ),
669
- );
670
- },
671
-
672
- /**
673
- * Extract Ok or compute default from Err. `Result<TValue, TError> → TValue`
674
- *
675
- * Takes an action that receives the Err payload and produces a fallback.
676
- * Uses covariant output checking so throw tokens (Out=never) are assignable
677
- * when TValue is provided explicitly: `Result.unwrapOr<string, string>(throwError)`.
678
- *
679
- * For inference-free usage with throw tokens, prefer the postfix method:
680
- * `handler.unwrapOr(throwError)` — the `this` constraint provides TValue.
681
- *
682
- * Desugars to: `branch({ Ok: identity(), Err: defaultAction })`
683
- */
684
- unwrapOr<TValue, TError>(
685
- defaultAction: Action & {
686
- __in?: (input: TError) => void;
687
- __out?: () => TValue;
688
- },
689
- ): TypedAction<ResultT<TValue, TError>, TValue> {
690
- return typedAction(resultBranch(IDENTITY, defaultAction as Action));
691
- },
692
-
693
- /**
694
- * Unwrap nested Result. `Result<Result<TValue, TError>, TError> → Result<TValue, TError>`
695
- *
696
- * Desugars to: `branch({ Ok: identity(), Err: tag("Err") })`
697
- */
698
- flatten<TValue, TError>(): TypedAction<
699
- ResultT<ResultT<TValue, TError>, TError>,
700
- ResultT<TValue, TError>
701
- > {
702
- return typedAction(resultBranch(IDENTITY, TAG_ERR));
703
- },
704
-
705
- /**
706
- * Convert Ok to Some, Err to None. `Result<TValue, TError> → Option<TValue>`
707
- *
708
- * Desugars to: `branch({ Ok: tag("Some"), Err: pipe(drop(), tag("None")) })`
709
- */
710
- toOption<TValue, TError>(): TypedAction<
711
- ResultT<TValue, TError>,
712
- OptionT<TValue>
713
- > {
714
- return typedAction(
715
- resultBranch(TAG_SOME, { kind: "Chain", first: DROP, rest: TAG_NONE }),
716
- );
717
- },
718
-
719
- /**
720
- * Convert Err to Some, Ok to None. `Result<TValue, TError> → Option<TError>`
721
- *
722
- * Desugars to: `branch({ Ok: pipe(drop(), tag("None")), Err: tag("Some") })`
723
- */
724
- toOptionErr<TValue, TError>(): TypedAction<
725
- ResultT<TValue, TError>,
726
- OptionT<TError>
727
- > {
728
- return typedAction(
729
- resultBranch({ kind: "Chain", first: DROP, rest: TAG_NONE }, TAG_SOME),
730
- );
731
- },
732
-
733
- /**
734
- * Swap Result/Option nesting.
735
- * `Result<Option<TValue>, TError> → Option<Result<TValue, TError>>`
736
- */
737
- transpose<TValue, TError>(): TypedAction<
738
- ResultT<OptionT<TValue>, TError>,
739
- OptionT<ResultT<TValue, TError>>
740
- > {
741
- return typedAction(
742
- resultBranch(
743
- // Ok case: receives Option<TValue>, branch on Some/None
744
- {
745
- kind: "Branch",
746
- cases: {
747
- Some: {
748
- kind: "Chain",
749
- first: EXTRACT_VALUE,
750
- rest: { kind: "Chain", first: TAG_OK, rest: TAG_SOME },
751
- },
752
- None: {
753
- kind: "Chain",
754
- first: EXTRACT_VALUE,
755
- rest: { kind: "Chain", first: DROP, rest: TAG_NONE },
756
- },
757
- },
758
- },
759
- // Err case: receives TError, wrap as Result.err then Option.some
760
- { kind: "Chain", first: TAG_ERR, rest: TAG_SOME },
761
- ),
762
- );
763
- },
764
-
765
- /**
766
- * Test if the value is Ok. `Result<TValue, TError> → boolean`
767
- */
768
- isOk<TValue, TError>(): TypedAction<ResultT<TValue, TError>, boolean> {
769
- const constTrue: Action = {
770
- kind: "Invoke",
771
- handler: { kind: "Builtin", builtin: { kind: "Constant", value: true } },
772
- };
773
- const constFalse: Action = {
774
- kind: "Invoke",
775
- handler: { kind: "Builtin", builtin: { kind: "Constant", value: false } },
776
- };
777
- return typedAction(
778
- resultBranch(
779
- { kind: "Chain", first: DROP, rest: constTrue },
780
- { kind: "Chain", first: DROP, rest: constFalse },
781
- ),
782
- );
783
- },
784
-
785
- /**
786
- * Test if the value is Err. `Result<TValue, TError> → boolean`
787
- */
788
- isErr<TValue, TError>(): TypedAction<ResultT<TValue, TError>, boolean> {
789
- const constTrue: Action = {
790
- kind: "Invoke",
791
- handler: { kind: "Builtin", builtin: { kind: "Constant", value: true } },
792
- };
793
- const constFalse: Action = {
794
- kind: "Invoke",
795
- handler: { kind: "Builtin", builtin: { kind: "Constant", value: false } },
796
- };
797
- return typedAction(
798
- resultBranch(
799
- { kind: "Chain", first: DROP, rest: constFalse },
800
- { kind: "Chain", first: DROP, rest: constTrue },
801
- ),
802
- );
803
- },
804
- } as const;