@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.
- package/artifacts/linux-arm64/barnum +0 -0
- package/artifacts/linux-x64/barnum +0 -0
- package/artifacts/macos-arm64/barnum +0 -0
- package/artifacts/macos-x64/barnum +0 -0
- package/artifacts/win-x64/barnum.exe +0 -0
- package/cli.cjs +33 -0
- package/dist/all.d.ts +43 -0
- package/dist/all.d.ts.map +1 -0
- package/dist/all.js +8 -0
- package/dist/ast.d.ts +476 -0
- package/dist/ast.d.ts.map +1 -0
- package/dist/ast.js +419 -0
- package/dist/bind.d.ts +59 -0
- package/dist/bind.d.ts.map +1 -0
- package/dist/bind.js +69 -0
- package/dist/builtins/array.d.ts +36 -0
- package/dist/builtins/array.d.ts.map +1 -0
- package/dist/builtins/array.js +93 -0
- package/dist/builtins/index.d.ts +6 -0
- package/dist/builtins/index.d.ts.map +1 -0
- package/dist/builtins/index.js +5 -0
- package/dist/builtins/scalar.d.ts +12 -0
- package/dist/builtins/scalar.d.ts.map +1 -0
- package/dist/builtins/scalar.js +41 -0
- package/dist/builtins/struct.d.ts +25 -0
- package/dist/builtins/struct.d.ts.map +1 -0
- package/dist/builtins/struct.js +67 -0
- package/dist/builtins/tagged-union.d.ts +54 -0
- package/dist/builtins/tagged-union.d.ts.map +1 -0
- package/dist/builtins/tagged-union.js +81 -0
- package/dist/builtins/with-resource.d.ts +23 -0
- package/dist/builtins/with-resource.d.ts.map +1 -0
- package/dist/builtins/with-resource.js +35 -0
- package/dist/chain.d.ts +3 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +8 -0
- package/dist/effect-id.d.ts +15 -0
- package/dist/effect-id.d.ts.map +1 -0
- package/dist/effect-id.js +16 -0
- package/dist/handler.d.ts +51 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +130 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/iterator.d.ts +32 -0
- package/dist/iterator.d.ts.map +1 -0
- package/dist/iterator.js +123 -0
- package/dist/option.d.ts +74 -0
- package/dist/option.d.ts.map +1 -0
- package/dist/option.js +141 -0
- package/dist/pipe.d.ts +12 -0
- package/dist/pipe.d.ts.map +1 -0
- package/dist/pipe.js +12 -0
- package/dist/race.d.ts +54 -0
- package/dist/race.d.ts.map +1 -0
- package/dist/race.js +116 -0
- package/dist/recursive.d.ts +40 -0
- package/dist/recursive.d.ts.map +1 -0
- package/dist/recursive.js +58 -0
- package/dist/result.d.ts +50 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +117 -0
- package/dist/run.d.ts +14 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +160 -0
- package/dist/runtime.d.ts +6 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +7 -0
- package/dist/schema.d.ts +9 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +95 -0
- package/dist/schemas.d.ts +5 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +13 -0
- package/dist/try-catch.d.ts +24 -0
- package/dist/try-catch.d.ts.map +1 -0
- package/dist/try-catch.js +37 -0
- package/dist/values.d.ts +6 -0
- package/dist/values.d.ts.map +1 -0
- package/dist/values.js +12 -0
- package/dist/worker.d.ts +15 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +58 -0
- package/package.json +42 -16
- package/src/all.ts +133 -0
- package/src/ast.ts +1301 -0
- package/src/bind.ts +162 -0
- package/src/builtins/array.ts +121 -0
- package/src/builtins/index.ts +17 -0
- package/src/builtins/scalar.ts +49 -0
- package/src/builtins/struct.ts +111 -0
- package/src/builtins/tagged-union.ts +142 -0
- package/src/builtins/with-resource.ts +69 -0
- package/src/chain.ts +17 -0
- package/src/effect-id.ts +30 -0
- package/src/handler.ts +263 -0
- package/src/index.ts +37 -0
- package/src/iterator.ts +243 -0
- package/src/option.ts +199 -0
- package/src/pipe.ts +138 -0
- package/src/race.ts +173 -0
- package/src/recursive.ts +129 -0
- package/src/result.ts +168 -0
- package/src/run.ts +209 -0
- package/src/runtime.ts +16 -0
- package/src/schema.ts +118 -0
- package/src/schemas.ts +21 -0
- package/src/try-catch.ts +57 -0
- package/src/values.ts +21 -0
- package/src/worker.ts +71 -0
- package/README.md +0 -19
- package/barnum-config-schema.json +0 -408
- package/cli.js +0 -20
- package/index.js +0 -23
package/src/effect-id.ts
ADDED
|
@@ -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>>;
|
package/src/iterator.ts
ADDED
|
@@ -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;
|