@onrails/result 0.1.3 → 0.2.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 (68) hide show
  1. package/DESIGN.md +2 -2
  2. package/README.md +0 -3
  3. package/RECIPES.md +16 -15
  4. package/dist/$.cjs +40 -0
  5. package/dist/$.cjs.map +1 -0
  6. package/dist/$.js +36 -0
  7. package/dist/$.js.map +1 -0
  8. package/dist/_.d.cts +2 -0
  9. package/dist/_.d.ts +2 -0
  10. package/dist/async-1fIhfzFu.d.ts +290 -0
  11. package/dist/async-u8U5P2EH.d.cts +290 -0
  12. package/dist/compat/neverthrow.cjs +234 -50
  13. package/dist/compat/neverthrow.cjs.map +1 -1
  14. package/dist/compat/neverthrow.d.cts +2 -2
  15. package/dist/compat/neverthrow.d.ts +2 -2
  16. package/dist/compat/neverthrow.js +231 -47
  17. package/dist/compat/neverthrow.js.map +1 -1
  18. package/dist/extra.cjs +1 -24
  19. package/dist/extra.cjs.map +1 -1
  20. package/dist/extra.d.cts +3 -9
  21. package/dist/extra.d.ts +3 -9
  22. package/dist/extra.js +2 -24
  23. package/dist/extra.js.map +1 -1
  24. package/dist/fluent.cjs +0 -2
  25. package/dist/fluent.cjs.map +1 -1
  26. package/dist/fluent.d.cts +2 -6
  27. package/dist/fluent.d.ts +2 -6
  28. package/dist/fluent.js +0 -2
  29. package/dist/fluent.js.map +1 -1
  30. package/dist/index.cjs +226 -47
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.d.cts +174 -51
  33. package/dist/index.d.ts +174 -51
  34. package/dist/index.js +227 -44
  35. package/dist/index.js.map +1 -1
  36. package/dist/interop.cjs +209 -23
  37. package/dist/interop.cjs.map +1 -1
  38. package/dist/interop.d.cts +60 -10
  39. package/dist/interop.d.ts +60 -10
  40. package/dist/interop.js +209 -23
  41. package/dist/interop.js.map +1 -1
  42. package/dist/pipe.cjs.map +1 -1
  43. package/dist/pipe.d.cts +7 -5
  44. package/dist/pipe.d.ts +7 -5
  45. package/dist/pipe.js.map +1 -1
  46. package/dist/railway.cjs +205 -24
  47. package/dist/railway.cjs.map +1 -1
  48. package/dist/railway.d.cts +20 -3
  49. package/dist/railway.d.ts +20 -3
  50. package/dist/railway.js +205 -25
  51. package/dist/railway.js.map +1 -1
  52. package/dist/try-gen.cjs.map +1 -1
  53. package/dist/try-gen.d.cts +29 -3
  54. package/dist/try-gen.d.ts +29 -3
  55. package/dist/try-gen.js.map +1 -1
  56. package/dist/types-BQ9vv0nD.d.cts +55 -0
  57. package/dist/types-BQ9vv0nD.d.ts +55 -0
  58. package/dist/validation.cjs.map +1 -1
  59. package/dist/validation.d.cts +1 -1
  60. package/dist/validation.d.ts +1 -1
  61. package/dist/validation.js.map +1 -1
  62. package/package.json +10 -3
  63. package/skills/railway-do-notation/SKILL.md +436 -0
  64. package/skills/result-composition/SKILL.md +325 -0
  65. package/dist/async-CH8Cs8KE.d.cts +0 -161
  66. package/dist/async-DKISqIBc.d.ts +0 -161
  67. package/dist/types-C2Dp1d5J.d.cts +0 -21
  68. package/dist/types-C2Dp1d5J.d.ts +0 -21
package/dist/railway.js CHANGED
@@ -69,9 +69,32 @@ var ResultAsync = class _ResultAsync {
69
69
  }
70
70
  run;
71
71
  promise = null;
72
+ /**
73
+ * Lifts an already-settled sync {@link Result} into a {@link ResultAsync}.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * const ra = ResultAsync.fromResult(ok(42)); // ResultAsync<number, never>
78
+ * ```
79
+ */
72
80
  static fromResult(result) {
73
81
  return new _ResultAsync(async () => result);
74
82
  }
83
+ /**
84
+ * Wraps a `PromiseLike<T>` that may reject. Rejections pass through `onReject`
85
+ * to become a typed `Err`; resolution becomes `Ok<T>`.
86
+ *
87
+ * @param promise - the promise to wrap
88
+ * @param onReject - maps a rejection reason to the `Err` channel
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * const body = ResultAsync.fromPromise(
93
+ * fetch(url).then((r) => r.text()),
94
+ * (e): NetError => ({ kind: "net", cause: String(e) }),
95
+ * );
96
+ * ```
97
+ */
75
98
  static fromPromise(promise, onReject) {
76
99
  return new _ResultAsync(async () => {
77
100
  try {
@@ -81,12 +104,29 @@ var ResultAsync = class _ResultAsync {
81
104
  }
82
105
  });
83
106
  }
107
+ /**
108
+ * Wraps a `PromiseLike<T>` that is **guaranteed not to reject**, skipping the
109
+ * `onReject` mapper. Use only when rejection is provably impossible.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * const now = ResultAsync.fromSafePromise(Promise.resolve(Date.now()));
114
+ * ```
115
+ */
84
116
  static fromSafePromise(promise) {
85
117
  return new _ResultAsync(async () => ok(await promise));
86
118
  }
87
119
  /**
88
- * Defers work until {@link resolve}. Unlike {@link fromPromise}, nothing runs
89
- * until the `ResultAsync` is resolved (e.g. by `combineTuple` / `combineTupleParallel`).
120
+ * Defers work until {@link resolve}. Unlike {@link fromPromise}, the factory
121
+ * does not run until the `ResultAsync` is resolved (e.g. by `combineTuple` /
122
+ * `combineTupleParallel`). The factory runs at most once — `resolve` memoizes.
123
+ *
124
+ * @example
125
+ * ```ts
126
+ * const insert = ResultAsync.defer(() => db.orders.insert(row));
127
+ * // nothing has run yet
128
+ * const r = await insert; // factory runs exactly once here
129
+ * ```
90
130
  */
91
131
  static defer(fn) {
92
132
  return new _ResultAsync(fn);
@@ -94,14 +134,42 @@ var ResultAsync = class _ResultAsync {
94
134
  static ok(value) {
95
135
  return new _ResultAsync(async () => ok(value));
96
136
  }
137
+ /**
138
+ * Lifts an error into an `Err` async result.
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * const r = ResultAsync.err({ kind: "not_found" as const });
143
+ * ```
144
+ */
97
145
  static err(error) {
98
146
  return new _ResultAsync(async () => err(error));
99
147
  }
100
- /** @see {@link fromAsync} from `@onrails/result/interop` */
148
+ /**
149
+ * Lifts an existing `Promise<Result<T, E>>` (e.g. from interop code) into a
150
+ * {@link ResultAsync}, auto-flattening one level of double-wrapping. A thrown
151
+ * defect is routed through `onDefect`, defaulting to {@link UnexpectedError}.
152
+ *
153
+ * @param promise - a promise that already yields a `Result`
154
+ * @param onDefect - maps an unexpected throw to the `Err` channel
155
+ * @see {@link fromAsync} from `@onrails/result/interop`
156
+ */
101
157
  static fromResultPromise(promise, onDefect) {
102
158
  const mapDefect = onDefect ?? ((error) => new UnexpectedError("Unexpected async defect", error));
103
159
  return new _ResultAsync(() => liftPromiseResult(promise, mapDefect));
104
160
  }
161
+ /**
162
+ * Combines a homogeneous array of async results into one. Resolves
163
+ * **sequentially** in input order, short-circuiting on the first `Err`.
164
+ * For heterogeneous tuples that preserve per-index types, use
165
+ * {@link combineTuple}; for wall-clock overlap, {@link combineTupleParallel}.
166
+ *
167
+ * @example
168
+ * ```ts
169
+ * const all = ResultAsync.combine([loadA(), loadB(), loadC()]);
170
+ * // ResultAsync<Item[], LoadError>
171
+ * ```
172
+ */
105
173
  static combine(results) {
106
174
  return new _ResultAsync(async () => {
107
175
  const values = [];
@@ -115,6 +183,23 @@ var ResultAsync = class _ResultAsync {
115
183
  return ok(values);
116
184
  });
117
185
  }
186
+ /**
187
+ * Heterogeneous async tuple combine — resolves branches **sequentially**
188
+ * (left-to-right), returning the first `Err` in input order. Preserves each
189
+ * branch's `Ok` type by position, so the result destructures type-safely.
190
+ * This is the canonical sequential async combine (replaces the former
191
+ * `sequenceTupleAsync`).
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * const combined = ResultAsync.combineTuple([loadCfg(), loadCatalog()] as const);
196
+ * // ResultAsync<readonly [Cfg, Catalog], CfgError | CatalogError>
197
+ * const r = await combined;
198
+ * if (isOk(r)) {
199
+ * const [cfg, catalog] = r.value; // typed per position
200
+ * }
201
+ * ```
202
+ */
118
203
  static combineTuple(results) {
119
204
  return _ResultAsync.combine(
120
205
  results
@@ -123,18 +208,55 @@ var ResultAsync = class _ResultAsync {
123
208
  /**
124
209
  * Like {@link combineTuple}, but starts every branch before awaiting (wall-clock
125
210
  * parallel for independent IO). On failure, returns the first `Err` in input order.
211
+ *
212
+ * @example
213
+ * ```ts
214
+ * // independent IO — overlap them
215
+ * const combined = ResultAsync.combineTupleParallel([
216
+ * loadProfile(id),
217
+ * loadMetrics(id),
218
+ * ] as const);
219
+ * ```
126
220
  */
127
221
  static combineTupleParallel(results) {
128
222
  return new _ResultAsync(
129
223
  async () => sequenceSettled(await Promise.all(results.map((ra) => ra.resolve())))
130
224
  );
131
225
  }
226
+ /**
227
+ * Transforms the `Ok` value, passing `Err` through unchanged.
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * ResultAsync.ok(2).map((n) => n * 3); // ResultAsync<number> → Ok 6
232
+ * ```
233
+ */
132
234
  map(fn) {
133
235
  return new _ResultAsync(async () => map(await this.resolve(), fn));
134
236
  }
237
+ /**
238
+ * Transforms the `Err` value, passing `Ok` through unchanged — useful for
239
+ * unifying heterogeneous failures into one app-level union.
240
+ *
241
+ * @example
242
+ * ```ts
243
+ * load(id).mapErr((e): AppError => ({ kind: "load", cause: e }));
244
+ * ```
245
+ */
135
246
  mapErr(fn) {
136
247
  return new _ResultAsync(async () => mapErr(await this.resolve(), fn));
137
248
  }
249
+ /**
250
+ * Canonical bind — chains a step that itself returns a `ResultAsync` or sync
251
+ * `Result`, short-circuiting on `Err`. Error types accumulate (`E | F`).
252
+ *
253
+ * @example
254
+ * ```ts
255
+ * authenticate(req)
256
+ * .flatMap((user) => loadProfile(user.id)) // ResultAsync
257
+ * .flatMap((p) => validate(p)); // sync Result also accepted
258
+ * ```
259
+ */
138
260
  flatMap(fn) {
139
261
  return new _ResultAsync(async () => {
140
262
  const first = await this.resolve();
@@ -151,16 +273,28 @@ var ResultAsync = class _ResultAsync {
151
273
  return isCompatLike(next) ? next.inner : next;
152
274
  });
153
275
  }
276
+ /**
277
+ * neverthrow-compat alias of {@link flatMap}. Kept as the documented compat
278
+ * tier; prefer `flatMap` in new code.
279
+ *
280
+ * @example
281
+ * ```ts
282
+ * authenticate(req).andThen((user) => loadProfile(user.id));
283
+ * ```
284
+ */
154
285
  andThen(fn) {
155
286
  return this.flatMap(fn);
156
287
  }
157
288
  /**
158
- * Alias for {@link flatMap}.
159
- * @deprecated Use {@link flatMap} or keep/use compat {@link andThen} instead.
289
+ * Error-channel bind — runs `fn` only on `Err`, swapping in a recovery
290
+ * `ResultAsync` or sync `Result`. `Ok` passes through. The mirror of
291
+ * {@link flatMap} on the error track.
292
+ *
293
+ * @example
294
+ * ```ts
295
+ * loadFromCache(id).recover(() => loadFromOrigin(id));
296
+ * ```
160
297
  */
161
- chain(fn) {
162
- return this.flatMap(fn);
163
- }
164
298
  recover(fn) {
165
299
  return new _ResultAsync(async () => {
166
300
  const first = await this.resolve();
@@ -171,9 +305,27 @@ var ResultAsync = class _ResultAsync {
171
305
  return next instanceof _ResultAsync ? next.resolve() : next;
172
306
  });
173
307
  }
308
+ /**
309
+ * neverthrow-compat alias of {@link recover}. Kept as the documented compat
310
+ * tier; prefer `recover` in new code.
311
+ *
312
+ * @example
313
+ * ```ts
314
+ * loadFromCache(id).orElse(() => loadFromOrigin(id));
315
+ * ```
316
+ */
174
317
  orElse(fn) {
175
318
  return this.recover(fn);
176
319
  }
320
+ /**
321
+ * Runs a side effect on the `Ok` value and passes the result through
322
+ * unchanged; a no-op on `Err`. Mirrors {@link tapErr}.
323
+ *
324
+ * @example
325
+ * ```ts
326
+ * saveUser(u).tap((saved) => analytics.track("user_saved", saved.id));
327
+ * ```
328
+ */
177
329
  tap(fn) {
178
330
  return new _ResultAsync(async () => {
179
331
  const result = await this.resolve();
@@ -183,6 +335,15 @@ var ResultAsync = class _ResultAsync {
183
335
  return result;
184
336
  });
185
337
  }
338
+ /**
339
+ * Runs a side effect on the `Err` value and passes the result through
340
+ * unchanged; a no-op on `Ok`. The error-track mirror of {@link tap}.
341
+ *
342
+ * @example
343
+ * ```ts
344
+ * saveUser(u).tapErr((e) => logger.warn("save failed", e));
345
+ * ```
346
+ */
186
347
  tapErr(fn) {
187
348
  return new _ResultAsync(async () => {
188
349
  const result = await this.resolve();
@@ -192,28 +353,49 @@ var ResultAsync = class _ResultAsync {
192
353
  return result;
193
354
  });
194
355
  }
195
- unwrapOr(defaultValue) {
196
- return this.resolve().then((result) => isErr(result) ? defaultValue : result.value);
197
- }
198
356
  /**
199
- * @deprecated Await the ResultAsync and narrow with {@link isOk} — this method
200
- * re-executes deferred factories and cannot narrow.
357
+ * Settles to the `Ok` value, or `defaultValue` if `Err`. Terminal — returns a
358
+ * plain `Promise`, not a `ResultAsync`.
359
+ *
360
+ * @example
361
+ * ```ts
362
+ * const profile = await loadProfile(id).unwrapOr(guestProfile);
363
+ * ```
201
364
  */
202
- isOk() {
203
- return this.resolve().then((result) => !isErr(result));
365
+ unwrapOr(defaultValue) {
366
+ return this.resolve().then((result) => isErr(result) ? defaultValue : result.value);
204
367
  }
205
368
  /**
206
- * @deprecated Await the ResultAsync and narrow with {@link isErr} — this method
207
- * re-executes deferred factories and cannot narrow.
369
+ * Terminal collapse — folds both tracks into a single awaited value. Returns
370
+ * a plain `Promise`, settling the carrier exactly once.
371
+ *
372
+ * @param onOk - handles the `Ok` value
373
+ * @param onErr - handles the `Err` value
374
+ *
375
+ * @example
376
+ * ```ts
377
+ * const status = await save(row).match(
378
+ * () => 200,
379
+ * (e) => (e.kind === "conflict" ? 409 : 500),
380
+ * );
381
+ * ```
208
382
  */
209
- isErr() {
210
- return this.resolve().then((result) => isErr(result));
211
- }
212
383
  match(onOk, onErr) {
213
384
  return this.resolve().then(
214
385
  (result) => isErr(result) ? onErr(result.error) : onOk(result.value)
215
386
  );
216
387
  }
388
+ /**
389
+ * Settles the carrier to a bare sync {@link Result}, memoizing so the
390
+ * underlying factory runs at most once. Prefer `await ra` (the thenable) or
391
+ * {@link match} in app code; use `resolve` when you need the tagged union back.
392
+ *
393
+ * @example
394
+ * ```ts
395
+ * const r = await load(id).resolve(); // Result<Data, LoadError>
396
+ * if (isOk(r)) use(r.value);
397
+ * ```
398
+ */
217
399
  resolve() {
218
400
  if (!this.promise) {
219
401
  this.promise = this.run();
@@ -300,10 +482,7 @@ var Railway = class _Railway {
300
482
  return new _Railway({ mode: "sync", result: err(current.error) });
301
483
  }
302
484
  const next = fn(current.value);
303
- if (isErr(next)) {
304
- return new _Railway({ mode: "sync", result: err(next.error) });
305
- }
306
- return new _Railway({
485
+ return isErr(next) ? new _Railway({ mode: "sync", result: err(next.error) }) : new _Railway({
307
486
  mode: "sync",
308
487
  result: ok(addField(current.value, key, next.value))
309
488
  });
@@ -413,6 +592,7 @@ function railway(input, ...steps) {
413
592
  var parseWith = (parser, onThrow) => ({
414
593
  as: (key) => (workflow) => workflow.fromSync(key, ({ input }) => parseWithParser(parser, input), onThrow)
415
594
  });
595
+ var parseNamed = (key, parser, onThrow) => (workflow) => workflow.fromSync(key, ({ input }) => parseWithParser(parser, input), onThrow);
416
596
  var fromSyncNamed = (key, fn, onThrow) => (workflow) => workflow.fromSync(key, fn, onThrow);
417
597
  var fromResultNamed = (key, fn) => (workflow) => workflow.fromResult(key, fn);
418
598
  var fromPromiseNamed = (key, fn, onReject) => (workflow) => workflow.fromPromise(key, fn, onReject);
@@ -425,6 +605,6 @@ var parallelNamed = (branches) => (workflow) => {
425
605
  };
426
606
  var select = (fn) => (workflow) => workflow.select(fn);
427
607
 
428
- export { Railway, deriveNamed, fromAsyncNamed, fromPromiseNamed, fromResultNamed, fromSyncNamed, parallelNamed, parseWith, railway, requireNamed, select };
608
+ export { Railway, deriveNamed, fromAsyncNamed, fromPromiseNamed, fromResultNamed, fromSyncNamed, parallelNamed, parseNamed, parseWith, railway, requireNamed, select };
429
609
  //# sourceMappingURL=railway.js.map
430
610
  //# sourceMappingURL=railway.js.map