@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
@@ -39,18 +39,6 @@ function trySync(fn, onThrow) {
39
39
  }
40
40
  };
41
41
  }
42
- var combine = (results) => {
43
- const values = [];
44
- for (const result of results) {
45
- if (isErr(result)) return err(result.error);
46
- values.push(result.value);
47
- }
48
- return ok(values);
49
- };
50
- var combineTuple = (results) => (
51
- // Runtime identical to combine; the cast restores per-index tuple types.
52
- combine(results)
53
- );
54
42
 
55
43
  // src/types.ts
56
44
  var UnexpectedError = class extends Error {
@@ -90,9 +78,32 @@ var ResultAsync = class _ResultAsync {
90
78
  }
91
79
  run;
92
80
  promise = null;
81
+ /**
82
+ * Lifts an already-settled sync {@link Result} into a {@link ResultAsync}.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * const ra = ResultAsync.fromResult(ok(42)); // ResultAsync<number, never>
87
+ * ```
88
+ */
93
89
  static fromResult(result) {
94
90
  return new _ResultAsync(async () => result);
95
91
  }
92
+ /**
93
+ * Wraps a `PromiseLike<T>` that may reject. Rejections pass through `onReject`
94
+ * to become a typed `Err`; resolution becomes `Ok<T>`.
95
+ *
96
+ * @param promise - the promise to wrap
97
+ * @param onReject - maps a rejection reason to the `Err` channel
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * const body = ResultAsync.fromPromise(
102
+ * fetch(url).then((r) => r.text()),
103
+ * (e): NetError => ({ kind: "net", cause: String(e) }),
104
+ * );
105
+ * ```
106
+ */
96
107
  static fromPromise(promise, onReject) {
97
108
  return new _ResultAsync(async () => {
98
109
  try {
@@ -102,12 +113,29 @@ var ResultAsync = class _ResultAsync {
102
113
  }
103
114
  });
104
115
  }
116
+ /**
117
+ * Wraps a `PromiseLike<T>` that is **guaranteed not to reject**, skipping the
118
+ * `onReject` mapper. Use only when rejection is provably impossible.
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * const now = ResultAsync.fromSafePromise(Promise.resolve(Date.now()));
123
+ * ```
124
+ */
105
125
  static fromSafePromise(promise) {
106
126
  return new _ResultAsync(async () => ok(await promise));
107
127
  }
108
128
  /**
109
- * Defers work until {@link resolve}. Unlike {@link fromPromise}, nothing runs
110
- * until the `ResultAsync` is resolved (e.g. by `combineTuple` / `combineTupleParallel`).
129
+ * Defers work until {@link resolve}. Unlike {@link fromPromise}, the factory
130
+ * does not run until the `ResultAsync` is resolved (e.g. by `combineTuple` /
131
+ * `combineTupleParallel`). The factory runs at most once — `resolve` memoizes.
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * const insert = ResultAsync.defer(() => db.orders.insert(row));
136
+ * // nothing has run yet
137
+ * const r = await insert; // factory runs exactly once here
138
+ * ```
111
139
  */
112
140
  static defer(fn) {
113
141
  return new _ResultAsync(fn);
@@ -115,14 +143,42 @@ var ResultAsync = class _ResultAsync {
115
143
  static ok(value) {
116
144
  return new _ResultAsync(async () => ok(value));
117
145
  }
146
+ /**
147
+ * Lifts an error into an `Err` async result.
148
+ *
149
+ * @example
150
+ * ```ts
151
+ * const r = ResultAsync.err({ kind: "not_found" as const });
152
+ * ```
153
+ */
118
154
  static err(error) {
119
155
  return new _ResultAsync(async () => err(error));
120
156
  }
121
- /** @see {@link fromAsync} from `@onrails/result/interop` */
157
+ /**
158
+ * Lifts an existing `Promise<Result<T, E>>` (e.g. from interop code) into a
159
+ * {@link ResultAsync}, auto-flattening one level of double-wrapping. A thrown
160
+ * defect is routed through `onDefect`, defaulting to {@link UnexpectedError}.
161
+ *
162
+ * @param promise - a promise that already yields a `Result`
163
+ * @param onDefect - maps an unexpected throw to the `Err` channel
164
+ * @see {@link fromAsync} from `@onrails/result/interop`
165
+ */
122
166
  static fromResultPromise(promise, onDefect) {
123
167
  const mapDefect = onDefect ?? ((error) => new UnexpectedError("Unexpected async defect", error));
124
168
  return new _ResultAsync(() => liftPromiseResult(promise, mapDefect));
125
169
  }
170
+ /**
171
+ * Combines a homogeneous array of async results into one. Resolves
172
+ * **sequentially** in input order, short-circuiting on the first `Err`.
173
+ * For heterogeneous tuples that preserve per-index types, use
174
+ * {@link combineTuple}; for wall-clock overlap, {@link combineTupleParallel}.
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * const all = ResultAsync.combine([loadA(), loadB(), loadC()]);
179
+ * // ResultAsync<Item[], LoadError>
180
+ * ```
181
+ */
126
182
  static combine(results) {
127
183
  return new _ResultAsync(async () => {
128
184
  const values = [];
@@ -136,6 +192,23 @@ var ResultAsync = class _ResultAsync {
136
192
  return ok(values);
137
193
  });
138
194
  }
195
+ /**
196
+ * Heterogeneous async tuple combine — resolves branches **sequentially**
197
+ * (left-to-right), returning the first `Err` in input order. Preserves each
198
+ * branch's `Ok` type by position, so the result destructures type-safely.
199
+ * This is the canonical sequential async combine (replaces the former
200
+ * `sequenceTupleAsync`).
201
+ *
202
+ * @example
203
+ * ```ts
204
+ * const combined = ResultAsync.combineTuple([loadCfg(), loadCatalog()] as const);
205
+ * // ResultAsync<readonly [Cfg, Catalog], CfgError | CatalogError>
206
+ * const r = await combined;
207
+ * if (isOk(r)) {
208
+ * const [cfg, catalog] = r.value; // typed per position
209
+ * }
210
+ * ```
211
+ */
139
212
  static combineTuple(results) {
140
213
  return _ResultAsync.combine(
141
214
  results
@@ -144,18 +217,55 @@ var ResultAsync = class _ResultAsync {
144
217
  /**
145
218
  * Like {@link combineTuple}, but starts every branch before awaiting (wall-clock
146
219
  * parallel for independent IO). On failure, returns the first `Err` in input order.
220
+ *
221
+ * @example
222
+ * ```ts
223
+ * // independent IO — overlap them
224
+ * const combined = ResultAsync.combineTupleParallel([
225
+ * loadProfile(id),
226
+ * loadMetrics(id),
227
+ * ] as const);
228
+ * ```
147
229
  */
148
230
  static combineTupleParallel(results) {
149
231
  return new _ResultAsync(
150
232
  async () => sequenceSettled(await Promise.all(results.map((ra) => ra.resolve())))
151
233
  );
152
234
  }
235
+ /**
236
+ * Transforms the `Ok` value, passing `Err` through unchanged.
237
+ *
238
+ * @example
239
+ * ```ts
240
+ * ResultAsync.ok(2).map((n) => n * 3); // ResultAsync<number> → Ok 6
241
+ * ```
242
+ */
153
243
  map(fn) {
154
244
  return new _ResultAsync(async () => map(await this.resolve(), fn));
155
245
  }
246
+ /**
247
+ * Transforms the `Err` value, passing `Ok` through unchanged — useful for
248
+ * unifying heterogeneous failures into one app-level union.
249
+ *
250
+ * @example
251
+ * ```ts
252
+ * load(id).mapErr((e): AppError => ({ kind: "load", cause: e }));
253
+ * ```
254
+ */
156
255
  mapErr(fn) {
157
256
  return new _ResultAsync(async () => mapErr(await this.resolve(), fn));
158
257
  }
258
+ /**
259
+ * Canonical bind — chains a step that itself returns a `ResultAsync` or sync
260
+ * `Result`, short-circuiting on `Err`. Error types accumulate (`E | F`).
261
+ *
262
+ * @example
263
+ * ```ts
264
+ * authenticate(req)
265
+ * .flatMap((user) => loadProfile(user.id)) // ResultAsync
266
+ * .flatMap((p) => validate(p)); // sync Result also accepted
267
+ * ```
268
+ */
159
269
  flatMap(fn) {
160
270
  return new _ResultAsync(async () => {
161
271
  const first = await this.resolve();
@@ -172,16 +282,28 @@ var ResultAsync = class _ResultAsync {
172
282
  return isCompatLike(next) ? next.inner : next;
173
283
  });
174
284
  }
285
+ /**
286
+ * neverthrow-compat alias of {@link flatMap}. Kept as the documented compat
287
+ * tier; prefer `flatMap` in new code.
288
+ *
289
+ * @example
290
+ * ```ts
291
+ * authenticate(req).andThen((user) => loadProfile(user.id));
292
+ * ```
293
+ */
175
294
  andThen(fn) {
176
295
  return this.flatMap(fn);
177
296
  }
178
297
  /**
179
- * Alias for {@link flatMap}.
180
- * @deprecated Use {@link flatMap} or keep/use compat {@link andThen} instead.
298
+ * Error-channel bind — runs `fn` only on `Err`, swapping in a recovery
299
+ * `ResultAsync` or sync `Result`. `Ok` passes through. The mirror of
300
+ * {@link flatMap} on the error track.
301
+ *
302
+ * @example
303
+ * ```ts
304
+ * loadFromCache(id).recover(() => loadFromOrigin(id));
305
+ * ```
181
306
  */
182
- chain(fn) {
183
- return this.flatMap(fn);
184
- }
185
307
  recover(fn) {
186
308
  return new _ResultAsync(async () => {
187
309
  const first = await this.resolve();
@@ -192,9 +314,27 @@ var ResultAsync = class _ResultAsync {
192
314
  return next instanceof _ResultAsync ? next.resolve() : next;
193
315
  });
194
316
  }
317
+ /**
318
+ * neverthrow-compat alias of {@link recover}. Kept as the documented compat
319
+ * tier; prefer `recover` in new code.
320
+ *
321
+ * @example
322
+ * ```ts
323
+ * loadFromCache(id).orElse(() => loadFromOrigin(id));
324
+ * ```
325
+ */
195
326
  orElse(fn) {
196
327
  return this.recover(fn);
197
328
  }
329
+ /**
330
+ * Runs a side effect on the `Ok` value and passes the result through
331
+ * unchanged; a no-op on `Err`. Mirrors {@link tapErr}.
332
+ *
333
+ * @example
334
+ * ```ts
335
+ * saveUser(u).tap((saved) => analytics.track("user_saved", saved.id));
336
+ * ```
337
+ */
198
338
  tap(fn) {
199
339
  return new _ResultAsync(async () => {
200
340
  const result = await this.resolve();
@@ -204,6 +344,15 @@ var ResultAsync = class _ResultAsync {
204
344
  return result;
205
345
  });
206
346
  }
347
+ /**
348
+ * Runs a side effect on the `Err` value and passes the result through
349
+ * unchanged; a no-op on `Ok`. The error-track mirror of {@link tap}.
350
+ *
351
+ * @example
352
+ * ```ts
353
+ * saveUser(u).tapErr((e) => logger.warn("save failed", e));
354
+ * ```
355
+ */
207
356
  tapErr(fn) {
208
357
  return new _ResultAsync(async () => {
209
358
  const result = await this.resolve();
@@ -213,28 +362,49 @@ var ResultAsync = class _ResultAsync {
213
362
  return result;
214
363
  });
215
364
  }
216
- unwrapOr(defaultValue) {
217
- return this.resolve().then((result) => isErr(result) ? defaultValue : result.value);
218
- }
219
365
  /**
220
- * @deprecated Await the ResultAsync and narrow with {@link isOk} — this method
221
- * re-executes deferred factories and cannot narrow.
366
+ * Settles to the `Ok` value, or `defaultValue` if `Err`. Terminal — returns a
367
+ * plain `Promise`, not a `ResultAsync`.
368
+ *
369
+ * @example
370
+ * ```ts
371
+ * const profile = await loadProfile(id).unwrapOr(guestProfile);
372
+ * ```
222
373
  */
223
- isOk() {
224
- return this.resolve().then((result) => !isErr(result));
374
+ unwrapOr(defaultValue) {
375
+ return this.resolve().then((result) => isErr(result) ? defaultValue : result.value);
225
376
  }
226
377
  /**
227
- * @deprecated Await the ResultAsync and narrow with {@link isErr} — this method
228
- * re-executes deferred factories and cannot narrow.
378
+ * Terminal collapse — folds both tracks into a single awaited value. Returns
379
+ * a plain `Promise`, settling the carrier exactly once.
380
+ *
381
+ * @param onOk - handles the `Ok` value
382
+ * @param onErr - handles the `Err` value
383
+ *
384
+ * @example
385
+ * ```ts
386
+ * const status = await save(row).match(
387
+ * () => 200,
388
+ * (e) => (e.kind === "conflict" ? 409 : 500),
389
+ * );
390
+ * ```
229
391
  */
230
- isErr() {
231
- return this.resolve().then((result) => isErr(result));
232
- }
233
392
  match(onOk, onErr) {
234
393
  return this.resolve().then(
235
394
  (result) => isErr(result) ? onErr(result.error) : onOk(result.value)
236
395
  );
237
396
  }
397
+ /**
398
+ * Settles the carrier to a bare sync {@link Result}, memoizing so the
399
+ * underlying factory runs at most once. Prefer `await ra` (the thenable) or
400
+ * {@link match} in app code; use `resolve` when you need the tagged union back.
401
+ *
402
+ * @example
403
+ * ```ts
404
+ * const r = await load(id).resolve(); // Result<Data, LoadError>
405
+ * if (isOk(r)) use(r.value);
406
+ * ```
407
+ */
238
408
  resolve() {
239
409
  if (!this.promise) {
240
410
  this.promise = this.run();
@@ -261,6 +431,20 @@ function isCompatLike(v) {
261
431
  return typeof v === "object" && v !== null && "inner" in v && isResultLike(v.inner);
262
432
  }
263
433
 
434
+ // src/collections.ts
435
+ var combine = (results) => {
436
+ const values = [];
437
+ for (const result of results) {
438
+ if (isErr(result)) return err(result.error);
439
+ values.push(result.value);
440
+ }
441
+ return ok(values);
442
+ };
443
+ var combineTuple = (results) => (
444
+ // Runtime identical to combine; the cast restores per-index tuple types.
445
+ combine(results)
446
+ );
447
+
264
448
  // src/compat/neverthrow.ts
265
449
  var CompatResult = class _CompatResult {
266
450
  constructor(inner) {
@@ -298,16 +482,10 @@ var CompatResult = class _CompatResult {
298
482
  return new _CompatResult(flatMap(this.inner, (value) => fn(value).inner));
299
483
  }
300
484
  asyncAndThen(fn) {
301
- if (isErr(this.inner)) {
302
- return CompatResultAsync.err(this.inner.error);
303
- }
304
- return CompatResultAsync.fromInner(coerceToCoreAsync(fn(this.inner.value)));
485
+ return isErr(this.inner) ? CompatResultAsync.err(this.inner.error) : CompatResultAsync.fromInner(coerceToCoreAsync(fn(this.inner.value)));
305
486
  }
306
487
  orElse(fn) {
307
- if (isOk(this.inner)) {
308
- return new _CompatResult(this.inner);
309
- }
310
- return fn(this.inner.error);
488
+ return isOk(this.inner) ? new _CompatResult(this.inner) : fn(this.inner.error);
311
489
  }
312
490
  match(onOk, onErr) {
313
491
  return isOk(this.inner) ? onOk(this.inner.value) : onErr(this.inner.error);
@@ -398,10 +576,16 @@ var CompatResultAsync = class _CompatResultAsync {
398
576
  return this.inner.unwrapOr(defaultValue);
399
577
  }
400
578
  isOk() {
401
- return this.inner.isOk();
579
+ return this.inner.match(
580
+ () => true,
581
+ () => false
582
+ );
402
583
  }
403
584
  isErr() {
404
- return this.inner.isErr();
585
+ return this.inner.match(
586
+ () => false,
587
+ () => true
588
+ );
405
589
  }
406
590
  match(onOk, onErr) {
407
591
  return this.inner.match(onOk, onErr);
@@ -442,20 +626,20 @@ function coerceToCore(next) {
442
626
  return next;
443
627
  }
444
628
  var coerceToCoreAsync = (next) => next instanceof CompatResultAsync ? next.toCore() : next;
445
- var okAsync2 = CompatResultAsync.ok;
446
- var errAsync2 = CompatResultAsync.err;
447
- var fromPromise2 = CompatResultAsync.fromPromise;
448
- var fromSafePromise2 = CompatResultAsync.fromSafePromise;
629
+ var okAsync = CompatResultAsync.ok;
630
+ var errAsync = CompatResultAsync.err;
631
+ var fromPromise = CompatResultAsync.fromPromise;
632
+ var fromSafePromise = CompatResultAsync.fromSafePromise;
449
633
 
450
634
  exports.CompatResult = CompatResult;
451
635
  exports.CompatResultAsync = CompatResultAsync;
452
636
  exports.Result = Result;
453
637
  exports.ResultAsync = CompatResultAsync;
454
638
  exports.err = err2;
455
- exports.errAsync = errAsync2;
456
- exports.fromPromise = fromPromise2;
457
- exports.fromSafePromise = fromSafePromise2;
639
+ exports.errAsync = errAsync;
640
+ exports.fromPromise = fromPromise;
641
+ exports.fromSafePromise = fromSafePromise;
458
642
  exports.ok = ok2;
459
- exports.okAsync = okAsync2;
643
+ exports.okAsync = okAsync;
460
644
  //# sourceMappingURL=neverthrow.cjs.map
461
645
  //# sourceMappingURL=neverthrow.cjs.map