@barnum/barnum 0.2.3 → 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 (115) 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/cli.cjs +33 -0
  7. package/dist/all.d.ts +43 -0
  8. package/dist/all.d.ts.map +1 -0
  9. package/dist/all.js +8 -0
  10. package/dist/ast.d.ts +476 -0
  11. package/dist/ast.d.ts.map +1 -0
  12. package/dist/ast.js +419 -0
  13. package/dist/bind.d.ts +59 -0
  14. package/dist/bind.d.ts.map +1 -0
  15. package/dist/bind.js +69 -0
  16. package/dist/builtins/array.d.ts +36 -0
  17. package/dist/builtins/array.d.ts.map +1 -0
  18. package/dist/builtins/array.js +93 -0
  19. package/dist/builtins/index.d.ts +6 -0
  20. package/dist/builtins/index.d.ts.map +1 -0
  21. package/dist/builtins/index.js +5 -0
  22. package/dist/builtins/scalar.d.ts +12 -0
  23. package/dist/builtins/scalar.d.ts.map +1 -0
  24. package/dist/builtins/scalar.js +41 -0
  25. package/dist/builtins/struct.d.ts +25 -0
  26. package/dist/builtins/struct.d.ts.map +1 -0
  27. package/dist/builtins/struct.js +67 -0
  28. package/dist/builtins/tagged-union.d.ts +54 -0
  29. package/dist/builtins/tagged-union.d.ts.map +1 -0
  30. package/dist/builtins/tagged-union.js +81 -0
  31. package/dist/builtins/with-resource.d.ts +23 -0
  32. package/dist/builtins/with-resource.d.ts.map +1 -0
  33. package/dist/builtins/with-resource.js +35 -0
  34. package/dist/chain.d.ts +3 -0
  35. package/dist/chain.d.ts.map +1 -0
  36. package/dist/chain.js +8 -0
  37. package/dist/effect-id.d.ts +15 -0
  38. package/dist/effect-id.d.ts.map +1 -0
  39. package/dist/effect-id.js +16 -0
  40. package/dist/handler.d.ts +51 -0
  41. package/dist/handler.d.ts.map +1 -0
  42. package/dist/handler.js +130 -0
  43. package/dist/index.d.ts +12 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +7 -0
  46. package/dist/iterator.d.ts +32 -0
  47. package/dist/iterator.d.ts.map +1 -0
  48. package/dist/iterator.js +123 -0
  49. package/dist/option.d.ts +74 -0
  50. package/dist/option.d.ts.map +1 -0
  51. package/dist/option.js +141 -0
  52. package/dist/pipe.d.ts +12 -0
  53. package/dist/pipe.d.ts.map +1 -0
  54. package/dist/pipe.js +12 -0
  55. package/dist/race.d.ts +54 -0
  56. package/dist/race.d.ts.map +1 -0
  57. package/dist/race.js +116 -0
  58. package/dist/recursive.d.ts +40 -0
  59. package/dist/recursive.d.ts.map +1 -0
  60. package/dist/recursive.js +58 -0
  61. package/dist/result.d.ts +50 -0
  62. package/dist/result.d.ts.map +1 -0
  63. package/dist/result.js +117 -0
  64. package/dist/run.d.ts +14 -0
  65. package/dist/run.d.ts.map +1 -0
  66. package/dist/run.js +160 -0
  67. package/dist/runtime.d.ts +6 -0
  68. package/dist/runtime.d.ts.map +1 -0
  69. package/dist/runtime.js +7 -0
  70. package/dist/schema.d.ts +9 -0
  71. package/dist/schema.d.ts.map +1 -0
  72. package/dist/schema.js +95 -0
  73. package/dist/schemas.d.ts +5 -0
  74. package/dist/schemas.d.ts.map +1 -0
  75. package/dist/schemas.js +13 -0
  76. package/dist/try-catch.d.ts +24 -0
  77. package/dist/try-catch.d.ts.map +1 -0
  78. package/dist/try-catch.js +37 -0
  79. package/dist/values.d.ts +6 -0
  80. package/dist/values.d.ts.map +1 -0
  81. package/dist/values.js +12 -0
  82. package/dist/worker.d.ts +15 -0
  83. package/dist/worker.d.ts.map +1 -0
  84. package/dist/worker.js +58 -0
  85. package/package.json +42 -16
  86. package/src/all.ts +133 -0
  87. package/src/ast.ts +1301 -0
  88. package/src/bind.ts +162 -0
  89. package/src/builtins/array.ts +121 -0
  90. package/src/builtins/index.ts +17 -0
  91. package/src/builtins/scalar.ts +49 -0
  92. package/src/builtins/struct.ts +111 -0
  93. package/src/builtins/tagged-union.ts +142 -0
  94. package/src/builtins/with-resource.ts +69 -0
  95. package/src/chain.ts +17 -0
  96. package/src/effect-id.ts +30 -0
  97. package/src/handler.ts +263 -0
  98. package/src/index.ts +37 -0
  99. package/src/iterator.ts +243 -0
  100. package/src/option.ts +199 -0
  101. package/src/pipe.ts +138 -0
  102. package/src/race.ts +173 -0
  103. package/src/recursive.ts +129 -0
  104. package/src/result.ts +168 -0
  105. package/src/run.ts +209 -0
  106. package/src/runtime.ts +16 -0
  107. package/src/schema.ts +118 -0
  108. package/src/schemas.ts +21 -0
  109. package/src/try-catch.ts +57 -0
  110. package/src/values.ts +21 -0
  111. package/src/worker.ts +71 -0
  112. package/README.md +0 -19
  113. package/barnum-config-schema.json +0 -408
  114. package/cli.js +0 -20
  115. package/index.js +0 -23
@@ -0,0 +1,30 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Shared effect ID counter for gensym'd effect identifiers
3
+ // ---------------------------------------------------------------------------
4
+
5
+ /** Branded ID for resume-style effect handlers. */
6
+ export type ResumeHandlerId = number & {
7
+ readonly __resumeHandlerBrand: unique symbol;
8
+ };
9
+
10
+ /** Branded ID for restart-style effect handlers. */
11
+ export type RestartHandlerId = number & {
12
+ readonly __restartHandlerBrand: unique symbol;
13
+ };
14
+
15
+ let nextId = 0;
16
+
17
+ /** Allocate a fresh, unique resume handler ID. */
18
+ export function allocateResumeHandlerId(): ResumeHandlerId {
19
+ return nextId++ as ResumeHandlerId;
20
+ }
21
+
22
+ /** Allocate a fresh, unique restart handler ID. */
23
+ export function allocateRestartHandlerId(): RestartHandlerId {
24
+ return nextId++ as RestartHandlerId;
25
+ }
26
+
27
+ /** Reset the ID counter. For test isolation only. */
28
+ export function resetEffectIdCounter(): void {
29
+ nextId = 0;
30
+ }
package/src/handler.ts ADDED
@@ -0,0 +1,263 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import type { JSONSchema7 } from "json-schema";
3
+ import type { z } from "zod";
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";
8
+ import { zodToCheckedJsonSchema } from "./schema.js";
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // HandlerDefinition — the user's handle function + optional validators
12
+ // ---------------------------------------------------------------------------
13
+
14
+ export interface HandlerDefinition<
15
+ TValue = unknown,
16
+ TOutput = unknown,
17
+ TStepConfig = unknown,
18
+ > {
19
+ inputValidator?: z.ZodType<TValue>;
20
+ outputValidator?: z.ZodType<TOutput>;
21
+ stepConfigValidator?: z.ZodType<TStepConfig>;
22
+ handle: (context: {
23
+ value: TValue;
24
+ stepConfig: TStepConfig;
25
+ }) => Promise<TOutput>;
26
+ }
27
+
28
+ /** Runtime-only handler definition shape — erases generic type info. */
29
+ interface UntypedHandlerDefinition {
30
+ inputValidator?: z.ZodType;
31
+ outputValidator?: z.ZodType;
32
+ stepConfigValidator?: z.ZodType;
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
+ handle: (...args: any[]) => Promise<unknown>;
35
+ }
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Handler — opaque typed handler reference
39
+ // ---------------------------------------------------------------------------
40
+
41
+ const HANDLER_BRAND = Symbol.for("barnum:handler");
42
+
43
+ /**
44
+ * Opaque handler reference with typed metadata. The `__definition` property
45
+ * is non-enumerable — invisible to `JSON.stringify`, visible to the worker.
46
+ */
47
+ export type Handler<TValue = unknown, TOutput = unknown> = TypedAction<
48
+ TValue,
49
+ TOutput
50
+ > & {
51
+ readonly [HANDLER_BRAND]: true;
52
+ readonly __definition: UntypedHandlerDefinition;
53
+ };
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // getCallerFilePath
57
+ // ---------------------------------------------------------------------------
58
+
59
+ /**
60
+ * Deduces the caller's file path from the V8 stack trace API.
61
+ * Frame 0 = getCallerFilePath, Frame 1 = createHandler, Frame 2 = the caller.
62
+ */
63
+ function getCallerFilePath(): string {
64
+ const original = Error.prepareStackTrace;
65
+ let callerFile: string | undefined;
66
+
67
+ Error.prepareStackTrace = (_err, stack): string => {
68
+ const frame = stack[2];
69
+ callerFile = frame?.getFileName() ?? undefined;
70
+ return "";
71
+ };
72
+
73
+ const err = new Error("stack trace capture");
74
+ void err.stack;
75
+ Error.prepareStackTrace = original;
76
+
77
+ if (!callerFile) {
78
+ throw new Error(
79
+ "createHandler: could not determine caller file path from stack trace.",
80
+ );
81
+ }
82
+
83
+ if (callerFile.startsWith("file://")) {
84
+ return fileURLToPath(callerFile);
85
+ }
86
+ return callerFile;
87
+ }
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // HandlerOutput — maps void → void (null at runtime)
91
+ // ---------------------------------------------------------------------------
92
+
93
+ /**
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.
97
+ */
98
+ type HandlerOutput<TOutput> = [TOutput] extends [void] ? void : TOutput;
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // createHandler — single overload, validators optional
102
+ // ---------------------------------------------------------------------------
103
+
104
+ export function createHandler<TValue = void, TOutput = unknown>(
105
+ definition: {
106
+ inputValidator?: z.ZodType<TValue>;
107
+ outputValidator?: z.ZodType<NoInfer<TOutput>>;
108
+ handle: (context: { value: TValue }) => Promise<TOutput>;
109
+ },
110
+ exportName?: string,
111
+ ): Handler<TValue, HandlerOutput<TOutput>>;
112
+
113
+ // Implementation
114
+ export function createHandler(
115
+ definition: UntypedHandlerDefinition,
116
+ exportName?: string,
117
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
118
+ ): any {
119
+ const filePath = getCallerFilePath();
120
+ const funcName = exportName ?? "default";
121
+
122
+ const inputSchema = definition.inputValidator
123
+ ? zodToCheckedJsonSchema(
124
+ definition.inputValidator,
125
+ `${filePath}:${funcName} input`,
126
+ )
127
+ : undefined;
128
+ const outputSchema = definition.outputValidator
129
+ ? zodToCheckedJsonSchema(
130
+ definition.outputValidator,
131
+ `${filePath}:${funcName} output`,
132
+ )
133
+ : undefined;
134
+
135
+ const action = typedAction({
136
+ kind: "Invoke",
137
+ handler: {
138
+ kind: "TypeScript",
139
+ module: filePath,
140
+ func: funcName,
141
+ ...(inputSchema && { input_schema: inputSchema }),
142
+ ...(outputSchema && { output_schema: outputSchema }),
143
+ },
144
+ });
145
+
146
+ // Non-enumerable: invisible to JSON.stringify, visible to the worker
147
+ Object.defineProperty(action, HANDLER_BRAND, {
148
+ value: true,
149
+ enumerable: false,
150
+ });
151
+ Object.defineProperty(action, "__definition", {
152
+ value: definition,
153
+ enumerable: false,
154
+ });
155
+
156
+ return action;
157
+ }
158
+
159
+ // ---------------------------------------------------------------------------
160
+ // createHandlerWithConfig — single overload, validators optional
161
+ // ---------------------------------------------------------------------------
162
+
163
+ export function createHandlerWithConfig<
164
+ TValue = void,
165
+ TOutput = unknown,
166
+ TStepConfig = unknown,
167
+ >(
168
+ definition: {
169
+ inputValidator?: z.ZodType<TValue>;
170
+ outputValidator?: z.ZodType<NoInfer<TOutput>>;
171
+ stepConfigValidator?: z.ZodType<TStepConfig>;
172
+ handle: (context: {
173
+ value: TValue;
174
+ stepConfig: TStepConfig;
175
+ }) => Promise<TOutput>;
176
+ },
177
+ exportName?: string,
178
+ ): (config: TStepConfig) => TypedAction<TValue, HandlerOutput<TOutput>>;
179
+
180
+ // Implementation
181
+ export function createHandlerWithConfig(
182
+ definition: UntypedHandlerDefinition,
183
+ exportName?: string,
184
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
185
+ ): any {
186
+ const filePath = getCallerFilePath();
187
+ const funcName = exportName ?? "default";
188
+
189
+ // The invoke receives [value, config] from All(Identity, Constant(config)).
190
+ // Build a tuple schema manually — the Rust engine doesn't support draft-07
191
+ // array-form `items` for tuples, so use `prefixItems` (2020-12 style).
192
+ const valueSchema = definition.inputValidator
193
+ ? zodToCheckedJsonSchema(
194
+ definition.inputValidator,
195
+ `${filePath}:${funcName} input`,
196
+ )
197
+ : {};
198
+ const configSchema = definition.stepConfigValidator
199
+ ? zodToCheckedJsonSchema(
200
+ definition.stepConfigValidator,
201
+ `${filePath}:${funcName} stepConfig`,
202
+ )
203
+ : {};
204
+ const inputSchema: JSONSchema7 = {
205
+ type: "array",
206
+ prefixItems: [valueSchema, configSchema],
207
+ items: false,
208
+ minItems: 2,
209
+ maxItems: 2,
210
+ } as JSONSchema7;
211
+ const outputSchema = definition.outputValidator
212
+ ? zodToCheckedJsonSchema(
213
+ definition.outputValidator,
214
+ `${filePath}:${funcName} output`,
215
+ )
216
+ : undefined;
217
+
218
+ // Internal handle that unpacks the [value, config] tuple from All
219
+ const internalDefinition: UntypedHandlerDefinition = {
220
+ handle: ({ value }: { value: unknown }) => {
221
+ const [pipelineValue, config] = value as [unknown, unknown];
222
+ return definition.handle({ value: pipelineValue, stepConfig: config });
223
+ },
224
+ };
225
+
226
+ const invokeAction = typedAction({
227
+ kind: "Invoke",
228
+ handler: {
229
+ kind: "TypeScript",
230
+ module: filePath,
231
+ func: funcName,
232
+ input_schema: inputSchema,
233
+ ...(outputSchema && { output_schema: outputSchema }),
234
+ },
235
+ });
236
+
237
+ // Non-enumerable: invisible to JSON.stringify, visible to the worker
238
+ Object.defineProperty(invokeAction, HANDLER_BRAND, {
239
+ value: true,
240
+ enumerable: false,
241
+ });
242
+ Object.defineProperty(invokeAction, "__definition", {
243
+ value: internalDefinition,
244
+ enumerable: false,
245
+ });
246
+
247
+ // The factory function is the module export, so it must also carry
248
+ // __definition for the worker to find (the worker imports the module
249
+ // and accesses the named export, which is this function).
250
+ const factory = (config: unknown): TypedAction =>
251
+ chain(toAction(all(identity(), constant(config))), toAction(invokeAction));
252
+
253
+ Object.defineProperty(factory, HANDLER_BRAND, {
254
+ value: true,
255
+ enumerable: false,
256
+ });
257
+ Object.defineProperty(factory, "__definition", {
258
+ value: internalDefinition,
259
+ enumerable: false,
260
+ });
261
+
262
+ return factory;
263
+ }
package/src/index.ts ADDED
@@ -0,0 +1,37 @@
1
+ import type { TaggedUnion, OptionDef, ResultDef, IteratorDef } from "./ast.js";
2
+
3
+ export * from "./ast.js";
4
+ export {
5
+ allObject,
6
+ asOption,
7
+ constant,
8
+ drop,
9
+ flatten,
10
+ getField,
11
+ getIndex,
12
+ identity,
13
+ panic,
14
+ pick,
15
+ range,
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";
27
+ export { zodToCheckedJsonSchema } from "./schema.js";
28
+
29
+ // Declaration merge: the explicit value exports of Option/Result from builtins
30
+ // shadow the type-only exports from ast's `export *`. Re-declare the generic
31
+ // type aliases here so consumers get both the type and value under one name.
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;