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