@onrails/result 0.1.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/DESIGN.md +11 -19
  2. package/README.md +14 -62
  3. package/RECIPES.md +12 -25
  4. package/dist/async-BR3AX0ck.d.cts +326 -0
  5. package/dist/async-SZ7BEl0g.d.ts +326 -0
  6. package/dist/compat/neverthrow.cjs +266 -94
  7. package/dist/compat/neverthrow.cjs.map +1 -1
  8. package/dist/compat/neverthrow.d.cts +4 -4
  9. package/dist/compat/neverthrow.d.ts +4 -4
  10. package/dist/compat/neverthrow.js +263 -91
  11. package/dist/compat/neverthrow.js.map +1 -1
  12. package/dist/extra.cjs +2 -25
  13. package/dist/extra.cjs.map +1 -1
  14. package/dist/extra.d.cts +7 -19
  15. package/dist/extra.d.ts +7 -19
  16. package/dist/extra.js +3 -25
  17. package/dist/extra.js.map +1 -1
  18. package/dist/fluent.cjs +55 -29
  19. package/dist/fluent.cjs.map +1 -1
  20. package/dist/fluent.d.cts +38 -11
  21. package/dist/fluent.d.ts +38 -11
  22. package/dist/fluent.js +55 -29
  23. package/dist/fluent.js.map +1 -1
  24. package/dist/index.cjs +290 -131
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +294 -69
  27. package/dist/index.d.ts +294 -69
  28. package/dist/index.js +287 -127
  29. package/dist/index.js.map +1 -1
  30. package/dist/pipe.cjs.map +1 -1
  31. package/dist/pipe.d.cts +7 -5
  32. package/dist/pipe.d.ts +7 -5
  33. package/dist/pipe.js.map +1 -1
  34. package/dist/railway.cjs +293 -188
  35. package/dist/railway.cjs.map +1 -1
  36. package/dist/railway.d.cts +26 -180
  37. package/dist/railway.d.ts +26 -180
  38. package/dist/railway.js +294 -178
  39. package/dist/railway.js.map +1 -1
  40. package/dist/try-gen.cjs +1 -1
  41. package/dist/try-gen.cjs.map +1 -1
  42. package/dist/try-gen.d.cts +29 -3
  43. package/dist/try-gen.d.ts +29 -3
  44. package/dist/try-gen.js +1 -1
  45. package/dist/try-gen.js.map +1 -1
  46. package/dist/types-BQ9vv0nD.d.cts +55 -0
  47. package/dist/types-BQ9vv0nD.d.ts +55 -0
  48. package/package.json +5 -18
  49. package/skills/railway-do-notation/SKILL.md +397 -0
  50. package/skills/result-composition/SKILL.md +323 -0
  51. package/dist/$.cjs +0 -40
  52. package/dist/$.cjs.map +0 -1
  53. package/dist/$.js +0 -36
  54. package/dist/$.js.map +0 -1
  55. package/dist/_.d.cts +0 -2
  56. package/dist/_.d.ts +0 -2
  57. package/dist/async-CH8Cs8KE.d.cts +0 -161
  58. package/dist/async-DKISqIBc.d.ts +0 -161
  59. package/dist/interop.cjs +0 -253
  60. package/dist/interop.cjs.map +0 -1
  61. package/dist/interop.d.cts +0 -35
  62. package/dist/interop.d.ts +0 -35
  63. package/dist/interop.js +0 -249
  64. package/dist/interop.js.map +0 -1
  65. package/dist/types-C2Dp1d5J.d.cts +0 -21
  66. package/dist/types-C2Dp1d5J.d.ts +0 -21
  67. package/dist/validation.cjs +0 -53
  68. package/dist/validation.cjs.map +0 -1
  69. package/dist/validation.d.cts +0 -72
  70. package/dist/validation.d.ts +0 -72
  71. package/dist/validation.js +0 -48
  72. package/dist/validation.js.map +0 -1
@@ -0,0 +1,326 @@
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;
29
+ /**
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
+ * ```
38
+ */
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<{
46
+ [K in keyof R]: InferOk<R[K]>;
47
+ }, {
48
+ [K in keyof R]: InferErr<R[K]>;
49
+ }[number]>;
50
+ /**
51
+ * 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<…>>`
54
+ * directly: `await` the instance (it is thenable) or call {@link resolve} /
55
+ * {@link match} to settle it.
56
+ *
57
+ * Construct via the static factories ({@link ok}, {@link err}, {@link fromPromise},
58
+ * {@link defer}, …) rather than `new` — the constructor is `protected`.
59
+ *
60
+ * @typeParam T - the `Ok` value type
61
+ * @typeParam E - the `Err` error type
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * const user = ResultAsync.fromPromise(api.getUser(id), toAppError)
66
+ * .map((u) => u.profile)
67
+ * .recover(() => ResultAsync.ok(guestProfile));
68
+ *
69
+ * const r = await user; // Result<Profile, AppError>
70
+ * ```
71
+ */
72
+ declare class ResultAsync<T, E> {
73
+ protected readonly run: PromiseFactory<T, E>;
74
+ private promise;
75
+ protected constructor(run: PromiseFactory<T, E>);
76
+ /**
77
+ * Lifts an already-settled sync {@link Result} into a {@link ResultAsync}.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * const ra = ResultAsync.fromResult(ok(42)); // ResultAsync<number, never>
82
+ * ```
83
+ */
84
+ static fromResult<T, E>(result: Result<T, E>): ResultAsync<T, E>;
85
+ /**
86
+ * Wraps a `PromiseLike<T>` that may reject. Rejections pass through `onReject`
87
+ * to become a typed `Err`; resolution becomes `Ok<T>`.
88
+ *
89
+ * @param promise - the promise to wrap
90
+ * @param onReject - maps a rejection reason to the `Err` channel
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * const body = ResultAsync.fromPromise(
95
+ * fetch(url).then((r) => r.text()),
96
+ * (e): NetError => ({ kind: "net", cause: String(e) }),
97
+ * );
98
+ * ```
99
+ */
100
+ static fromPromise<T, E>(promise: PromiseLike<T>, onReject: (error: unknown) => E): ResultAsync<T, E>;
101
+ /**
102
+ * Wraps a `PromiseLike<T>` that is **guaranteed not to reject**, skipping the
103
+ * `onReject` mapper. Use only when rejection is provably impossible.
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * const now = ResultAsync.fromSafePromise(Promise.resolve(Date.now()));
108
+ * ```
109
+ */
110
+ static fromSafePromise<T, E = never>(promise: PromiseLike<T>): ResultAsync<T, E>;
111
+ /**
112
+ * Defers work until {@link resolve}. Unlike {@link fromPromise}, the factory
113
+ * does not run until the `ResultAsync` is resolved (e.g. by `combineTuple` /
114
+ * `combineTupleParallel`). The factory runs at most once — `resolve` memoizes.
115
+ *
116
+ * @example
117
+ * ```ts
118
+ * const insert = ResultAsync.defer(() => db.orders.insert(row));
119
+ * // nothing has run yet
120
+ * const r = await insert; // factory runs exactly once here
121
+ * ```
122
+ */
123
+ static defer<T, E>(fn: () => Promise<Result<T, E>>): ResultAsync<T, E>;
124
+ /**
125
+ * Lifts a value into an `Ok` async result.
126
+ *
127
+ * @example
128
+ * ```ts
129
+ * const r = ResultAsync.ok(42); // ResultAsync<number, never>
130
+ * ```
131
+ */
132
+ static ok<T>(value: T): ResultAsync<T, never>;
133
+ static ok<T, E>(value: T): ResultAsync<T, E>;
134
+ /**
135
+ * Lifts an error into an `Err` async result.
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * const r = ResultAsync.err({ kind: "not_found" as const });
140
+ * ```
141
+ */
142
+ static err<T = never, E = unknown>(error: E): ResultAsync<T, E>;
143
+ /**
144
+ * Lifts an existing `Promise<Result<T, E>>` (e.g. from interop code) into a
145
+ * {@link ResultAsync}. A thrown defect is routed through `onDefect`,
146
+ * defaulting to {@link UnexpectedError}. The `Ok` value passes through
147
+ * verbatim — even when it happens to be Result-shaped.
148
+ *
149
+ * @param promise - a promise that already yields a `Result`
150
+ * @param onDefect - maps an unexpected throw to the `Err` channel
151
+ * @see {@link fromAsync} from `@onrails/result`
152
+ */
153
+ static fromResultPromise<T, E>(promise: Promise<Result<T, E>>, onDefect?: (error: unknown) => E | UnexpectedError): ResultAsync<T, E | UnexpectedError>;
154
+ /**
155
+ * Combines a homogeneous array of async results into one. Resolves
156
+ * **sequentially** in input order, short-circuiting on the first `Err`.
157
+ * For heterogeneous tuples that preserve per-index types, use
158
+ * {@link combineTuple}; for wall-clock overlap, {@link combineTupleParallel}.
159
+ *
160
+ * @example
161
+ * ```ts
162
+ * const all = ResultAsync.combine([loadA(), loadB(), loadC()]);
163
+ * // ResultAsync<Item[], LoadError>
164
+ * ```
165
+ */
166
+ static combine<T, E>(results: readonly ResultAsync<T, E>[]): ResultAsync<T[], E>;
167
+ /**
168
+ * Heterogeneous async tuple combine — resolves branches **sequentially**
169
+ * (left-to-right), returning the first `Err` in input order. Preserves each
170
+ * branch's `Ok` type by position, so the result destructures type-safely.
171
+ * This is the canonical sequential async combine (replaces the former
172
+ * `sequenceTupleAsync`).
173
+ *
174
+ * @example
175
+ * ```ts
176
+ * const combined = ResultAsync.combineTuple([loadCfg(), loadCatalog()] as const);
177
+ * // ResultAsync<readonly [Cfg, Catalog], CfgError | CatalogError>
178
+ * const r = await combined;
179
+ * if (isOk(r)) {
180
+ * const [cfg, catalog] = r.value; // typed per position
181
+ * }
182
+ * ```
183
+ */
184
+ static combineTuple<const R extends readonly ResultAsync<unknown, unknown>[]>(results: R): CombineTupleAsync<R>;
185
+ /**
186
+ * Like {@link combineTuple}, but starts every branch before awaiting (wall-clock
187
+ * parallel for independent IO). On failure, returns the first `Err` in input order.
188
+ *
189
+ * @example
190
+ * ```ts
191
+ * // independent IO — overlap them
192
+ * const combined = ResultAsync.combineTupleParallel([
193
+ * loadProfile(id),
194
+ * loadMetrics(id),
195
+ * ] as const);
196
+ * ```
197
+ */
198
+ static combineTupleParallel<const R extends readonly ResultAsync<unknown, unknown>[]>(results: R): CombineTupleAsync<R>;
199
+ /**
200
+ * Transforms the `Ok` value, passing `Err` through unchanged.
201
+ *
202
+ * @example
203
+ * ```ts
204
+ * ResultAsync.ok(2).map((n) => n * 3); // ResultAsync<number> → Ok 6
205
+ * ```
206
+ */
207
+ map<U>(fn: (value: T) => U): ResultAsync<U, E>;
208
+ /**
209
+ * Transforms the `Err` value, passing `Ok` through unchanged — useful for
210
+ * unifying heterogeneous failures into one app-level union.
211
+ *
212
+ * @example
213
+ * ```ts
214
+ * load(id).mapErr((e): AppError => ({ kind: "load", cause: e })); // ResultAsync<T, AppError>
215
+ * ```
216
+ */
217
+ mapErr<F>(fn: (error: E) => F): ResultAsync<T, F>;
218
+ /**
219
+ * Canonical bind — chains a step that itself returns a `ResultAsync` or sync
220
+ * `Result`, short-circuiting on `Err`. Error types accumulate (`E | F`).
221
+ *
222
+ * @example
223
+ * ```ts
224
+ * authenticate(req)
225
+ * .flatMap((user) => loadProfile(user.id)) // ResultAsync
226
+ * .flatMap((p) => validate(p)); // sync Result also accepted
227
+ * ```
228
+ */
229
+ flatMap<U, F = E>(fn: (value: T) => ResultAsync<U, F> | Result<U, F>): ResultAsync<U, E | F>;
230
+ /**
231
+ * neverthrow-compat alias of {@link flatMap}. Kept as the documented compat
232
+ * tier; prefer {@link flatMap} in new code.
233
+ *
234
+ * @example
235
+ * ```ts
236
+ * authenticate(req).andThen((user) => loadProfile(user.id));
237
+ * ```
238
+ */
239
+ andThen<U, F = E>(fn: (value: T) => ResultAsync<U, F> | Result<U, F>): ResultAsync<U, E | F>;
240
+ /**
241
+ * Error-channel bind — runs `fn` only on `Err`, swapping in a recovery
242
+ * `ResultAsync` or sync `Result`. `Ok` passes through. The mirror of
243
+ * {@link flatMap} on the error track.
244
+ *
245
+ * @example
246
+ * ```ts
247
+ * loadFromCache(id).recover(() => loadFromOrigin(id)); // ResultAsync<T, F>
248
+ * ```
249
+ */
250
+ recover<F>(fn: (error: E) => ResultAsync<T, F> | Result<T, F>): ResultAsync<T, F>;
251
+ /**
252
+ * neverthrow-compat alias of {@link recover}. Kept as the documented compat
253
+ * tier; prefer {@link recover} in new code.
254
+ *
255
+ * @example
256
+ * ```ts
257
+ * loadFromCache(id).orElse(() => loadFromOrigin(id));
258
+ * ```
259
+ */
260
+ orElse<F>(fn: (error: E) => ResultAsync<T, F> | Result<T, F>): ResultAsync<T, F>;
261
+ /**
262
+ * Runs a side effect on the `Ok` value and passes the result through
263
+ * unchanged; a no-op on `Err`. Mirrors {@link tapErr}.
264
+ *
265
+ * @example
266
+ * ```ts
267
+ * saveUser(u).tap((saved) => analytics.track("user_saved", saved.id));
268
+ * ```
269
+ */
270
+ tap(fn: (value: T) => void): ResultAsync<T, E>;
271
+ /**
272
+ * Runs a side effect on the `Err` value and passes the result through
273
+ * unchanged; a no-op on `Ok`. The error-track mirror of {@link tap}.
274
+ *
275
+ * @example
276
+ * ```ts
277
+ * saveUser(u).tapErr((e) => logger.warn("save failed", e));
278
+ * ```
279
+ */
280
+ tapErr(fn: (error: E) => void): ResultAsync<T, E>;
281
+ /**
282
+ * Settles to the `Ok` value, or `defaultValue` if `Err`. Terminal — returns a
283
+ * plain `Promise`, not a `ResultAsync`.
284
+ *
285
+ * @example
286
+ * ```ts
287
+ * const profile = await loadProfile(id).unwrapOr(guestProfile);
288
+ * ```
289
+ */
290
+ unwrapOr<U>(defaultValue: U): Promise<T | U>;
291
+ /**
292
+ * Terminal collapse — folds both tracks into a single awaited value. Returns
293
+ * a plain `Promise`, settling the carrier exactly once.
294
+ *
295
+ * @param onOk - handles the `Ok` value
296
+ * @param onErr - handles the `Err` value
297
+ *
298
+ * @example
299
+ * ```ts
300
+ * const status = await save(row).match(
301
+ * () => 200,
302
+ * (e) => (e.kind === "conflict" ? 409 : 500),
303
+ * );
304
+ * ```
305
+ */
306
+ match<U1, U2 = U1>(onOk: (value: T) => U1, onErr: (error: E) => U2): Promise<U1 | U2>;
307
+ /**
308
+ * Settles the carrier to a bare sync {@link Result}, memoizing so the
309
+ * underlying factory runs at most once. Prefer `await ra` (the thenable) or
310
+ * {@link match} in app code; use `resolve` when you need the tagged union back.
311
+ *
312
+ * @example
313
+ * ```ts
314
+ * const r = await load(id).resolve(); // Result<Data, LoadError>
315
+ * if (isOk(r)) use(r.value);
316
+ * ```
317
+ */
318
+ resolve(): Promise<Result<T, E>>;
319
+ /**
320
+ * Thenable shim — `await ra` resolves to a bare tagged-union `Result<T, E>`.
321
+ * Narrow with `isOk(r)` / `isErr(r)` to read `.value` / `.error`.
322
+ */
323
+ 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>;
324
+ }
325
+
326
+ export { type InferErr as I, ResultAsync as R, type InferOk as a };