@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/run.ts CHANGED
@@ -8,9 +8,22 @@ import { createRequire } from "node:module";
8
8
  import { existsSync } from "node:fs";
9
9
  import os from "node:os";
10
10
  import path from "node:path";
11
- import type { Action, Config, ExtractOutput, Pipeable } from "./ast.js";
11
+ import {
12
+ type Action,
13
+ type Config,
14
+ type ExtractOutput,
15
+ toAction,
16
+ } from "./ast.js";
12
17
  import { chain } from "./chain.js";
13
- import { constant } from "./builtins.js";
18
+ import { constant } from "./builtins/index.js";
19
+
20
+ /** Log verbosity for the barnum engine runtime. Passed to the CLI's `--log-level`. */
21
+ export type LogLevel = "off" | "error" | "warn" | "info" | "debug" | "trace";
22
+
23
+ export interface RunPipelineOptions {
24
+ /** Engine log verbosity. Default: "off" (only handler stderr is visible). */
25
+ logLevel?: LogLevel;
26
+ }
14
27
 
15
28
  const __dirname = import.meta.dirname;
16
29
 
@@ -101,12 +114,17 @@ function resolveWorker(): string {
101
114
  return path.resolve(__dirname, "../src/worker.ts");
102
115
  }
103
116
 
104
- /** Build the barnum binary if using the local dev path. */
105
- function buildBinary(): void {
117
+ /** Build the barnum binary if using the local dev path. Skips if binary already exists. */
118
+ function buildBinaryIfNeeded(binaryPath: string): void {
119
+ if (existsSync(binaryPath)) {
120
+ return;
121
+ }
106
122
  const repoRoot = path.resolve(__dirname, "../../..");
123
+ // eslint-disable-next-line no-console
124
+ console.error("[barnum] building CLI binary (cargo build -p barnum_cli)...");
107
125
  execFileSync("cargo", ["build", "-p", "barnum_cli"], {
108
126
  cwd: repoRoot,
109
- stdio: "ignore",
127
+ stdio: "inherit",
110
128
  });
111
129
  }
112
130
 
@@ -114,54 +132,64 @@ function buildBinary(): void {
114
132
  export function runPipeline<TPipeline extends Action>(
115
133
  pipeline: TPipeline,
116
134
  input?: unknown,
135
+ options?: RunPipelineOptions,
117
136
  ): Promise<ExtractOutput<TPipeline>> {
118
137
  const workflow =
119
138
  input === undefined
120
139
  ? pipeline
121
- : (chain(constant(input) as Pipeable, pipeline as Pipeable) as Action);
122
- return spawnBarnum({ workflow });
140
+ : toAction(chain(toAction(constant(input)), toAction(pipeline)));
141
+ return spawnBarnum({ workflow }, options?.logLevel);
123
142
  }
124
143
 
125
144
  /** Spawn the barnum CLI with the given config. Returns the parsed final value from stdout. */
126
- function spawnBarnum<TOut>(config: Config): Promise<TOut> {
145
+ function spawnBarnum<TOut>(config: Config, logLevel?: LogLevel): Promise<TOut> {
127
146
  const binaryResolution = resolveBinary();
128
147
  if (binaryResolution.kind === "Local") {
129
- buildBinary();
148
+ buildBinaryIfNeeded(binaryResolution.path);
130
149
  }
131
150
  const executor = resolveExecutor();
132
151
  const worker = resolveWorker();
133
152
  const configJson = JSON.stringify(config);
134
153
 
154
+ const cliArgs = [
155
+ "run",
156
+ "--config",
157
+ configJson,
158
+ "--executor",
159
+ executor,
160
+ "--worker",
161
+ worker,
162
+ ];
163
+ if (logLevel) {
164
+ cliArgs.push("--log-level", logLevel);
165
+ }
166
+
135
167
  return new Promise<TOut>((resolve, reject) => {
136
- const child = nodeSpawn(
137
- binaryResolution.path,
138
- [
139
- "run",
140
- "--config",
141
- configJson,
142
- "--executor",
143
- executor,
144
- "--worker",
145
- worker,
146
- ],
147
- {
148
- stdio: ["inherit", "pipe", "inherit"],
149
- },
150
- );
168
+ const child = nodeSpawn(binaryResolution.path, cliArgs, {
169
+ stdio: ["inherit", "pipe", "pipe"],
170
+ });
151
171
 
152
172
  const stdoutChunks: Buffer[] = [];
173
+ const stderrChunks: Buffer[] = [];
153
174
 
154
175
  child.stdout?.on("data", (chunk: Buffer) => {
155
176
  stdoutChunks.push(chunk);
156
177
  });
157
178
 
179
+ child.stderr?.on("data", (chunk: Buffer) => {
180
+ stderrChunks.push(chunk);
181
+ process.stderr.write(chunk);
182
+ });
183
+
158
184
  child.on("error", (error) => {
159
185
  reject(new Error(`Failed to spawn barnum: ${error.message}`));
160
186
  });
161
187
 
162
188
  child.on("close", (code) => {
163
189
  if (code !== 0) {
164
- reject(new Error(`barnum exited with code ${code}`));
190
+ const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
191
+ const detail = stderr ? `\n${stderr}` : "";
192
+ reject(new Error(`barnum exited with code ${code}${detail}`));
165
193
  return;
166
194
  }
167
195
  const stdout = Buffer.concat(stdoutChunks).toString("utf8").trim();
package/src/runtime.ts ADDED
@@ -0,0 +1,16 @@
1
+ // Runtime value constructors
2
+ export { ok, err, some, none } from "./values.js";
3
+
4
+ // Handler creation
5
+ export {
6
+ createHandler,
7
+ createHandlerWithConfig,
8
+ type Handler,
9
+ } from "./handler.js";
10
+
11
+ // Schema builders
12
+ export { resultSchema, optionSchema } from "./schemas.js";
13
+ export { taggedUnionSchema } from "./builtins/index.js";
14
+
15
+ // Types only
16
+ export type { Result, Option, TaggedUnion } from "./ast.js";
package/src/schemas.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+ import type { Result, Option } from "./ast.js";
3
+
4
+ export function resultSchema<TValue, TError>(
5
+ okSchema: z.ZodType<TValue>,
6
+ errSchema: z.ZodType<TError>,
7
+ ): z.ZodType<Result<TValue, TError>> {
8
+ return z.discriminatedUnion("kind", [
9
+ z.object({ kind: z.literal("Result.Ok"), value: okSchema }),
10
+ z.object({ kind: z.literal("Result.Err"), value: errSchema }),
11
+ ]) as z.ZodType<Result<TValue, TError>>;
12
+ }
13
+
14
+ export function optionSchema<TValue>(
15
+ valueSchema: z.ZodType<TValue>,
16
+ ): z.ZodType<Option<TValue>> {
17
+ return z.discriminatedUnion("kind", [
18
+ z.object({ kind: z.literal("Option.Some"), value: valueSchema }),
19
+ z.object({ kind: z.literal("Option.None"), value: z.null() }),
20
+ ]) as z.ZodType<Option<TValue>>;
21
+ }
package/src/try-catch.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import {
2
- type Action,
3
2
  type Pipeable,
4
3
  type TypedAction,
4
+ toAction,
5
5
  typedAction,
6
6
  buildRestartBranchAction,
7
- TAG_BREAK,
8
7
  } from "./ast.js";
8
+ import { chain } from "./chain.js";
9
+ import { tag } from "./builtins/index.js";
9
10
  import { allocateRestartHandlerId } from "./effect-id.js";
10
11
 
11
12
  // ---------------------------------------------------------------------------
@@ -25,7 +26,7 @@ import { allocateRestartHandlerId } from "./effect-id.js";
25
26
  *
26
27
  * Compiled form (restart+Branch, same substrate as loop/earlyReturn):
27
28
  * `Chain(Tag("Continue"),`
28
- * `RestartHandle(id, ExtractIndex(0),`
29
+ * `RestartHandle(id, GetIndex(0),`
29
30
  * `Branch({ Continue: body, Break: recovery })))`
30
31
  *
31
32
  * throwError = `Chain(Tag("Break"), RestartPerform(id))`
@@ -39,15 +40,18 @@ export function tryCatch<TIn, TOut, TError>(
39
40
  ): TypedAction<TIn, TOut> {
40
41
  const restartHandlerId = allocateRestartHandlerId();
41
42
 
42
- const throwError = typedAction<TError, never>({
43
- kind: "Chain",
44
- first: TAG_BREAK,
45
- rest: { kind: "RestartPerform", restart_handler_id: restartHandlerId },
46
- });
43
+ const throwError = typedAction<TError, never>(
44
+ toAction(
45
+ chain(toAction(tag("Break", "LoopResult")), {
46
+ kind: "RestartPerform",
47
+ restart_handler_id: restartHandlerId,
48
+ }),
49
+ ),
50
+ );
47
51
 
48
- const bodyAction = body(throwError) as Action;
52
+ const bodyAction = toAction(body(throwError));
49
53
 
50
54
  return typedAction(
51
- buildRestartBranchAction(restartHandlerId, bodyAction, recovery as Action),
55
+ buildRestartBranchAction(restartHandlerId, bodyAction, toAction(recovery)),
52
56
  );
53
57
  }
package/src/values.ts ADDED
@@ -0,0 +1,21 @@
1
+ import type { Result, Option } from "./ast.js";
2
+
3
+ export function ok<TValue, TError = unknown>(
4
+ value: TValue,
5
+ ): Result<TValue, TError> {
6
+ return { kind: "Result.Ok", value } as Result<TValue, TError>;
7
+ }
8
+
9
+ export function err<TValue = unknown, TError = never>(
10
+ error: TError,
11
+ ): Result<TValue, TError> {
12
+ return { kind: "Result.Err", value: error } as Result<TValue, TError>;
13
+ }
14
+
15
+ export function some<T>(value: T): Option<T> {
16
+ return { kind: "Option.Some", value } as Option<T>;
17
+ }
18
+
19
+ export function none<T = unknown>(): Option<T> {
20
+ return { kind: "Option.None", value: null } as Option<T>;
21
+ }
package/src/worker.ts CHANGED
@@ -5,10 +5,20 @@
5
5
  * Rust → stdin: JSON `{ "value": <any> }`
6
6
  * stdout → Rust: JSON result (handler return value)
7
7
  *
8
+ * stdout is reserved for the protocol. All console output is redirected to
9
+ * stderr so that handler code can freely use console.log for debugging.
10
+ *
8
11
  * If the handler throws or the module/export can't be resolved, the
9
12
  * process exits non-zero. Rust interprets that as a fatal workflow error.
10
13
  */
11
14
 
15
+ // Redirect all console output to stderr — stdout is the protocol channel.
16
+ // This must happen before any handler code is imported or executed.
17
+ import { Console } from "node:console";
18
+
19
+ const stderrConsole = new Console({ stdout: process.stderr });
20
+ globalThis.console = stderrConsole;
21
+
12
22
  // Suppress EPIPE — when the Rust binary exits (e.g., a race was resolved),
13
23
  // orphan workers get broken pipe on stdout. This is expected, not an error.
14
24
  process.stdout.on("error", (error: NodeJS.ErrnoException) => {
@@ -46,8 +56,13 @@ async function main(): Promise<void> {
46
56
 
47
57
  const result = await handler.__definition.handle({ value: input.value });
48
58
 
49
- // Write result to stdout
50
- process.stdout.write(JSON.stringify(result) ?? "null");
59
+ // Write result to stdout, then exit. Explicit exit is required because
60
+ // importing the handler module may leave open handles (timers, servers,
61
+ // etc.) that keep the Node event loop alive indefinitely.
62
+ const json = JSON.stringify(result) ?? "null";
63
+ process.stdout.write(json, () => {
64
+ process.exit(0);
65
+ });
51
66
  }
52
67
 
53
68
  main().catch((error) => {
@@ -1,257 +0,0 @@
1
- import { type Action, type Option as OptionT, type Pipeable, type Result as ResultT, type TaggedUnion, type TypedAction } from "./ast.js";
2
- /**
3
- * Typed combinators for structural data transformations.
4
- *
5
- * All builtins emit `{ kind: "Builtin", builtin: { kind: ... } }` handler
6
- * kinds. The Rust scheduler executes them inline (no subprocess).
7
- */
8
- export declare function constant<TValue>(value: TValue): TypedAction<any, TValue>;
9
- export declare const identity: TypedAction<any, any>;
10
- export declare const drop: TypedAction<any, never>;
11
- /**
12
- * Wrap input as a tagged union member. Requires the full variant map TDef
13
- * so the output type carries __def for branch decomposition.
14
- *
15
- * Usage: tag<{ Ok: string; Err: number }, "Ok">("Ok")
16
- * input: string → output: TaggedUnion<{ Ok: string; Err: number }>
17
- */
18
- export declare function tag<TDef extends Record<string, unknown>, TKind extends keyof TDef & string>(kind: TKind): TypedAction<TDef[TKind], TaggedUnion<TDef>>;
19
- type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
20
- export declare function merge<TObjects extends Record<string, unknown>[]>(): TypedAction<TObjects, UnionToIntersection<TObjects[number]>>;
21
- export declare function flatten<TElement>(): TypedAction<TElement[][], TElement[]>;
22
- export declare function extractField<TObj extends Record<string, unknown>, TField extends keyof TObj & string>(field: TField): TypedAction<TObj, TObj[TField]>;
23
- export declare function extractIndex<TTuple extends unknown[], TIndex extends number>(index: TIndex): TypedAction<TTuple, TTuple[TIndex]>;
24
- export declare function pick<TObj extends Record<string, unknown>, TKeys extends (keyof TObj & string)[]>(...keys: TKeys): TypedAction<TObj, Pick<TObj, TKeys[number]>>;
25
- export declare function dropResult<TInput, TOutput>(action: Pipeable<TInput, TOutput>): TypedAction<TInput, never>;
26
- /**
27
- * RAII-style resource management combinator.
28
- *
29
- * Runs `create` to acquire a resource, then merges the resource with the
30
- * original input into a flat object (`TResource & TIn`) for the action.
31
- * After the action completes, `dispose` receives the resource for cleanup.
32
- * The overall combinator returns the action's output.
33
- *
34
- * ```
35
- * TIn → create → TResource
36
- * → merge(TResource, TIn) → TResource & TIn
37
- * → action(TResource & TIn) → TOut
38
- * → dispose(TResource) → (discarded)
39
- * → TOut
40
- * ```
41
- */
42
- export declare function withResource<TIn extends Record<string, unknown>, TResource extends Record<string, unknown>, TOut, TDisposeOut = unknown>({ create, action, dispose, }: {
43
- create: Pipeable<TIn, TResource>;
44
- action: Pipeable<TResource & TIn, TOut>;
45
- dispose: Pipeable<TResource, TDisposeOut>;
46
- }): TypedAction<TIn, TOut>;
47
- /**
48
- * Run `action` on the input, then merge the action's output fields back
49
- * into the original input object. The action must accept exactly `TInput`.
50
- * Use `pick` inside the action's pipe if the inner handler needs a subset.
51
- *
52
- * Example:
53
- * augment(pipe(pick("file"), migrate))
54
- * // { file, outputPath } → { file, outputPath, content, migrated }
55
- */
56
- export declare function augment<TInput extends Record<string, unknown>, TOutput extends Record<string, unknown>>(action: Pipeable<TInput, TOutput>): TypedAction<TInput, TInput & TOutput>;
57
- /**
58
- * Run `action` on the input for its side effects, then discard the action's
59
- * output and return the original input unchanged. The action must accept
60
- * exactly `TInput`. Use `pick` inside the action's pipe if the inner
61
- * handler needs a subset.
62
- *
63
- * Constraint: input must be an object (uses augment internally, which
64
- * relies on all + merge).
65
- *
66
- * Example:
67
- * pipe(tap(pipe(pick("worktreePath", "description"), implement)), createPR)
68
- */
69
- export declare function tap<TInput extends Record<string, unknown>>(action: Pipeable<TInput, any>): TypedAction<TInput, TInput>;
70
- export declare function range(start: number, end: number): TypedAction<any, number[]>;
71
- /**
72
- * Option namespace. All combinators produce TypedAction AST nodes that
73
- * desugar to branch + existing builtins, except collect which uses the
74
- * CollectSome builtin.
75
- */
76
- export declare const Option: {
77
- /**
78
- * Wrap a value as Some. `T → Option<T>`
79
- *
80
- * Equivalent to `tag<OptionDef<T>, "Some">("Some")`.
81
- */
82
- readonly some: <T>() => TypedAction<T, OptionT<T>>;
83
- /**
84
- * Produce a None. `never → Option<T>`
85
- *
86
- * Chain after `.drop()` to discard the current value first.
87
- * Equivalent to `tag<OptionDef<T>, "None">("None")`.
88
- */
89
- readonly none: <T>() => TypedAction<never, OptionT<T>>;
90
- /**
91
- * Transform the Some value. `Option<T> → Option<U>`
92
- *
93
- * Desugars to: `branch({ Some: pipe(action, tag("Some")), None: tag("None") })`
94
- */
95
- readonly map: <T, U>(action: Pipeable<T, U>) => TypedAction<OptionT<T>, OptionT<U>>;
96
- /**
97
- * Monadic bind (flatMap). If Some, pass the value to action which
98
- * returns Option<U>. If None, stay None. `Option<T> → Option<U>`
99
- *
100
- * This is the most fundamental combinator — map, flatten, and filter
101
- * are all derivable from andThen + constructors.
102
- *
103
- * Desugars to: `branch({ Some: action, None: tag("None") })`
104
- */
105
- readonly andThen: <T, U>(action: Pipeable<T, OptionT<U>>) => TypedAction<OptionT<T>, OptionT<U>>;
106
- /**
107
- * Extract the Some value or produce a default from an action.
108
- * `Option<T> → T`
109
- *
110
- * The defaultAction takes no meaningful input (never) and must produce T.
111
- * Use `Option.unwrapOr(constant("fallback"))`.
112
- *
113
- * The None branch drops its void payload before calling defaultAction,
114
- * matching Rust's `unwrap_or_else(|| default)` where the closure takes
115
- * no arguments.
116
- *
117
- * Desugars to: `branch({ Some: identity(), None: pipe(drop(), defaultAction) })`
118
- */
119
- readonly unwrapOr: <T>(defaultAction: Pipeable<never, T>) => TypedAction<OptionT<T>, T>;
120
- /**
121
- * Unwrap a nested Option. `Option<Option<T>> → Option<T>`
122
- *
123
- * Desugars to: `branch({ Some: identity(), None: tag("None") })`
124
- */
125
- readonly flatten: <T>() => TypedAction<OptionT<OptionT<T>>, OptionT<T>>;
126
- /**
127
- * Conditional keep. If Some, pass value to predicate which returns
128
- * Option<T> (some() to keep, none() to discard). If None, stay None.
129
- * `Option<T> → Option<T>`
130
- *
131
- * This has the same signature and desugaring as andThen with T=U.
132
- * Named "filter" for readability when the intent is filtering.
133
- *
134
- * Desugars to: `branch({ Some: predicate, None: tag("None") })`
135
- */
136
- readonly filter: <T>(predicate: Pipeable<T, OptionT<T>>) => TypedAction<OptionT<T>, OptionT<T>>;
137
- /**
138
- * Collect Some values from an array, discarding Nones.
139
- * `Option<T>[] → T[]`
140
- *
141
- * This is a builtin handler (CollectSome) — it can't be expressed
142
- * as a composition of existing AST nodes because it requires
143
- * array-level filtering logic.
144
- */
145
- readonly collect: <T = any>() => TypedAction<OptionT<T>[], T[]>;
146
- /**
147
- * Test if the value is Some. `Option<T> → boolean`
148
- *
149
- * Rarely useful — branch on Some/None directly instead.
150
- *
151
- * Desugars to: `branch({ Some: pipe(drop(), constant(true)), None: pipe(drop(), constant(false)) })`
152
- */
153
- readonly isSome: <T>() => TypedAction<OptionT<T>, boolean>;
154
- /**
155
- * Test if the value is None. `Option<T> → boolean`
156
- *
157
- * Rarely useful — branch on Some/None directly instead.
158
- *
159
- * Desugars to: `branch({ Some: pipe(drop(), constant(false)), None: pipe(drop(), constant(true)) })`
160
- */
161
- readonly isNone: <T>() => TypedAction<OptionT<T>, boolean>;
162
- };
163
- /**
164
- * Result namespace. All combinators produce TypedAction AST nodes that
165
- * desugar to branch + existing builtins.
166
- */
167
- export declare const Result: {
168
- /**
169
- * Wrap a value as Ok. `TValue → Result<TValue, TError>`
170
- */
171
- readonly ok: <TValue, TError>() => TypedAction<TValue, ResultT<TValue, TError>>;
172
- /**
173
- * Wrap a value as Err. `TError → Result<TValue, TError>`
174
- */
175
- readonly err: <TValue, TError>() => TypedAction<TError, ResultT<TValue, TError>>;
176
- /**
177
- * Transform the Ok value. `Result<TValue, TError> → Result<TOut, TError>`
178
- *
179
- * Desugars to: `branch({ Ok: pipe(action, tag("Ok")), Err: tag("Err") })`
180
- */
181
- readonly map: <TValue, TOut, TError>(action: Pipeable<TValue, TOut>) => TypedAction<ResultT<TValue, TError>, ResultT<TOut, TError>>;
182
- /**
183
- * Transform the Err value. `Result<TValue, TError> → Result<TValue, TErrorOut>`
184
- *
185
- * Desugars to: `branch({ Ok: tag("Ok"), Err: pipe(action, tag("Err")) })`
186
- */
187
- readonly mapErr: <TValue, TError, TErrorOut>(action: Pipeable<TError, TErrorOut>) => TypedAction<ResultT<TValue, TError>, ResultT<TValue, TErrorOut>>;
188
- /**
189
- * Monadic bind (flatMap) for Ok. If Ok, pass value to action which
190
- * returns Result<TOut, TError>. If Err, propagate.
191
- *
192
- * Desugars to: `branch({ Ok: action, Err: tag("Err") })`
193
- */
194
- readonly andThen: <TValue, TOut, TError>(action: Pipeable<TValue, ResultT<TOut, TError>>) => TypedAction<ResultT<TValue, TError>, ResultT<TOut, TError>>;
195
- /**
196
- * Fallback on Err. If Ok, keep it. If Err, pass error to fallback
197
- * which returns a new Result.
198
- *
199
- * Desugars to: `branch({ Ok: tag("Ok"), Err: fallback })`
200
- */
201
- readonly or: <TValue, TError, TErrorOut>(fallback: Pipeable<TError, ResultT<TValue, TErrorOut>>) => TypedAction<ResultT<TValue, TError>, ResultT<TValue, TErrorOut>>;
202
- /**
203
- * Replace Ok value with another Result. If Ok, discard value and
204
- * return other. If Err, propagate.
205
- *
206
- * Desugars to: `branch({ Ok: pipe(drop(), other), Err: tag("Err") })`
207
- */
208
- readonly and: <TValue, TOut, TError>(other: Pipeable<never, ResultT<TOut, TError>>) => TypedAction<ResultT<TValue, TError>, ResultT<TOut, TError>>;
209
- /**
210
- * Extract Ok or compute default from Err. `Result<TValue, TError> → TValue`
211
- *
212
- * Takes an action that receives the Err payload and produces a fallback.
213
- * Uses covariant output checking so throw tokens (Out=never) are assignable
214
- * when TValue is provided explicitly: `Result.unwrapOr<string, string>(throwError)`.
215
- *
216
- * For inference-free usage with throw tokens, prefer the postfix method:
217
- * `handler.unwrapOr(throwError)` — the `this` constraint provides TValue.
218
- *
219
- * Desugars to: `branch({ Ok: identity(), Err: defaultAction })`
220
- */
221
- readonly unwrapOr: <TValue, TError>(defaultAction: Action & {
222
- __in?: (input: TError) => void;
223
- __out?: () => TValue;
224
- }) => TypedAction<ResultT<TValue, TError>, TValue>;
225
- /**
226
- * Unwrap nested Result. `Result<Result<TValue, TError>, TError> → Result<TValue, TError>`
227
- *
228
- * Desugars to: `branch({ Ok: identity(), Err: tag("Err") })`
229
- */
230
- readonly flatten: <TValue, TError>() => TypedAction<ResultT<ResultT<TValue, TError>, TError>, ResultT<TValue, TError>>;
231
- /**
232
- * Convert Ok to Some, Err to None. `Result<TValue, TError> → Option<TValue>`
233
- *
234
- * Desugars to: `branch({ Ok: tag("Some"), Err: pipe(drop(), tag("None")) })`
235
- */
236
- readonly toOption: <TValue, TError>() => TypedAction<ResultT<TValue, TError>, OptionT<TValue>>;
237
- /**
238
- * Convert Err to Some, Ok to None. `Result<TValue, TError> → Option<TError>`
239
- *
240
- * Desugars to: `branch({ Ok: pipe(drop(), tag("None")), Err: tag("Some") })`
241
- */
242
- readonly toOptionErr: <TValue, TError>() => TypedAction<ResultT<TValue, TError>, OptionT<TError>>;
243
- /**
244
- * Swap Result/Option nesting.
245
- * `Result<Option<TValue>, TError> → Option<Result<TValue, TError>>`
246
- */
247
- readonly transpose: <TValue, TError>() => TypedAction<ResultT<OptionT<TValue>, TError>, OptionT<ResultT<TValue, TError>>>;
248
- /**
249
- * Test if the value is Ok. `Result<TValue, TError> → boolean`
250
- */
251
- readonly isOk: <TValue, TError>() => TypedAction<ResultT<TValue, TError>, boolean>;
252
- /**
253
- * Test if the value is Err. `Result<TValue, TError> → boolean`
254
- */
255
- readonly isErr: <TValue, TError>() => TypedAction<ResultT<TValue, TError>, boolean>;
256
- };
257
- export {};