@onrails/result 0.3.1 → 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.
Files changed (73) hide show
  1. package/DESIGN.md +4 -3
  2. package/README.md +166 -13
  3. package/RECIPES.md +6 -4
  4. package/dist/async-fns.cjs +52 -0
  5. package/dist/async-fns.cjs.map +1 -0
  6. package/dist/async-fns.d.cts +143 -0
  7. package/dist/async-fns.d.ts +143 -0
  8. package/dist/async-fns.js +42 -0
  9. package/dist/async-fns.js.map +1 -0
  10. package/dist/async-lift.d.cts +109 -0
  11. package/dist/async-lift.d.ts +109 -0
  12. package/dist/{async-Ci6WSL5-.d.ts → async.d.cts} +42 -58
  13. package/dist/{async-DcXR7k9K.d.cts → async.d.ts} +42 -58
  14. package/dist/collections.d.cts +105 -0
  15. package/dist/collections.d.ts +105 -0
  16. package/dist/compat/neverthrow.cjs +57 -45
  17. package/dist/compat/neverthrow.cjs.map +1 -1
  18. package/dist/compat/neverthrow.d.cts +21 -24
  19. package/dist/compat/neverthrow.d.ts +21 -24
  20. package/dist/compat/neverthrow.js +56 -44
  21. package/dist/compat/neverthrow.js.map +1 -1
  22. package/dist/extra.cjs +3 -1
  23. package/dist/extra.cjs.map +1 -1
  24. package/dist/extra.d.cts +11 -14
  25. package/dist/extra.d.ts +11 -14
  26. package/dist/extra.js +3 -1
  27. package/dist/extra.js.map +1 -1
  28. package/dist/fluent.cjs +15 -28
  29. package/dist/fluent.cjs.map +1 -1
  30. package/dist/fluent.d.cts +4 -34
  31. package/dist/fluent.d.ts +4 -34
  32. package/dist/fluent.js +16 -28
  33. package/dist/fluent.js.map +1 -1
  34. package/dist/index.cjs +77 -52
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +28 -490
  37. package/dist/index.d.ts +28 -490
  38. package/dist/index.js +77 -53
  39. package/dist/index.js.map +1 -1
  40. package/dist/internal/ctor.d.cts +12 -0
  41. package/dist/internal/ctor.d.ts +12 -0
  42. package/dist/internal/dual.d.cts +42 -0
  43. package/dist/internal/dual.d.ts +42 -0
  44. package/dist/internal/infer.d.cts +40 -0
  45. package/dist/internal/infer.d.ts +40 -0
  46. package/dist/internal/lock.d.cts +16 -0
  47. package/dist/internal/lock.d.ts +16 -0
  48. package/dist/internal/to-error.d.cts +2 -0
  49. package/dist/internal/to-error.d.ts +2 -0
  50. package/dist/pipe.cjs +8 -0
  51. package/dist/pipe.cjs.map +1 -1
  52. package/dist/pipe.d.cts +58 -11
  53. package/dist/pipe.d.ts +58 -11
  54. package/dist/pipe.js +8 -1
  55. package/dist/pipe.js.map +1 -1
  56. package/dist/railway.cjs +87 -63
  57. package/dist/railway.cjs.map +1 -1
  58. package/dist/railway.d.cts +49 -19
  59. package/dist/railway.d.ts +49 -19
  60. package/dist/railway.js +87 -63
  61. package/dist/railway.js.map +1 -1
  62. package/dist/result.d.cts +315 -0
  63. package/dist/result.d.ts +315 -0
  64. package/dist/try-gen.cjs +3 -3
  65. package/dist/try-gen.cjs.map +1 -1
  66. package/dist/try-gen.d.cts +4 -7
  67. package/dist/try-gen.d.ts +4 -7
  68. package/dist/try-gen.js +3 -3
  69. package/dist/try-gen.js.map +1 -1
  70. package/dist/{types-BQ9vv0nD.d.cts → types.d.cts} +6 -8
  71. package/dist/{types-BQ9vv0nD.d.ts → types.d.ts} +6 -8
  72. package/package.json +69 -24
  73. package/skills/railway-do-notation/SKILL.md +9 -9
@@ -1,30 +1,53 @@
1
- import { R as ResultAsync } from './async-DcXR7k9K.cjs';
2
- import { R as Result } from './types-BQ9vv0nD.cjs';
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 & Record<K, T>, E, M>;
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 & Record<K, T>, E | F, M>;
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 & Record<K, T>, E | F, M>;
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 & Record<K, T>, E | F, "async">;
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 & Record<K, T>, E | F, "async">;
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 & Record<K, NonNullable<C[S]>>, E | F, M>;
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>, E | ParallelError<R>, "async">;
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 { R as ResultAsync } from './async-Ci6WSL5-.js';
2
- import { R as Result } from './types-BQ9vv0nD.js';
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 & Record<K, T>, E, M>;
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 & Record<K, T>, E | F, M>;
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 & Record<K, T>, E | F, M>;
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 & Record<K, T>, E | F, "async">;
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 & Record<K, T>, E | F, "async">;
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 & Record<K, NonNullable<C[S]>>, E | F, M>;
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>, E | ParallelError<R>, "async">;
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) ? ok(fn(result.value)) : err(result.error)
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) ? err(fn(result.error)) : ok(result.value)
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) : err(result.error)
35
+ (result, fn) => isOk(result) ? fn(result.value) : mkErr(result.error)
30
36
  );
31
- function trySync(fn, onThrow) {
37
+ function fromThrowable(fn, onThrow) {
32
38
  return (...args) => {
33
39
  try {
34
- return ok(fn(...args));
40
+ return mkOk(fn(...args));
35
41
  } catch (error) {
36
- return err(onThrow(error));
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 err(result.error);
62
+ return mkErr(result.error);
57
63
  }
58
64
  values.push(result.value);
59
65
  }
60
- return ok(values);
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 ok(await promise);
103
+ return mkOk(await promise);
98
104
  } catch (error) {
99
- return err(onReject(error));
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 () => ok(await promise));
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 () => ok(value));
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 () => err(error));
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 err(mapDefect(error));
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 err(result.error);
183
+ return mkErr(result.error);
186
184
  }
187
185
  values.push(result.value);
188
186
  }
189
- return ok(values);
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()] as const);
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
- * ] as const);
222
+ * ]);
225
223
  * ```
226
224
  */
227
- static combineTupleParallel(results) {
225
+ static combineTupleParallel(results, ..._guard) {
228
226
  return new _ResultAsync(
229
- async () => sequenceSettled(await Promise.all(results.map((ra) => ra.resolve())))
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: ok({}) });
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: ok(context) });
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().fromSync(key, fn, onThrow);
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().fromResult(key, fn);
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().fromPromise(key, fn, onReject);
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().fromAsync(key, fn);
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
- /** Pure sync derivation. */
474
- derive(key, fn) {
475
- return this.fromResult(key, (ctx) => ok(fn(ctx)));
476
- }
477
- /** Throwing sync transform. */
478
- fromSync(key, fn, onThrow) {
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
- /** Promise-returning step — upgrades the workflow to async mode. */
491
- fromPromise(key, fn, onReject) {
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.fromResult(key, (ctx) => {
526
+ require(key, source, onMissing, ..._guard) {
527
+ return this.addSync(key, (ctx) => {
504
528
  const value = ctx[source];
505
- return value == null ? err(onMissing(ctx)) : ok(value);
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))