@onrails/result 0.4.0 → 0.4.2

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 +1 -1
  2. package/README.md +168 -12
  3. package/RECIPES.md +11 -9
  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} +45 -58
  13. package/dist/{async-DcXR7k9K.d.cts → async.d.ts} +45 -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 -13
  29. package/dist/fluent.cjs.map +1 -1
  30. package/dist/fluent.d.cts +4 -7
  31. package/dist/fluent.d.ts +4 -7
  32. package/dist/fluent.js +15 -13
  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 +11 -0
  41. package/dist/internal/ctor.d.ts +11 -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/payload.d.cts +17 -0
  47. package/dist/internal/payload.d.ts +17 -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 +317 -0
  63. package/dist/result.d.ts +317 -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
@@ -0,0 +1,143 @@
1
+ /**
2
+ * `@onrails/result/async` — data-last twins of the {@link ResultAsync} methods,
3
+ * so an async railway composes with `pipe` / `flow` the same way a sync one does.
4
+ *
5
+ * `ResultAsync` is a class, so dot-chaining is always available and remains the
6
+ * shortest form for a one-off chain. Reach for this module when you want the
7
+ * sync side's other two syntaxes on the async carrier:
8
+ *
9
+ * ```ts
10
+ * import * as RA from "@onrails/result/async";
11
+ * import { pipe } from "@onrails/result";
12
+ * import { flow } from "@onrails/result/pipe";
13
+ *
14
+ * // value-first pipe
15
+ * pipe(loadUser(id), RA.flatMap(loadOrders), RA.map((os) => os.length));
16
+ *
17
+ * // reusable point-free pipeline — no value yet
18
+ * const orderCount = flow(loadUser, RA.flatMap(loadOrders), RA.map((os) => os.length));
19
+ * ```
20
+ *
21
+ * Every export mirrors one `ResultAsync` method exactly, in both forms:
22
+ * data-first (`RA.map(ra, fn)`) and curried (`RA.map(fn)`). Import as a
23
+ * namespace — the names deliberately match the sync core, and `test/parity.spec.ts`
24
+ * enforces that the mirror stays complete.
25
+ */
26
+ import type { ResultAsync } from "./async.js";
27
+ import type { Result } from "./types.js";
28
+ /** A step may hand back either carrier; the async one absorbs a sync `Result`. */
29
+ type Step<U, F> = ResultAsync<U, F> | Result<U, F>;
30
+ /**
31
+ * Transforms the `Ok` value, passing `Err` through unchanged.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * pipe(loadUser(id), map((u) => u.name)); // ResultAsync<string, LoadError>
36
+ * ```
37
+ */
38
+ export declare const map: {
39
+ <T, U, E>(ra: ResultAsync<T, E>, fn: (value: T) => U): ResultAsync<U, E>;
40
+ <T, U>(fn: (value: T) => U): <E>(ra: ResultAsync<T, E>) => ResultAsync<U, E>;
41
+ };
42
+ /**
43
+ * Transforms the `Err` value, passing `Ok` through unchanged — useful for
44
+ * unifying heterogeneous failures into one app-level union.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * pipe(loadUser(id), mapErr((e): AppError => ({ kind: "load", cause: e })));
49
+ * ```
50
+ */
51
+ export declare const mapErr: {
52
+ <T, E, F>(ra: ResultAsync<T, E>, fn: (error: E) => F): ResultAsync<T, F>;
53
+ <E, F>(fn: (error: E) => F): <T>(ra: ResultAsync<T, E>) => ResultAsync<T, F>;
54
+ };
55
+ /**
56
+ * Transforms both tracks in one step. Exactly one of `onOk` / `onErr` runs.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * pipe(loadUser(id), bimap(toDto, (e): AppError => ({ kind: "load", cause: e })));
61
+ * ```
62
+ */
63
+ export declare const bimap: {
64
+ <T, U, E, F>(ra: ResultAsync<T, E>, onOk: (value: T) => U, onErr: (error: E) => F): ResultAsync<U, F>;
65
+ <T, U, E, F>(onOk: (value: T) => U, onErr: (error: E) => F): (ra: ResultAsync<T, E>) => ResultAsync<U, F>;
66
+ };
67
+ /**
68
+ * Canonical bind — chains a step returning a `ResultAsync` or a sync `Result`,
69
+ * short-circuiting on `Err`. Error types accumulate (`E | F`).
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * pipe(authenticate(req), flatMap((user) => loadProfile(user.id)));
74
+ * ```
75
+ */
76
+ export declare const flatMap: {
77
+ <T, U, E, F>(ra: ResultAsync<T, E>, fn: (value: T) => Step<U, F>): ResultAsync<U, E | F>;
78
+ <T, U, F>(fn: (value: T) => Step<U, F>): <E>(ra: ResultAsync<T, E>) => ResultAsync<U, E | F>;
79
+ };
80
+ /**
81
+ * Error-track bind — runs `fn` only on `Err`, letting a failed workflow
82
+ * recover to `Ok` or remap the failure.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * pipe(loadUser(id), recover((e) => (e === "notFound" ? okAsync(guest) : errAsync(e))));
87
+ * ```
88
+ */
89
+ export declare const recover: {
90
+ <T, E, F>(ra: ResultAsync<T, E>, fn: (error: E) => Step<T, F>): ResultAsync<T, F>;
91
+ <T, E, F>(fn: (error: E) => Step<T, F>): (ra: ResultAsync<T, E>) => ResultAsync<T, F>;
92
+ };
93
+ /**
94
+ * Observes the `Ok` value for side effects without changing it.
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * pipe(loadUser(id), tap((u) => log.info({ msg: "loaded", id: u.id })));
99
+ * ```
100
+ */
101
+ export declare const tap: {
102
+ <T, E>(ra: ResultAsync<T, E>, fn: (value: T) => void): ResultAsync<T, E>;
103
+ <T>(fn: (value: T) => void): <E>(ra: ResultAsync<T, E>) => ResultAsync<T, E>;
104
+ };
105
+ /**
106
+ * Observes the `Err` value for side effects without changing it.
107
+ *
108
+ * @example
109
+ * ```ts
110
+ * pipe(loadUser(id), tapErr((e) => metrics.inc("user.load.fail", { kind: e.kind })));
111
+ * ```
112
+ */
113
+ export declare const tapErr: {
114
+ <T, E>(ra: ResultAsync<T, E>, fn: (error: E) => void): ResultAsync<T, E>;
115
+ <E>(fn: (error: E) => void): <T>(ra: ResultAsync<T, E>) => ResultAsync<T, E>;
116
+ };
117
+ /**
118
+ * Terminal collapse — folds both tracks into a single awaited value, settling
119
+ * the carrier exactly once. Ends the pipeline, so it returns a plain `Promise`.
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * const status = await pipe(save(row), match(() => 200, (e) => (e.kind === "conflict" ? 409 : 500)));
124
+ * ```
125
+ */
126
+ export declare const match: {
127
+ <T, E, U1, U2 = U1>(ra: ResultAsync<T, E>, onOk: (value: T) => U1, onErr: (error: E) => U2): Promise<U1 | U2>;
128
+ <T, E, U1, U2 = U1>(onOk: (value: T) => U1, onErr: (error: E) => U2): (ra: ResultAsync<T, E>) => Promise<U1 | U2>;
129
+ };
130
+ /**
131
+ * Terminal — resolves to the `Ok` value, or `defaultValue` on `Err`. The
132
+ * fallback may differ from `T`; the result widens to `T | U`.
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * const profile = await pipe(loadProfile(id), unwrapOr(guestProfile));
137
+ * ```
138
+ */
139
+ export declare const unwrapOr: {
140
+ <T, E, U>(ra: ResultAsync<T, E>, defaultValue: U): Promise<T | U>;
141
+ <U>(defaultValue: U): <T, E>(ra: ResultAsync<T, E>) => Promise<T | U>;
142
+ };
143
+ export {};
@@ -0,0 +1,42 @@
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
+
8
+ // src/async-fns.ts
9
+ var map = dual(2, (ra, fn) => ra.map(fn));
10
+ var mapErr = dual(
11
+ 2,
12
+ (ra, fn) => ra.mapErr(fn)
13
+ );
14
+ var bimap = dual(
15
+ 3,
16
+ (ra, onOk, onErr) => ra.bimap(onOk, onErr)
17
+ );
18
+ var flatMap = dual(
19
+ 2,
20
+ (ra, fn) => ra.flatMap(fn)
21
+ );
22
+ var recover = dual(
23
+ 2,
24
+ (ra, fn) => ra.recover(fn)
25
+ );
26
+ var tap = dual(2, (ra, fn) => ra.tap(fn));
27
+ var tapErr = dual(
28
+ 2,
29
+ (ra, fn) => ra.tapErr(fn)
30
+ );
31
+ var match = dual(
32
+ 3,
33
+ (ra, onOk, onErr) => ra.match(onOk, onErr)
34
+ );
35
+ var unwrapOr = dual(
36
+ 2,
37
+ (ra, defaultValue) => ra.unwrapOr(defaultValue)
38
+ );
39
+
40
+ export { bimap, flatMap, map, mapErr, match, recover, tap, tapErr, unwrapOr };
41
+ //# sourceMappingURL=async-fns.js.map
42
+ //# sourceMappingURL=async-fns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/dual.ts","../src/async-fns.ts"],"names":[],"mappings":";AA0CO,IAAM,IAAA,GAAO,CAAI,KAAA,EAAe,IAAA,KAA2C;AAMhF,EAAA,MAAM,GAAA,GAAM,IAAA;AACZ,EAAA,MAAM,UAAA,GAAa,CAAA,GAAI,IAAA,KACrB,IAAA,CAAK,UAAU,KAAA,GAAQ,GAAA,CAAI,GAAG,IAAI,IAAI,CAAC,IAAA,KAAkB,GAAA,CAAI,IAAA,EAAM,GAAG,IAAI,CAAA;AAG5E,EAAA,OAAO,UAAA;AACT,CAAA;;;ACbO,IAAM,GAAA,GAGT,KAAK,CAAA,EAAG,CAAU,IAAuB,EAAA,KAA2C,EAAA,CAAG,GAAA,CAAI,EAAE,CAAC;AAW3F,IAAM,MAAA,GAGT,IAAA;AAAA,EACF,CAAA;AAAA,EACA,CAAU,EAAA,EAAuB,EAAA,KAA2C,EAAA,CAAG,OAAO,EAAE;AAC1F;AAUO,IAAM,KAAA,GAUT,IAAA;AAAA,EACF,CAAA;AAAA,EACA,CACE,EAAA,EACA,IAAA,EACA,UACsB,EAAA,CAAG,KAAA,CAAM,MAAM,KAAK;AAC9C;AAWO,IAAM,OAAA,GAGT,IAAA;AAAA,EACF,CAAA;AAAA,EACA,CAAa,EAAA,EAAuB,EAAA,KAClC,EAAA,CAAG,QAAQ,EAAE;AACjB;AAWO,IAAM,OAAA,GAGT,IAAA;AAAA,EACF,CAAA;AAAA,EACA,CAAU,EAAA,EAAuB,EAAA,KAC/B,EAAA,CAAG,QAAQ,EAAE;AACjB;AAUO,IAAM,GAAA,GAGT,KAAK,CAAA,EAAG,CAAO,IAAuB,EAAA,KAA8C,EAAA,CAAG,GAAA,CAAI,EAAE,CAAC;AAU3F,IAAM,MAAA,GAGT,IAAA;AAAA,EACF,CAAA;AAAA,EACA,CAAO,EAAA,EAAuB,EAAA,KAA8C,EAAA,CAAG,OAAO,EAAE;AAC1F;AAWO,IAAM,KAAA,GAUT,IAAA;AAAA,EACF,CAAA;AAAA,EACA,CACE,EAAA,EACA,IAAA,EACA,UACqB,EAAA,CAAG,KAAA,CAAM,MAAM,KAAK;AAC7C;AAWO,IAAM,QAAA,GAGT,IAAA;AAAA,EACF,CAAA;AAAA,EACA,CAAU,EAAA,EAAuB,YAAA,KAAoC,EAAA,CAAG,SAAS,YAAY;AAC/F","file":"async-fns.js","sourcesContent":["/**\n * @onrails/result internal — owning module for the **dual-form idiom**.\n *\n * Every dual-form transform in this package (`map`, `flatMap`, `match`, …)\n * accepts either shape:\n *\n * data-first: `map(result, fn)` — full arity, runs immediately\n * curried: `map(fn)(result)` — arity minus the leading `self`, returns\n * a unary function awaiting the data\n *\n * {@link dual} is the single arity dispatcher those transforms derive from;\n * hand-rolled `args.length` checks are not allowed elsewhere.\n *\n * NOTE: a twin copy of this module lives at `packages/maybe/src/internal/dual.ts`\n * — keep the two in sync when editing.\n *\n * Internal only: not listed in package.json `exports` or tsup entries.\n */\n\n/**\n * Derive a dual-form (data-first / curried) function from a single\n * data-first `body`.\n *\n * When called with `args.length >= arity` the wrapper runs `body(...args)`\n * (data-first). With fewer arguments it returns `(self) => body(self, ...args)`\n * — the curried, data-last-`self` form used with `pipe`/`flow`.\n *\n * The public type is supplied entirely by the caller: annotate the exported\n * const with the exact overload set and let `F` be inferred from it.\n *\n * @example\n * ```ts\n * export const map: {\n * <T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E>;\n * <T, U>(fn: (value: T) => U): <E>(result: Result<T, E>) => Result<U, E>;\n * } = dual(\n * 2,\n * <T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E> =>\n * isOk(result) ? ok(fn(result.value)) : err(result.error),\n * );\n * ```\n */\nexport const dual = <F>(arity: number, body: (...args: never[]) => unknown): F => {\n // `(...args: never[]) => unknown` is the top function type, so every typed\n // body is assignable to the parameter — but it cannot be invoked with\n // `unknown` arguments. Re-view it through the variadic shape the dispatcher\n // actually forwards; the constraint is that `body` only ever receives the\n // arguments the caller's overload annotation admitted.\n const run = body as (...args: readonly unknown[]) => unknown;\n const dispatcher = (...args: readonly unknown[]): unknown =>\n args.length >= arity ? run(...args) : (self: unknown) => run(self, ...args);\n // The caller's overload annotation on the exported const supplies the real\n // public type; internally the dispatcher stays `unknown`-typed.\n return dispatcher as F;\n};\n","/**\n * `@onrails/result/async` — data-last twins of the {@link ResultAsync} methods,\n * so an async railway composes with `pipe` / `flow` the same way a sync one does.\n *\n * `ResultAsync` is a class, so dot-chaining is always available and remains the\n * shortest form for a one-off chain. Reach for this module when you want the\n * sync side's other two syntaxes on the async carrier:\n *\n * ```ts\n * import * as RA from \"@onrails/result/async\";\n * import { pipe } from \"@onrails/result\";\n * import { flow } from \"@onrails/result/pipe\";\n *\n * // value-first pipe\n * pipe(loadUser(id), RA.flatMap(loadOrders), RA.map((os) => os.length));\n *\n * // reusable point-free pipeline — no value yet\n * const orderCount = flow(loadUser, RA.flatMap(loadOrders), RA.map((os) => os.length));\n * ```\n *\n * Every export mirrors one `ResultAsync` method exactly, in both forms:\n * data-first (`RA.map(ra, fn)`) and curried (`RA.map(fn)`). Import as a\n * namespace — the names deliberately match the sync core, and `test/parity.spec.ts`\n * enforces that the mirror stays complete.\n */\n\nimport type { ResultAsync } from \"./async.js\";\nimport { dual } from \"./internal/dual.js\";\nimport type { Result } from \"./types.js\";\n\n/** A step may hand back either carrier; the async one absorbs a sync `Result`. */\ntype Step<U, F> = ResultAsync<U, F> | Result<U, F>;\n\n/**\n * Transforms the `Ok` value, passing `Err` through unchanged.\n *\n * @example\n * ```ts\n * pipe(loadUser(id), map((u) => u.name)); // ResultAsync<string, LoadError>\n * ```\n */\nexport const map: {\n <T, U, E>(ra: ResultAsync<T, E>, fn: (value: T) => U): ResultAsync<U, E>;\n <T, U>(fn: (value: T) => U): <E>(ra: ResultAsync<T, E>) => ResultAsync<U, E>;\n} = dual(2, <T, U, E>(ra: ResultAsync<T, E>, fn: (value: T) => U): ResultAsync<U, E> => ra.map(fn));\n\n/**\n * Transforms the `Err` value, passing `Ok` through unchanged — useful for\n * unifying heterogeneous failures into one app-level union.\n *\n * @example\n * ```ts\n * pipe(loadUser(id), mapErr((e): AppError => ({ kind: \"load\", cause: e })));\n * ```\n */\nexport const mapErr: {\n <T, E, F>(ra: ResultAsync<T, E>, fn: (error: E) => F): ResultAsync<T, F>;\n <E, F>(fn: (error: E) => F): <T>(ra: ResultAsync<T, E>) => ResultAsync<T, F>;\n} = dual(\n 2,\n <T, E, F>(ra: ResultAsync<T, E>, fn: (error: E) => F): ResultAsync<T, F> => ra.mapErr(fn),\n);\n\n/**\n * Transforms both tracks in one step. Exactly one of `onOk` / `onErr` runs.\n *\n * @example\n * ```ts\n * pipe(loadUser(id), bimap(toDto, (e): AppError => ({ kind: \"load\", cause: e })));\n * ```\n */\nexport const bimap: {\n <T, U, E, F>(\n ra: ResultAsync<T, E>,\n onOk: (value: T) => U,\n onErr: (error: E) => F,\n ): ResultAsync<U, F>;\n <T, U, E, F>(\n onOk: (value: T) => U,\n onErr: (error: E) => F,\n ): (ra: ResultAsync<T, E>) => ResultAsync<U, F>;\n} = dual(\n 3,\n <T, U, E, F>(\n ra: ResultAsync<T, E>,\n onOk: (value: T) => U,\n onErr: (error: E) => F,\n ): ResultAsync<U, F> => ra.bimap(onOk, onErr),\n);\n\n/**\n * Canonical bind — chains a step returning a `ResultAsync` or a sync `Result`,\n * short-circuiting on `Err`. Error types accumulate (`E | F`).\n *\n * @example\n * ```ts\n * pipe(authenticate(req), flatMap((user) => loadProfile(user.id)));\n * ```\n */\nexport const flatMap: {\n <T, U, E, F>(ra: ResultAsync<T, E>, fn: (value: T) => Step<U, F>): ResultAsync<U, E | F>;\n <T, U, F>(fn: (value: T) => Step<U, F>): <E>(ra: ResultAsync<T, E>) => ResultAsync<U, E | F>;\n} = dual(\n 2,\n <T, U, E, F>(ra: ResultAsync<T, E>, fn: (value: T) => Step<U, F>): ResultAsync<U, E | F> =>\n ra.flatMap(fn),\n);\n\n/**\n * Error-track bind — runs `fn` only on `Err`, letting a failed workflow\n * recover to `Ok` or remap the failure.\n *\n * @example\n * ```ts\n * pipe(loadUser(id), recover((e) => (e === \"notFound\" ? okAsync(guest) : errAsync(e))));\n * ```\n */\nexport const recover: {\n <T, E, F>(ra: ResultAsync<T, E>, fn: (error: E) => Step<T, F>): ResultAsync<T, F>;\n <T, E, F>(fn: (error: E) => Step<T, F>): (ra: ResultAsync<T, E>) => ResultAsync<T, F>;\n} = dual(\n 2,\n <T, E, F>(ra: ResultAsync<T, E>, fn: (error: E) => Step<T, F>): ResultAsync<T, F> =>\n ra.recover(fn),\n);\n\n/**\n * Observes the `Ok` value for side effects without changing it.\n *\n * @example\n * ```ts\n * pipe(loadUser(id), tap((u) => log.info({ msg: \"loaded\", id: u.id })));\n * ```\n */\nexport const tap: {\n <T, E>(ra: ResultAsync<T, E>, fn: (value: T) => void): ResultAsync<T, E>;\n <T>(fn: (value: T) => void): <E>(ra: ResultAsync<T, E>) => ResultAsync<T, E>;\n} = dual(2, <T, E>(ra: ResultAsync<T, E>, fn: (value: T) => void): ResultAsync<T, E> => ra.tap(fn));\n\n/**\n * Observes the `Err` value for side effects without changing it.\n *\n * @example\n * ```ts\n * pipe(loadUser(id), tapErr((e) => metrics.inc(\"user.load.fail\", { kind: e.kind })));\n * ```\n */\nexport const tapErr: {\n <T, E>(ra: ResultAsync<T, E>, fn: (error: E) => void): ResultAsync<T, E>;\n <E>(fn: (error: E) => void): <T>(ra: ResultAsync<T, E>) => ResultAsync<T, E>;\n} = dual(\n 2,\n <T, E>(ra: ResultAsync<T, E>, fn: (error: E) => void): ResultAsync<T, E> => ra.tapErr(fn),\n);\n\n/**\n * Terminal collapse — folds both tracks into a single awaited value, settling\n * the carrier exactly once. Ends the pipeline, so it returns a plain `Promise`.\n *\n * @example\n * ```ts\n * const status = await pipe(save(row), match(() => 200, (e) => (e.kind === \"conflict\" ? 409 : 500)));\n * ```\n */\nexport const match: {\n <T, E, U1, U2 = U1>(\n ra: ResultAsync<T, E>,\n onOk: (value: T) => U1,\n onErr: (error: E) => U2,\n ): Promise<U1 | U2>;\n <T, E, U1, U2 = U1>(\n onOk: (value: T) => U1,\n onErr: (error: E) => U2,\n ): (ra: ResultAsync<T, E>) => Promise<U1 | U2>;\n} = dual(\n 3,\n <T, E, U1, U2>(\n ra: ResultAsync<T, E>,\n onOk: (value: T) => U1,\n onErr: (error: E) => U2,\n ): Promise<U1 | U2> => ra.match(onOk, onErr),\n);\n\n/**\n * Terminal — resolves to the `Ok` value, or `defaultValue` on `Err`. The\n * fallback may differ from `T`; the result widens to `T | U`.\n *\n * @example\n * ```ts\n * const profile = await pipe(loadProfile(id), unwrapOr(guestProfile));\n * ```\n */\nexport const unwrapOr: {\n <T, E, U>(ra: ResultAsync<T, E>, defaultValue: U): Promise<T | U>;\n <U>(defaultValue: U): <T, E>(ra: ResultAsync<T, E>) => Promise<T | U>;\n} = dual(\n 2,\n <T, E, U>(ra: ResultAsync<T, E>, defaultValue: U): Promise<T | U> => ra.unwrapOr(defaultValue),\n);\n"]}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @onrails/result — THE lift module. Every "value → {@link ResultAsync}"
3
+ * entry point is defined exactly once, here: `okAsync`, `errAsync`,
4
+ * `fromPromise`, `fromSafePromise`, `fromResult`, `fromAsync`, `asyncAfter`,
5
+ * `tryAsync`.
6
+ */
7
+ import { ResultAsync } from "./async.js";
8
+ import type { InferErr, InferOk } from "./internal/infer.js";
9
+ import type { Result, UnexpectedError } from "./types.js";
10
+ /**
11
+ * Lifts a value into an `Ok` async result.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const r = okAsync(42); // ResultAsync<42, never> — literal locked
16
+ * ```
17
+ */
18
+ export declare const okAsync: typeof ResultAsync.ok;
19
+ /**
20
+ * Lifts a value into an `Err` async result.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const r = errAsync({ kind: "not_found" as const });
25
+ * ```
26
+ */
27
+ export declare const errAsync: typeof ResultAsync.err;
28
+ /**
29
+ * Wraps a `PromiseLike<T>` into a {@link ResultAsync}. Reject reasons go
30
+ * through `onReject` to become typed `Err`s; success becomes `Ok<T>`.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * const body = fromPromise(
35
+ * fetch(url).then((r) => r.text()),
36
+ * (e): NetworkError => ({ kind: "network", cause: String(e) }),
37
+ * );
38
+ * ```
39
+ */
40
+ export declare const fromPromise: typeof ResultAsync.fromPromise;
41
+ /**
42
+ * Wraps a `PromiseLike<T>` that **never rejects** into {@link ResultAsync}.
43
+ * Skips the `onReject` mapper. Use only when the promise is provably safe.
44
+ */
45
+ export declare const fromSafePromise: typeof ResultAsync.fromSafePromise;
46
+ /**
47
+ * Lifts an already-settled sync {@link Result} into a {@link ResultAsync}, so
48
+ * it can be chained alongside async steps in a railway.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * const ra = fromResult(ok(1)); // ResultAsync<number, never>
53
+ * await fromResult(err(error)).resolve(); // Err(error)
54
+ * ```
55
+ */
56
+ export declare const fromResult: typeof ResultAsync.fromResult;
57
+ /**
58
+ * Bind a sync {@link Result} into an async step without widening defects.
59
+ * Dual-form: data-first for one-shots, data-last (curried) for `pipe`/`flow`.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * asyncAfter(result, (u) => tryAsync(db.insert(u), toErr)); // data-first
64
+ * asyncAfter((u) => tryAsync(db.insert(u), toErr)); // data-last
65
+ * ```
66
+ */
67
+ export declare const asyncAfter: {
68
+ <T, U, E, F>(result: Result<T, E>, fn: (value: T) => ResultAsync<U, F>): ResultAsync<U, E | F>;
69
+ <T, U, F>(fn: (value: T) => ResultAsync<U, F>): <E>(result: Result<T, E>) => ResultAsync<U, E | F>;
70
+ };
71
+ type AnyResult = Result<unknown, unknown>;
72
+ /**
73
+ * Lifts a `(...args) => Promise<Result<T, E>>` function (typical of interop or
74
+ * boundary code) into one returning `(...args) => ResultAsync<T, E>`. Expected
75
+ * `Err`s pass through; an unexpected promise rejection (a defect) is routed via
76
+ * `onDefect`, defaulting to {@link UnexpectedError} and widening the error union.
77
+ *
78
+ * @param fn - a function returning a promise that already yields a `Result`
79
+ * @param onDefect - maps an unexpected rejection to the `Err` channel
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * const loadUser = fromAsync(
84
+ * (id: string): Promise<Result<User, NotFound>> => api.getUser(id),
85
+ * );
86
+ * const ra = loadUser("u1"); // ResultAsync<User, NotFound | UnexpectedError>
87
+ * ```
88
+ */
89
+ export declare const fromAsync: <A extends readonly unknown[], R extends AnyResult>(fn: (...args: A) => Promise<R>, onDefect?: (error: unknown) => InferErr<R> | UnexpectedError) => ((...args: A) => ResultAsync<InferOk<R>, InferErr<R> | UnexpectedError>);
90
+ /**
91
+ * Convenience wrapper over {@link fromPromise} with default `Error`
92
+ * normalization. Call without `onReject` to get `ResultAsync<T, Error>`,
93
+ * or pass a custom mapper for a typed error.
94
+ *
95
+ * @example
96
+ * ```ts
97
+ * // Default: rejection → Err(Error)
98
+ * const a = tryAsync(db.users.insert(row));
99
+ *
100
+ * // Custom: typed error
101
+ * const b = tryAsync(db.users.insert(row), (e): DbError => ({
102
+ * kind: "db",
103
+ * cause: e,
104
+ * }));
105
+ * ```
106
+ */
107
+ export declare function tryAsync<T>(promise: PromiseLike<T>): ResultAsync<T, Error>;
108
+ export declare function tryAsync<T, E>(promise: PromiseLike<T>, onReject: (error: unknown) => E): ResultAsync<T, E>;
109
+ export {};
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @onrails/result — THE lift module. Every "value → {@link ResultAsync}"
3
+ * entry point is defined exactly once, here: `okAsync`, `errAsync`,
4
+ * `fromPromise`, `fromSafePromise`, `fromResult`, `fromAsync`, `asyncAfter`,
5
+ * `tryAsync`.
6
+ */
7
+ import { ResultAsync } from "./async.js";
8
+ import type { InferErr, InferOk } from "./internal/infer.js";
9
+ import type { Result, UnexpectedError } from "./types.js";
10
+ /**
11
+ * Lifts a value into an `Ok` async result.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const r = okAsync(42); // ResultAsync<42, never> — literal locked
16
+ * ```
17
+ */
18
+ export declare const okAsync: typeof ResultAsync.ok;
19
+ /**
20
+ * Lifts a value into an `Err` async result.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const r = errAsync({ kind: "not_found" as const });
25
+ * ```
26
+ */
27
+ export declare const errAsync: typeof ResultAsync.err;
28
+ /**
29
+ * Wraps a `PromiseLike<T>` into a {@link ResultAsync}. Reject reasons go
30
+ * through `onReject` to become typed `Err`s; success becomes `Ok<T>`.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * const body = fromPromise(
35
+ * fetch(url).then((r) => r.text()),
36
+ * (e): NetworkError => ({ kind: "network", cause: String(e) }),
37
+ * );
38
+ * ```
39
+ */
40
+ export declare const fromPromise: typeof ResultAsync.fromPromise;
41
+ /**
42
+ * Wraps a `PromiseLike<T>` that **never rejects** into {@link ResultAsync}.
43
+ * Skips the `onReject` mapper. Use only when the promise is provably safe.
44
+ */
45
+ export declare const fromSafePromise: typeof ResultAsync.fromSafePromise;
46
+ /**
47
+ * Lifts an already-settled sync {@link Result} into a {@link ResultAsync}, so
48
+ * it can be chained alongside async steps in a railway.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * const ra = fromResult(ok(1)); // ResultAsync<number, never>
53
+ * await fromResult(err(error)).resolve(); // Err(error)
54
+ * ```
55
+ */
56
+ export declare const fromResult: typeof ResultAsync.fromResult;
57
+ /**
58
+ * Bind a sync {@link Result} into an async step without widening defects.
59
+ * Dual-form: data-first for one-shots, data-last (curried) for `pipe`/`flow`.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * asyncAfter(result, (u) => tryAsync(db.insert(u), toErr)); // data-first
64
+ * asyncAfter((u) => tryAsync(db.insert(u), toErr)); // data-last
65
+ * ```
66
+ */
67
+ export declare const asyncAfter: {
68
+ <T, U, E, F>(result: Result<T, E>, fn: (value: T) => ResultAsync<U, F>): ResultAsync<U, E | F>;
69
+ <T, U, F>(fn: (value: T) => ResultAsync<U, F>): <E>(result: Result<T, E>) => ResultAsync<U, E | F>;
70
+ };
71
+ type AnyResult = Result<unknown, unknown>;
72
+ /**
73
+ * Lifts a `(...args) => Promise<Result<T, E>>` function (typical of interop or
74
+ * boundary code) into one returning `(...args) => ResultAsync<T, E>`. Expected
75
+ * `Err`s pass through; an unexpected promise rejection (a defect) is routed via
76
+ * `onDefect`, defaulting to {@link UnexpectedError} and widening the error union.
77
+ *
78
+ * @param fn - a function returning a promise that already yields a `Result`
79
+ * @param onDefect - maps an unexpected rejection to the `Err` channel
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * const loadUser = fromAsync(
84
+ * (id: string): Promise<Result<User, NotFound>> => api.getUser(id),
85
+ * );
86
+ * const ra = loadUser("u1"); // ResultAsync<User, NotFound | UnexpectedError>
87
+ * ```
88
+ */
89
+ export declare const fromAsync: <A extends readonly unknown[], R extends AnyResult>(fn: (...args: A) => Promise<R>, onDefect?: (error: unknown) => InferErr<R> | UnexpectedError) => ((...args: A) => ResultAsync<InferOk<R>, InferErr<R> | UnexpectedError>);
90
+ /**
91
+ * Convenience wrapper over {@link fromPromise} with default `Error`
92
+ * normalization. Call without `onReject` to get `ResultAsync<T, Error>`,
93
+ * or pass a custom mapper for a typed error.
94
+ *
95
+ * @example
96
+ * ```ts
97
+ * // Default: rejection → Err(Error)
98
+ * const a = tryAsync(db.users.insert(row));
99
+ *
100
+ * // Custom: typed error
101
+ * const b = tryAsync(db.users.insert(row), (e): DbError => ({
102
+ * kind: "db",
103
+ * cause: e,
104
+ * }));
105
+ * ```
106
+ */
107
+ export declare function tryAsync<T>(promise: PromiseLike<T>): ResultAsync<T, Error>;
108
+ export declare function tryAsync<T, E>(promise: PromiseLike<T>, onReject: (error: unknown) => E): ResultAsync<T, E>;
109
+ export {};
@@ -1,56 +1,25 @@
1
- import { R as Result, U as UnexpectedError } from './types-BQ9vv0nD.js';
2
-
3
- /**
4
- * @onrails/result internal — the single payload-extraction pair.
5
- *
6
- * `InferOk` / `InferErr` pull the success/error type out of a {@link Result}
7
- * (tagged union) or a {@link ResultAsync} (class carrier), resolving to
8
- * `never` for anything else. Every module that needs to extract a payload
9
- * type imports from here; do not redeclare local copies.
10
- *
11
- * Internal only: not listed in package.json `exports` or tsup entries.
12
- * The package index re-exports the pair under the same names.
13
- */
14
-
15
- /**
16
- * Extracts the `Ok` value type from a {@link Result} or {@link ResultAsync},
17
- * resolving to `never` for any other type.
18
- *
19
- * @example
20
- * ```ts
21
- * type T = InferOk<Result<number, string>>; // number
22
- * type U = InferOk<ResultAsync<User, AppError>>; // User
23
- * ```
24
- */
25
- type InferOk<R> = R extends {
26
- _tag: "Ok";
27
- readonly value: infer T;
28
- } ? T : R extends ResultAsync<infer T, unknown> ? T : never;
1
+ import type { InferErr, InferOk } from "./internal/infer.js";
2
+ import type { Payload } from "./internal/payload.js";
3
+ import type { Result } from "./types.js";
4
+ import { UnexpectedError } from "./types.js";
5
+ type PromiseFactory<T, E> = () => Promise<Result<T, E>>;
6
+ type AnyResultAsync = ResultAsync<unknown, unknown>;
29
7
  /**
30
- * Extracts the `Err` error type from a {@link Result} or {@link ResultAsync},
31
- * resolving to `never` for any other type. Mirror of {@link InferOk}.
32
- *
33
- * @example
34
- * ```ts
35
- * type E = InferErr<Result<number, string>>; // string
36
- * type F = InferErr<ResultAsync<User, AppError>>; // AppError
37
- * ```
8
+ * Mirrors the sync `CombineTuple` in `./collections.ts`: the element check
9
+ * lives in the RETURN type, never in the parameter constraint. A
10
+ * `ResultAsync<unknown, unknown>` constraint contextually types the arguments
11
+ * and widens `okAsync(1)`'s defaulted `E = never` to `unknown`, so call sites
12
+ * would need hoisted intermediates to keep their error union.
38
13
  */
39
- type InferErr<R> = R extends {
40
- _tag: "Err";
41
- readonly error: infer E;
42
- } ? E : R extends ResultAsync<unknown, infer E> ? E : never;
43
-
44
- type PromiseFactory<T, E> = () => Promise<Result<T, E>>;
45
- type CombineTupleAsync<R extends readonly ResultAsync<unknown, unknown>[]> = ResultAsync<{
14
+ type CombineTupleAsync<R extends readonly unknown[]> = R[number] extends AnyResultAsync ? ResultAsync<{
46
15
  [K in keyof R]: InferOk<R[K]>;
47
- }, {
48
- [K in keyof R]: InferErr<R[K]>;
49
- }[number]>;
16
+ }, InferErr<R[number]>> : never;
17
+ /** Mirrors `TupleGuard` in `./collections.ts` — see the note there. */
18
+ type AsyncTupleGuard<R extends readonly unknown[]> = R[number] extends AnyResultAsync ? [] : [error: "expected an array of ResultAsync values"];
50
19
  /**
51
20
  * Async railway carrier. Wraps a deferred `Promise<Result<T, E>>` and exposes
52
- * the same dual-track transforms as the sync {@link Result} — `map`, `flatMap`,
53
- * `recover`, `tap`, `match`. The public API never surfaces `Promise<Result<…>>`
21
+ * the same dual-track transforms as the sync {@link Result} — `map`, `mapErr`,
22
+ * `bimap`, `flatMap`, `recover`, `tap`, `match`. The public API never surfaces `Promise<Result<…>>`
54
23
  * directly: `await` the instance (it is thenable) or call {@link resolve} /
55
24
  * {@link match} to settle it.
56
25
  *
@@ -69,7 +38,7 @@ type CombineTupleAsync<R extends readonly ResultAsync<unknown, unknown>[]> = Res
69
38
  * const r = await user; // Result<Profile, AppError>
70
39
  * ```
71
40
  */
72
- declare class ResultAsync<T, E> {
41
+ export declare class ResultAsync<T, E> {
73
42
  protected readonly run: PromiseFactory<T, E>;
74
43
  private promise;
75
44
  protected constructor(run: PromiseFactory<T, E>);
@@ -129,13 +98,15 @@ declare class ResultAsync<T, E> {
129
98
  * const r = ResultAsync.ok(42); // ResultAsync<number, never>
130
99
  * ```
131
100
  */
132
- static ok<T>(value: T): ResultAsync<T, never>;
101
+ static ok<const T extends Payload[]>(value: T): ResultAsync<T, never>;
102
+ static ok<const T>(value: T): ResultAsync<T, never>;
133
103
  static ok<T, E>(value: T): ResultAsync<T, E>;
134
104
  /**
135
105
  * Fantasy Land `pure` — alias of {@link ResultAsync.ok}. One lift name
136
106
  * shared across the trio (`of` / `Maybe.of` / `ResultAsync.of`).
137
107
  */
138
- static of<T>(value: T): ResultAsync<T, never>;
108
+ static of<const T extends Payload[]>(value: T): ResultAsync<T, never>;
109
+ static of<const T>(value: T): ResultAsync<T, never>;
139
110
  static of<T, E>(value: T): ResultAsync<T, E>;
140
111
  /**
141
112
  * Lifts an error into an `Err` async result.
@@ -145,7 +116,9 @@ declare class ResultAsync<T, E> {
145
116
  * const r = ResultAsync.err({ kind: "not_found" as const });
146
117
  * ```
147
118
  */
148
- static err<T = never, E = unknown>(error: E): ResultAsync<T, E>;
119
+ static err<const E extends Payload[]>(error: E): ResultAsync<never, E>;
120
+ static err<const E>(error: E): ResultAsync<never, E>;
121
+ static err<T, E>(error: E): ResultAsync<T, E>;
149
122
  /**
150
123
  * Lifts an existing `Promise<Result<T, E>>` (e.g. from interop code) into a
151
124
  * {@link ResultAsync}. A thrown defect is routed through `onDefect`,
@@ -179,7 +152,7 @@ declare class ResultAsync<T, E> {
179
152
  *
180
153
  * @example
181
154
  * ```ts
182
- * const combined = ResultAsync.combineTuple([loadCfg(), loadCatalog()] as const);
155
+ * const combined = ResultAsync.combineTuple([loadCfg(), loadCatalog()]);
183
156
  * // ResultAsync<readonly [Cfg, Catalog], CfgError | CatalogError>
184
157
  * const r = await combined;
185
158
  * if (isOk(r)) {
@@ -187,7 +160,7 @@ declare class ResultAsync<T, E> {
187
160
  * }
188
161
  * ```
189
162
  */
190
- static combineTuple<const R extends readonly ResultAsync<unknown, unknown>[]>(results: R): CombineTupleAsync<R>;
163
+ static combineTuple<const R extends readonly unknown[]>(results: R, ..._guard: AsyncTupleGuard<R>): CombineTupleAsync<R>;
191
164
  /**
192
165
  * Like {@link combineTuple}, but starts every branch before awaiting (wall-clock
193
166
  * parallel for independent IO). On failure, returns the first `Err` in input order.
@@ -198,10 +171,10 @@ declare class ResultAsync<T, E> {
198
171
  * const combined = ResultAsync.combineTupleParallel([
199
172
  * loadProfile(id),
200
173
  * loadMetrics(id),
201
- * ] as const);
174
+ * ]);
202
175
  * ```
203
176
  */
204
- static combineTupleParallel<const R extends readonly ResultAsync<unknown, unknown>[]>(results: R): CombineTupleAsync<R>;
177
+ static combineTupleParallel<const R extends readonly unknown[]>(results: R, ..._guard: AsyncTupleGuard<R>): CombineTupleAsync<R>;
205
178
  /**
206
179
  * Transforms the `Ok` value, passing `Err` through unchanged.
207
180
  *
@@ -221,6 +194,16 @@ declare class ResultAsync<T, E> {
221
194
  * ```
222
195
  */
223
196
  mapErr<F>(fn: (error: E) => F): ResultAsync<T, F>;
197
+ /**
198
+ * Transforms both tracks in one step — the async mirror of the sync
199
+ * `bimap`. Exactly one of `onOk` / `onErr` runs.
200
+ *
201
+ * @example
202
+ * ```ts
203
+ * load(id).bimap(toDto, (e): AppError => ({ kind: "load", cause: e }));
204
+ * ```
205
+ */
206
+ bimap<U, F>(onOk: (value: T) => U, onErr: (error: E) => F): ResultAsync<U, F>;
224
207
  /**
225
208
  * Canonical bind — chains a step that itself returns a `ResultAsync` or sync
226
209
  * `Result`, short-circuiting on `Err`. Error types accumulate (`E | F`).
@@ -328,5 +311,9 @@ declare class ResultAsync<T, E> {
328
311
  */
329
312
  then<R1 = Result<T, E>, R2 = never>(onfulfilled?: ((value: Result<T, E>) => R1 | PromiseLike<R1>) | undefined | null, onrejected?: ((reason: unknown) => R2 | PromiseLike<R2>) | undefined | null): Promise<R1 | R2>;
330
313
  }
331
-
332
- export { type InferErr as I, ResultAsync as R, type InferOk as a };
314
+ export {};
315
+ /**
316
+ * The free-function lift helpers (`okAsync`, `errAsync`, `fromPromise`,
317
+ * `fromSafePromise`, `fromResult`, `fromAsync`, `asyncAfter`, `tryAsync`)
318
+ * live in `./async-lift.ts` and are re-exported from the package index.
319
+ */