@onrails/result 0.2.0 → 0.3.1
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/DESIGN.md +11 -18
- package/README.md +14 -59
- package/RECIPES.md +12 -25
- package/dist/{async-1fIhfzFu.d.ts → async-Ci6WSL5-.d.ts} +60 -18
- package/dist/{async-u8U5P2EH.d.cts → async-DcXR7k9K.d.cts} +60 -18
- package/dist/compat/neverthrow.cjs +42 -51
- package/dist/compat/neverthrow.cjs.map +1 -1
- package/dist/compat/neverthrow.d.cts +3 -3
- package/dist/compat/neverthrow.d.ts +3 -3
- package/dist/compat/neverthrow.js +42 -51
- package/dist/compat/neverthrow.js.map +1 -1
- package/dist/extra.cjs +1 -1
- package/dist/extra.cjs.map +1 -1
- package/dist/extra.d.cts +5 -11
- package/dist/extra.d.ts +5 -11
- package/dist/extra.js +1 -1
- package/dist/extra.js.map +1 -1
- package/dist/fluent.cjs +66 -28
- package/dist/fluent.cjs.map +1 -1
- package/dist/fluent.d.cts +41 -6
- package/dist/fluent.d.ts +41 -6
- package/dist/fluent.js +66 -28
- package/dist/fluent.js.map +1 -1
- package/dist/index.cjs +95 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +168 -44
- package/dist/index.d.ts +168 -44
- package/dist/index.js +90 -95
- package/dist/index.js.map +1 -1
- package/dist/railway.cjs +101 -172
- package/dist/railway.cjs.map +1 -1
- package/dist/railway.d.cts +25 -179
- package/dist/railway.d.ts +25 -179
- package/dist/railway.js +102 -162
- package/dist/railway.js.map +1 -1
- package/dist/try-gen.cjs +1 -1
- package/dist/try-gen.cjs.map +1 -1
- package/dist/try-gen.js +1 -1
- package/dist/try-gen.js.map +1 -1
- package/package.json +1 -16
- package/skills/railway-do-notation/SKILL.md +29 -68
- package/skills/result-composition/SKILL.md +2 -4
- package/dist/$.cjs +0 -40
- package/dist/$.cjs.map +0 -1
- package/dist/$.js +0 -36
- package/dist/$.js.map +0 -1
- package/dist/_.d.cts +0 -2
- package/dist/_.d.ts +0 -2
- package/dist/interop.cjs +0 -433
- package/dist/interop.cjs.map +0 -1
- package/dist/interop.d.cts +0 -75
- package/dist/interop.d.ts +0 -75
- package/dist/interop.js +0 -429
- package/dist/interop.js.map +0 -1
- package/dist/validation.cjs +0 -53
- package/dist/validation.cjs.map +0 -1
- package/dist/validation.d.cts +0 -72
- package/dist/validation.d.ts +0 -72
- package/dist/validation.js +0 -48
- package/dist/validation.js.map +0 -1
package/dist/railway.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R as ResultAsync } from './async-
|
|
1
|
+
import { R as ResultAsync } from './async-Ci6WSL5-.js';
|
|
2
2
|
import { R as Result } from './types-BQ9vv0nD.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -11,20 +11,10 @@ type RailwayMode = "sync" | "async";
|
|
|
11
11
|
* `Result<T, E>`, async mode → `ResultAsync<T, E>`.
|
|
12
12
|
*/
|
|
13
13
|
type RailwayOutput<T, E, M extends RailwayMode> = M extends "async" ? ResultAsync<T, E> : Result<T, E>;
|
|
14
|
-
/**
|
|
15
|
-
* Initial context shape for a `railway` functional pipeline.
|
|
16
|
-
* Wraps the raw input under the `input` key so subsequent named steps
|
|
17
|
-
* can reference it via `ctx.input`.
|
|
18
|
-
*/
|
|
19
|
-
type RailwayInput<I> = {
|
|
20
|
-
readonly input: I;
|
|
21
|
-
};
|
|
22
14
|
type BranchFn<C extends object> = (ctx: C) => ResultAsync<unknown, unknown>;
|
|
23
15
|
type BranchRecord = Record<string, (ctx: never) => ResultAsync<unknown, unknown>>;
|
|
24
16
|
type BranchOk<R> = R extends (ctx: never) => ResultAsync<infer T, infer _E> ? T : never;
|
|
25
17
|
type BranchErr<R> = R extends (ctx: never) => ResultAsync<infer _T, infer E> ? E : never;
|
|
26
|
-
type BranchInput<R> = R extends (ctx: infer C) => ResultAsync<unknown, unknown> ? C : never;
|
|
27
|
-
type ParallelInput<R extends BranchRecord> = BranchInput<R[keyof R]> & object;
|
|
28
18
|
type ParallelOutput<R extends BranchRecord> = {
|
|
29
19
|
[K in keyof R]: BranchOk<R[K]>;
|
|
30
20
|
};
|
|
@@ -33,27 +23,6 @@ type ParallelError<R extends BranchRecord> = BranchErr<R[keyof R]>;
|
|
|
33
23
|
* Named-context workflow builder. Each step appends a typed field to the
|
|
34
24
|
* accumulating context object; the workflow tracks sync/async mode so the
|
|
35
25
|
* final output type ({@link RailwayOutput}) is correct.
|
|
36
|
-
*
|
|
37
|
-
* Use `Railway` when a service workflow has 4+ named steps, mixed sync and
|
|
38
|
-
* async boundaries, or independent async branches that should run in
|
|
39
|
-
* parallel. For 1–2 step flows, prefer `flatMap` / `asyncAfter` directly.
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* ```ts
|
|
43
|
-
* const summary = Railway
|
|
44
|
-
* .fromSync("id", () => IdSchema.parse(raw), toError)
|
|
45
|
-
* .fromPromise("row", ({ id }) => db.profiles.findFirst({ where: eq(profiles.id, id) }), toError)
|
|
46
|
-
* .require("profile", "row", ({ id }) => ({ kind: "not_found" as const, id }))
|
|
47
|
-
* .derive("normalized", ({ profile }) => normalizeProfile(profile))
|
|
48
|
-
* .parallel({
|
|
49
|
-
* artifacts: ({ normalized }) => loadArtifacts(normalized.id),
|
|
50
|
-
* metrics: ({ normalized }) => loadMetrics(normalized.id),
|
|
51
|
-
* })
|
|
52
|
-
* .select(({ normalized, artifacts, metrics }) =>
|
|
53
|
-
* toProfileSummary({ profile: normalized, artifacts, metrics }),
|
|
54
|
-
* );
|
|
55
|
-
* // ResultAsync<ProfileSummary, ParseError | DbError | NotFound>
|
|
56
|
-
* ```
|
|
57
26
|
*/
|
|
58
27
|
declare class Railway<C extends object, E, M extends RailwayMode> {
|
|
59
28
|
private readonly state;
|
|
@@ -62,10 +31,7 @@ declare class Railway<C extends object, E, M extends RailwayMode> {
|
|
|
62
31
|
static empty(): Railway<Record<never, never>, never, "sync">;
|
|
63
32
|
/** Start a sync workflow with the given context as the initial state. */
|
|
64
33
|
static context<C extends object>(context: C): Railway<C, never, "sync">;
|
|
65
|
-
/**
|
|
66
|
-
* Start a sync workflow with a throwing function — `onThrow` maps any
|
|
67
|
-
* exception to a typed error.
|
|
68
|
-
*/
|
|
34
|
+
/** Start a sync workflow with a throwing function. */
|
|
69
35
|
static fromSync<K extends string, T, E>(key: K, fn: () => T, onThrow: (error: unknown) => E): Railway<Record<K, T>, E, "sync">;
|
|
70
36
|
/** Start a sync workflow with a `Result`-returning function. */
|
|
71
37
|
static fromResult<K extends string, T, E>(key: K, fn: () => Result<T, E>): Railway<Record<K, T>, E, "sync">;
|
|
@@ -74,159 +40,39 @@ declare class Railway<C extends object, E, M extends RailwayMode> {
|
|
|
74
40
|
/** Start an async workflow with a `ResultAsync`-returning function. */
|
|
75
41
|
static fromAsync<K extends string, T, E>(key: K, fn: () => ResultAsync<T, E>): Railway<Record<K, T>, E, "async">;
|
|
76
42
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
43
|
+
* Rebuild the workflow in its current mode after transforming the carried
|
|
44
|
+
* result. Single re-link point for the state/phantom invariant: the mode
|
|
45
|
+
* tag is preserved verbatim, so `M` still describes the new state — TS
|
|
46
|
+
* cannot narrow the phantom `M` from the runtime tag, hence the cast.
|
|
79
47
|
*/
|
|
80
|
-
|
|
48
|
+
private step;
|
|
81
49
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
50
|
+
* Project the carried result into the mode-aware output type. Counterpart
|
|
51
|
+
* of {@link Railway.step} for terminal steps: the runtime branch matches
|
|
52
|
+
* the branch `RailwayOutput` picks for `M` (state/phantom invariant), but
|
|
53
|
+
* TS cannot resolve the conditional on an unresolved `M`, hence the cast.
|
|
84
54
|
*/
|
|
55
|
+
private out;
|
|
56
|
+
/** Lift the carried result into `ResultAsync` for async-upgrading steps. */
|
|
57
|
+
private toAsync;
|
|
58
|
+
/** Pure sync derivation. */
|
|
59
|
+
derive<K extends string, T>(key: K, fn: (ctx: C) => T): Railway<C & Record<K, T>, E, M>;
|
|
60
|
+
/** Throwing sync transform. */
|
|
85
61
|
fromSync<K extends string, T, F>(key: K, fn: (ctx: C) => T, onThrow: (error: unknown) => F): Railway<C & Record<K, T>, E | F, M>;
|
|
86
|
-
/**
|
|
87
|
-
* Sync `Result`-returning step. Adds `{ [key]: T }` to context on `Ok`;
|
|
88
|
-
* short-circuits the workflow on `Err`. Error union widens to `E | F`.
|
|
89
|
-
*/
|
|
62
|
+
/** Sync `Result`-returning step. */
|
|
90
63
|
fromResult<K extends string, T, F>(key: K, fn: (ctx: C) => Result<T, F>): Railway<C & Record<K, T>, E | F, M>;
|
|
91
|
-
/**
|
|
92
|
-
* Promise-returning step — upgrades the workflow to async mode. Reject
|
|
93
|
-
* reasons go through `onReject` to become typed `Err<F>`.
|
|
94
|
-
*/
|
|
64
|
+
/** Promise-returning step — upgrades the workflow to async mode. */
|
|
95
65
|
fromPromise<K extends string, T, F>(key: K, fn: (ctx: C) => PromiseLike<T>, onReject: (error: unknown) => F): Railway<C & Record<K, T>, E | F, "async">;
|
|
96
|
-
/**
|
|
97
|
-
* `ResultAsync`-returning step — upgrades the workflow to async mode.
|
|
98
|
-
* Already-typed error: no mapper needed.
|
|
99
|
-
*/
|
|
66
|
+
/** `ResultAsync`-returning step — upgrades the workflow to async mode. */
|
|
100
67
|
fromAsync<K extends string, T, F>(key: K, fn: (ctx: C) => ResultAsync<T, F>): Railway<C & Record<K, T>, E | F, "async">;
|
|
101
|
-
/**
|
|
102
|
-
* Narrow a nullable context field into a required non-null field. If the
|
|
103
|
-
* source field is `null` / `undefined`, the workflow short-circuits with
|
|
104
|
-
* `onMissing(ctx)`.
|
|
105
|
-
*
|
|
106
|
-
* @example
|
|
107
|
-
* ```ts
|
|
108
|
-
* .fromPromise("row", ({ id }) => db.users.findFirst({ where: eq(users.id, id) }))
|
|
109
|
-
* .require("user", "row", ({ id }) => ({ kind: "not_found" as const, id }))
|
|
110
|
-
* // user is now User (non-null), not User | null
|
|
111
|
-
* ```
|
|
112
|
-
*/
|
|
68
|
+
/** Narrow a nullable context field into a required non-null field. */
|
|
113
69
|
require<K extends string, S extends keyof C, F>(key: K, source: S, onMissing: (ctx: C) => F): Railway<C & Record<K, NonNullable<C[S]>>, E | F, M>;
|
|
114
|
-
/**
|
|
115
|
-
* Run independent `ResultAsync` branches concurrently and merge their
|
|
116
|
-
* named outputs back into context. Upgrades the workflow to async mode.
|
|
117
|
-
* On multiple failures, the first `Err` in record-iteration order wins.
|
|
118
|
-
*
|
|
119
|
-
* @example
|
|
120
|
-
* ```ts
|
|
121
|
-
* .parallel({
|
|
122
|
-
* recent: ({ userId }) => loadRecent(userId),
|
|
123
|
-
* metrics: ({ userId }) => loadMetrics(userId),
|
|
124
|
-
* })
|
|
125
|
-
* // ctx now has { ..., recent, metrics }
|
|
126
|
-
* ```
|
|
127
|
-
*/
|
|
70
|
+
/** Run independent `ResultAsync` branches concurrently and merge outputs. */
|
|
128
71
|
parallel<R extends Record<string, BranchFn<C>>>(branches: R): Railway<C & ParallelOutput<R>, E | ParallelError<R>, "async">;
|
|
129
|
-
/**
|
|
130
|
-
* Project the final context into the workflow's output type. Hides the
|
|
131
|
-
* internal context shape from callers.
|
|
132
|
-
*/
|
|
72
|
+
/** Project the final context into the workflow's output type. */
|
|
133
73
|
select<T>(fn: (ctx: C) => T): RailwayOutput<T, E, M>;
|
|
134
|
-
/**
|
|
135
|
-
* Return the accumulated context as-is. Use when downstream code needs
|
|
136
|
-
* every named field; prefer {@link select} when you can project to a DTO.
|
|
137
|
-
*/
|
|
74
|
+
/** Return the accumulated context as-is. */
|
|
138
75
|
done(): RailwayOutput<C, E, M>;
|
|
139
76
|
}
|
|
140
77
|
|
|
141
|
-
type
|
|
142
|
-
readonly parse: (input: I) => T;
|
|
143
|
-
} | ((input: I) => T);
|
|
144
|
-
type UnaryStep<I, O> = (input: I) => O;
|
|
145
|
-
/**
|
|
146
|
-
* Functional companion to {@link Railway} — point-free composition of
|
|
147
|
-
* reusable workflow steps. Starts from `Railway.context({ input })` and
|
|
148
|
-
* applies each step in order. Step factories live below: {@link parseWith},
|
|
149
|
-
* {@link fromSyncNamed}, {@link fromResultNamed}, {@link fromPromiseNamed},
|
|
150
|
-
* {@link fromAsyncNamed}, {@link deriveNamed}, {@link requireNamed},
|
|
151
|
-
* {@link parallelNamed}, {@link select}.
|
|
152
|
-
*
|
|
153
|
-
* @example
|
|
154
|
-
* ```ts
|
|
155
|
-
* const summary = railway(
|
|
156
|
-
* rawId,
|
|
157
|
-
* parseWith(IdSchema, toError).as("id"),
|
|
158
|
-
* fromPromiseNamed("row", ({ id }) => db.profiles.findFirst({ where: eq(profiles.id, id) }), toError),
|
|
159
|
-
* requireNamed("profile", "row", ({ id }) => ({ kind: "not_found" as const, id })),
|
|
160
|
-
* deriveNamed("normalized", ({ profile }) => normalizeProfile(profile)),
|
|
161
|
-
* select(({ normalized }) => toProfileSummary(normalized)),
|
|
162
|
-
* );
|
|
163
|
-
* ```
|
|
164
|
-
*/
|
|
165
|
-
declare function railway<I, A>(input: I, step1: UnaryStep<Railway<RailwayInput<I>, never, "sync">, A>): A;
|
|
166
|
-
declare function railway<I, A, B>(input: I, step1: UnaryStep<Railway<RailwayInput<I>, never, "sync">, A>, step2: UnaryStep<A, B>): B;
|
|
167
|
-
declare function railway<I, A, B, C>(input: I, step1: UnaryStep<Railway<RailwayInput<I>, never, "sync">, A>, step2: UnaryStep<A, B>, step3: UnaryStep<B, C>): C;
|
|
168
|
-
declare function railway<I, A, B, C, D>(input: I, step1: UnaryStep<Railway<RailwayInput<I>, never, "sync">, A>, step2: UnaryStep<A, B>, step3: UnaryStep<B, C>, step4: UnaryStep<C, D>): D;
|
|
169
|
-
declare function railway<I, A, B, C, D, E>(input: I, step1: UnaryStep<Railway<RailwayInput<I>, never, "sync">, A>, step2: UnaryStep<A, B>, step3: UnaryStep<B, C>, step4: UnaryStep<C, D>, step5: UnaryStep<D, E>): E;
|
|
170
|
-
declare function railway<I, A, B, C, D, E, F>(input: I, step1: UnaryStep<Railway<RailwayInput<I>, never, "sync">, A>, step2: UnaryStep<A, B>, step3: UnaryStep<B, C>, step4: UnaryStep<C, D>, step5: UnaryStep<D, E>, step6: UnaryStep<E, F>): F;
|
|
171
|
-
declare function railway<I, A, B, C, D, E, F, G>(input: I, step1: UnaryStep<Railway<RailwayInput<I>, never, "sync">, A>, step2: UnaryStep<A, B>, step3: UnaryStep<B, C>, step4: UnaryStep<C, D>, step5: UnaryStep<D, E>, step6: UnaryStep<E, F>, step7: UnaryStep<F, G>): G;
|
|
172
|
-
declare function railway<I, A, B, C, D, E, F, G, H>(input: I, step1: UnaryStep<Railway<RailwayInput<I>, never, "sync">, A>, step2: UnaryStep<A, B>, step3: UnaryStep<B, C>, step4: UnaryStep<C, D>, step5: UnaryStep<D, E>, step6: UnaryStep<E, F>, step7: UnaryStep<F, G>, step8: UnaryStep<G, H>): H;
|
|
173
|
-
/**
|
|
174
|
-
* Step factory: parse the workflow input with a Zod-like schema (or a
|
|
175
|
-
* unary parse function). Call `.as(key)` to name the output field.
|
|
176
|
-
*
|
|
177
|
-
* @example
|
|
178
|
-
* ```ts
|
|
179
|
-
* parseWith(IdSchema, toError).as("id");
|
|
180
|
-
* // Used as the first step in `railway(rawId, parseId, ...)`
|
|
181
|
-
* ```
|
|
182
|
-
*/
|
|
183
|
-
declare const parseWith: <I, T, E>(parser: ParserLike<I, T>, onThrow: (error: unknown) => E) => {
|
|
184
|
-
as: <K extends string>(key: K) => <F, M extends RailwayMode>(workflow: Railway<RailwayInput<I>, F, M>) => Railway<RailwayInput<I> & Record<K, T>, F | E, M>;
|
|
185
|
-
};
|
|
186
|
-
/**
|
|
187
|
-
* Name-first companion to {@link parseWith}: parse the workflow input with a
|
|
188
|
-
* Zod-like schema (or unary parse fn), naming the output field in the same
|
|
189
|
-
* `(key, ...)` shape as {@link fromSyncNamed}, {@link deriveNamed}, et al.
|
|
190
|
-
* Prefer this in `railway(...)` pipelines so every step's name aligns at the
|
|
191
|
-
* left edge.
|
|
192
|
-
*
|
|
193
|
-
* @example
|
|
194
|
-
* ```ts
|
|
195
|
-
* railway(
|
|
196
|
-
* rawId,
|
|
197
|
-
* parseNamed("id", IdSchema, toError),
|
|
198
|
-
* fromPromiseNamed("profile", ({ id }) => fetchProfile(id), toError),
|
|
199
|
-
* );
|
|
200
|
-
* ```
|
|
201
|
-
*/
|
|
202
|
-
declare const parseNamed: <K extends string, I, T, E>(key: K, parser: ParserLike<I, T>, onThrow: (error: unknown) => E) => <F, M extends RailwayMode>(workflow: Railway<RailwayInput<I>, F, M>) => Railway<RailwayInput<I> & Record<K, T>, F | E, M>;
|
|
203
|
-
/**
|
|
204
|
-
* Reusable wrapper around {@link Railway.fromSync}. Captures key + fn +
|
|
205
|
-
* onThrow once; applies to any compatible workflow.
|
|
206
|
-
*/
|
|
207
|
-
declare const fromSyncNamed: <K extends string, C extends object, T, E>(key: K, fn: (ctx: C) => T, onThrow: (error: unknown) => E) => <I extends C, F, M extends RailwayMode>(workflow: Railway<I, F, M>) => Railway<I & Record<K, T>, F | E, M>;
|
|
208
|
-
/** Reusable wrapper around {@link Railway.fromResult}. */
|
|
209
|
-
declare const fromResultNamed: <K extends string, C extends object, T, E>(key: K, fn: (ctx: C) => Result<T, E>) => <I extends C, F, M extends RailwayMode>(workflow: Railway<I, F, M>) => Railway<I & Record<K, T>, F | E, M>;
|
|
210
|
-
/**
|
|
211
|
-
* Reusable wrapper around {@link Railway.fromPromise}. Upgrades the
|
|
212
|
-
* workflow to async mode when applied.
|
|
213
|
-
*/
|
|
214
|
-
declare const fromPromiseNamed: <K extends string, C extends object, T, E>(key: K, fn: (ctx: C) => PromiseLike<T>, onReject: (error: unknown) => E) => <I extends C, F, M extends RailwayMode>(workflow: Railway<I, F, M>) => Railway<I & Record<K, T>, F | E, "async">;
|
|
215
|
-
/** Reusable wrapper around {@link Railway.fromAsync}. */
|
|
216
|
-
declare const fromAsyncNamed: <K extends string, C extends object, T, E>(key: K, fn: (ctx: C) => ResultAsync<T, E>) => <I extends C, F, M extends RailwayMode>(workflow: Railway<I, F, M>) => Railway<I & Record<K, T>, F | E, "async">;
|
|
217
|
-
/** Reusable wrapper around {@link Railway.derive} — pure sync derivation. */
|
|
218
|
-
declare const deriveNamed: <K extends string, C extends object, T>(key: K, fn: (ctx: C) => T) => <I extends C, E, M extends RailwayMode>(workflow: Railway<I, E, M>) => Railway<I & Record<K, T>, E, M>;
|
|
219
|
-
/** Reusable wrapper around {@link Railway.require} — narrows a nullable field. */
|
|
220
|
-
declare const requireNamed: <K extends string, S extends string, C extends object, E>(key: K, source: S, onMissing: (ctx: C & Record<S, unknown>) => E) => <I extends C & Record<S, unknown>, F, M extends RailwayMode>(workflow: Railway<I, F, M>) => Railway<I & Record<K, NonNullable<I[S]>>, F | E, M>;
|
|
221
|
-
/**
|
|
222
|
-
* Reusable wrapper around {@link Railway.parallel}. Branches run
|
|
223
|
-
* concurrently and merge their named outputs back into context.
|
|
224
|
-
*/
|
|
225
|
-
declare const parallelNamed: <R extends BranchRecord>(branches: R) => <I extends ParallelInput<R>, E, M extends RailwayMode>(workflow: Railway<I, E, M>) => Railway<I & ParallelOutput<R>, E | ParallelError<R>, "async">;
|
|
226
|
-
/**
|
|
227
|
-
* Reusable wrapper around {@link Railway.select} — projects the final
|
|
228
|
-
* context into the workflow's output type.
|
|
229
|
-
*/
|
|
230
|
-
declare const select: <C extends object, T>(fn: (ctx: C) => T) => <E, M extends RailwayMode>(workflow: Railway<C, E, M>) => RailwayOutput<T, E, M>;
|
|
231
|
-
|
|
232
|
-
export { Railway, type RailwayInput, type RailwayMode, type RailwayOutput, deriveNamed, fromAsyncNamed, fromPromiseNamed, fromResultNamed, fromSyncNamed, parallelNamed, parseNamed, parseWith, railway, requireNamed, select };
|
|
78
|
+
export { type BranchFn, type BranchRecord, type ParallelError, type ParallelOutput, Railway, type RailwayMode, type RailwayOutput };
|
package/dist/railway.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
// src/internal/dual.ts
|
|
2
|
+
var dual = (arity, body) => {
|
|
3
|
+
const run = body;
|
|
4
|
+
const dispatcher = (...args) => args.length >= arity ? run(...args) : (self) => run(self, ...args);
|
|
5
|
+
return dispatcher;
|
|
6
|
+
};
|
|
7
|
+
|
|
1
8
|
// src/result.ts
|
|
2
9
|
var ok = (value) => ({
|
|
3
10
|
_tag: "Ok",
|
|
@@ -9,18 +16,18 @@ var err = (error) => ({
|
|
|
9
16
|
});
|
|
10
17
|
var isOk = (result) => result._tag === "Ok";
|
|
11
18
|
var isErr = (result) => result._tag === "Err";
|
|
12
|
-
var
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
var map = dual(
|
|
20
|
+
2,
|
|
21
|
+
(result, fn) => isOk(result) ? ok(fn(result.value)) : err(result.error)
|
|
22
|
+
);
|
|
23
|
+
var mapErr = dual(
|
|
24
|
+
2,
|
|
25
|
+
(result, fn) => isErr(result) ? err(fn(result.error)) : ok(result.value)
|
|
26
|
+
);
|
|
27
|
+
var flatMap = dual(
|
|
28
|
+
2,
|
|
29
|
+
(result, fn) => isOk(result) ? fn(result.value) : err(result.error)
|
|
30
|
+
);
|
|
24
31
|
function trySync(fn, onThrow) {
|
|
25
32
|
return (...args) => {
|
|
26
33
|
try {
|
|
@@ -42,17 +49,6 @@ var UnexpectedError = class extends Error {
|
|
|
42
49
|
};
|
|
43
50
|
|
|
44
51
|
// src/async.ts
|
|
45
|
-
var liftPromiseResult = async (promise, onDefect) => {
|
|
46
|
-
try {
|
|
47
|
-
const result = await promise;
|
|
48
|
-
if (isErr(result)) {
|
|
49
|
-
return result;
|
|
50
|
-
}
|
|
51
|
-
return isResultLike(result.value) ? result.value : result;
|
|
52
|
-
} catch (error) {
|
|
53
|
-
return err(onDefect(error));
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
52
|
var sequenceSettled = (settled) => {
|
|
57
53
|
const values = [];
|
|
58
54
|
for (const result of settled) {
|
|
@@ -134,6 +130,9 @@ var ResultAsync = class _ResultAsync {
|
|
|
134
130
|
static ok(value) {
|
|
135
131
|
return new _ResultAsync(async () => ok(value));
|
|
136
132
|
}
|
|
133
|
+
static of(value) {
|
|
134
|
+
return _ResultAsync.ok(value);
|
|
135
|
+
}
|
|
137
136
|
/**
|
|
138
137
|
* Lifts an error into an `Err` async result.
|
|
139
138
|
*
|
|
@@ -147,16 +146,23 @@ var ResultAsync = class _ResultAsync {
|
|
|
147
146
|
}
|
|
148
147
|
/**
|
|
149
148
|
* Lifts an existing `Promise<Result<T, E>>` (e.g. from interop code) into a
|
|
150
|
-
* {@link ResultAsync}
|
|
151
|
-
*
|
|
149
|
+
* {@link ResultAsync}. A thrown defect is routed through `onDefect`,
|
|
150
|
+
* defaulting to {@link UnexpectedError}. The `Ok` value passes through
|
|
151
|
+
* verbatim — even when it happens to be Result-shaped.
|
|
152
152
|
*
|
|
153
153
|
* @param promise - a promise that already yields a `Result`
|
|
154
154
|
* @param onDefect - maps an unexpected throw to the `Err` channel
|
|
155
|
-
* @see {@link fromAsync} from `@onrails/result
|
|
155
|
+
* @see {@link fromAsync} from `@onrails/result`
|
|
156
156
|
*/
|
|
157
157
|
static fromResultPromise(promise, onDefect) {
|
|
158
158
|
const mapDefect = onDefect ?? ((error) => new UnexpectedError("Unexpected async defect", error));
|
|
159
|
-
return new _ResultAsync(() =>
|
|
159
|
+
return new _ResultAsync(async () => {
|
|
160
|
+
try {
|
|
161
|
+
return await promise;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
return err(mapDefect(error));
|
|
164
|
+
}
|
|
165
|
+
});
|
|
160
166
|
}
|
|
161
167
|
/**
|
|
162
168
|
* Combines a homogeneous array of async results into one. Resolves
|
|
@@ -240,7 +246,7 @@ var ResultAsync = class _ResultAsync {
|
|
|
240
246
|
*
|
|
241
247
|
* @example
|
|
242
248
|
* ```ts
|
|
243
|
-
* load(id).mapErr((e): AppError => ({ kind: "load", cause: e }));
|
|
249
|
+
* load(id).mapErr((e): AppError => ({ kind: "load", cause: e })); // ResultAsync<T, AppError>
|
|
244
250
|
* ```
|
|
245
251
|
*/
|
|
246
252
|
mapErr(fn) {
|
|
@@ -264,18 +270,12 @@ var ResultAsync = class _ResultAsync {
|
|
|
264
270
|
return first;
|
|
265
271
|
}
|
|
266
272
|
const next = fn(first.value);
|
|
267
|
-
|
|
268
|
-
return next.resolve();
|
|
269
|
-
}
|
|
270
|
-
if (isResultLike(next)) {
|
|
271
|
-
return next;
|
|
272
|
-
}
|
|
273
|
-
return isCompatLike(next) ? next.inner : next;
|
|
273
|
+
return next instanceof _ResultAsync ? next.resolve() : next;
|
|
274
274
|
});
|
|
275
275
|
}
|
|
276
276
|
/**
|
|
277
277
|
* neverthrow-compat alias of {@link flatMap}. Kept as the documented compat
|
|
278
|
-
* tier; prefer
|
|
278
|
+
* tier; prefer {@link flatMap} in new code.
|
|
279
279
|
*
|
|
280
280
|
* @example
|
|
281
281
|
* ```ts
|
|
@@ -292,7 +292,7 @@ var ResultAsync = class _ResultAsync {
|
|
|
292
292
|
*
|
|
293
293
|
* @example
|
|
294
294
|
* ```ts
|
|
295
|
-
* loadFromCache(id).recover(() => loadFromOrigin(id));
|
|
295
|
+
* loadFromCache(id).recover(() => loadFromOrigin(id)); // ResultAsync<T, F>
|
|
296
296
|
* ```
|
|
297
297
|
*/
|
|
298
298
|
recover(fn) {
|
|
@@ -307,7 +307,7 @@ var ResultAsync = class _ResultAsync {
|
|
|
307
307
|
}
|
|
308
308
|
/**
|
|
309
309
|
* neverthrow-compat alias of {@link recover}. Kept as the documented compat
|
|
310
|
-
* tier; prefer
|
|
310
|
+
* tier; prefer {@link recover} in new code.
|
|
311
311
|
*
|
|
312
312
|
* @example
|
|
313
313
|
* ```ts
|
|
@@ -415,16 +415,9 @@ var ResultAsync = class _ResultAsync {
|
|
|
415
415
|
);
|
|
416
416
|
}
|
|
417
417
|
};
|
|
418
|
-
function isResultLike(v) {
|
|
419
|
-
return typeof v === "object" && v !== null && "_tag" in v && (v._tag === "Ok" || v._tag === "Err");
|
|
420
|
-
}
|
|
421
|
-
function isCompatLike(v) {
|
|
422
|
-
return typeof v === "object" && v !== null && "inner" in v && isResultLike(v.inner);
|
|
423
|
-
}
|
|
424
418
|
|
|
425
|
-
// src/railway
|
|
419
|
+
// src/railway.ts
|
|
426
420
|
var addField = (ctx, key, value) => ({ ...ctx, [key]: value });
|
|
427
|
-
var liftResult = (result) => ResultAsync.fromResult(result);
|
|
428
421
|
var Railway = class _Railway {
|
|
429
422
|
constructor(state) {
|
|
430
423
|
this.state = state;
|
|
@@ -438,10 +431,7 @@ var Railway = class _Railway {
|
|
|
438
431
|
static context(context) {
|
|
439
432
|
return new _Railway({ mode: "sync", result: ok(context) });
|
|
440
433
|
}
|
|
441
|
-
/**
|
|
442
|
-
* Start a sync workflow with a throwing function — `onThrow` maps any
|
|
443
|
-
* exception to a typed error.
|
|
444
|
-
*/
|
|
434
|
+
/** Start a sync workflow with a throwing function. */
|
|
445
435
|
static fromSync(key, fn, onThrow) {
|
|
446
436
|
return _Railway.empty().fromSync(key, fn, onThrow);
|
|
447
437
|
}
|
|
@@ -458,153 +448,103 @@ var Railway = class _Railway {
|
|
|
458
448
|
return _Railway.empty().fromAsync(key, fn);
|
|
459
449
|
}
|
|
460
450
|
/**
|
|
461
|
-
*
|
|
462
|
-
*
|
|
451
|
+
* Rebuild the workflow in its current mode after transforming the carried
|
|
452
|
+
* result. Single re-link point for the state/phantom invariant: the mode
|
|
453
|
+
* tag is preserved verbatim, so `M` still describes the new state — TS
|
|
454
|
+
* cannot narrow the phantom `M` from the runtime tag, hence the cast.
|
|
463
455
|
*/
|
|
464
|
-
|
|
465
|
-
|
|
456
|
+
step(onSync, onAsync) {
|
|
457
|
+
const next = this.state.mode === "sync" ? { mode: "sync", result: onSync(this.state.result) } : { mode: "async", result: onAsync(this.state.result) };
|
|
458
|
+
return new _Railway(next);
|
|
466
459
|
}
|
|
467
460
|
/**
|
|
468
|
-
*
|
|
469
|
-
*
|
|
461
|
+
* Project the carried result into the mode-aware output type. Counterpart
|
|
462
|
+
* of {@link Railway.step} for terminal steps: the runtime branch matches
|
|
463
|
+
* the branch `RailwayOutput` picks for `M` (state/phantom invariant), but
|
|
464
|
+
* TS cannot resolve the conditional on an unresolved `M`, hence the cast.
|
|
470
465
|
*/
|
|
466
|
+
out(onSync, onAsync) {
|
|
467
|
+
return this.state.mode === "sync" ? onSync(this.state.result) : onAsync(this.state.result);
|
|
468
|
+
}
|
|
469
|
+
/** Lift the carried result into `ResultAsync` for async-upgrading steps. */
|
|
470
|
+
toAsync() {
|
|
471
|
+
return this.state.mode === "sync" ? ResultAsync.fromResult(this.state.result) : this.state.result;
|
|
472
|
+
}
|
|
473
|
+
/** Pure sync derivation. */
|
|
474
|
+
derive(key, fn) {
|
|
475
|
+
return this.fromResult(key, (ctx) => ok(fn(ctx)));
|
|
476
|
+
}
|
|
477
|
+
/** Throwing sync transform. */
|
|
471
478
|
fromSync(key, fn, onThrow) {
|
|
472
479
|
return this.fromResult(key, (ctx) => trySync(fn, onThrow)(ctx));
|
|
473
480
|
}
|
|
474
|
-
/**
|
|
475
|
-
* Sync `Result`-returning step. Adds `{ [key]: T }` to context on `Ok`;
|
|
476
|
-
* short-circuits the workflow on `Err`. Error union widens to `E | F`.
|
|
477
|
-
*/
|
|
481
|
+
/** Sync `Result`-returning step. */
|
|
478
482
|
fromResult(key, fn) {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
return new _Railway({ mode: "sync", result: err(current.error) });
|
|
483
|
-
}
|
|
484
|
-
const next = fn(current.value);
|
|
485
|
-
return isErr(next) ? new _Railway({ mode: "sync", result: err(next.error) }) : new _Railway({
|
|
486
|
-
mode: "sync",
|
|
487
|
-
result: ok(addField(current.value, key, next.value))
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
return new _Railway({
|
|
491
|
-
mode: "async",
|
|
492
|
-
result: this.state.result.flatMap(
|
|
483
|
+
return this.step(
|
|
484
|
+
(result) => flatMap(result, (ctx) => map(fn(ctx), (value) => addField(ctx, key, value))),
|
|
485
|
+
(result) => result.flatMap(
|
|
493
486
|
(ctx) => ResultAsync.fromResult(fn(ctx)).map((value) => addField(ctx, key, value))
|
|
494
487
|
)
|
|
495
|
-
|
|
488
|
+
);
|
|
496
489
|
}
|
|
497
|
-
/**
|
|
498
|
-
* Promise-returning step — upgrades the workflow to async mode. Reject
|
|
499
|
-
* reasons go through `onReject` to become typed `Err<F>`.
|
|
500
|
-
*/
|
|
490
|
+
/** Promise-returning step — upgrades the workflow to async mode. */
|
|
501
491
|
fromPromise(key, fn, onReject) {
|
|
502
492
|
return this.fromAsync(key, (ctx) => ResultAsync.fromPromise(fn(ctx), onReject));
|
|
503
493
|
}
|
|
504
|
-
/**
|
|
505
|
-
* `ResultAsync`-returning step — upgrades the workflow to async mode.
|
|
506
|
-
* Already-typed error: no mapper needed.
|
|
507
|
-
*/
|
|
494
|
+
/** `ResultAsync`-returning step — upgrades the workflow to async mode. */
|
|
508
495
|
fromAsync(key, fn) {
|
|
509
|
-
const current = this.state.mode === "sync" ? liftResult(this.state.result) : this.state.result;
|
|
510
496
|
return new _Railway({
|
|
511
497
|
mode: "async",
|
|
512
|
-
result:
|
|
498
|
+
result: this.toAsync().flatMap((ctx) => fn(ctx).map((value) => addField(ctx, key, value)))
|
|
513
499
|
});
|
|
514
500
|
}
|
|
515
|
-
/**
|
|
516
|
-
* Narrow a nullable context field into a required non-null field. If the
|
|
517
|
-
* source field is `null` / `undefined`, the workflow short-circuits with
|
|
518
|
-
* `onMissing(ctx)`.
|
|
519
|
-
*
|
|
520
|
-
* @example
|
|
521
|
-
* ```ts
|
|
522
|
-
* .fromPromise("row", ({ id }) => db.users.findFirst({ where: eq(users.id, id) }))
|
|
523
|
-
* .require("user", "row", ({ id }) => ({ kind: "not_found" as const, id }))
|
|
524
|
-
* // user is now User (non-null), not User | null
|
|
525
|
-
* ```
|
|
526
|
-
*/
|
|
501
|
+
/** Narrow a nullable context field into a required non-null field. */
|
|
527
502
|
require(key, source, onMissing) {
|
|
528
503
|
return this.fromResult(key, (ctx) => {
|
|
529
504
|
const value = ctx[source];
|
|
530
505
|
return value == null ? err(onMissing(ctx)) : ok(value);
|
|
531
506
|
});
|
|
532
507
|
}
|
|
533
|
-
/**
|
|
534
|
-
* Run independent `ResultAsync` branches concurrently and merge their
|
|
535
|
-
* named outputs back into context. Upgrades the workflow to async mode.
|
|
536
|
-
* On multiple failures, the first `Err` in record-iteration order wins.
|
|
537
|
-
*
|
|
538
|
-
* @example
|
|
539
|
-
* ```ts
|
|
540
|
-
* .parallel({
|
|
541
|
-
* recent: ({ userId }) => loadRecent(userId),
|
|
542
|
-
* metrics: ({ userId }) => loadMetrics(userId),
|
|
543
|
-
* })
|
|
544
|
-
* // ctx now has { ..., recent, metrics }
|
|
545
|
-
* ```
|
|
546
|
-
*/
|
|
508
|
+
/** Run independent `ResultAsync` branches concurrently and merge outputs. */
|
|
547
509
|
parallel(branches) {
|
|
548
|
-
const
|
|
510
|
+
const merged = this.toAsync().flatMap(
|
|
511
|
+
(ctx) => ResultAsync.combineTupleParallel(
|
|
512
|
+
Object.entries(branches).map(([, branch]) => branch(ctx))
|
|
513
|
+
).map(
|
|
514
|
+
(values) => (
|
|
515
|
+
// Branch outputs land under exactly R's keys; Object.fromEntries
|
|
516
|
+
// erases that correspondence, so reassert the merged shape.
|
|
517
|
+
{
|
|
518
|
+
...ctx,
|
|
519
|
+
...Object.fromEntries(Object.keys(branches).map((key, index) => [key, values[index]]))
|
|
520
|
+
}
|
|
521
|
+
)
|
|
522
|
+
)
|
|
523
|
+
);
|
|
549
524
|
return new _Railway({
|
|
550
525
|
mode: "async",
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
const next = { ...ctx };
|
|
556
|
-
Object.keys(branches).forEach((key, index) => {
|
|
557
|
-
next[key] = values[index];
|
|
558
|
-
});
|
|
559
|
-
return next;
|
|
560
|
-
})
|
|
561
|
-
)
|
|
526
|
+
// BranchFn erases per-key types to `ResultAsync<unknown, unknown>`
|
|
527
|
+
// (ParallelOutput/ParallelError recover them from R), so re-assert the
|
|
528
|
+
// precise value/error union the branches actually produce.
|
|
529
|
+
result: merged
|
|
562
530
|
});
|
|
563
531
|
}
|
|
564
|
-
/**
|
|
565
|
-
* Project the final context into the workflow's output type. Hides the
|
|
566
|
-
* internal context shape from callers.
|
|
567
|
-
*/
|
|
532
|
+
/** Project the final context into the workflow's output type. */
|
|
568
533
|
select(fn) {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
return this.state.result.map(fn);
|
|
534
|
+
return this.out(
|
|
535
|
+
(result) => map(result, fn),
|
|
536
|
+
(result) => result.map(fn)
|
|
537
|
+
);
|
|
574
538
|
}
|
|
575
|
-
/**
|
|
576
|
-
* Return the accumulated context as-is. Use when downstream code needs
|
|
577
|
-
* every named field; prefer {@link select} when you can project to a DTO.
|
|
578
|
-
*/
|
|
539
|
+
/** Return the accumulated context as-is. */
|
|
579
540
|
done() {
|
|
580
|
-
return this.
|
|
541
|
+
return this.out(
|
|
542
|
+
(result) => result,
|
|
543
|
+
(result) => result
|
|
544
|
+
);
|
|
581
545
|
}
|
|
582
546
|
};
|
|
583
547
|
|
|
584
|
-
|
|
585
|
-
var parseWithParser = (parser, input) => typeof parser === "function" ? parser(input) : parser.parse(input);
|
|
586
|
-
function railway(input, ...steps) {
|
|
587
|
-
return steps.reduce(
|
|
588
|
-
(current, step) => step(current),
|
|
589
|
-
Railway.context({ input })
|
|
590
|
-
);
|
|
591
|
-
}
|
|
592
|
-
var parseWith = (parser, onThrow) => ({
|
|
593
|
-
as: (key) => (workflow) => workflow.fromSync(key, ({ input }) => parseWithParser(parser, input), onThrow)
|
|
594
|
-
});
|
|
595
|
-
var parseNamed = (key, parser, onThrow) => (workflow) => workflow.fromSync(key, ({ input }) => parseWithParser(parser, input), onThrow);
|
|
596
|
-
var fromSyncNamed = (key, fn, onThrow) => (workflow) => workflow.fromSync(key, fn, onThrow);
|
|
597
|
-
var fromResultNamed = (key, fn) => (workflow) => workflow.fromResult(key, fn);
|
|
598
|
-
var fromPromiseNamed = (key, fn, onReject) => (workflow) => workflow.fromPromise(key, fn, onReject);
|
|
599
|
-
var fromAsyncNamed = (key, fn) => (workflow) => workflow.fromAsync(key, fn);
|
|
600
|
-
var deriveNamed = (key, fn) => (workflow) => workflow.derive(key, fn);
|
|
601
|
-
var requireNamed = (key, source, onMissing) => (workflow) => workflow.require(key, source, onMissing);
|
|
602
|
-
var parallelNamed = (branches) => (workflow) => {
|
|
603
|
-
const typedBranches = branches;
|
|
604
|
-
return workflow.parallel(typedBranches);
|
|
605
|
-
};
|
|
606
|
-
var select = (fn) => (workflow) => workflow.select(fn);
|
|
607
|
-
|
|
608
|
-
export { Railway, deriveNamed, fromAsyncNamed, fromPromiseNamed, fromResultNamed, fromSyncNamed, parallelNamed, parseNamed, parseWith, railway, requireNamed, select };
|
|
548
|
+
export { Railway };
|
|
609
549
|
//# sourceMappingURL=railway.js.map
|
|
610
550
|
//# sourceMappingURL=railway.js.map
|