@onrails/result 0.4.0 → 0.4.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 +1 -1
- package/README.md +165 -12
- package/RECIPES.md +6 -4
- package/dist/async-fns.cjs +52 -0
- package/dist/async-fns.cjs.map +1 -0
- package/dist/async-fns.d.cts +143 -0
- package/dist/async-fns.d.ts +143 -0
- package/dist/async-fns.js +42 -0
- package/dist/async-fns.js.map +1 -0
- package/dist/async-lift.d.cts +109 -0
- package/dist/async-lift.d.ts +109 -0
- package/dist/{async-Ci6WSL5-.d.ts → async.d.cts} +42 -58
- package/dist/{async-DcXR7k9K.d.cts → async.d.ts} +42 -58
- package/dist/collections.d.cts +105 -0
- package/dist/collections.d.ts +105 -0
- package/dist/compat/neverthrow.cjs +57 -45
- package/dist/compat/neverthrow.cjs.map +1 -1
- package/dist/compat/neverthrow.d.cts +21 -24
- package/dist/compat/neverthrow.d.ts +21 -24
- package/dist/compat/neverthrow.js +56 -44
- package/dist/compat/neverthrow.js.map +1 -1
- package/dist/extra.cjs +3 -1
- package/dist/extra.cjs.map +1 -1
- package/dist/extra.d.cts +11 -14
- package/dist/extra.d.ts +11 -14
- package/dist/extra.js +3 -1
- package/dist/extra.js.map +1 -1
- package/dist/fluent.cjs +15 -13
- package/dist/fluent.cjs.map +1 -1
- package/dist/fluent.d.cts +4 -7
- package/dist/fluent.d.ts +4 -7
- package/dist/fluent.js +15 -13
- package/dist/fluent.js.map +1 -1
- package/dist/index.cjs +77 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -490
- package/dist/index.d.ts +28 -490
- package/dist/index.js +77 -53
- package/dist/index.js.map +1 -1
- package/dist/internal/ctor.d.cts +12 -0
- package/dist/internal/ctor.d.ts +12 -0
- package/dist/internal/dual.d.cts +42 -0
- package/dist/internal/dual.d.ts +42 -0
- package/dist/internal/infer.d.cts +40 -0
- package/dist/internal/infer.d.ts +40 -0
- package/dist/internal/lock.d.cts +16 -0
- package/dist/internal/lock.d.ts +16 -0
- package/dist/internal/to-error.d.cts +2 -0
- package/dist/internal/to-error.d.ts +2 -0
- package/dist/pipe.cjs +8 -0
- package/dist/pipe.cjs.map +1 -1
- package/dist/pipe.d.cts +58 -11
- package/dist/pipe.d.ts +58 -11
- package/dist/pipe.js +8 -1
- package/dist/pipe.js.map +1 -1
- package/dist/railway.cjs +87 -63
- package/dist/railway.cjs.map +1 -1
- package/dist/railway.d.cts +49 -19
- package/dist/railway.d.ts +49 -19
- package/dist/railway.js +87 -63
- package/dist/railway.js.map +1 -1
- package/dist/result.d.cts +315 -0
- package/dist/result.d.ts +315 -0
- package/dist/try-gen.cjs +3 -3
- package/dist/try-gen.cjs.map +1 -1
- package/dist/try-gen.d.cts +4 -7
- package/dist/try-gen.d.ts +4 -7
- package/dist/try-gen.js +3 -3
- package/dist/try-gen.js.map +1 -1
- package/dist/{types-BQ9vv0nD.d.cts → types.d.cts} +6 -8
- package/dist/{types-BQ9vv0nD.d.ts → types.d.ts} +6 -8
- package/package.json +69 -24
- package/skills/railway-do-notation/SKILL.md +9 -9
package/dist/railway.d.cts
CHANGED
|
@@ -1,30 +1,53 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { ResultAsync } from "./async.js";
|
|
2
|
+
import type { Result } from "./types.js";
|
|
4
3
|
/**
|
|
5
4
|
* Tracks whether a {@link Railway} workflow has crossed an async boundary.
|
|
6
5
|
* Sync workflows return {@link Result}; async workflows return {@link ResultAsync}.
|
|
7
6
|
*/
|
|
8
|
-
type RailwayMode = "sync" | "async";
|
|
7
|
+
export type RailwayMode = "sync" | "async";
|
|
9
8
|
/**
|
|
10
9
|
* Mode-aware output type for a {@link Railway} workflow: sync mode →
|
|
11
10
|
* `Result<T, E>`, async mode → `ResultAsync<T, E>`.
|
|
12
11
|
*/
|
|
13
|
-
type RailwayOutput<T, E, M extends RailwayMode> = M extends "async" ? ResultAsync<T, E> : Result<T, E>;
|
|
14
|
-
type BranchFn<C extends object> = (ctx: C) => ResultAsync<unknown, unknown>;
|
|
15
|
-
type BranchRecord = Record<string, (ctx: never) => ResultAsync<unknown, unknown>>;
|
|
12
|
+
export type RailwayOutput<T, E, M extends RailwayMode> = M extends "async" ? ResultAsync<T, E> : Result<T, E>;
|
|
13
|
+
export type BranchFn<C extends object> = (ctx: C) => ResultAsync<unknown, unknown>;
|
|
14
|
+
export type BranchRecord = Record<string, (ctx: never) => ResultAsync<unknown, unknown>>;
|
|
16
15
|
type BranchOk<R> = R extends (ctx: never) => ResultAsync<infer T, infer _E> ? T : never;
|
|
17
16
|
type BranchErr<R> = R extends (ctx: never) => ResultAsync<infer _T, infer E> ? E : never;
|
|
18
|
-
type ParallelOutput<R extends BranchRecord> = {
|
|
17
|
+
export type ParallelOutput<R extends BranchRecord> = {
|
|
19
18
|
[K in keyof R]: BranchOk<R[K]>;
|
|
20
19
|
};
|
|
21
|
-
type ParallelError<R extends BranchRecord> = BranchErr<R[keyof R]>;
|
|
20
|
+
export type ParallelError<R extends BranchRecord> = BranchErr<R[keyof R]>;
|
|
21
|
+
/** Collapses an intersection into a single displayed object type. */
|
|
22
|
+
type Prettify<T> = {
|
|
23
|
+
[K in keyof T]: T[K];
|
|
24
|
+
} & {};
|
|
25
|
+
/**
|
|
26
|
+
* The context after one more named step. Chaining `C & Record<K, T>` instead
|
|
27
|
+
* builds an intersection that TS echoes verbatim in every hover and error
|
|
28
|
+
* message (`Record<never, never> & Record<"id", string> & …`); `Omit` keeps
|
|
29
|
+
* property modifiers and `Prettify` collapses the result to a flat object.
|
|
30
|
+
*/
|
|
31
|
+
type Extend<C extends object, K extends string, T> = Prettify<Omit<C, K> & Record<K, T>>;
|
|
32
|
+
/**
|
|
33
|
+
* Rejects a key already present in the context. Railway merges each step with
|
|
34
|
+
* a spread, so a repeated key overwrites at runtime while the old
|
|
35
|
+
* `C & Record<K, T>` type intersected the two field types — `number & string`
|
|
36
|
+
* became `never` and nothing complained. Resolves to `[]` for a fresh key and
|
|
37
|
+
* to an unsatisfiable extra parameter otherwise, so the collision fails at the
|
|
38
|
+
* call site with the offending name in the message.
|
|
39
|
+
*/
|
|
40
|
+
type FreshKey<C, K extends string> = K extends keyof C ? [error: `key "${K}" already exists in the workflow context`] : [];
|
|
41
|
+
/** {@link FreshKey} for a whole record of parallel branches. */
|
|
42
|
+
type FreshKeys<C, R> = Extract<keyof R, keyof C> extends never ? [] : [
|
|
43
|
+
error: `branch key "${Extract<keyof R, keyof C> & string}" already exists in the workflow context`
|
|
44
|
+
];
|
|
22
45
|
/**
|
|
23
46
|
* Named-context workflow builder. Each step appends a typed field to the
|
|
24
47
|
* accumulating context object; the workflow tracks sync/async mode so the
|
|
25
48
|
* final output type ({@link RailwayOutput}) is correct.
|
|
26
49
|
*/
|
|
27
|
-
declare class Railway<C extends object, E, M extends RailwayMode> {
|
|
50
|
+
export declare class Railway<C extends object, E, M extends RailwayMode> {
|
|
28
51
|
private readonly state;
|
|
29
52
|
private constructor();
|
|
30
53
|
/** Start an empty sync workflow with no fields in context. */
|
|
@@ -55,24 +78,31 @@ declare class Railway<C extends object, E, M extends RailwayMode> {
|
|
|
55
78
|
private out;
|
|
56
79
|
/** Lift the carried result into `ResultAsync` for async-upgrading steps. */
|
|
57
80
|
private toAsync;
|
|
81
|
+
/**
|
|
82
|
+
* Internal plumbing for a sync field append. Public steps wrap this and add
|
|
83
|
+
* the {@link FreshKey} collision guard; the static factories bypass the
|
|
84
|
+
* guard because they always start from an empty context.
|
|
85
|
+
*/
|
|
86
|
+
private addSync;
|
|
87
|
+
/** Internal plumbing for an async field append — upgrades the mode. */
|
|
88
|
+
private addAsync;
|
|
58
89
|
/** Pure sync derivation. */
|
|
59
|
-
derive<K extends string, T>(key: K, fn: (ctx: C) => T): Railway<C
|
|
90
|
+
derive<K extends string, T>(key: K, fn: (ctx: C) => T, ..._guard: FreshKey<C, K>): Railway<Extend<C, K, T>, E, M>;
|
|
60
91
|
/** Throwing sync transform. */
|
|
61
|
-
fromSync<K extends string, T, F>(key: K, fn: (ctx: C) => T, onThrow: (error: unknown) => F): Railway<C
|
|
92
|
+
fromSync<K extends string, T, F>(key: K, fn: (ctx: C) => T, onThrow: (error: unknown) => F, ..._guard: FreshKey<C, K>): Railway<Extend<C, K, T>, E | F, M>;
|
|
62
93
|
/** Sync `Result`-returning step. */
|
|
63
|
-
fromResult<K extends string, T, F>(key: K, fn: (ctx: C) => Result<T, F>): Railway<C
|
|
94
|
+
fromResult<K extends string, T, F>(key: K, fn: (ctx: C) => Result<T, F>, ..._guard: FreshKey<C, K>): Railway<Extend<C, K, T>, E | F, M>;
|
|
64
95
|
/** Promise-returning step — upgrades the workflow to async mode. */
|
|
65
|
-
fromPromise<K extends string, T, F>(key: K, fn: (ctx: C) => PromiseLike<T>, onReject: (error: unknown) => F): Railway<C
|
|
96
|
+
fromPromise<K extends string, T, F>(key: K, fn: (ctx: C) => PromiseLike<T>, onReject: (error: unknown) => F, ..._guard: FreshKey<C, K>): Railway<Extend<C, K, T>, E | F, "async">;
|
|
66
97
|
/** `ResultAsync`-returning step — upgrades the workflow to async mode. */
|
|
67
|
-
fromAsync<K extends string, T, F>(key: K, fn: (ctx: C) => ResultAsync<T, F>): Railway<C
|
|
98
|
+
fromAsync<K extends string, T, F>(key: K, fn: (ctx: C) => ResultAsync<T, F>, ..._guard: FreshKey<C, K>): Railway<Extend<C, K, T>, E | F, "async">;
|
|
68
99
|
/** Narrow a nullable context field into a required non-null field. */
|
|
69
|
-
require<K extends string, S extends keyof C, F>(key: K, source: S, onMissing: (ctx: C) => F): Railway<C
|
|
100
|
+
require<K extends string, S extends keyof C, F>(key: K, source: S, onMissing: (ctx: C) => F, ..._guard: FreshKey<C, K>): Railway<Extend<C, K, NonNullable<C[S]>>, E | F, M>;
|
|
70
101
|
/** Run independent `ResultAsync` branches concurrently and merge outputs. */
|
|
71
|
-
parallel<R extends Record<string, BranchFn<C>>>(branches: R): Railway<C & ParallelOutput<R
|
|
102
|
+
parallel<R extends Record<string, BranchFn<C>>>(branches: R, ..._guard: FreshKeys<C, R>): Railway<Prettify<Omit<C, keyof R> & ParallelOutput<R>>, E | ParallelError<R>, "async">;
|
|
72
103
|
/** Project the final context into the workflow's output type. */
|
|
73
104
|
select<T>(fn: (ctx: C) => T): RailwayOutput<T, E, M>;
|
|
74
105
|
/** Return the accumulated context as-is. */
|
|
75
106
|
done(): RailwayOutput<C, E, M>;
|
|
76
107
|
}
|
|
77
|
-
|
|
78
|
-
export { type BranchFn, type BranchRecord, type ParallelError, type ParallelOutput, Railway, type RailwayMode, type RailwayOutput };
|
|
108
|
+
export {};
|
package/dist/railway.d.ts
CHANGED
|
@@ -1,30 +1,53 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { ResultAsync } from "./async.js";
|
|
2
|
+
import type { Result } from "./types.js";
|
|
4
3
|
/**
|
|
5
4
|
* Tracks whether a {@link Railway} workflow has crossed an async boundary.
|
|
6
5
|
* Sync workflows return {@link Result}; async workflows return {@link ResultAsync}.
|
|
7
6
|
*/
|
|
8
|
-
type RailwayMode = "sync" | "async";
|
|
7
|
+
export type RailwayMode = "sync" | "async";
|
|
9
8
|
/**
|
|
10
9
|
* Mode-aware output type for a {@link Railway} workflow: sync mode →
|
|
11
10
|
* `Result<T, E>`, async mode → `ResultAsync<T, E>`.
|
|
12
11
|
*/
|
|
13
|
-
type RailwayOutput<T, E, M extends RailwayMode> = M extends "async" ? ResultAsync<T, E> : Result<T, E>;
|
|
14
|
-
type BranchFn<C extends object> = (ctx: C) => ResultAsync<unknown, unknown>;
|
|
15
|
-
type BranchRecord = Record<string, (ctx: never) => ResultAsync<unknown, unknown>>;
|
|
12
|
+
export type RailwayOutput<T, E, M extends RailwayMode> = M extends "async" ? ResultAsync<T, E> : Result<T, E>;
|
|
13
|
+
export type BranchFn<C extends object> = (ctx: C) => ResultAsync<unknown, unknown>;
|
|
14
|
+
export type BranchRecord = Record<string, (ctx: never) => ResultAsync<unknown, unknown>>;
|
|
16
15
|
type BranchOk<R> = R extends (ctx: never) => ResultAsync<infer T, infer _E> ? T : never;
|
|
17
16
|
type BranchErr<R> = R extends (ctx: never) => ResultAsync<infer _T, infer E> ? E : never;
|
|
18
|
-
type ParallelOutput<R extends BranchRecord> = {
|
|
17
|
+
export type ParallelOutput<R extends BranchRecord> = {
|
|
19
18
|
[K in keyof R]: BranchOk<R[K]>;
|
|
20
19
|
};
|
|
21
|
-
type ParallelError<R extends BranchRecord> = BranchErr<R[keyof R]>;
|
|
20
|
+
export type ParallelError<R extends BranchRecord> = BranchErr<R[keyof R]>;
|
|
21
|
+
/** Collapses an intersection into a single displayed object type. */
|
|
22
|
+
type Prettify<T> = {
|
|
23
|
+
[K in keyof T]: T[K];
|
|
24
|
+
} & {};
|
|
25
|
+
/**
|
|
26
|
+
* The context after one more named step. Chaining `C & Record<K, T>` instead
|
|
27
|
+
* builds an intersection that TS echoes verbatim in every hover and error
|
|
28
|
+
* message (`Record<never, never> & Record<"id", string> & …`); `Omit` keeps
|
|
29
|
+
* property modifiers and `Prettify` collapses the result to a flat object.
|
|
30
|
+
*/
|
|
31
|
+
type Extend<C extends object, K extends string, T> = Prettify<Omit<C, K> & Record<K, T>>;
|
|
32
|
+
/**
|
|
33
|
+
* Rejects a key already present in the context. Railway merges each step with
|
|
34
|
+
* a spread, so a repeated key overwrites at runtime while the old
|
|
35
|
+
* `C & Record<K, T>` type intersected the two field types — `number & string`
|
|
36
|
+
* became `never` and nothing complained. Resolves to `[]` for a fresh key and
|
|
37
|
+
* to an unsatisfiable extra parameter otherwise, so the collision fails at the
|
|
38
|
+
* call site with the offending name in the message.
|
|
39
|
+
*/
|
|
40
|
+
type FreshKey<C, K extends string> = K extends keyof C ? [error: `key "${K}" already exists in the workflow context`] : [];
|
|
41
|
+
/** {@link FreshKey} for a whole record of parallel branches. */
|
|
42
|
+
type FreshKeys<C, R> = Extract<keyof R, keyof C> extends never ? [] : [
|
|
43
|
+
error: `branch key "${Extract<keyof R, keyof C> & string}" already exists in the workflow context`
|
|
44
|
+
];
|
|
22
45
|
/**
|
|
23
46
|
* Named-context workflow builder. Each step appends a typed field to the
|
|
24
47
|
* accumulating context object; the workflow tracks sync/async mode so the
|
|
25
48
|
* final output type ({@link RailwayOutput}) is correct.
|
|
26
49
|
*/
|
|
27
|
-
declare class Railway<C extends object, E, M extends RailwayMode> {
|
|
50
|
+
export declare class Railway<C extends object, E, M extends RailwayMode> {
|
|
28
51
|
private readonly state;
|
|
29
52
|
private constructor();
|
|
30
53
|
/** Start an empty sync workflow with no fields in context. */
|
|
@@ -55,24 +78,31 @@ declare class Railway<C extends object, E, M extends RailwayMode> {
|
|
|
55
78
|
private out;
|
|
56
79
|
/** Lift the carried result into `ResultAsync` for async-upgrading steps. */
|
|
57
80
|
private toAsync;
|
|
81
|
+
/**
|
|
82
|
+
* Internal plumbing for a sync field append. Public steps wrap this and add
|
|
83
|
+
* the {@link FreshKey} collision guard; the static factories bypass the
|
|
84
|
+
* guard because they always start from an empty context.
|
|
85
|
+
*/
|
|
86
|
+
private addSync;
|
|
87
|
+
/** Internal plumbing for an async field append — upgrades the mode. */
|
|
88
|
+
private addAsync;
|
|
58
89
|
/** Pure sync derivation. */
|
|
59
|
-
derive<K extends string, T>(key: K, fn: (ctx: C) => T): Railway<C
|
|
90
|
+
derive<K extends string, T>(key: K, fn: (ctx: C) => T, ..._guard: FreshKey<C, K>): Railway<Extend<C, K, T>, E, M>;
|
|
60
91
|
/** Throwing sync transform. */
|
|
61
|
-
fromSync<K extends string, T, F>(key: K, fn: (ctx: C) => T, onThrow: (error: unknown) => F): Railway<C
|
|
92
|
+
fromSync<K extends string, T, F>(key: K, fn: (ctx: C) => T, onThrow: (error: unknown) => F, ..._guard: FreshKey<C, K>): Railway<Extend<C, K, T>, E | F, M>;
|
|
62
93
|
/** Sync `Result`-returning step. */
|
|
63
|
-
fromResult<K extends string, T, F>(key: K, fn: (ctx: C) => Result<T, F>): Railway<C
|
|
94
|
+
fromResult<K extends string, T, F>(key: K, fn: (ctx: C) => Result<T, F>, ..._guard: FreshKey<C, K>): Railway<Extend<C, K, T>, E | F, M>;
|
|
64
95
|
/** Promise-returning step — upgrades the workflow to async mode. */
|
|
65
|
-
fromPromise<K extends string, T, F>(key: K, fn: (ctx: C) => PromiseLike<T>, onReject: (error: unknown) => F): Railway<C
|
|
96
|
+
fromPromise<K extends string, T, F>(key: K, fn: (ctx: C) => PromiseLike<T>, onReject: (error: unknown) => F, ..._guard: FreshKey<C, K>): Railway<Extend<C, K, T>, E | F, "async">;
|
|
66
97
|
/** `ResultAsync`-returning step — upgrades the workflow to async mode. */
|
|
67
|
-
fromAsync<K extends string, T, F>(key: K, fn: (ctx: C) => ResultAsync<T, F>): Railway<C
|
|
98
|
+
fromAsync<K extends string, T, F>(key: K, fn: (ctx: C) => ResultAsync<T, F>, ..._guard: FreshKey<C, K>): Railway<Extend<C, K, T>, E | F, "async">;
|
|
68
99
|
/** Narrow a nullable context field into a required non-null field. */
|
|
69
|
-
require<K extends string, S extends keyof C, F>(key: K, source: S, onMissing: (ctx: C) => F): Railway<C
|
|
100
|
+
require<K extends string, S extends keyof C, F>(key: K, source: S, onMissing: (ctx: C) => F, ..._guard: FreshKey<C, K>): Railway<Extend<C, K, NonNullable<C[S]>>, E | F, M>;
|
|
70
101
|
/** Run independent `ResultAsync` branches concurrently and merge outputs. */
|
|
71
|
-
parallel<R extends Record<string, BranchFn<C>>>(branches: R): Railway<C & ParallelOutput<R
|
|
102
|
+
parallel<R extends Record<string, BranchFn<C>>>(branches: R, ..._guard: FreshKeys<C, R>): Railway<Prettify<Omit<C, keyof R> & ParallelOutput<R>>, E | ParallelError<R>, "async">;
|
|
72
103
|
/** Project the final context into the workflow's output type. */
|
|
73
104
|
select<T>(fn: (ctx: C) => T): RailwayOutput<T, E, M>;
|
|
74
105
|
/** Return the accumulated context as-is. */
|
|
75
106
|
done(): RailwayOutput<C, E, M>;
|
|
76
107
|
}
|
|
77
|
-
|
|
78
|
-
export { type BranchFn, type BranchRecord, type ParallelError, type ParallelOutput, Railway, type RailwayMode, type RailwayOutput };
|
|
108
|
+
export {};
|
package/dist/railway.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
// src/internal/ctor.ts
|
|
2
|
+
var mkOk = (value) => ({
|
|
3
|
+
_tag: "Ok",
|
|
4
|
+
value
|
|
5
|
+
});
|
|
6
|
+
var mkErr = (error) => ({
|
|
7
|
+
_tag: "Err",
|
|
8
|
+
error
|
|
9
|
+
});
|
|
10
|
+
|
|
1
11
|
// src/internal/dual.ts
|
|
2
12
|
var dual = (arity, body) => {
|
|
3
13
|
const run = body;
|
|
@@ -6,34 +16,30 @@ var dual = (arity, body) => {
|
|
|
6
16
|
};
|
|
7
17
|
|
|
8
18
|
// src/result.ts
|
|
9
|
-
var ok = (value) => ({
|
|
10
|
-
_tag: "Ok",
|
|
11
|
-
value
|
|
12
|
-
});
|
|
13
|
-
var err = (error) => ({
|
|
14
|
-
_tag: "Err",
|
|
15
|
-
error
|
|
16
|
-
});
|
|
17
19
|
var isOk = (result) => result._tag === "Ok";
|
|
18
20
|
var isErr = (result) => result._tag === "Err";
|
|
19
21
|
var map = dual(
|
|
20
22
|
2,
|
|
21
|
-
(result, fn) => isOk(result) ?
|
|
23
|
+
(result, fn) => isOk(result) ? mkOk(fn(result.value)) : mkErr(result.error)
|
|
22
24
|
);
|
|
23
25
|
var mapErr = dual(
|
|
24
26
|
2,
|
|
25
|
-
(result, fn) => isErr(result) ?
|
|
27
|
+
(result, fn) => isErr(result) ? mkErr(fn(result.error)) : mkOk(result.value)
|
|
28
|
+
);
|
|
29
|
+
var bimap = dual(
|
|
30
|
+
3,
|
|
31
|
+
(result, onOk, onErr) => isOk(result) ? mkOk(onOk(result.value)) : mkErr(onErr(result.error))
|
|
26
32
|
);
|
|
27
33
|
var flatMap = dual(
|
|
28
34
|
2,
|
|
29
|
-
(result, fn) => isOk(result) ? fn(result.value) :
|
|
35
|
+
(result, fn) => isOk(result) ? fn(result.value) : mkErr(result.error)
|
|
30
36
|
);
|
|
31
|
-
function
|
|
37
|
+
function fromThrowable(fn, onThrow) {
|
|
32
38
|
return (...args) => {
|
|
33
39
|
try {
|
|
34
|
-
return
|
|
40
|
+
return mkOk(fn(...args));
|
|
35
41
|
} catch (error) {
|
|
36
|
-
return
|
|
42
|
+
return mkErr(onThrow(error));
|
|
37
43
|
}
|
|
38
44
|
};
|
|
39
45
|
}
|
|
@@ -53,11 +59,11 @@ var sequenceSettled = (settled) => {
|
|
|
53
59
|
const values = [];
|
|
54
60
|
for (const result of settled) {
|
|
55
61
|
if (isErr(result)) {
|
|
56
|
-
return
|
|
62
|
+
return mkErr(result.error);
|
|
57
63
|
}
|
|
58
64
|
values.push(result.value);
|
|
59
65
|
}
|
|
60
|
-
return
|
|
66
|
+
return mkOk(values);
|
|
61
67
|
};
|
|
62
68
|
var ResultAsync = class _ResultAsync {
|
|
63
69
|
constructor(run) {
|
|
@@ -94,9 +100,9 @@ var ResultAsync = class _ResultAsync {
|
|
|
94
100
|
static fromPromise(promise, onReject) {
|
|
95
101
|
return new _ResultAsync(async () => {
|
|
96
102
|
try {
|
|
97
|
-
return
|
|
103
|
+
return mkOk(await promise);
|
|
98
104
|
} catch (error) {
|
|
99
|
-
return
|
|
105
|
+
return mkErr(onReject(error));
|
|
100
106
|
}
|
|
101
107
|
});
|
|
102
108
|
}
|
|
@@ -110,7 +116,7 @@ var ResultAsync = class _ResultAsync {
|
|
|
110
116
|
* ```
|
|
111
117
|
*/
|
|
112
118
|
static fromSafePromise(promise) {
|
|
113
|
-
return new _ResultAsync(async () =>
|
|
119
|
+
return new _ResultAsync(async () => mkOk(await promise));
|
|
114
120
|
}
|
|
115
121
|
/**
|
|
116
122
|
* Defers work until {@link resolve}. Unlike {@link fromPromise}, the factory
|
|
@@ -128,21 +134,13 @@ var ResultAsync = class _ResultAsync {
|
|
|
128
134
|
return new _ResultAsync(fn);
|
|
129
135
|
}
|
|
130
136
|
static ok(value) {
|
|
131
|
-
return new _ResultAsync(async () =>
|
|
137
|
+
return new _ResultAsync(async () => mkOk(value));
|
|
132
138
|
}
|
|
133
139
|
static of(value) {
|
|
134
140
|
return _ResultAsync.ok(value);
|
|
135
141
|
}
|
|
136
|
-
/**
|
|
137
|
-
* Lifts an error into an `Err` async result.
|
|
138
|
-
*
|
|
139
|
-
* @example
|
|
140
|
-
* ```ts
|
|
141
|
-
* const r = ResultAsync.err({ kind: "not_found" as const });
|
|
142
|
-
* ```
|
|
143
|
-
*/
|
|
144
142
|
static err(error) {
|
|
145
|
-
return new _ResultAsync(async () =>
|
|
143
|
+
return new _ResultAsync(async () => mkErr(error));
|
|
146
144
|
}
|
|
147
145
|
/**
|
|
148
146
|
* Lifts an existing `Promise<Result<T, E>>` (e.g. from interop code) into a
|
|
@@ -160,7 +158,7 @@ var ResultAsync = class _ResultAsync {
|
|
|
160
158
|
try {
|
|
161
159
|
return await promise;
|
|
162
160
|
} catch (error) {
|
|
163
|
-
return
|
|
161
|
+
return mkErr(mapDefect(error));
|
|
164
162
|
}
|
|
165
163
|
});
|
|
166
164
|
}
|
|
@@ -182,11 +180,11 @@ var ResultAsync = class _ResultAsync {
|
|
|
182
180
|
for (const ra of results) {
|
|
183
181
|
const result = await ra.resolve();
|
|
184
182
|
if (isErr(result)) {
|
|
185
|
-
return
|
|
183
|
+
return mkErr(result.error);
|
|
186
184
|
}
|
|
187
185
|
values.push(result.value);
|
|
188
186
|
}
|
|
189
|
-
return
|
|
187
|
+
return mkOk(values);
|
|
190
188
|
});
|
|
191
189
|
}
|
|
192
190
|
/**
|
|
@@ -198,7 +196,7 @@ var ResultAsync = class _ResultAsync {
|
|
|
198
196
|
*
|
|
199
197
|
* @example
|
|
200
198
|
* ```ts
|
|
201
|
-
* const combined = ResultAsync.combineTuple([loadCfg(), loadCatalog()]
|
|
199
|
+
* const combined = ResultAsync.combineTuple([loadCfg(), loadCatalog()]);
|
|
202
200
|
* // ResultAsync<readonly [Cfg, Catalog], CfgError | CatalogError>
|
|
203
201
|
* const r = await combined;
|
|
204
202
|
* if (isOk(r)) {
|
|
@@ -206,7 +204,7 @@ var ResultAsync = class _ResultAsync {
|
|
|
206
204
|
* }
|
|
207
205
|
* ```
|
|
208
206
|
*/
|
|
209
|
-
static combineTuple(results) {
|
|
207
|
+
static combineTuple(results, ..._guard) {
|
|
210
208
|
return _ResultAsync.combine(
|
|
211
209
|
results
|
|
212
210
|
);
|
|
@@ -221,12 +219,14 @@ var ResultAsync = class _ResultAsync {
|
|
|
221
219
|
* const combined = ResultAsync.combineTupleParallel([
|
|
222
220
|
* loadProfile(id),
|
|
223
221
|
* loadMetrics(id),
|
|
224
|
-
* ]
|
|
222
|
+
* ]);
|
|
225
223
|
* ```
|
|
226
224
|
*/
|
|
227
|
-
static combineTupleParallel(results) {
|
|
225
|
+
static combineTupleParallel(results, ..._guard) {
|
|
228
226
|
return new _ResultAsync(
|
|
229
|
-
async () => sequenceSettled(
|
|
227
|
+
async () => sequenceSettled(
|
|
228
|
+
await Promise.all(results.map((ra) => ra.resolve()))
|
|
229
|
+
)
|
|
230
230
|
);
|
|
231
231
|
}
|
|
232
232
|
/**
|
|
@@ -252,6 +252,18 @@ var ResultAsync = class _ResultAsync {
|
|
|
252
252
|
mapErr(fn) {
|
|
253
253
|
return new _ResultAsync(async () => mapErr(await this.resolve(), fn));
|
|
254
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Transforms both tracks in one step — the async mirror of the sync
|
|
257
|
+
* `bimap`. Exactly one of `onOk` / `onErr` runs.
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```ts
|
|
261
|
+
* load(id).bimap(toDto, (e): AppError => ({ kind: "load", cause: e }));
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
bimap(onOk, onErr) {
|
|
265
|
+
return new _ResultAsync(async () => bimap(await this.resolve(), onOk, onErr));
|
|
266
|
+
}
|
|
255
267
|
/**
|
|
256
268
|
* Canonical bind — chains a step that itself returns a `ResultAsync` or sync
|
|
257
269
|
* `Result`, short-circuiting on `Err`. Error types accumulate (`E | F`).
|
|
@@ -425,27 +437,27 @@ var Railway = class _Railway {
|
|
|
425
437
|
state;
|
|
426
438
|
/** Start an empty sync workflow with no fields in context. */
|
|
427
439
|
static empty() {
|
|
428
|
-
return new _Railway({ mode: "sync", result:
|
|
440
|
+
return new _Railway({ mode: "sync", result: mkOk({}) });
|
|
429
441
|
}
|
|
430
442
|
/** Start a sync workflow with the given context as the initial state. */
|
|
431
443
|
static context(context) {
|
|
432
|
-
return new _Railway({ mode: "sync", result:
|
|
444
|
+
return new _Railway({ mode: "sync", result: mkOk(context) });
|
|
433
445
|
}
|
|
434
446
|
/** Start a sync workflow with a throwing function. */
|
|
435
447
|
static fromSync(key, fn, onThrow) {
|
|
436
|
-
return _Railway.empty().
|
|
448
|
+
return _Railway.empty().addSync(key, () => fromThrowable(fn, onThrow)());
|
|
437
449
|
}
|
|
438
450
|
/** Start a sync workflow with a `Result`-returning function. */
|
|
439
451
|
static fromResult(key, fn) {
|
|
440
|
-
return _Railway.empty().
|
|
452
|
+
return _Railway.empty().addSync(key, fn);
|
|
441
453
|
}
|
|
442
454
|
/** Start an async workflow with a `PromiseLike`-returning function. */
|
|
443
455
|
static fromPromise(key, fn, onReject) {
|
|
444
|
-
return _Railway.empty().
|
|
456
|
+
return _Railway.empty().addAsync(key, () => ResultAsync.fromPromise(fn(), onReject));
|
|
445
457
|
}
|
|
446
458
|
/** Start an async workflow with a `ResultAsync`-returning function. */
|
|
447
459
|
static fromAsync(key, fn) {
|
|
448
|
-
return _Railway.empty().
|
|
460
|
+
return _Railway.empty().addAsync(key, fn);
|
|
449
461
|
}
|
|
450
462
|
/**
|
|
451
463
|
* Rebuild the workflow in its current mode after transforming the carried
|
|
@@ -470,16 +482,12 @@ var Railway = class _Railway {
|
|
|
470
482
|
toAsync() {
|
|
471
483
|
return this.state.mode === "sync" ? ResultAsync.fromResult(this.state.result) : this.state.result;
|
|
472
484
|
}
|
|
473
|
-
/**
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
return this.fromResult(key, (ctx) => trySync(fn, onThrow)(ctx));
|
|
480
|
-
}
|
|
481
|
-
/** Sync `Result`-returning step. */
|
|
482
|
-
fromResult(key, fn) {
|
|
485
|
+
/**
|
|
486
|
+
* Internal plumbing for a sync field append. Public steps wrap this and add
|
|
487
|
+
* the {@link FreshKey} collision guard; the static factories bypass the
|
|
488
|
+
* guard because they always start from an empty context.
|
|
489
|
+
*/
|
|
490
|
+
addSync(key, fn) {
|
|
483
491
|
return this.step(
|
|
484
492
|
(result) => flatMap(result, (ctx) => map(fn(ctx), (value) => addField(ctx, key, value))),
|
|
485
493
|
(result) => result.flatMap(
|
|
@@ -487,26 +495,42 @@ var Railway = class _Railway {
|
|
|
487
495
|
)
|
|
488
496
|
);
|
|
489
497
|
}
|
|
490
|
-
/**
|
|
491
|
-
|
|
492
|
-
return this.fromAsync(key, (ctx) => ResultAsync.fromPromise(fn(ctx), onReject));
|
|
493
|
-
}
|
|
494
|
-
/** `ResultAsync`-returning step — upgrades the workflow to async mode. */
|
|
495
|
-
fromAsync(key, fn) {
|
|
498
|
+
/** Internal plumbing for an async field append — upgrades the mode. */
|
|
499
|
+
addAsync(key, fn) {
|
|
496
500
|
return new _Railway({
|
|
497
501
|
mode: "async",
|
|
498
502
|
result: this.toAsync().flatMap((ctx) => fn(ctx).map((value) => addField(ctx, key, value)))
|
|
499
503
|
});
|
|
500
504
|
}
|
|
505
|
+
/** Pure sync derivation. */
|
|
506
|
+
derive(key, fn, ..._guard) {
|
|
507
|
+
return this.addSync(key, (ctx) => mkOk(fn(ctx)));
|
|
508
|
+
}
|
|
509
|
+
/** Throwing sync transform. */
|
|
510
|
+
fromSync(key, fn, onThrow, ..._guard) {
|
|
511
|
+
return this.addSync(key, (ctx) => fromThrowable(fn, onThrow)(ctx));
|
|
512
|
+
}
|
|
513
|
+
/** Sync `Result`-returning step. */
|
|
514
|
+
fromResult(key, fn, ..._guard) {
|
|
515
|
+
return this.addSync(key, fn);
|
|
516
|
+
}
|
|
517
|
+
/** Promise-returning step — upgrades the workflow to async mode. */
|
|
518
|
+
fromPromise(key, fn, onReject, ..._guard) {
|
|
519
|
+
return this.addAsync(key, (ctx) => ResultAsync.fromPromise(fn(ctx), onReject));
|
|
520
|
+
}
|
|
521
|
+
/** `ResultAsync`-returning step — upgrades the workflow to async mode. */
|
|
522
|
+
fromAsync(key, fn, ..._guard) {
|
|
523
|
+
return this.addAsync(key, fn);
|
|
524
|
+
}
|
|
501
525
|
/** Narrow a nullable context field into a required non-null field. */
|
|
502
|
-
require(key, source, onMissing) {
|
|
503
|
-
return this.
|
|
526
|
+
require(key, source, onMissing, ..._guard) {
|
|
527
|
+
return this.addSync(key, (ctx) => {
|
|
504
528
|
const value = ctx[source];
|
|
505
|
-
return value == null ?
|
|
529
|
+
return value == null ? mkErr(onMissing(ctx)) : mkOk(value);
|
|
506
530
|
});
|
|
507
531
|
}
|
|
508
532
|
/** Run independent `ResultAsync` branches concurrently and merge outputs. */
|
|
509
|
-
parallel(branches) {
|
|
533
|
+
parallel(branches, ..._guard) {
|
|
510
534
|
const merged = this.toAsync().flatMap(
|
|
511
535
|
(ctx) => ResultAsync.combineTupleParallel(
|
|
512
536
|
Object.entries(branches).map(([, branch]) => branch(ctx))
|