@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.
- 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/dist/all.d.ts +41 -10
- package/dist/all.d.ts.map +1 -0
- package/dist/all.js +1 -1
- package/dist/ast.d.ts +199 -98
- package/dist/ast.d.ts.map +1 -0
- package/dist/ast.js +271 -233
- package/dist/bind.d.ts +9 -12
- package/dist/bind.d.ts.map +1 -0
- package/dist/bind.js +14 -51
- 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 +1 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +3 -3
- package/dist/effect-id.d.ts +1 -0
- package/dist/effect-id.d.ts.map +1 -0
- package/dist/handler.d.ts +7 -6
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +5 -21
- package/dist/index.d.ts +10 -6
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -2
- 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 +11 -10
- package/dist/pipe.d.ts.map +1 -0
- package/dist/pipe.js +5 -4
- package/dist/race.d.ts +5 -4
- package/dist/race.d.ts.map +1 -0
- package/dist/race.js +17 -42
- package/dist/recursive.d.ts +9 -3
- package/dist/recursive.d.ts.map +1 -0
- package/dist/recursive.js +18 -13
- 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 +9 -2
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +37 -20
- 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 +1 -0
- package/dist/schema.d.ts.map +1 -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 +2 -1
- package/dist/try-catch.d.ts.map +1 -0
- package/dist/try-catch.js +10 -9
- 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 +5 -1
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +15 -3
- package/package.json +8 -6
- package/src/all.ts +118 -74
- package/src/ast.ts +773 -350
- package/src/bind.ts +32 -62
- 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 +4 -4
- package/src/handler.ts +12 -28
- package/src/index.ts +24 -17
- package/src/iterator.ts +243 -0
- package/src/option.ts +199 -0
- package/src/pipe.ts +123 -78
- package/src/race.ts +41 -51
- package/src/recursive.ts +44 -27
- package/src/result.ts +168 -0
- package/src/run.ts +53 -25
- package/src/runtime.ts +16 -0
- package/src/schemas.ts +21 -0
- package/src/try-catch.ts +14 -10
- package/src/values.ts +21 -0
- package/src/worker.ts +17 -2
- package/dist/builtins.d.ts +0 -257
- package/dist/builtins.js +0 -600
- package/src/builtins.ts +0 -804
package/src/bind.ts
CHANGED
|
@@ -3,9 +3,12 @@ import {
|
|
|
3
3
|
type ExtractInput,
|
|
4
4
|
type ExtractOutput,
|
|
5
5
|
type TypedAction,
|
|
6
|
+
toAction,
|
|
6
7
|
typedAction,
|
|
7
8
|
} from "./ast.js";
|
|
8
|
-
import {
|
|
9
|
+
import { chain } from "./chain.js";
|
|
10
|
+
import { all } from "./all.js";
|
|
11
|
+
import { identity, drop, getIndex } from "./builtins/index.js";
|
|
9
12
|
import { allocateResumeHandlerId, type ResumeHandlerId } from "./effect-id.js";
|
|
10
13
|
import { pipe } from "./pipe.js";
|
|
11
14
|
|
|
@@ -17,10 +20,10 @@ import { pipe } from "./pipe.js";
|
|
|
17
20
|
* A typed reference to a bound value. Output is `TValue`.
|
|
18
21
|
*
|
|
19
22
|
* Use `.then()` (not `pipe()`) when chaining a VarRef into a generic
|
|
20
|
-
* action like `pick` or `
|
|
23
|
+
* action like `pick` or `getField` — pipe overloads can't infer
|
|
21
24
|
* the generic's type parameter from the VarRef's output.
|
|
22
25
|
*/
|
|
23
|
-
export type VarRef<TValue> = TypedAction<
|
|
26
|
+
export type VarRef<TValue> = TypedAction<any, TValue>;
|
|
24
27
|
|
|
25
28
|
function createVarRef<TValue>(
|
|
26
29
|
resumeHandlerId: ResumeHandlerId,
|
|
@@ -39,12 +42,9 @@ function createVarRef<TValue>(
|
|
|
39
42
|
* Maps each binding's output type to a VarRef. TypeScript resolves
|
|
40
43
|
* ExtractOutput from each binding expression.
|
|
41
44
|
*
|
|
42
|
-
* Constraint is `Action[]` (not `Pipeable<any, any>[]`)
|
|
43
|
-
* `
|
|
44
|
-
*
|
|
45
|
-
* Action union. Using raw `Action[]` avoids the phantom field
|
|
46
|
-
* assignability issue while `ExtractOutput` still extracts the correct
|
|
47
|
-
* output type from the phantom fields on the concrete types.
|
|
45
|
+
* Constraint is `Action[]` (not `Pipeable<any, any>[]`) so that
|
|
46
|
+
* `ExtractOutput` extracts the correct output type from the phantom
|
|
47
|
+
* fields on the concrete types without fighting invariant `__in` checks.
|
|
48
48
|
*/
|
|
49
49
|
export type InferVarRefs<TBindings extends Action[]> = {
|
|
50
50
|
[K in keyof TBindings]: VarRef<ExtractOutput<TBindings[K]>>;
|
|
@@ -61,38 +61,15 @@ export type InferVarRefs<TBindings extends Action[]> = {
|
|
|
61
61
|
* `state` (index 1) is the full All output tuple. The handler produces
|
|
62
62
|
* `[state[n], state]` — value is state[n], new_state is state (unchanged).
|
|
63
63
|
*
|
|
64
|
-
* Expanded AST: All(Chain(
|
|
64
|
+
* Expanded AST: All(Chain(GetIndex(1).unwrap(), GetIndex(n).unwrap()), GetIndex(1).unwrap())
|
|
65
65
|
*/
|
|
66
66
|
function readVar(n: number): Action {
|
|
67
|
-
return
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
kind: "Invoke",
|
|
74
|
-
handler: {
|
|
75
|
-
kind: "Builtin",
|
|
76
|
-
builtin: { kind: "ExtractIndex", value: 1 },
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
rest: {
|
|
80
|
-
kind: "Invoke",
|
|
81
|
-
handler: {
|
|
82
|
-
kind: "Builtin",
|
|
83
|
-
builtin: { kind: "ExtractIndex", value: n },
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
kind: "Invoke",
|
|
89
|
-
handler: {
|
|
90
|
-
kind: "Builtin",
|
|
91
|
-
builtin: { kind: "ExtractIndex", value: 1 },
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
],
|
|
95
|
-
};
|
|
67
|
+
return toAction(
|
|
68
|
+
all(
|
|
69
|
+
chain(toAction(getIndex(1).unwrap()), toAction(getIndex(n).unwrap())),
|
|
70
|
+
toAction(getIndex(1).unwrap()),
|
|
71
|
+
),
|
|
72
|
+
);
|
|
96
73
|
}
|
|
97
74
|
|
|
98
75
|
// ---------------------------------------------------------------------------
|
|
@@ -111,19 +88,18 @@ function readVar(n: number): Action {
|
|
|
111
88
|
* All(...bindings, Identity),
|
|
112
89
|
* ResumeHandle(r0, readVar(0),
|
|
113
90
|
* ResumeHandle(r1, readVar(1),
|
|
114
|
-
* Chain(
|
|
91
|
+
* Chain(GetIndex(N), body)
|
|
115
92
|
* )
|
|
116
93
|
* )
|
|
117
94
|
* )
|
|
118
95
|
*/
|
|
119
96
|
/**
|
|
120
97
|
* Constraint for the body callback return type. Only requires the output
|
|
121
|
-
* phantom
|
|
122
|
-
*
|
|
98
|
+
* phantom field — omits `__in` and `__in_co` so that body actions with
|
|
99
|
+
* any input type (e.g. pipelines starting from a VarRef) are assignable.
|
|
123
100
|
*/
|
|
124
101
|
type BodyResult<TOut> = Action & {
|
|
125
102
|
__out?: () => TOut;
|
|
126
|
-
__out_contra?: (output: TOut) => void;
|
|
127
103
|
};
|
|
128
104
|
|
|
129
105
|
export function bind<TBindings extends Action[], TOut>(
|
|
@@ -137,22 +113,17 @@ export function bind<TBindings extends Action[], TOut>(
|
|
|
137
113
|
const varRefs = resumeHandlerIds.map((id) => createVarRef(id));
|
|
138
114
|
|
|
139
115
|
// 3. Invoke the body callback with the VarRefs.
|
|
140
|
-
const bodyAction = body(varRefs as InferVarRefs<TBindings>)
|
|
116
|
+
const bodyAction = toAction(body(varRefs as InferVarRefs<TBindings>));
|
|
141
117
|
|
|
142
118
|
// 4. Build nested Handles from inside out.
|
|
143
119
|
// Innermost: extract pipeline_input (last All element) → user body
|
|
144
120
|
const pipelineInputIndex = bindings.length;
|
|
145
|
-
let inner: Action =
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
builtin: { kind: "ExtractIndex", value: pipelineInputIndex },
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
rest: bodyAction,
|
|
155
|
-
};
|
|
121
|
+
let inner: Action = toAction(
|
|
122
|
+
chain(
|
|
123
|
+
toAction(getIndex(pipelineInputIndex).unwrap()),
|
|
124
|
+
toAction(bodyAction),
|
|
125
|
+
),
|
|
126
|
+
);
|
|
156
127
|
for (let i = resumeHandlerIds.length - 1; i >= 0; i--) {
|
|
157
128
|
inner = {
|
|
158
129
|
kind: "ResumeHandle",
|
|
@@ -163,12 +134,11 @@ export function bind<TBindings extends Action[], TOut>(
|
|
|
163
134
|
}
|
|
164
135
|
|
|
165
136
|
// 5. All(...bindings, identity()) → nested Handles
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
});
|
|
137
|
+
const allAction: Action = {
|
|
138
|
+
kind: "All",
|
|
139
|
+
actions: [...bindings.map((b) => toAction(b)), toAction(identity())],
|
|
140
|
+
};
|
|
141
|
+
return typedAction(toAction(chain(toAction(allAction), toAction(inner))));
|
|
172
142
|
}
|
|
173
143
|
|
|
174
144
|
// ---------------------------------------------------------------------------
|
|
@@ -188,5 +158,5 @@ export function bind<TBindings extends Action[], TOut>(
|
|
|
188
158
|
export function bindInput<TIn, TOut = any>(
|
|
189
159
|
body: (input: VarRef<TIn>) => BodyResult<TOut>,
|
|
190
160
|
): TypedAction<TIn, TOut> {
|
|
191
|
-
return bind([identity], ([input]) => pipe(drop, body(input)));
|
|
161
|
+
return bind([identity()], ([input]) => pipe(drop, body(input)));
|
|
192
162
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Option as OptionT,
|
|
3
|
+
type TypedAction,
|
|
4
|
+
typedAction,
|
|
5
|
+
} from "../ast.js";
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// GetIndex — extract a single element from an array by index
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
export function getIndex<TTuple extends unknown[], TIndex extends number>(
|
|
12
|
+
index: TIndex,
|
|
13
|
+
): TypedAction<TTuple, OptionT<TTuple[TIndex]>> {
|
|
14
|
+
return typedAction({
|
|
15
|
+
kind: "Invoke",
|
|
16
|
+
handler: {
|
|
17
|
+
kind: "Builtin",
|
|
18
|
+
builtin: { kind: "GetIndex", index },
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Flatten — flatten a nested array one level
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
export function flatten<TElement>(): TypedAction<TElement[][], TElement[]> {
|
|
28
|
+
return typedAction({
|
|
29
|
+
kind: "Invoke",
|
|
30
|
+
handler: { kind: "Builtin", builtin: { kind: "Flatten" } },
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// SplitFirst — head/tail decomposition of an array
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Deconstruct an array into its first element and the remaining elements.
|
|
40
|
+
* `TElement[] → Option<[TElement, TElement[]]>`
|
|
41
|
+
*
|
|
42
|
+
* Returns `Some([first, rest])` for non-empty arrays, `None` for empty arrays.
|
|
43
|
+
* This is the array equivalent of cons/uncons — enables recursive iteration
|
|
44
|
+
* patterns via `loop` + `splitFirst` + `branch`.
|
|
45
|
+
*
|
|
46
|
+
* This is a builtin (SplitFirst) because it requires array-length branching
|
|
47
|
+
* that can't be composed from existing AST nodes.
|
|
48
|
+
*/
|
|
49
|
+
export function splitFirst<TElement>(): TypedAction<
|
|
50
|
+
TElement[],
|
|
51
|
+
OptionT<[TElement, TElement[]]>
|
|
52
|
+
> {
|
|
53
|
+
return typedAction({
|
|
54
|
+
kind: "Invoke",
|
|
55
|
+
handler: { kind: "Builtin", builtin: { kind: "SplitFirst" } },
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// SplitLast — init/last decomposition of an array
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Deconstruct an array into the leading elements and the last element.
|
|
65
|
+
* `TElement[] → Option<[TElement[], TElement]>`
|
|
66
|
+
*
|
|
67
|
+
* Returns `Some([init, last])` for non-empty arrays, `None` for empty arrays.
|
|
68
|
+
* Mirror of `splitFirst` — enables processing from the tail end.
|
|
69
|
+
*
|
|
70
|
+
* This is a builtin (SplitLast) because it requires array-length branching
|
|
71
|
+
* that can't be composed from existing AST nodes.
|
|
72
|
+
*/
|
|
73
|
+
export function splitLast<TElement>(): TypedAction<
|
|
74
|
+
TElement[],
|
|
75
|
+
OptionT<[TElement[], TElement]>
|
|
76
|
+
> {
|
|
77
|
+
return typedAction({
|
|
78
|
+
kind: "Invoke",
|
|
79
|
+
handler: { kind: "Builtin", builtin: { kind: "SplitLast" } },
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Slice — extract a sub-array from start to end
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Slice an array from `start` (inclusive) to `end` (exclusive).
|
|
89
|
+
* `T[] → T[]`
|
|
90
|
+
*
|
|
91
|
+
* Both indices are clamped to array length. If `end` is omitted, slices
|
|
92
|
+
* to the end of the array. Returns empty array if `start >= end`.
|
|
93
|
+
*/
|
|
94
|
+
export function slice<TElement>(
|
|
95
|
+
start: number,
|
|
96
|
+
end?: number,
|
|
97
|
+
): TypedAction<TElement[], TElement[]> {
|
|
98
|
+
const builtin: { kind: "Slice"; start: number; end?: number } =
|
|
99
|
+
end === undefined
|
|
100
|
+
? { kind: "Slice", start }
|
|
101
|
+
: { kind: "Slice", start, end };
|
|
102
|
+
return typedAction({
|
|
103
|
+
kind: "Invoke",
|
|
104
|
+
handler: { kind: "Builtin", builtin },
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Range — produce an integer array [start, start+1, ..., end-1]
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
export function range(start: number, end: number): TypedAction<any, number[]> {
|
|
113
|
+
const result: number[] = [];
|
|
114
|
+
for (let i = start; i < end; i++) {
|
|
115
|
+
result.push(i);
|
|
116
|
+
}
|
|
117
|
+
return typedAction({
|
|
118
|
+
kind: "Invoke",
|
|
119
|
+
handler: { kind: "Builtin", builtin: { kind: "Constant", value: result } },
|
|
120
|
+
});
|
|
121
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { constant, identity, drop, panic } from "./scalar.js";
|
|
2
|
+
export { getField, wrapInField, merge, pick, allObject } from "./struct.js";
|
|
3
|
+
export {
|
|
4
|
+
getIndex,
|
|
5
|
+
flatten,
|
|
6
|
+
splitFirst,
|
|
7
|
+
splitLast,
|
|
8
|
+
slice,
|
|
9
|
+
range,
|
|
10
|
+
} from "./array.js";
|
|
11
|
+
export {
|
|
12
|
+
tag,
|
|
13
|
+
extractPrefix,
|
|
14
|
+
asOption,
|
|
15
|
+
taggedUnionSchema,
|
|
16
|
+
} from "./tagged-union.js";
|
|
17
|
+
export { withResource } from "./with-resource.js";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type TypedAction, typedAction } from "../ast.js";
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Constant — produce a fixed value (takes no pipeline input)
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
export function constant<TValue>(value: TValue): TypedAction<any, TValue> {
|
|
8
|
+
return typedAction({
|
|
9
|
+
kind: "Invoke",
|
|
10
|
+
handler: { kind: "Builtin", builtin: { kind: "Constant", value } },
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Identity — pass input through unchanged
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
export function identity<TValue = any>(): TypedAction<TValue, TValue> {
|
|
19
|
+
return typedAction({
|
|
20
|
+
kind: "Invoke",
|
|
21
|
+
handler: { kind: "Builtin", builtin: { kind: "Identity" } },
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Drop — discard pipeline value
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
export const drop: TypedAction<any, void> = typedAction({
|
|
30
|
+
kind: "Invoke",
|
|
31
|
+
handler: { kind: "Builtin", builtin: { kind: "Drop" } },
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Panic — halt execution with an error message
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Halt execution with a fatal error. Not caught by tryCatch.
|
|
40
|
+
* Analogous to Rust's `panic!`.
|
|
41
|
+
*
|
|
42
|
+
* Output type is `never` — a panic never produces a value.
|
|
43
|
+
*/
|
|
44
|
+
export function panic(message: string): TypedAction<any, never> {
|
|
45
|
+
return typedAction({
|
|
46
|
+
kind: "Invoke",
|
|
47
|
+
handler: { kind: "Builtin", builtin: { kind: "Panic", message } },
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Action,
|
|
3
|
+
type ExtractOutput,
|
|
4
|
+
type MergeTuple,
|
|
5
|
+
type Pipeable,
|
|
6
|
+
type TypedAction,
|
|
7
|
+
toAction,
|
|
8
|
+
typedAction,
|
|
9
|
+
} from "../ast.js";
|
|
10
|
+
import { chain } from "../chain.js";
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// GetField — extract a single field from an object
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
export function getField<
|
|
17
|
+
TObj extends Record<string, unknown>,
|
|
18
|
+
TField extends keyof TObj & string,
|
|
19
|
+
>(field: TField): TypedAction<TObj, TObj[TField]> {
|
|
20
|
+
return typedAction({
|
|
21
|
+
kind: "Invoke",
|
|
22
|
+
handler: {
|
|
23
|
+
kind: "Builtin",
|
|
24
|
+
builtin: { kind: "GetField", field },
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// WrapInField — wrap input as { <field>: <input> }
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
export function wrapInField<TField extends string, TValue>(
|
|
34
|
+
field: TField,
|
|
35
|
+
): TypedAction<TValue, Record<TField, TValue>> {
|
|
36
|
+
return typedAction({
|
|
37
|
+
kind: "Invoke",
|
|
38
|
+
handler: {
|
|
39
|
+
kind: "Builtin",
|
|
40
|
+
builtin: { kind: "WrapInField", field },
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Merge — merge a tuple of objects into a single object
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
export function merge<TTuple extends Record<string, unknown>[]>(): TypedAction<
|
|
50
|
+
TTuple,
|
|
51
|
+
MergeTuple<TTuple>
|
|
52
|
+
> {
|
|
53
|
+
return typedAction({
|
|
54
|
+
kind: "Invoke",
|
|
55
|
+
handler: { kind: "Builtin", builtin: { kind: "Merge" } },
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Pick — select named fields from an object
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
export function pick<
|
|
64
|
+
TObj extends Record<string, unknown>,
|
|
65
|
+
TKeys extends (keyof TObj & string)[],
|
|
66
|
+
>(...keys: TKeys): TypedAction<TObj, Pick<TObj, TKeys[number]>> {
|
|
67
|
+
const actions = keys.map((key) =>
|
|
68
|
+
toAction(chain(toAction(getField(key)), toAction(wrapInField(key)))),
|
|
69
|
+
);
|
|
70
|
+
const allAction: Action = { kind: "All", actions };
|
|
71
|
+
return chain(toAction(allAction), toAction(merge())) as TypedAction<
|
|
72
|
+
TObj,
|
|
73
|
+
Pick<TObj, TKeys[number]>
|
|
74
|
+
>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// AllObject — run named actions concurrently, collect into an object
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Run named actions concurrently on the same input, collecting results
|
|
83
|
+
* into an object with matching keys.
|
|
84
|
+
*
|
|
85
|
+
* ```ts
|
|
86
|
+
* allObject({
|
|
87
|
+
* files: listFiles,
|
|
88
|
+
* config: loadConfig,
|
|
89
|
+
* })
|
|
90
|
+
* // TIn → { files: string[], config: Config }
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* Each action receives the pipeline input. Results are wrapped in
|
|
94
|
+
* `{ key: value }` via `wrapInField`, run concurrently via `All`,
|
|
95
|
+
* then merged into a single object.
|
|
96
|
+
*/
|
|
97
|
+
export function allObject<TActions extends Record<string, Pipeable<any, any>>>(
|
|
98
|
+
actions: TActions,
|
|
99
|
+
): TypedAction<
|
|
100
|
+
any,
|
|
101
|
+
{ [K in keyof TActions & string]: ExtractOutput<TActions[K]> }
|
|
102
|
+
> {
|
|
103
|
+
const wrapped = Object.entries(actions).map(([key, action]) =>
|
|
104
|
+
toAction(chain(action, wrapInField(key))),
|
|
105
|
+
);
|
|
106
|
+
const allAction: Action = { kind: "All", actions: wrapped };
|
|
107
|
+
return chain(toAction(allAction), toAction(merge())) as TypedAction<
|
|
108
|
+
any,
|
|
109
|
+
{ [K in keyof TActions & string]: ExtractOutput<TActions[K]> }
|
|
110
|
+
>;
|
|
111
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Option,
|
|
3
|
+
type TaggedUnion,
|
|
4
|
+
type TypedAction,
|
|
5
|
+
toAction,
|
|
6
|
+
typedAction,
|
|
7
|
+
} from "../ast.js";
|
|
8
|
+
import { chain } from "../chain.js";
|
|
9
|
+
import { all } from "../all.js";
|
|
10
|
+
import { constant } from "./scalar.js";
|
|
11
|
+
import { wrapInField, merge } from "./struct.js";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Tag — wrap input as a tagged union variant
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Wrap input as a tagged union member. Requires the full variant map TDef
|
|
20
|
+
* so the output type carries __def for branch decomposition.
|
|
21
|
+
*
|
|
22
|
+
* Usage: tag<{ Ok: string; Err: number }, "Ok">("Ok")
|
|
23
|
+
* input: string → output: TaggedUnion<{ Ok: string; Err: number }>
|
|
24
|
+
*/
|
|
25
|
+
export function tag<
|
|
26
|
+
TEnumName extends string,
|
|
27
|
+
TDef extends Record<string, unknown>,
|
|
28
|
+
TKind extends keyof TDef & string,
|
|
29
|
+
>(
|
|
30
|
+
kind: TKind,
|
|
31
|
+
enumName: TEnumName,
|
|
32
|
+
): TypedAction<TDef[TKind], TaggedUnion<TEnumName, TDef>> {
|
|
33
|
+
const namespacedKind = `${enumName}.${kind}`;
|
|
34
|
+
return chain(
|
|
35
|
+
toAction(
|
|
36
|
+
all(
|
|
37
|
+
chain(
|
|
38
|
+
toAction(constant(namespacedKind)),
|
|
39
|
+
toAction(wrapInField("kind")),
|
|
40
|
+
),
|
|
41
|
+
wrapInField("value"),
|
|
42
|
+
),
|
|
43
|
+
),
|
|
44
|
+
toAction(merge()),
|
|
45
|
+
) as TypedAction<TDef[TKind], TaggedUnion<TEnumName, TDef>>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// ExtractPrefix — extract enum prefix from tagged value kind
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Extract the enum prefix from a tagged value's `kind` field.
|
|
54
|
+
*
|
|
55
|
+
* Input: `{ kind: "Result.Ok", value: 42 }`
|
|
56
|
+
* Output: `{ kind: "Result", value: { kind: "Result.Ok", value: 42 } }`
|
|
57
|
+
*
|
|
58
|
+
* If `kind` contains no `'.'`, the entire kind string becomes the prefix.
|
|
59
|
+
* Used internally by `branchFamily` for two-level dispatch.
|
|
60
|
+
*/
|
|
61
|
+
export function extractPrefix(): TypedAction {
|
|
62
|
+
return typedAction({
|
|
63
|
+
kind: "Invoke",
|
|
64
|
+
handler: { kind: "Builtin", builtin: { kind: "ExtractPrefix" } },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// AsOption — convert boolean to Option<void>
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Convert a boolean to `Option<void>`.
|
|
74
|
+
*
|
|
75
|
+
* `true` → `{ kind: "Option.Some", value: null }`
|
|
76
|
+
* `false` → `{ kind: "Option.None", value: null }`
|
|
77
|
+
*/
|
|
78
|
+
export function asOption(): TypedAction<boolean, Option<void>> {
|
|
79
|
+
return typedAction({
|
|
80
|
+
kind: "Invoke",
|
|
81
|
+
handler: { kind: "Builtin", builtin: { kind: "AsOption" } },
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// TaggedUnion Zod schema constructor
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Reverse of VoidToNull: maps `null` back to `void` in the def so that
|
|
91
|
+
* `taggedUnionSchema({ Clean: z.null() })` produces the same phantom __def
|
|
92
|
+
* as `TaggedUnion<{ Clean: void }>`.
|
|
93
|
+
*/
|
|
94
|
+
type NullToVoid<TDef> = {
|
|
95
|
+
[K in keyof TDef]: TDef[K] extends null ? void : TDef[K];
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Build a Zod schema for a `TaggedUnion<TEnumName, TDef>` — a discriminated
|
|
100
|
+
* union of `{ kind: "EnumName.Variant"; value: V }` objects.
|
|
101
|
+
*
|
|
102
|
+
* Each key in `cases` becomes a variant with a namespaced kind string.
|
|
103
|
+
* Use `z.null()` for void variants.
|
|
104
|
+
*
|
|
105
|
+
* ```ts
|
|
106
|
+
* const schema = taggedUnionSchema("ClassifyResult", {
|
|
107
|
+
* HasErrors: z.array(TypeErrorValidator),
|
|
108
|
+
* Clean: z.null(),
|
|
109
|
+
* });
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export function taggedUnionSchema<
|
|
113
|
+
TEnumName extends string,
|
|
114
|
+
TDef extends Record<string, z.ZodTypeAny>,
|
|
115
|
+
>(
|
|
116
|
+
enumName: TEnumName,
|
|
117
|
+
cases: TDef,
|
|
118
|
+
): z.ZodType<
|
|
119
|
+
TaggedUnion<
|
|
120
|
+
TEnumName,
|
|
121
|
+
NullToVoid<{ [K in keyof TDef & string]: z.infer<TDef[K]> }>
|
|
122
|
+
>
|
|
123
|
+
> {
|
|
124
|
+
type Out = TaggedUnion<
|
|
125
|
+
TEnumName,
|
|
126
|
+
NullToVoid<{ [K in keyof TDef & string]: z.infer<TDef[K]> }>
|
|
127
|
+
>;
|
|
128
|
+
const variants = Object.entries(cases).map(([kind, valueSchema]) =>
|
|
129
|
+
z.object({ kind: z.literal(`${enumName}.${kind}`), value: valueSchema }),
|
|
130
|
+
);
|
|
131
|
+
if (variants.length === 0) {
|
|
132
|
+
return z.never() as z.ZodType<Out>;
|
|
133
|
+
}
|
|
134
|
+
if (variants.length === 1) {
|
|
135
|
+
return variants[0] as z.ZodType<Out>;
|
|
136
|
+
}
|
|
137
|
+
return z.discriminatedUnion("kind", [
|
|
138
|
+
variants[0],
|
|
139
|
+
variants[1],
|
|
140
|
+
...variants.slice(2),
|
|
141
|
+
]) as z.ZodType<Out>;
|
|
142
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type Pipeable, type TypedAction, toAction } from "../ast.js";
|
|
2
|
+
import { chain } from "../chain.js";
|
|
3
|
+
import { all } from "../all.js";
|
|
4
|
+
import { identity } from "./scalar.js";
|
|
5
|
+
import { merge } from "./struct.js";
|
|
6
|
+
import { getIndex } from "./array.js";
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// WithResource — RAII-style create/action/dispose
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* RAII-style resource management combinator.
|
|
14
|
+
*
|
|
15
|
+
* Runs `create` to acquire a resource, then merges the resource with the
|
|
16
|
+
* original input into a flat object (`TResource & TIn`) for the action.
|
|
17
|
+
* After the action completes, `dispose` receives the resource for cleanup.
|
|
18
|
+
* The overall combinator returns the action's output.
|
|
19
|
+
*
|
|
20
|
+
* ```
|
|
21
|
+
* TIn → create → TResource
|
|
22
|
+
* → merge(TResource, TIn) → TResource & TIn
|
|
23
|
+
* → action(TResource & TIn) → TOut
|
|
24
|
+
* → dispose(TResource) → (discarded)
|
|
25
|
+
* → TOut
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function withResource<
|
|
29
|
+
TIn extends Record<string, unknown>,
|
|
30
|
+
TResource extends Record<string, unknown>,
|
|
31
|
+
TOut,
|
|
32
|
+
TDisposeOut = unknown,
|
|
33
|
+
>({
|
|
34
|
+
create,
|
|
35
|
+
action,
|
|
36
|
+
dispose,
|
|
37
|
+
}: {
|
|
38
|
+
create: Pipeable<TIn, TResource>;
|
|
39
|
+
action: Pipeable<TResource & TIn, TOut>;
|
|
40
|
+
dispose: Pipeable<TResource, TDisposeOut>;
|
|
41
|
+
}): TypedAction<TIn, TOut> {
|
|
42
|
+
// Step 1: all(create, identity) → [TResource, TIn] → merge → TResource & TIn
|
|
43
|
+
const acquireAndMerge = chain(
|
|
44
|
+
toAction(all(create, identity())),
|
|
45
|
+
toAction(merge()),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Step 2: all(action, identity) → [TOut, TResource & TIn]
|
|
49
|
+
const actionAndKeepMerged = all(toAction(action), toAction(identity()));
|
|
50
|
+
|
|
51
|
+
// Step 3: all(getIndex(0).unwrap(), chain(getIndex(1).unwrap(), dispose)) → [TOut, unknown]
|
|
52
|
+
const disposeAndKeepResult = all(
|
|
53
|
+
toAction(getIndex(0).unwrap()),
|
|
54
|
+
chain(toAction(getIndex(1).unwrap()), toAction(dispose)),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Step 4: getIndex(0).unwrap() → TOut
|
|
58
|
+
return chain(
|
|
59
|
+
toAction(
|
|
60
|
+
chain(
|
|
61
|
+
toAction(
|
|
62
|
+
chain(toAction(acquireAndMerge), toAction(actionAndKeepMerged)),
|
|
63
|
+
),
|
|
64
|
+
toAction(disposeAndKeepResult),
|
|
65
|
+
),
|
|
66
|
+
),
|
|
67
|
+
toAction(getIndex(0).unwrap()),
|
|
68
|
+
) as TypedAction<TIn, TOut>;
|
|
69
|
+
}
|