@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/chain.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
- type Action,
3
2
  type Pipeable,
4
3
  type TypedAction,
4
+ toAction,
5
5
  typedAction,
6
6
  } from "./ast.js";
7
7
 
@@ -9,9 +9,9 @@ export function chain<T1, T2, T3>(
9
9
  first: Pipeable<T1, T2>,
10
10
  rest: Pipeable<T2, T3>,
11
11
  ): TypedAction<T1, T3> {
12
- return typedAction({
12
+ return typedAction<T1, T3>({
13
13
  kind: "Chain",
14
- first: first as Action,
15
- rest: rest as Action,
14
+ first: toAction(first),
15
+ rest: toAction(rest),
16
16
  });
17
17
  }
package/src/handler.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  import { fileURLToPath } from "node:url";
2
2
  import type { JSONSchema7 } from "json-schema";
3
3
  import type { z } from "zod";
4
- import { type TypedAction, typedAction } from "./ast.js";
4
+ import { type TypedAction, toAction, typedAction } from "./ast.js";
5
+ import { chain } from "./chain.js";
6
+ import { all } from "./all.js";
7
+ import { constant, identity } from "./builtins/index.js";
5
8
  import { zodToCheckedJsonSchema } from "./schema.js";
6
9
 
7
10
  // ---------------------------------------------------------------------------
@@ -84,21 +87,21 @@ function getCallerFilePath(): string {
84
87
  }
85
88
 
86
89
  // ---------------------------------------------------------------------------
87
- // HandlerOutput — maps void → never so fire-and-forget handlers compose
90
+ // HandlerOutput — maps void → void (null at runtime)
88
91
  // ---------------------------------------------------------------------------
89
92
 
90
93
  /**
91
- * Handlers that return `Promise<void>` produce `never` output. This means
92
- * they naturally compose in pipes without needing `.drop()` a handler
93
- * that returns nothing produces a value no one can observe.
94
+ * Handlers that return `Promise<void>` produce `void` output (null at
95
+ * runtime). This is honest the handler completes and returns nothing
96
+ * useful, but execution continues.
94
97
  */
95
- type HandlerOutput<TOutput> = [TOutput] extends [void] ? never : TOutput;
98
+ type HandlerOutput<TOutput> = [TOutput] extends [void] ? void : TOutput;
96
99
 
97
100
  // ---------------------------------------------------------------------------
98
101
  // createHandler — single overload, validators optional
99
102
  // ---------------------------------------------------------------------------
100
103
 
101
- export function createHandler<TValue = never, TOutput = unknown>(
104
+ export function createHandler<TValue = void, TOutput = unknown>(
102
105
  definition: {
103
106
  inputValidator?: z.ZodType<TValue>;
104
107
  outputValidator?: z.ZodType<NoInfer<TOutput>>;
@@ -158,7 +161,7 @@ export function createHandler(
158
161
  // ---------------------------------------------------------------------------
159
162
 
160
163
  export function createHandlerWithConfig<
161
- TValue = never,
164
+ TValue = void,
162
165
  TOutput = unknown,
163
166
  TStepConfig = unknown,
164
167
  >(
@@ -245,26 +248,7 @@ export function createHandlerWithConfig(
245
248
  // __definition for the worker to find (the worker imports the module
246
249
  // and accesses the named export, which is this function).
247
250
  const factory = (config: unknown): TypedAction =>
248
- typedAction({
249
- kind: "Chain",
250
- first: {
251
- kind: "All",
252
- actions: [
253
- {
254
- kind: "Invoke",
255
- handler: { kind: "Builtin", builtin: { kind: "Identity" } },
256
- },
257
- {
258
- kind: "Invoke",
259
- handler: {
260
- kind: "Builtin",
261
- builtin: { kind: "Constant", value: config },
262
- },
263
- },
264
- ],
265
- },
266
- rest: invokeAction,
267
- });
251
+ chain(toAction(all(identity(), constant(config))), toAction(invokeAction));
268
252
 
269
253
  Object.defineProperty(factory, HANDLER_BRAND, {
270
254
  value: true,
package/src/index.ts CHANGED
@@ -1,30 +1,37 @@
1
- import type { TaggedUnion, OptionDef, ResultDef } from "./ast.js";
1
+ import type { TaggedUnion, OptionDef, ResultDef, IteratorDef } from "./ast.js";
2
2
 
3
3
  export * from "./ast.js";
4
4
  export {
5
+ allObject,
6
+ asOption,
5
7
  constant,
6
- identity,
7
8
  drop,
8
- tag,
9
- merge,
10
9
  flatten,
11
- extractField,
12
- extractIndex,
10
+ getField,
11
+ getIndex,
12
+ identity,
13
+ panic,
13
14
  pick,
14
- dropResult,
15
- withResource,
16
- augment,
17
- tap,
18
15
  range,
19
- Option,
20
- Result,
21
- } from "./builtins.js";
22
- export * from "./handler.js";
23
- export { runPipeline } from "./run.js";
16
+ splitFirst,
17
+ splitLast,
18
+ tag,
19
+ taggedUnionSchema,
20
+ withResource,
21
+ wrapInField,
22
+ } from "./builtins/index.js";
23
+ export { Option, first, last } from "./option.js";
24
+ export { Result } from "./result.js";
25
+ export { Iterator } from "./iterator.js";
26
+ export { runPipeline, type RunPipelineOptions, type LogLevel } from "./run.js";
24
27
  export { zodToCheckedJsonSchema } from "./schema.js";
25
28
 
26
29
  // Declaration merge: the explicit value exports of Option/Result from builtins
27
30
  // shadow the type-only exports from ast's `export *`. Re-declare the generic
28
31
  // type aliases here so consumers get both the type and value under one name.
29
- export type Option<T> = TaggedUnion<OptionDef<T>>;
30
- export type Result<TValue, TError> = TaggedUnion<ResultDef<TValue, TError>>;
32
+ export type Option<T> = TaggedUnion<"Option", OptionDef<T>>;
33
+ export type Result<TValue, TError> = TaggedUnion<
34
+ "Result",
35
+ ResultDef<TValue, TError>
36
+ >;
37
+ export type Iterator<TElement> = TaggedUnion<"Iterator", IteratorDef<TElement>>;
@@ -0,0 +1,243 @@
1
+ import {
2
+ type Iterator as IteratorT,
3
+ type IteratorDef,
4
+ type Option as OptionT,
5
+ type Pipeable,
6
+ type Result as ResultT,
7
+ type TypedAction,
8
+ toAction,
9
+ typedAction,
10
+ branch,
11
+ branchFamily,
12
+ forEach,
13
+ loop,
14
+ } from "./ast.js";
15
+ import { chain } from "./chain.js";
16
+ import {
17
+ constant,
18
+ drop,
19
+ flatten,
20
+ getField,
21
+ getIndex,
22
+ identity,
23
+ slice,
24
+ splitFirst,
25
+ splitLast,
26
+ tag,
27
+ } from "./builtins/index.js";
28
+ import { all } from "./all.js";
29
+ import { Option } from "./option.js";
30
+ import { bindInput } from "./bind.js";
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Helpers
34
+ // ---------------------------------------------------------------------------
35
+
36
+ /**
37
+ * Wrap a single value in an array. `T → T[]`
38
+ * Implemented as `all(identity())`. May warrant a dedicated builtin later.
39
+ */
40
+ function wrapInArray<TElement>(): TypedAction<TElement, TElement[]> {
41
+ return all(identity()) as TypedAction<TElement, TElement[]>;
42
+ }
43
+
44
+ /**
45
+ * Normalize any IntoIterator return type to a plain array.
46
+ * Used inside `.flatMap()` to handle Iterator, Option, Result, and Array returns.
47
+ */
48
+ const intoIteratorNormalize = branchFamily({
49
+ Iterator: branch({ Iterator: identity() }),
50
+ Option: branch({ Some: wrapInArray(), None: constant([]) }),
51
+ Result: branch({ Ok: wrapInArray(), Err: constant([]) }),
52
+ Array: identity(),
53
+ });
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Iterator namespace
57
+ // ---------------------------------------------------------------------------
58
+
59
+ export const Iterator = {
60
+ /** Wrap an array as Iterator. `T[] → Iterator<T>` */
61
+ fromArray<TElement>(): TypedAction<TElement[], IteratorT<TElement>> {
62
+ return tag<"Iterator", IteratorDef<TElement>, "Iterator">(
63
+ "Iterator",
64
+ "Iterator",
65
+ );
66
+ },
67
+
68
+ /** Wrap an Option as Iterator. `Option<T> → Iterator<T>` */
69
+ fromOption<TElement>(): TypedAction<OptionT<TElement>, IteratorT<TElement>> {
70
+ return branch({
71
+ Some: chain(wrapInArray<TElement>(), Iterator.fromArray<TElement>()),
72
+ None: chain(constant<TElement[]>([]), Iterator.fromArray<TElement>()),
73
+ }) as TypedAction<OptionT<TElement>, IteratorT<TElement>>;
74
+ },
75
+
76
+ /** Wrap a Result as Iterator (Ok kept, Err dropped). `Result<T, E> → Iterator<T>` */
77
+ fromResult<TElement, TError>(): TypedAction<
78
+ ResultT<TElement, TError>,
79
+ IteratorT<TElement>
80
+ > {
81
+ return branch({
82
+ Ok: chain(wrapInArray<TElement>(), Iterator.fromArray<TElement>()),
83
+ Err: chain(constant<TElement[]>([]), Iterator.fromArray<TElement>()),
84
+ }) as TypedAction<ResultT<TElement, TError>, IteratorT<TElement>>;
85
+ },
86
+
87
+ /** Unwrap Iterator to array. `Iterator<T> → T[]` */
88
+ collect<TElement>(): TypedAction<IteratorT<TElement>, TElement[]> {
89
+ return getField("value") as TypedAction<IteratorT<TElement>, TElement[]>;
90
+ },
91
+
92
+ /** Transform each element. `Iterator<T> → Iterator<U>` */
93
+ map<TIn, TOut>(
94
+ action: Pipeable<TIn, TOut>,
95
+ ): TypedAction<IteratorT<TIn>, IteratorT<TOut>> {
96
+ return chain(
97
+ toAction(getField("value")),
98
+ chain(toAction(forEach(action)), Iterator.fromArray<TOut>()),
99
+ ) as TypedAction<IteratorT<TIn>, IteratorT<TOut>>;
100
+ },
101
+
102
+ /** Flat-map each element. `f` returns any IntoIterator type. `Iterator<T> → Iterator<U>` */
103
+ flatMap<TIn, TOut>(
104
+ action: Pipeable<TIn, unknown>,
105
+ ): TypedAction<IteratorT<TIn>, IteratorT<TOut>> {
106
+ return chain(
107
+ toAction(getField("value")),
108
+ chain(
109
+ toAction(forEach(chain(action, intoIteratorNormalize))),
110
+ chain(toAction(flatten()), Iterator.fromArray<TOut>()),
111
+ ),
112
+ ) as TypedAction<IteratorT<TIn>, IteratorT<TOut>>;
113
+ },
114
+
115
+ /** Keep elements where predicate returns true. `Iterator<T> → Iterator<T>` */
116
+ filter<TElement>(
117
+ predicate: Pipeable<TElement, boolean>,
118
+ ): TypedAction<IteratorT<TElement>, IteratorT<TElement>> {
119
+ return Iterator.flatMap<TElement, TElement>(
120
+ bindInput<TElement>((element) =>
121
+ element
122
+ .then(predicate)
123
+ .asOption()
124
+ .branch({
125
+ Some: element.some(),
126
+ None: chain(drop, Option.none<TElement>()),
127
+ }),
128
+ ),
129
+ );
130
+ },
131
+
132
+ /** Head/tail decomposition. `Iterator<T> → Option<[T, Iterator<T>]>` */
133
+ splitFirst<TElement>(): TypedAction<
134
+ IteratorT<TElement>,
135
+ OptionT<[TElement, IteratorT<TElement>]>
136
+ > {
137
+ return Iterator.collect<TElement>()
138
+ .then(splitFirst<TElement>())
139
+ .then(
140
+ Option.map(
141
+ all(
142
+ getIndex<[TElement, TElement[]], 0>(0).unwrap(),
143
+ getIndex<[TElement, TElement[]], 1>(1).unwrap().iterate(),
144
+ ),
145
+ ),
146
+ );
147
+ },
148
+
149
+ /** Init/last decomposition. `Iterator<T> → Option<[Iterator<T>, T]>` */
150
+ splitLast<TElement>(): TypedAction<
151
+ IteratorT<TElement>,
152
+ OptionT<[IteratorT<TElement>, TElement]>
153
+ > {
154
+ return Iterator.collect<TElement>()
155
+ .then(splitLast<TElement>())
156
+ .then(
157
+ Option.map(
158
+ all(
159
+ getIndex<[TElement[], TElement], 0>(0).unwrap().iterate(),
160
+ getIndex<[TElement[], TElement], 1>(1).unwrap(),
161
+ ),
162
+ ),
163
+ );
164
+ },
165
+
166
+ /** Fold elements with accumulator. `Iterator<T> → TAcc` */
167
+ fold<TElement, TAcc>(
168
+ init: Pipeable<void, TAcc>,
169
+ body: Pipeable<[TAcc, TElement], TAcc>,
170
+ ): TypedAction<IteratorT<TElement>, TAcc> {
171
+ return Iterator.collect<TElement>().then(
172
+ bindInput<TElement[]>((elements) =>
173
+ all(init, elements).then(
174
+ loop<TAcc, [TAcc, TElement[]]>((recur, done) => {
175
+ // Re-wrap done to bridge VoidToNull<TAcc> → TAcc (TypeScript
176
+ // can't simplify the conditional type for generic TAcc).
177
+ const doneTAcc = typedAction<TAcc, never>(toAction(done));
178
+
179
+ // Wrap return with typedAction — branch output inference fails
180
+ // for generic types inside loop bodies.
181
+ return typedAction<[TAcc, TElement[]], never>(
182
+ toAction(
183
+ bindInput<[TAcc, TElement[]]>((state) => {
184
+ const acc = state.getIndex(0).unwrap();
185
+ const remaining = state.getIndex(1).unwrap();
186
+
187
+ return remaining.splitFirst().branch({
188
+ None: acc.then(doneTAcc),
189
+ Some: bindInput<[TElement, TElement[]]>((headTail) => {
190
+ const head = headTail.getIndex(0).unwrap();
191
+ const tail = headTail.getIndex(1).unwrap();
192
+
193
+ return all(acc, head)
194
+ .then(body)
195
+ .then(
196
+ bindInput<TAcc>((newAcc) =>
197
+ all(newAcc, tail).then(recur),
198
+ ),
199
+ );
200
+ }),
201
+ });
202
+ }),
203
+ ),
204
+ );
205
+ }),
206
+ ),
207
+ ),
208
+ );
209
+ },
210
+
211
+ /** Slice elements from start to end. `Iterator<T> → Iterator<T>` */
212
+ slice<TElement>(
213
+ start: number,
214
+ end?: number,
215
+ ): TypedAction<IteratorT<TElement>, IteratorT<TElement>> {
216
+ return chain(
217
+ Iterator.collect<TElement>(),
218
+ chain(
219
+ toAction(slice<TElement>(start, end)),
220
+ Iterator.fromArray<TElement>(),
221
+ ),
222
+ );
223
+ },
224
+
225
+ /** First n elements. `Iterator<T> → Iterator<T>` */
226
+ take<TElement>(
227
+ n: number,
228
+ ): TypedAction<IteratorT<TElement>, IteratorT<TElement>> {
229
+ return Iterator.slice(0, n);
230
+ },
231
+
232
+ /** Drop first n elements. `Iterator<T> → Iterator<T>` */
233
+ skip<TElement>(
234
+ n: number,
235
+ ): TypedAction<IteratorT<TElement>, IteratorT<TElement>> {
236
+ return Iterator.slice(n);
237
+ },
238
+
239
+ /** Check if iterator is empty. `Iterator<T> → boolean` */
240
+ isEmpty<TElement>(): TypedAction<IteratorT<TElement>, boolean> {
241
+ return Iterator.collect<TElement>().splitFirst().isNone();
242
+ },
243
+ } as const;
package/src/option.ts ADDED
@@ -0,0 +1,199 @@
1
+ import {
2
+ type Option as OptionT,
3
+ type OptionDef,
4
+ type Pipeable,
5
+ type Result as ResultT,
6
+ type TypedAction,
7
+ toAction,
8
+ typedAction,
9
+ branch,
10
+ } from "./ast.js";
11
+ import { chain } from "./chain.js";
12
+ import {
13
+ constant,
14
+ drop,
15
+ getIndex,
16
+ identity,
17
+ panic,
18
+ splitFirst,
19
+ splitLast,
20
+ tag,
21
+ } from "./builtins/index.js";
22
+ import { Result } from "./result.js";
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Option namespace — combinators for Option<T> tagged unions
26
+ // ---------------------------------------------------------------------------
27
+
28
+ /**
29
+ * Option namespace. All combinators produce TypedAction AST nodes that
30
+ * desugar to branch + existing builtins, except collect which uses the
31
+ * CollectSome builtin.
32
+ */
33
+ export const Option = {
34
+ /** Tag combinator: wrap value as `Option.Some`. `T → Option<T>` */
35
+ some<T>(): TypedAction<T, OptionT<T>> {
36
+ return tag<"Option", OptionDef<T>, "Some">("Some", "Option");
37
+ },
38
+ /** Tag combinator: wrap value as `Option.None`. `void → Option<T>` */
39
+ none<T>(): TypedAction<void, OptionT<T>> {
40
+ return tag<"Option", OptionDef<T>, "None">("None", "Option");
41
+ },
42
+
43
+ /** Transform the Some value. `Option<T> → Option<U>` */
44
+ map<T, U>(action: Pipeable<T, U>): TypedAction<OptionT<T>, OptionT<U>> {
45
+ return branch({
46
+ Some: chain(action, Option.some<U>()),
47
+ None: Option.none<U>(),
48
+ }) as TypedAction<OptionT<T>, OptionT<U>>;
49
+ },
50
+
51
+ /**
52
+ * Monadic bind (flatMap). If Some, pass the value to action which
53
+ * returns Option<U>. If None, stay None. `Option<T> → Option<U>`
54
+ */
55
+ andThen<T, U>(
56
+ action: Pipeable<T, OptionT<U>>,
57
+ ): TypedAction<OptionT<T>, OptionT<U>> {
58
+ return branch({
59
+ Some: action,
60
+ None: Option.none<U>(),
61
+ }) as TypedAction<OptionT<T>, OptionT<U>>;
62
+ },
63
+
64
+ /**
65
+ * Extract the Some value or panic. `Option<T> → T`
66
+ *
67
+ * Panics (fatal, not caught by tryCatch) if the value is None.
68
+ */
69
+ unwrap<T>(): TypedAction<OptionT<T>, T> {
70
+ return branch({
71
+ Some: identity<T>(),
72
+ None: panic("called unwrap on None"),
73
+ }) as TypedAction<OptionT<T>, T>;
74
+ },
75
+
76
+ /**
77
+ * Extract the Some value or produce a default from an action.
78
+ * `Option<T> → T`
79
+ */
80
+ unwrapOr<T>(defaultAction: Pipeable<void, T>): TypedAction<OptionT<T>, T> {
81
+ return branch({
82
+ Some: identity<T>(),
83
+ None: defaultAction,
84
+ }) as TypedAction<OptionT<T>, T>;
85
+ },
86
+
87
+ /**
88
+ * Conditional keep. If Some, pass value to predicate which returns
89
+ * Option<T>. If None, stay None. `Option<T> → Option<T>`
90
+ */
91
+ filter<T>(
92
+ predicate: Pipeable<T, OptionT<T>>,
93
+ ): TypedAction<OptionT<T>, OptionT<T>> {
94
+ return branch({
95
+ Some: predicate,
96
+ None: Option.none<T>(),
97
+ }) as TypedAction<OptionT<T>, OptionT<T>>;
98
+ },
99
+
100
+ /**
101
+ * Collect Some values from an array, discarding Nones.
102
+ * `Option<T>[] → T[]`
103
+ */
104
+ collect<T = any>(): TypedAction<OptionT<T>[], T[]> {
105
+ return typedAction({
106
+ kind: "Invoke",
107
+ handler: { kind: "Builtin", builtin: { kind: "CollectSome" } },
108
+ });
109
+ },
110
+
111
+ /**
112
+ * Test if the value is Some. `Option<T> → boolean`
113
+ */
114
+ isSome<T>(): TypedAction<OptionT<T>, boolean> {
115
+ return branch({
116
+ Some: constant<boolean>(true),
117
+ None: constant<boolean>(false),
118
+ }) as TypedAction<OptionT<T>, boolean>;
119
+ },
120
+
121
+ /**
122
+ * Test if the value is None. `Option<T> → boolean`
123
+ */
124
+ isNone<T>(): TypedAction<OptionT<T>, boolean> {
125
+ return branch({
126
+ Some: constant<boolean>(false),
127
+ None: constant<boolean>(true),
128
+ }) as TypedAction<OptionT<T>, boolean>;
129
+ },
130
+
131
+ /**
132
+ * Swap Option/Result nesting.
133
+ * `Option<Result<TValue, TError>> → Result<Option<TValue>, TError>`
134
+ *
135
+ * - Some(Ok(t)) → Ok(Some(t))
136
+ * - Some(Err(e)) → Err(e)
137
+ * - None → Ok(None)
138
+ */
139
+ transpose<TValue, TError>(): TypedAction<
140
+ OptionT<ResultT<TValue, TError>>,
141
+ ResultT<OptionT<TValue>, TError>
142
+ > {
143
+ return branch({
144
+ Some: branch({
145
+ Ok: chain(Option.some<TValue>(), Result.ok<OptionT<TValue>, TError>()),
146
+ Err: Result.err<OptionT<TValue>, TError>(),
147
+ }),
148
+ None: chain(
149
+ chain(drop, Option.none<TValue>()),
150
+ Result.ok<OptionT<TValue>, TError>(),
151
+ ),
152
+ }) as TypedAction<
153
+ OptionT<ResultT<TValue, TError>>,
154
+ ResultT<OptionT<TValue>, TError>
155
+ >;
156
+ },
157
+ } as const;
158
+
159
+ // ---------------------------------------------------------------------------
160
+ // First — extract the first element of an array as Option<TElement>
161
+ // ---------------------------------------------------------------------------
162
+
163
+ /**
164
+ * Extract the first element of an array.
165
+ * `readonly TElement[] → Option<TElement>`
166
+ *
167
+ * Composes `splitFirst` (which returns `Option<[TElement, TElement[]]>`)
168
+ * with `Option.map(getIndex(0))` to extract just the element.
169
+ */
170
+ export function first<TElement>(): TypedAction<
171
+ readonly TElement[],
172
+ OptionT<TElement>
173
+ > {
174
+ return chain(
175
+ toAction(splitFirst()),
176
+ toAction(Option.map(toAction(getIndex(0).unwrap()))),
177
+ ) as TypedAction<readonly TElement[], OptionT<TElement>>;
178
+ }
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // Last — extract the last element of an array as Option<TElement>
182
+ // ---------------------------------------------------------------------------
183
+
184
+ /**
185
+ * Extract the last element of an array.
186
+ * `readonly TElement[] → Option<TElement>`
187
+ *
188
+ * Composes `splitLast` (which returns `Option<[TElement[], TElement]>`)
189
+ * with `Option.map(getIndex(1))` to extract just the element.
190
+ */
191
+ export function last<TElement>(): TypedAction<
192
+ readonly TElement[],
193
+ OptionT<TElement>
194
+ > {
195
+ return chain(
196
+ toAction(splitLast()),
197
+ toAction(Option.map(toAction(getIndex(1).unwrap()))),
198
+ ) as TypedAction<readonly TElement[], OptionT<TElement>>;
199
+ }