@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/DESIGN.md CHANGED
@@ -21,7 +21,7 @@ type Result<T, E> =
21
21
 
22
22
  - `flatMap` is the canonical bind and widens error types (`E | F`).
23
23
  - `match` is the canonical terminal collapse — positional, dual-form.
24
- - `fold({ ok, err })(result)` is the curried named-slot escape valve when positional `match` order is ambiguous at the call site.
24
+ - For files that also import `match` from `ts-pattern`, dissolve the collision with a namespace import (`import * as R from "@onrails/result"` → `R.match`).
25
25
  - `tap` / `tapErr` observe a track without changing the carried value.
26
26
  - `recover` binds the error track and may return a failed workflow back to success.
27
27
  - `pipe(value, ...fns)` is the variadic value-first pipe (up to 9 steps); `flow(...fns)` is the variadic point-free composition in `@onrails/result/pipe`.
@@ -69,7 +69,7 @@ Use `Railway` when named context removes nesting or positional tuple plumbing. P
69
69
  ## Combining results
70
70
 
71
71
  - `combine` / `combineTuple` — sync, first-Err wins.
72
- - `sequenceTupleAsync` — async, sequential, first-Err in input order.
72
+ - `ResultAsync.combineTuple` — async, sequential, first-Err in input order.
73
73
  - `parallelTupleAsync` — async, concurrent (branches overlap), first-Err in input order.
74
74
  - `ResultAsync.combine` — homogeneous async collection.
75
75
 
package/README.md CHANGED
@@ -141,9 +141,6 @@ import { match } from "ts-pattern";
141
141
  R.match(result, onOk, onErr);
142
142
  ```
143
143
 
144
- > [!NOTE]
145
- > `matchResult` is deprecated and will be removed in the next major version.
146
-
147
144
  `unwrapOk` and `unwrapErr` are test/assertion helpers. Prefer `match`, `isOk`, or `isErr` in production control flow.
148
145
 
149
146
  ```ts
package/RECIPES.md CHANGED
@@ -118,18 +118,17 @@ const saveOrder = (rawJson: string) =>
118
118
  flatMap((u) => (u.active ? ok(u) : err({ kind: "inactive" as const }))),
119
119
  mapErr((e) => ({ kind: "input" as const, cause: e })),
120
120
  tap((u) => log.info({ msg: "validated", userId: u.id })),
121
- (validated) =>
122
- asyncAfter(validated, (u) =>
123
- tryAsync(
124
- db.orders.insert({ userId: u.id, items: u.cart }),
125
- (e): DbError => ({ kind: "db", cause: e }),
126
- ),
121
+ asyncAfter((u) =>
122
+ tryAsync(
123
+ db.orders.insert({ userId: u.id, items: u.cart }),
124
+ (e): DbError => ({ kind: "db", cause: e }),
127
125
  ),
126
+ ),
128
127
  );
129
128
  // ResultAsync<OrderRow, { kind: "input"; cause: ... } | DbError>
130
129
  ```
131
130
 
132
- The last step isn't perfectly point-free because `asyncAfter` is value-first by design (it takes the sync Result, then the async bridge). That's fine — `pipe` slots arbitrary `(prev) => next` functions, point-free or not.
131
+ `asyncAfter` is dual-form like the rest of the core API: the data-last call `asyncAfter((u) => ...)` is the sync→async bridge that drops straight into `pipe`. The final step stays point-free — `pipe` feeds the upstream `Result` into the curried bridge automatically.
133
132
 
134
133
  ---
135
134
 
@@ -306,12 +305,10 @@ const fetchWithBackoff = (url: string) =>
306
305
  pipe(
307
306
  url,
308
307
  fetchSync, // Result<Body, NetworkError | RateLimit | Fatal>
309
- recover((e: NetworkError | RateLimit | Fatal) => {
310
- if (e.kind === "network" && e.retryable) return fetchSync(url);
311
- if (e.kind === "rate_limit") return err(e); // bubble up — caller schedules retry
312
- if (e.kind === "fatal") return err(e); // unrecoverable
313
- return err(e);
314
- }),
308
+ recover((e: NetworkError | RateLimit | Fatal) =>
309
+ // only retryable network errors do work; everything else bubbles up
310
+ e.kind === "network" && e.retryable ? fetchSync(url) : err(e),
311
+ ),
315
312
  );
316
313
  // Result<Body, NetworkError | RateLimit | Fatal>
317
314
  ```
@@ -434,6 +431,8 @@ const loadSummary = flow(
434
431
  // (id: string) => ResultAsync<Summary, Error>
435
432
  ```
436
433
 
434
+ The middle step can't go point-free: `profile` is reused across both the `flatMap` and the inner `map`. When a value is referenced by two or more async steps, this nested-closure shape is the cost of staying in `flow`. If it gets deeper, drop to the [`tryGen` escape hatch](#7-trygen-escape-hatch-for-branchy-logic) (Tier 3) and rejoin the pipe.
435
+
437
436
  ---
438
437
 
439
438
  ## 13. Functional Railway pipelines (`railway` + named steps)
@@ -441,7 +440,7 @@ const loadSummary = flow(
441
440
  Use `railway(input, ...steps)` from `@onrails/result/railway` to build a multi-step async pipeline using point-free reusable step wrappers. If any step is async, the entire pipeline resolves to a `ResultAsync`.
442
441
 
443
442
  ```ts
444
- import { railway, parseWith, fromPromiseNamed, deriveNamed, select } from "@onrails/result/railway";
443
+ import { railway, parseNamed, fromPromiseNamed, deriveNamed, select } from "@onrails/result/railway";
445
444
  import { type ResultAsync } from "@onrails/result";
446
445
 
447
446
  type Dashboard = { title: string };
@@ -457,7 +456,7 @@ declare const toError: (e: unknown) => Error;
457
456
  const loadDashboard = (rawId: unknown): ResultAsync<Dashboard, Error> =>
458
457
  railway(
459
458
  rawId,
460
- parseWith(IdSchema, toError).as("id"),
459
+ parseNamed("id", IdSchema, toError),
461
460
  fromPromiseNamed(
462
461
  "profile",
463
462
  ({ id }: IdContext) => fetchProfile(id),
@@ -470,6 +469,8 @@ const loadDashboard = (rawId: unknown): ResultAsync<Dashboard, Error> =>
470
469
  );
471
470
  ```
472
471
 
472
+ Every step now reads name-first (`parseNamed("id", …)`, `fromPromiseNamed("profile", …)`, `deriveNamed("title", …)`) so the field names line up at the left edge. `parseWith(schema).as("id")` remains for the fluent trailing-name style.
473
+
473
474
  ---
474
475
 
475
476
  ## When NOT to go point-free
package/dist/$.cjs ADDED
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ // src/result.ts
4
+ var err = (error) => ({
5
+ _tag: "Err",
6
+ error
7
+ });
8
+ var isErr = (result) => result._tag === "Err";
9
+
10
+ // src/try-gen.ts
11
+ var ErrSignal = class {
12
+ constructor(error) {
13
+ this.error = error;
14
+ }
15
+ error;
16
+ _tag = "ErrSignal";
17
+ };
18
+ var yieldResult = (result) => {
19
+ if (isErr(result)) {
20
+ throw new ErrSignal(result.error);
21
+ }
22
+ return result.value;
23
+ };
24
+ var $ = yieldResult;
25
+ var tryGen = (fn) => {
26
+ try {
27
+ return fn();
28
+ } catch (error) {
29
+ if (error instanceof ErrSignal) {
30
+ return err(error.error);
31
+ }
32
+ throw error;
33
+ }
34
+ };
35
+
36
+ exports.$ = $;
37
+ exports.tryGen = tryGen;
38
+ exports.yieldResult = yieldResult;
39
+ //# sourceMappingURL=$.cjs.map
40
+ //# sourceMappingURL=$.cjs.map
package/dist/$.cjs.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/result.ts","../src/try-gen.ts"],"names":[],"mappings":";;;AA2BO,IAAM,GAAA,GAAM,CAAyB,KAAA,MAA4B;AAAA,EACtE,IAAA,EAAM,KAAA;AAAA,EACN;AACF,CAAA,CAAA;AAwBO,IAAM,KAAA,GAAQ,CAAO,MAAA,KAA8C,MAAA,CAAO,IAAA,KAAS,KAAA;;;AClD1F,IAAM,YAAN,MAAmB;AAAA,EAEjB,YAAqB,KAAA,EAAU;AAAV,IAAA,IAAA,CAAA,KAAA,GAAA,KAAA;AAAA,EAAW;AAAA,EAAX,KAAA;AAAA,EADZ,IAAA,GAAO,WAAA;AAElB,CAAA;AAmBO,IAAM,WAAA,GAAc,CAAO,MAAA,KAA4B;AAC5D,EAAA,IAAI,KAAA,CAAM,MAAM,CAAA,EAAG;AACjB,IAAA,MAAM,IAAI,SAAA,CAAU,MAAA,CAAO,KAAK,CAAA;AAAA,EAClC;AACA,EAAA,OAAO,MAAA,CAAO,KAAA;AAChB;AAeO,IAAM,CAAA,GAAI;AAcV,IAAM,MAAA,GAAS,CAAO,EAAA,KAAyC;AACpE,EAAA,IAAI;AACF,IAAA,OAAO,EAAA,EAAG;AAAA,EACZ,SAAS,KAAA,EAAO;AACd,IAAA,IAAI,iBAAiB,SAAA,EAAW;AAC9B,MAAA,OAAO,GAAA,CAAI,MAAM,KAAK,CAAA;AAAA,IACxB;AACA,IAAA,MAAM,KAAA;AAAA,EACR;AACF","file":"$.cjs","sourcesContent":["import type { Err, Ok, Result } from \"./types.js\";\n\nexport type { Err, Ok, Result } from \"./types.js\";\n\n/**\n * Lifts a value into the success track.\n *\n * @example\n * ```ts\n * const r = ok(42); // Result<number, never>\n * const typed: Result<number, \"parse\"> = ok(1);\n * ```\n */\nexport const ok = <T, E = never>(value: T): Result<T, E> => ({\n _tag: \"Ok\",\n value,\n});\n\n/**\n * Lifts a value into the error track.\n *\n * @example\n * ```ts\n * const r = err({ kind: \"parse\", message: \"bad json\" });\n * // Result<never, { kind: \"parse\"; message: string }>\n * ```\n */\nexport const err = <T = never, E = unknown>(error: E): Result<T, E> => ({\n _tag: \"Err\",\n error,\n});\n\n/**\n * Type-narrowing predicate: returns `true` when the result is `Ok`.\n *\n * @example\n * ```ts\n * if (isOk(r)) {\n * console.log(r.value); // narrowed to Ok branch\n * }\n * ```\n */\nexport const isOk = <T, E>(result: Result<T, E>): result is Ok<T, E> => result._tag === \"Ok\";\n\n/**\n * Type-narrowing predicate: returns `true` when the result is `Err`.\n *\n * @example\n * ```ts\n * if (isErr(r)) {\n * metrics.inc(\"error\", { kind: r.error.kind });\n * }\n * ```\n */\nexport const isErr = <T, E>(result: Result<T, E>): result is Err<T, E> => result._tag === \"Err\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Dual-form helpers — each export accepts either shape:\n// data-first: `map(result, fn)`\n// curried: `map(fn)(result)`\n// Arity at the call site selects the overload.\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst mapImpl = <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 * Transform the `Ok` value, passing `Err` through unchanged. Dual-form:\n * call data-first or curried (for use with {@link pipe}).\n *\n * @example\n * ```ts\n * map(ok(2), (n) => n * 3); // Ok 6 — data-first\n * pipe(ok(\"x\"), map((s) => s.length));// Ok 1 — curried\n * ```\n */\nexport function map<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E>;\nexport function map<T, U>(fn: (value: T) => U): <E>(result: Result<T, E>) => Result<U, E>;\nexport function map(\n ...args: [Result<unknown, unknown>, (value: unknown) => unknown] | [(value: unknown) => unknown]\n): unknown {\n if (args.length === 2) return mapImpl(args[0], args[1]);\n const fn = args[0];\n return (result: Result<unknown, unknown>) => mapImpl(result, fn);\n}\n\nconst mapErrImpl = <T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F> =>\n isErr(result) ? err(fn(result.error)) : ok(result.value);\n\n/**\n * Transform the `Err` value, passing `Ok` through unchanged. Useful for\n * unifying heterogeneous failure types into one app-level union.\n *\n * @example\n * ```ts\n * type AppError = { kind: \"http\"; status: number } | { kind: \"parse\" };\n * pipe(\n * fetchSync(url), // Result<Body, { status: number }>\n * mapErr((e): AppError => ({ kind: \"http\", status: e.status })),\n * );\n * ```\n */\nexport function mapErr<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F>;\nexport function mapErr<E, F>(fn: (error: E) => F): <T>(result: Result<T, E>) => Result<T, F>;\nexport function mapErr(\n ...args: [Result<unknown, unknown>, (error: unknown) => unknown] | [(error: unknown) => unknown]\n): unknown {\n if (args.length === 2) return mapErrImpl(args[0], args[1]);\n const fn = args[0];\n return (result: Result<unknown, unknown>) => mapErrImpl(result, fn);\n}\n\nconst bimapImpl = <T, U, E, F>(\n result: Result<T, E>,\n onOk: (value: T) => U,\n onErr: (error: E) => F,\n): Result<U, F> => (isOk(result) ? ok(onOk(result.value)) : err(onErr(result.error)));\n\n/**\n * Transform both tracks at once — `Ok` via `onOk`, `Err` via `onErr`.\n * Equivalent to `mapErr(onErr)(map(onOk)(result))` but in one pass.\n *\n * @example\n * ```ts\n * bimap(parsed, (cfg) => cfg.name, (e) => ({ kind: \"input\", cause: e }));\n * ```\n */\nexport function bimap<T, U, E, F>(\n result: Result<T, E>,\n onOk: (value: T) => U,\n onErr: (error: E) => F,\n): Result<U, F>;\nexport function bimap<T, U, E, F>(\n onOk: (value: T) => U,\n onErr: (error: E) => F,\n): (result: Result<T, E>) => Result<U, F>;\nexport function bimap(\n ...args:\n | [Result<unknown, unknown>, (value: unknown) => unknown, (error: unknown) => unknown]\n | [(value: unknown) => unknown, (error: unknown) => unknown]\n): unknown {\n if (args.length === 3) return bimapImpl(args[0], args[1], args[2]);\n const [onOk, onErr] = args;\n return (result: Result<unknown, unknown>) => bimapImpl(result, onOk, onErr);\n}\n\nconst flatMapImpl = <T, U, E, F>(\n result: Result<T, E>,\n fn: (value: T) => Result<U, F>,\n): Result<U, E | F> => (isOk(result) ? fn(result.value) : err(result.error));\n\n/**\n * Canonical bind (Fantasy Land `chain`). Chains a Result-returning step,\n * widening the error union to `E | F`. Short-circuits on `Err`.\n *\n * @example\n * ```ts\n * flatMap(parseInput(raw), (data) =>\n * data.id != null ? ok(data) : err({ kind: \"missing_id\" as const }),\n * );\n * // Result<Data, ParseError | { kind: \"missing_id\" }>\n * ```\n */\nexport function flatMap<T, U, E, F>(\n result: Result<T, E>,\n fn: (value: T) => Result<U, F>,\n): Result<U, E | F>;\nexport function flatMap<T, U, F>(\n fn: (value: T) => Result<U, F>,\n): <E>(result: Result<T, E>) => Result<U, E | F>;\nexport function flatMap(\n ...args:\n | [Result<unknown, unknown>, (value: unknown) => Result<unknown, unknown>]\n | [(value: unknown) => Result<unknown, unknown>]\n): unknown {\n if (args.length === 2) return flatMapImpl(args[0], args[1]);\n const fn = args[0];\n return (result: Result<unknown, unknown>) => flatMapImpl(result, fn);\n}\n\nconst recoverImpl = <T, E, F>(\n result: Result<T, E>,\n fn: (error: E) => Result<T, F>,\n): Result<T, F> => (isErr(result) ? fn(result.error) : ok(result.value));\n\n/**\n * Error-track bind — runs `fn` only when the result is `Err`, allowing\n * a failed workflow to recover to `Ok` or remap the failure. Mirror of\n * {@link flatMap} on the error channel.\n *\n * @example\n * ```ts\n * recover(networkResult, (e) =>\n * e.kind === \"rate_limit\" ? ok(cachedBody) : err(e),\n * );\n * ```\n */\nexport function recover<T, E, F>(\n result: Result<T, E>,\n fn: (error: E) => Result<T, F>,\n): Result<T, F>;\nexport function recover<T, E, F>(\n fn: (error: E) => Result<T, F>,\n): (result: Result<T, E>) => Result<T, F>;\nexport function recover(\n ...args:\n | [Result<unknown, unknown>, (error: unknown) => Result<unknown, unknown>]\n | [(error: unknown) => Result<unknown, unknown>]\n): unknown {\n if (args.length === 2) return recoverImpl(args[0], args[1]);\n const fn = args[0];\n return (result: Result<unknown, unknown>) => recoverImpl(result, fn);\n}\n\nconst tapImpl = <T, E>(result: Result<T, E>, fn: (value: T) => void): Result<T, E> => {\n if (isOk(result)) fn(result.value);\n return result;\n};\n\n/**\n * Observe the `Ok` value for side effects (logging, metrics) without\n * changing the carried value. Passes `Err` through untouched.\n *\n * @example\n * ```ts\n * pipe(\n * parseConfig(raw),\n * tap((cfg) => log.info({ msg: \"parsed\", name: cfg.name })),\n * flatMap(validate),\n * );\n * ```\n */\nexport function tap<T, E>(result: Result<T, E>, fn: (value: T) => void): Result<T, E>;\nexport function tap<T>(fn: (value: T) => void): <E>(result: Result<T, E>) => Result<T, E>;\nexport function tap(\n ...args: [Result<unknown, unknown>, (value: unknown) => void] | [(value: unknown) => void]\n): unknown {\n if (args.length === 2) return tapImpl(args[0], args[1]);\n const fn = args[0];\n return (result: Result<unknown, unknown>) => tapImpl(result, fn);\n}\n\nconst tapErrImpl = <T, E>(result: Result<T, E>, fn: (error: E) => void): Result<T, E> => {\n if (isErr(result)) fn(result.error);\n return result;\n};\n\n/**\n * Observe the `Err` value for side effects (logging, metrics) without\n * changing the carried error. Passes `Ok` through untouched.\n *\n * @example\n * ```ts\n * pipe(\n * loadUser(id),\n * tapErr((e) => metrics.inc(\"user.load.fail\", { kind: e.kind })),\n * );\n * ```\n */\nexport function tapErr<T, E>(result: Result<T, E>, fn: (error: E) => void): Result<T, E>;\nexport function tapErr<E>(fn: (error: E) => void): <T>(result: Result<T, E>) => Result<T, E>;\nexport function tapErr(\n ...args: [Result<unknown, unknown>, (error: unknown) => void] | [(error: unknown) => void]\n): unknown {\n if (args.length === 2) return tapErrImpl(args[0], args[1]);\n const fn = args[0];\n return (result: Result<unknown, unknown>) => tapErrImpl(result, fn);\n}\n\nconst matchImpl = <T, E, U>(\n result: Result<T, E>,\n onOk: (value: T) => U,\n onErr: (error: E) => U,\n): U => (isOk(result) ? onOk(result.value) : onErr(result.error));\n\n/**\n * Terminal collapse — fold both tracks into a single value. Dual-form:\n * 3-args data-first, 2-args curried for {@link pipe}. Returns whatever\n * the handlers return.\n *\n * For files that also import `match` from `ts-pattern`, use a namespace\n * import (`import * as R from \"@onrails/result\"` → `R.match`) to dissolve\n * the collision.\n *\n * @example\n * ```ts\n * const html = match(parsed, (cfg) => render(cfg), (e) => renderError(e));\n * ```\n */\nexport function match<T, E, U>(\n result: Result<T, E>,\n onOk: (value: T) => U,\n onErr: (error: E) => U,\n): U;\nexport function match<T, E, U>(\n onOk: (value: T) => U,\n onErr: (error: E) => U,\n): (result: Result<T, E>) => U;\nexport function match(\n ...args:\n | [Result<unknown, unknown>, (value: unknown) => unknown, (error: unknown) => unknown]\n | [(value: unknown) => unknown, (error: unknown) => unknown]\n): unknown {\n if (args.length === 3) return matchImpl(args[0], args[1], args[2]);\n const [onOk, onErr] = args;\n return (result: Result<unknown, unknown>) => matchImpl(result, onOk, onErr);\n}\n\n/**\n * Returns the `Ok` value, or `defaultValue` when the result is `Err`.\n *\n * @example\n * ```ts\n * unwrapOr(parsedSetting, \"default-value\");\n * ```\n */\nexport const unwrapOr = <T, E>(result: Result<T, E>, defaultValue: T): T =>\n isOk(result) ? result.value : defaultValue;\n\n/**\n * Test/assert helper — returns the `Ok` value, or **throws the original `Err`\n * value** when called on an `Err`. This is the assertion tier (RFC 0001 §4):\n * intended for `*.spec.ts` / `*.test.ts`, where throwing fails the test loudly.\n * In business logic prefer {@link match} or {@link unwrapOr}; the lint plugins\n * flag `unwrapOk` outside test files.\n *\n * @returns the unwrapped `Ok` value\n * @throws the carried `Err` value when the result is `Err`\n *\n * @example\n * ```ts\n * // in a *.spec.ts\n * expect(unwrapOk(ok(5))).toBe(5);\n * expect(() => unwrapOk(err(error))).toThrow(error);\n * ```\n */\nexport function unwrapOk<T, E>(result: Result<T, E>): T {\n if (isErr(result)) throw result.error;\n return result.value;\n}\n\n/**\n * Test/assert helper — returns the `Err` value, or **throws a `TypeError`**\n * when called on an `Ok`. Mirror of {@link unwrapOk} on the error track and\n * part of the same assertion tier (RFC 0001 §4): intended for `*.spec.ts` /\n * `*.test.ts`. Prefer {@link match} / {@link unwrapOr} in business logic; the\n * lint plugins flag `unwrapErr` outside test files.\n *\n * @returns the unwrapped `Err` value\n * @throws `TypeError` when the result is `Ok`\n *\n * @example\n * ```ts\n * // in a *.spec.ts\n * expect(unwrapErr(err(\"x\"))).toBe(\"x\");\n * expect(() => unwrapErr(ok(5))).toThrow(TypeError);\n * ```\n */\nexport function unwrapErr<T, E>(result: Result<T, E>): E {\n if (isOk(result)) throw new TypeError(\"unwrapErr called on Ok\");\n return result.error;\n}\n\n/**\n * Wraps a throwing sync function, returning a function that produces a\n * {@link Result} instead of throwing. Thrown errors pass through `onThrow` to\n * become a typed `Err`; a normal return becomes `Ok`. The neverthrow analogue\n * is `Result.fromThrowable`.\n *\n * @param fn - the throwing function to wrap\n * @param onThrow - maps a thrown value to the `Err` channel\n * @returns a function with `fn`'s parameters that returns `Result<ReturnType, E>`\n *\n * @example\n * ```ts\n * type ParseError = { kind: \"parse\"; message: string };\n * const parse = trySync(\n * JSON.parse,\n * (e): ParseError => ({ kind: \"parse\", message: String(e) }),\n * );\n * parse(\"{}\"); // Ok({})\n * parse(\"nope\"); // Err({ kind: \"parse\", … })\n * ```\n */\nexport function trySync<A extends readonly unknown[], T, E>(\n fn: (...args: A) => T,\n onThrow: (error: unknown) => E,\n): (...args: A) => Result<T, E>;\nexport function trySync<F extends (...args: never) => unknown, E>(\n fn: F,\n onThrow: (error: unknown) => E,\n): (...args: Parameters<F>) => Result<ReturnType<F>, E>;\nexport function trySync(\n fn: (...args: never) => unknown,\n onThrow: (error: unknown) => unknown,\n): (...args: never) => Result<unknown, unknown> {\n return (...args: never) => {\n try {\n return ok(fn(...args));\n } catch (error) {\n return err(onThrow(error));\n }\n };\n}\n\n/**\n * Variadic value-first pipe — threads `value` through up to nine unary fns,\n * left-to-right. Use {@link pipe} when you already have a starting value;\n * use {@link flow} to define a reusable composed function with no value yet.\n *\n * @example\n * ```ts\n * pipe(\n * parseConfig(raw),\n * map((cfg) => cfg.name),\n * flatMap((name) => (name ? ok(name) : err({ kind: \"empty\" as const }))),\n * tap(log),\n * );\n * ```\n */\nexport function pipe<A>(value: A): A;\nexport function pipe<A, B>(value: A, ab: (a: A) => B): B;\nexport function pipe<A, B, C>(value: A, ab: (a: A) => B, bc: (b: B) => C): C;\nexport function pipe<A, B, C, D>(value: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D): D;\nexport function pipe<A, B, C, D, E>(\n value: A,\n ab: (a: A) => B,\n bc: (b: B) => C,\n cd: (c: C) => D,\n de: (d: D) => E,\n): E;\nexport function pipe<A, B, C, D, E, F>(\n value: A,\n ab: (a: A) => B,\n bc: (b: B) => C,\n cd: (c: C) => D,\n de: (d: D) => E,\n ef: (e: E) => F,\n): F;\nexport function pipe<A, B, C, D, E, F, G>(\n value: A,\n ab: (a: A) => B,\n bc: (b: B) => C,\n cd: (c: C) => D,\n de: (d: D) => E,\n ef: (e: E) => F,\n fg: (f: F) => G,\n): G;\nexport function pipe<A, B, C, D, E, F, G, H>(\n value: A,\n ab: (a: A) => B,\n bc: (b: B) => C,\n cd: (c: C) => D,\n de: (d: D) => E,\n ef: (e: E) => F,\n fg: (f: F) => G,\n gh: (g: G) => H,\n): H;\nexport function pipe<A, B, C, D, E, F, G, H, I>(\n value: A,\n ab: (a: A) => B,\n bc: (b: B) => C,\n cd: (c: C) => D,\n de: (d: D) => E,\n ef: (e: E) => F,\n fg: (f: F) => G,\n gh: (g: G) => H,\n hi: (h: H) => I,\n): I;\nexport function pipe(value: unknown, ...fns: ReadonlyArray<(x: unknown) => unknown>): unknown {\n let acc = value;\n for (const fn of fns) {\n acc = fn(acc);\n }\n return acc;\n}\n","import { err, isErr } from \"./result.js\";\nimport type { Result } from \"./types.js\";\n\n/** Internal control-flow signal — not part of public error types */\nclass ErrSignal<E> {\n readonly _tag = \"ErrSignal\" as const;\n constructor(readonly error: E) {}\n}\n\n/**\n * Unwraps a {@link Result} inside a {@link tryGen} block — the sync analogue of\n * Rust's `?` operator. On `Ok` it returns the value; on `Err` it short-circuits\n * the enclosing `tryGen`, which returns that `Err`. Only valid inside `tryGen`;\n * prefer {@link flatMap} / `pipe` for long pipelines.\n *\n * @returns the unwrapped `Ok` value\n *\n * @example\n * ```ts\n * const out = tryGen(() => {\n * const a = yieldResult(parseA()); // unwrap or short-circuit\n * const b = yieldResult(parseB());\n * return ok(a + b);\n * });\n * ```\n */\nexport const yieldResult = <T, E>(result: Result<T, E>): T => {\n if (isErr(result)) {\n throw new ErrSignal(result.error);\n }\n return result.value;\n};\n\n/**\n * Terse alias of {@link yieldResult} (mirrors Rust's `?`), for compact\n * {@link tryGen} blocks.\n *\n * @example\n * ```ts\n * const out = tryGen(() => {\n * const a = $(parseA());\n * const b = $(parseB());\n * return ok(a + b);\n * });\n * ```\n */\nexport const $ = yieldResult;\n\n/**\n * Run a block that uses {@link yieldResult}; returns the final `Result` or the first Err.\n *\n * @example\n * ```ts\n * const out = tryGen(() => {\n * const a = $(parseA());\n * const b = $(parseB());\n * return ok(a + b);\n * });\n * ```\n */\nexport const tryGen = <T, E>(fn: () => Result<T, E>): Result<T, E> => {\n try {\n return fn();\n } catch (error) {\n if (error instanceof ErrSignal) {\n return err(error.error);\n }\n throw error;\n }\n};\n"]}
package/dist/$.js ADDED
@@ -0,0 +1,36 @@
1
+ // src/result.ts
2
+ var err = (error) => ({
3
+ _tag: "Err",
4
+ error
5
+ });
6
+ var isErr = (result) => result._tag === "Err";
7
+
8
+ // src/try-gen.ts
9
+ var ErrSignal = class {
10
+ constructor(error) {
11
+ this.error = error;
12
+ }
13
+ error;
14
+ _tag = "ErrSignal";
15
+ };
16
+ var yieldResult = (result) => {
17
+ if (isErr(result)) {
18
+ throw new ErrSignal(result.error);
19
+ }
20
+ return result.value;
21
+ };
22
+ var $ = yieldResult;
23
+ var tryGen = (fn) => {
24
+ try {
25
+ return fn();
26
+ } catch (error) {
27
+ if (error instanceof ErrSignal) {
28
+ return err(error.error);
29
+ }
30
+ throw error;
31
+ }
32
+ };
33
+
34
+ export { $, tryGen, yieldResult };
35
+ //# sourceMappingURL=$.js.map
36
+ //# sourceMappingURL=$.js.map
package/dist/$.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/result.ts","../src/try-gen.ts"],"names":[],"mappings":";AA2BO,IAAM,GAAA,GAAM,CAAyB,KAAA,MAA4B;AAAA,EACtE,IAAA,EAAM,KAAA;AAAA,EACN;AACF,CAAA,CAAA;AAwBO,IAAM,KAAA,GAAQ,CAAO,MAAA,KAA8C,MAAA,CAAO,IAAA,KAAS,KAAA;;;AClD1F,IAAM,YAAN,MAAmB;AAAA,EAEjB,YAAqB,KAAA,EAAU;AAAV,IAAA,IAAA,CAAA,KAAA,GAAA,KAAA;AAAA,EAAW;AAAA,EAAX,KAAA;AAAA,EADZ,IAAA,GAAO,WAAA;AAElB,CAAA;AAmBO,IAAM,WAAA,GAAc,CAAO,MAAA,KAA4B;AAC5D,EAAA,IAAI,KAAA,CAAM,MAAM,CAAA,EAAG;AACjB,IAAA,MAAM,IAAI,SAAA,CAAU,MAAA,CAAO,KAAK,CAAA;AAAA,EAClC;AACA,EAAA,OAAO,MAAA,CAAO,KAAA;AAChB;AAeO,IAAM,CAAA,GAAI;AAcV,IAAM,MAAA,GAAS,CAAO,EAAA,KAAyC;AACpE,EAAA,IAAI;AACF,IAAA,OAAO,EAAA,EAAG;AAAA,EACZ,SAAS,KAAA,EAAO;AACd,IAAA,IAAI,iBAAiB,SAAA,EAAW;AAC9B,MAAA,OAAO,GAAA,CAAI,MAAM,KAAK,CAAA;AAAA,IACxB;AACA,IAAA,MAAM,KAAA;AAAA,EACR;AACF","file":"$.js","sourcesContent":["import type { Err, Ok, Result } from \"./types.js\";\n\nexport type { Err, Ok, Result } from \"./types.js\";\n\n/**\n * Lifts a value into the success track.\n *\n * @example\n * ```ts\n * const r = ok(42); // Result<number, never>\n * const typed: Result<number, \"parse\"> = ok(1);\n * ```\n */\nexport const ok = <T, E = never>(value: T): Result<T, E> => ({\n _tag: \"Ok\",\n value,\n});\n\n/**\n * Lifts a value into the error track.\n *\n * @example\n * ```ts\n * const r = err({ kind: \"parse\", message: \"bad json\" });\n * // Result<never, { kind: \"parse\"; message: string }>\n * ```\n */\nexport const err = <T = never, E = unknown>(error: E): Result<T, E> => ({\n _tag: \"Err\",\n error,\n});\n\n/**\n * Type-narrowing predicate: returns `true` when the result is `Ok`.\n *\n * @example\n * ```ts\n * if (isOk(r)) {\n * console.log(r.value); // narrowed to Ok branch\n * }\n * ```\n */\nexport const isOk = <T, E>(result: Result<T, E>): result is Ok<T, E> => result._tag === \"Ok\";\n\n/**\n * Type-narrowing predicate: returns `true` when the result is `Err`.\n *\n * @example\n * ```ts\n * if (isErr(r)) {\n * metrics.inc(\"error\", { kind: r.error.kind });\n * }\n * ```\n */\nexport const isErr = <T, E>(result: Result<T, E>): result is Err<T, E> => result._tag === \"Err\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Dual-form helpers — each export accepts either shape:\n// data-first: `map(result, fn)`\n// curried: `map(fn)(result)`\n// Arity at the call site selects the overload.\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst mapImpl = <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 * Transform the `Ok` value, passing `Err` through unchanged. Dual-form:\n * call data-first or curried (for use with {@link pipe}).\n *\n * @example\n * ```ts\n * map(ok(2), (n) => n * 3); // Ok 6 — data-first\n * pipe(ok(\"x\"), map((s) => s.length));// Ok 1 — curried\n * ```\n */\nexport function map<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E>;\nexport function map<T, U>(fn: (value: T) => U): <E>(result: Result<T, E>) => Result<U, E>;\nexport function map(\n ...args: [Result<unknown, unknown>, (value: unknown) => unknown] | [(value: unknown) => unknown]\n): unknown {\n if (args.length === 2) return mapImpl(args[0], args[1]);\n const fn = args[0];\n return (result: Result<unknown, unknown>) => mapImpl(result, fn);\n}\n\nconst mapErrImpl = <T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F> =>\n isErr(result) ? err(fn(result.error)) : ok(result.value);\n\n/**\n * Transform the `Err` value, passing `Ok` through unchanged. Useful for\n * unifying heterogeneous failure types into one app-level union.\n *\n * @example\n * ```ts\n * type AppError = { kind: \"http\"; status: number } | { kind: \"parse\" };\n * pipe(\n * fetchSync(url), // Result<Body, { status: number }>\n * mapErr((e): AppError => ({ kind: \"http\", status: e.status })),\n * );\n * ```\n */\nexport function mapErr<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F>;\nexport function mapErr<E, F>(fn: (error: E) => F): <T>(result: Result<T, E>) => Result<T, F>;\nexport function mapErr(\n ...args: [Result<unknown, unknown>, (error: unknown) => unknown] | [(error: unknown) => unknown]\n): unknown {\n if (args.length === 2) return mapErrImpl(args[0], args[1]);\n const fn = args[0];\n return (result: Result<unknown, unknown>) => mapErrImpl(result, fn);\n}\n\nconst bimapImpl = <T, U, E, F>(\n result: Result<T, E>,\n onOk: (value: T) => U,\n onErr: (error: E) => F,\n): Result<U, F> => (isOk(result) ? ok(onOk(result.value)) : err(onErr(result.error)));\n\n/**\n * Transform both tracks at once — `Ok` via `onOk`, `Err` via `onErr`.\n * Equivalent to `mapErr(onErr)(map(onOk)(result))` but in one pass.\n *\n * @example\n * ```ts\n * bimap(parsed, (cfg) => cfg.name, (e) => ({ kind: \"input\", cause: e }));\n * ```\n */\nexport function bimap<T, U, E, F>(\n result: Result<T, E>,\n onOk: (value: T) => U,\n onErr: (error: E) => F,\n): Result<U, F>;\nexport function bimap<T, U, E, F>(\n onOk: (value: T) => U,\n onErr: (error: E) => F,\n): (result: Result<T, E>) => Result<U, F>;\nexport function bimap(\n ...args:\n | [Result<unknown, unknown>, (value: unknown) => unknown, (error: unknown) => unknown]\n | [(value: unknown) => unknown, (error: unknown) => unknown]\n): unknown {\n if (args.length === 3) return bimapImpl(args[0], args[1], args[2]);\n const [onOk, onErr] = args;\n return (result: Result<unknown, unknown>) => bimapImpl(result, onOk, onErr);\n}\n\nconst flatMapImpl = <T, U, E, F>(\n result: Result<T, E>,\n fn: (value: T) => Result<U, F>,\n): Result<U, E | F> => (isOk(result) ? fn(result.value) : err(result.error));\n\n/**\n * Canonical bind (Fantasy Land `chain`). Chains a Result-returning step,\n * widening the error union to `E | F`. Short-circuits on `Err`.\n *\n * @example\n * ```ts\n * flatMap(parseInput(raw), (data) =>\n * data.id != null ? ok(data) : err({ kind: \"missing_id\" as const }),\n * );\n * // Result<Data, ParseError | { kind: \"missing_id\" }>\n * ```\n */\nexport function flatMap<T, U, E, F>(\n result: Result<T, E>,\n fn: (value: T) => Result<U, F>,\n): Result<U, E | F>;\nexport function flatMap<T, U, F>(\n fn: (value: T) => Result<U, F>,\n): <E>(result: Result<T, E>) => Result<U, E | F>;\nexport function flatMap(\n ...args:\n | [Result<unknown, unknown>, (value: unknown) => Result<unknown, unknown>]\n | [(value: unknown) => Result<unknown, unknown>]\n): unknown {\n if (args.length === 2) return flatMapImpl(args[0], args[1]);\n const fn = args[0];\n return (result: Result<unknown, unknown>) => flatMapImpl(result, fn);\n}\n\nconst recoverImpl = <T, E, F>(\n result: Result<T, E>,\n fn: (error: E) => Result<T, F>,\n): Result<T, F> => (isErr(result) ? fn(result.error) : ok(result.value));\n\n/**\n * Error-track bind — runs `fn` only when the result is `Err`, allowing\n * a failed workflow to recover to `Ok` or remap the failure. Mirror of\n * {@link flatMap} on the error channel.\n *\n * @example\n * ```ts\n * recover(networkResult, (e) =>\n * e.kind === \"rate_limit\" ? ok(cachedBody) : err(e),\n * );\n * ```\n */\nexport function recover<T, E, F>(\n result: Result<T, E>,\n fn: (error: E) => Result<T, F>,\n): Result<T, F>;\nexport function recover<T, E, F>(\n fn: (error: E) => Result<T, F>,\n): (result: Result<T, E>) => Result<T, F>;\nexport function recover(\n ...args:\n | [Result<unknown, unknown>, (error: unknown) => Result<unknown, unknown>]\n | [(error: unknown) => Result<unknown, unknown>]\n): unknown {\n if (args.length === 2) return recoverImpl(args[0], args[1]);\n const fn = args[0];\n return (result: Result<unknown, unknown>) => recoverImpl(result, fn);\n}\n\nconst tapImpl = <T, E>(result: Result<T, E>, fn: (value: T) => void): Result<T, E> => {\n if (isOk(result)) fn(result.value);\n return result;\n};\n\n/**\n * Observe the `Ok` value for side effects (logging, metrics) without\n * changing the carried value. Passes `Err` through untouched.\n *\n * @example\n * ```ts\n * pipe(\n * parseConfig(raw),\n * tap((cfg) => log.info({ msg: \"parsed\", name: cfg.name })),\n * flatMap(validate),\n * );\n * ```\n */\nexport function tap<T, E>(result: Result<T, E>, fn: (value: T) => void): Result<T, E>;\nexport function tap<T>(fn: (value: T) => void): <E>(result: Result<T, E>) => Result<T, E>;\nexport function tap(\n ...args: [Result<unknown, unknown>, (value: unknown) => void] | [(value: unknown) => void]\n): unknown {\n if (args.length === 2) return tapImpl(args[0], args[1]);\n const fn = args[0];\n return (result: Result<unknown, unknown>) => tapImpl(result, fn);\n}\n\nconst tapErrImpl = <T, E>(result: Result<T, E>, fn: (error: E) => void): Result<T, E> => {\n if (isErr(result)) fn(result.error);\n return result;\n};\n\n/**\n * Observe the `Err` value for side effects (logging, metrics) without\n * changing the carried error. Passes `Ok` through untouched.\n *\n * @example\n * ```ts\n * pipe(\n * loadUser(id),\n * tapErr((e) => metrics.inc(\"user.load.fail\", { kind: e.kind })),\n * );\n * ```\n */\nexport function tapErr<T, E>(result: Result<T, E>, fn: (error: E) => void): Result<T, E>;\nexport function tapErr<E>(fn: (error: E) => void): <T>(result: Result<T, E>) => Result<T, E>;\nexport function tapErr(\n ...args: [Result<unknown, unknown>, (error: unknown) => void] | [(error: unknown) => void]\n): unknown {\n if (args.length === 2) return tapErrImpl(args[0], args[1]);\n const fn = args[0];\n return (result: Result<unknown, unknown>) => tapErrImpl(result, fn);\n}\n\nconst matchImpl = <T, E, U>(\n result: Result<T, E>,\n onOk: (value: T) => U,\n onErr: (error: E) => U,\n): U => (isOk(result) ? onOk(result.value) : onErr(result.error));\n\n/**\n * Terminal collapse — fold both tracks into a single value. Dual-form:\n * 3-args data-first, 2-args curried for {@link pipe}. Returns whatever\n * the handlers return.\n *\n * For files that also import `match` from `ts-pattern`, use a namespace\n * import (`import * as R from \"@onrails/result\"` → `R.match`) to dissolve\n * the collision.\n *\n * @example\n * ```ts\n * const html = match(parsed, (cfg) => render(cfg), (e) => renderError(e));\n * ```\n */\nexport function match<T, E, U>(\n result: Result<T, E>,\n onOk: (value: T) => U,\n onErr: (error: E) => U,\n): U;\nexport function match<T, E, U>(\n onOk: (value: T) => U,\n onErr: (error: E) => U,\n): (result: Result<T, E>) => U;\nexport function match(\n ...args:\n | [Result<unknown, unknown>, (value: unknown) => unknown, (error: unknown) => unknown]\n | [(value: unknown) => unknown, (error: unknown) => unknown]\n): unknown {\n if (args.length === 3) return matchImpl(args[0], args[1], args[2]);\n const [onOk, onErr] = args;\n return (result: Result<unknown, unknown>) => matchImpl(result, onOk, onErr);\n}\n\n/**\n * Returns the `Ok` value, or `defaultValue` when the result is `Err`.\n *\n * @example\n * ```ts\n * unwrapOr(parsedSetting, \"default-value\");\n * ```\n */\nexport const unwrapOr = <T, E>(result: Result<T, E>, defaultValue: T): T =>\n isOk(result) ? result.value : defaultValue;\n\n/**\n * Test/assert helper — returns the `Ok` value, or **throws the original `Err`\n * value** when called on an `Err`. This is the assertion tier (RFC 0001 §4):\n * intended for `*.spec.ts` / `*.test.ts`, where throwing fails the test loudly.\n * In business logic prefer {@link match} or {@link unwrapOr}; the lint plugins\n * flag `unwrapOk` outside test files.\n *\n * @returns the unwrapped `Ok` value\n * @throws the carried `Err` value when the result is `Err`\n *\n * @example\n * ```ts\n * // in a *.spec.ts\n * expect(unwrapOk(ok(5))).toBe(5);\n * expect(() => unwrapOk(err(error))).toThrow(error);\n * ```\n */\nexport function unwrapOk<T, E>(result: Result<T, E>): T {\n if (isErr(result)) throw result.error;\n return result.value;\n}\n\n/**\n * Test/assert helper — returns the `Err` value, or **throws a `TypeError`**\n * when called on an `Ok`. Mirror of {@link unwrapOk} on the error track and\n * part of the same assertion tier (RFC 0001 §4): intended for `*.spec.ts` /\n * `*.test.ts`. Prefer {@link match} / {@link unwrapOr} in business logic; the\n * lint plugins flag `unwrapErr` outside test files.\n *\n * @returns the unwrapped `Err` value\n * @throws `TypeError` when the result is `Ok`\n *\n * @example\n * ```ts\n * // in a *.spec.ts\n * expect(unwrapErr(err(\"x\"))).toBe(\"x\");\n * expect(() => unwrapErr(ok(5))).toThrow(TypeError);\n * ```\n */\nexport function unwrapErr<T, E>(result: Result<T, E>): E {\n if (isOk(result)) throw new TypeError(\"unwrapErr called on Ok\");\n return result.error;\n}\n\n/**\n * Wraps a throwing sync function, returning a function that produces a\n * {@link Result} instead of throwing. Thrown errors pass through `onThrow` to\n * become a typed `Err`; a normal return becomes `Ok`. The neverthrow analogue\n * is `Result.fromThrowable`.\n *\n * @param fn - the throwing function to wrap\n * @param onThrow - maps a thrown value to the `Err` channel\n * @returns a function with `fn`'s parameters that returns `Result<ReturnType, E>`\n *\n * @example\n * ```ts\n * type ParseError = { kind: \"parse\"; message: string };\n * const parse = trySync(\n * JSON.parse,\n * (e): ParseError => ({ kind: \"parse\", message: String(e) }),\n * );\n * parse(\"{}\"); // Ok({})\n * parse(\"nope\"); // Err({ kind: \"parse\", … })\n * ```\n */\nexport function trySync<A extends readonly unknown[], T, E>(\n fn: (...args: A) => T,\n onThrow: (error: unknown) => E,\n): (...args: A) => Result<T, E>;\nexport function trySync<F extends (...args: never) => unknown, E>(\n fn: F,\n onThrow: (error: unknown) => E,\n): (...args: Parameters<F>) => Result<ReturnType<F>, E>;\nexport function trySync(\n fn: (...args: never) => unknown,\n onThrow: (error: unknown) => unknown,\n): (...args: never) => Result<unknown, unknown> {\n return (...args: never) => {\n try {\n return ok(fn(...args));\n } catch (error) {\n return err(onThrow(error));\n }\n };\n}\n\n/**\n * Variadic value-first pipe — threads `value` through up to nine unary fns,\n * left-to-right. Use {@link pipe} when you already have a starting value;\n * use {@link flow} to define a reusable composed function with no value yet.\n *\n * @example\n * ```ts\n * pipe(\n * parseConfig(raw),\n * map((cfg) => cfg.name),\n * flatMap((name) => (name ? ok(name) : err({ kind: \"empty\" as const }))),\n * tap(log),\n * );\n * ```\n */\nexport function pipe<A>(value: A): A;\nexport function pipe<A, B>(value: A, ab: (a: A) => B): B;\nexport function pipe<A, B, C>(value: A, ab: (a: A) => B, bc: (b: B) => C): C;\nexport function pipe<A, B, C, D>(value: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D): D;\nexport function pipe<A, B, C, D, E>(\n value: A,\n ab: (a: A) => B,\n bc: (b: B) => C,\n cd: (c: C) => D,\n de: (d: D) => E,\n): E;\nexport function pipe<A, B, C, D, E, F>(\n value: A,\n ab: (a: A) => B,\n bc: (b: B) => C,\n cd: (c: C) => D,\n de: (d: D) => E,\n ef: (e: E) => F,\n): F;\nexport function pipe<A, B, C, D, E, F, G>(\n value: A,\n ab: (a: A) => B,\n bc: (b: B) => C,\n cd: (c: C) => D,\n de: (d: D) => E,\n ef: (e: E) => F,\n fg: (f: F) => G,\n): G;\nexport function pipe<A, B, C, D, E, F, G, H>(\n value: A,\n ab: (a: A) => B,\n bc: (b: B) => C,\n cd: (c: C) => D,\n de: (d: D) => E,\n ef: (e: E) => F,\n fg: (f: F) => G,\n gh: (g: G) => H,\n): H;\nexport function pipe<A, B, C, D, E, F, G, H, I>(\n value: A,\n ab: (a: A) => B,\n bc: (b: B) => C,\n cd: (c: C) => D,\n de: (d: D) => E,\n ef: (e: E) => F,\n fg: (f: F) => G,\n gh: (g: G) => H,\n hi: (h: H) => I,\n): I;\nexport function pipe(value: unknown, ...fns: ReadonlyArray<(x: unknown) => unknown>): unknown {\n let acc = value;\n for (const fn of fns) {\n acc = fn(acc);\n }\n return acc;\n}\n","import { err, isErr } from \"./result.js\";\nimport type { Result } from \"./types.js\";\n\n/** Internal control-flow signal — not part of public error types */\nclass ErrSignal<E> {\n readonly _tag = \"ErrSignal\" as const;\n constructor(readonly error: E) {}\n}\n\n/**\n * Unwraps a {@link Result} inside a {@link tryGen} block — the sync analogue of\n * Rust's `?` operator. On `Ok` it returns the value; on `Err` it short-circuits\n * the enclosing `tryGen`, which returns that `Err`. Only valid inside `tryGen`;\n * prefer {@link flatMap} / `pipe` for long pipelines.\n *\n * @returns the unwrapped `Ok` value\n *\n * @example\n * ```ts\n * const out = tryGen(() => {\n * const a = yieldResult(parseA()); // unwrap or short-circuit\n * const b = yieldResult(parseB());\n * return ok(a + b);\n * });\n * ```\n */\nexport const yieldResult = <T, E>(result: Result<T, E>): T => {\n if (isErr(result)) {\n throw new ErrSignal(result.error);\n }\n return result.value;\n};\n\n/**\n * Terse alias of {@link yieldResult} (mirrors Rust's `?`), for compact\n * {@link tryGen} blocks.\n *\n * @example\n * ```ts\n * const out = tryGen(() => {\n * const a = $(parseA());\n * const b = $(parseB());\n * return ok(a + b);\n * });\n * ```\n */\nexport const $ = yieldResult;\n\n/**\n * Run a block that uses {@link yieldResult}; returns the final `Result` or the first Err.\n *\n * @example\n * ```ts\n * const out = tryGen(() => {\n * const a = $(parseA());\n * const b = $(parseB());\n * return ok(a + b);\n * });\n * ```\n */\nexport const tryGen = <T, E>(fn: () => Result<T, E>): Result<T, E> => {\n try {\n return fn();\n } catch (error) {\n if (error instanceof ErrSignal) {\n return err(error.error);\n }\n throw error;\n }\n};\n"]}
package/dist/_.d.cts ADDED
@@ -0,0 +1,2 @@
1
+ export { $, tryGen, yieldResult } from './try-gen.cjs';
2
+ import './types-BQ9vv0nD.cjs';
package/dist/_.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { $, tryGen, yieldResult } from './try-gen.js';
2
+ import './types-BQ9vv0nD.js';
@@ -0,0 +1,290 @@
1
+ import { R as Result, U as UnexpectedError } from './types-BQ9vv0nD.js';
2
+
3
+ type PromiseFactory<T, E> = () => Promise<Result<T, E>>;
4
+ type AsyncOk<R> = R extends ResultAsync<infer T, unknown> ? T : never;
5
+ type AsyncErr<R> = R extends ResultAsync<unknown, infer E> ? E : never;
6
+ type CombineTupleAsync<R extends readonly ResultAsync<unknown, unknown>[]> = ResultAsync<{
7
+ [K in keyof R]: AsyncOk<R[K]>;
8
+ }, {
9
+ [K in keyof R]: AsyncErr<R[K]>;
10
+ }[number]>;
11
+ /**
12
+ * Async railway carrier. Wraps a deferred `Promise<Result<T, E>>` and exposes
13
+ * the same dual-track transforms as the sync {@link Result} — `map`, `flatMap`,
14
+ * `recover`, `tap`, `match`. The public API never surfaces `Promise<Result<…>>`
15
+ * directly: `await` the instance (it is thenable) or call {@link resolve} /
16
+ * {@link match} to settle it.
17
+ *
18
+ * Construct via the static factories ({@link ok}, {@link err}, {@link fromPromise},
19
+ * {@link defer}, …) rather than `new` — the constructor is `protected`.
20
+ *
21
+ * @typeParam T - the `Ok` value type
22
+ * @typeParam E - the `Err` error type
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const user = ResultAsync.fromPromise(api.getUser(id), toAppError)
27
+ * .map((u) => u.profile)
28
+ * .recover(() => ResultAsync.ok(guestProfile));
29
+ *
30
+ * const r = await user; // Result<Profile, AppError>
31
+ * ```
32
+ */
33
+ declare class ResultAsync<T, E> {
34
+ protected readonly run: PromiseFactory<T, E>;
35
+ private promise;
36
+ protected constructor(run: PromiseFactory<T, E>);
37
+ /**
38
+ * Lifts an already-settled sync {@link Result} into a {@link ResultAsync}.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * const ra = ResultAsync.fromResult(ok(42)); // ResultAsync<number, never>
43
+ * ```
44
+ */
45
+ static fromResult<T, E>(result: Result<T, E>): ResultAsync<T, E>;
46
+ /**
47
+ * Wraps a `PromiseLike<T>` that may reject. Rejections pass through `onReject`
48
+ * to become a typed `Err`; resolution becomes `Ok<T>`.
49
+ *
50
+ * @param promise - the promise to wrap
51
+ * @param onReject - maps a rejection reason to the `Err` channel
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * const body = ResultAsync.fromPromise(
56
+ * fetch(url).then((r) => r.text()),
57
+ * (e): NetError => ({ kind: "net", cause: String(e) }),
58
+ * );
59
+ * ```
60
+ */
61
+ static fromPromise<T, E>(promise: PromiseLike<T>, onReject: (error: unknown) => E): ResultAsync<T, E>;
62
+ /**
63
+ * Wraps a `PromiseLike<T>` that is **guaranteed not to reject**, skipping the
64
+ * `onReject` mapper. Use only when rejection is provably impossible.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * const now = ResultAsync.fromSafePromise(Promise.resolve(Date.now()));
69
+ * ```
70
+ */
71
+ static fromSafePromise<T, E = never>(promise: PromiseLike<T>): ResultAsync<T, E>;
72
+ /**
73
+ * Defers work until {@link resolve}. Unlike {@link fromPromise}, the factory
74
+ * does not run until the `ResultAsync` is resolved (e.g. by `combineTuple` /
75
+ * `combineTupleParallel`). The factory runs at most once — `resolve` memoizes.
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * const insert = ResultAsync.defer(() => db.orders.insert(row));
80
+ * // nothing has run yet
81
+ * const r = await insert; // factory runs exactly once here
82
+ * ```
83
+ */
84
+ static defer<T, E>(fn: () => Promise<Result<T, E>>): ResultAsync<T, E>;
85
+ /**
86
+ * Lifts a value into an `Ok` async result.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * const r = ResultAsync.ok(42); // ResultAsync<number, never>
91
+ * ```
92
+ */
93
+ static ok<T>(value: T): ResultAsync<T, never>;
94
+ static ok<T, E>(value: T): ResultAsync<T, E>;
95
+ /**
96
+ * Lifts an error into an `Err` async result.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * const r = ResultAsync.err({ kind: "not_found" as const });
101
+ * ```
102
+ */
103
+ static err<T = never, E = unknown>(error: E): ResultAsync<T, E>;
104
+ /**
105
+ * Lifts an existing `Promise<Result<T, E>>` (e.g. from interop code) into a
106
+ * {@link ResultAsync}, auto-flattening one level of double-wrapping. A thrown
107
+ * defect is routed through `onDefect`, defaulting to {@link UnexpectedError}.
108
+ *
109
+ * @param promise - a promise that already yields a `Result`
110
+ * @param onDefect - maps an unexpected throw to the `Err` channel
111
+ * @see {@link fromAsync} from `@onrails/result/interop`
112
+ */
113
+ static fromResultPromise<T, E>(promise: Promise<Result<T, E>>, onDefect?: (error: unknown) => E | UnexpectedError): ResultAsync<T, E | UnexpectedError>;
114
+ /**
115
+ * Combines a homogeneous array of async results into one. Resolves
116
+ * **sequentially** in input order, short-circuiting on the first `Err`.
117
+ * For heterogeneous tuples that preserve per-index types, use
118
+ * {@link combineTuple}; for wall-clock overlap, {@link combineTupleParallel}.
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * const all = ResultAsync.combine([loadA(), loadB(), loadC()]);
123
+ * // ResultAsync<Item[], LoadError>
124
+ * ```
125
+ */
126
+ static combine<T, E>(results: readonly ResultAsync<T, E>[]): ResultAsync<T[], E>;
127
+ /**
128
+ * Heterogeneous async tuple combine — resolves branches **sequentially**
129
+ * (left-to-right), returning the first `Err` in input order. Preserves each
130
+ * branch's `Ok` type by position, so the result destructures type-safely.
131
+ * This is the canonical sequential async combine (replaces the former
132
+ * `sequenceTupleAsync`).
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * const combined = ResultAsync.combineTuple([loadCfg(), loadCatalog()] as const);
137
+ * // ResultAsync<readonly [Cfg, Catalog], CfgError | CatalogError>
138
+ * const r = await combined;
139
+ * if (isOk(r)) {
140
+ * const [cfg, catalog] = r.value; // typed per position
141
+ * }
142
+ * ```
143
+ */
144
+ static combineTuple<const R extends readonly ResultAsync<unknown, unknown>[]>(results: R): CombineTupleAsync<R>;
145
+ /**
146
+ * Like {@link combineTuple}, but starts every branch before awaiting (wall-clock
147
+ * parallel for independent IO). On failure, returns the first `Err` in input order.
148
+ *
149
+ * @example
150
+ * ```ts
151
+ * // independent IO — overlap them
152
+ * const combined = ResultAsync.combineTupleParallel([
153
+ * loadProfile(id),
154
+ * loadMetrics(id),
155
+ * ] as const);
156
+ * ```
157
+ */
158
+ static combineTupleParallel<const R extends readonly ResultAsync<unknown, unknown>[]>(results: R): CombineTupleAsync<R>;
159
+ /**
160
+ * Transforms the `Ok` value, passing `Err` through unchanged.
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * ResultAsync.ok(2).map((n) => n * 3); // ResultAsync<number> → Ok 6
165
+ * ```
166
+ */
167
+ map<U>(fn: (value: T) => U): ResultAsync<U, E>;
168
+ /**
169
+ * Transforms the `Err` value, passing `Ok` through unchanged — useful for
170
+ * unifying heterogeneous failures into one app-level union.
171
+ *
172
+ * @example
173
+ * ```ts
174
+ * load(id).mapErr((e): AppError => ({ kind: "load", cause: e }));
175
+ * ```
176
+ */
177
+ mapErr<F>(fn: (error: E) => F): ResultAsync<T, F>;
178
+ /**
179
+ * Canonical bind — chains a step that itself returns a `ResultAsync` or sync
180
+ * `Result`, short-circuiting on `Err`. Error types accumulate (`E | F`).
181
+ *
182
+ * @example
183
+ * ```ts
184
+ * authenticate(req)
185
+ * .flatMap((user) => loadProfile(user.id)) // ResultAsync
186
+ * .flatMap((p) => validate(p)); // sync Result also accepted
187
+ * ```
188
+ */
189
+ flatMap<U, F = E>(fn: (value: T) => ResultAsync<U, F> | Result<U, F> | {
190
+ inner: Result<U, F>;
191
+ }): ResultAsync<U, E | F>;
192
+ /**
193
+ * neverthrow-compat alias of {@link flatMap}. Kept as the documented compat
194
+ * tier; prefer `flatMap` in new code.
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * authenticate(req).andThen((user) => loadProfile(user.id));
199
+ * ```
200
+ */
201
+ andThen<U, F = E>(fn: (value: T) => ResultAsync<U, F> | Result<U, F> | {
202
+ inner: Result<U, F>;
203
+ }): ResultAsync<U, E | F>;
204
+ /**
205
+ * Error-channel bind — runs `fn` only on `Err`, swapping in a recovery
206
+ * `ResultAsync` or sync `Result`. `Ok` passes through. The mirror of
207
+ * {@link flatMap} on the error track.
208
+ *
209
+ * @example
210
+ * ```ts
211
+ * loadFromCache(id).recover(() => loadFromOrigin(id));
212
+ * ```
213
+ */
214
+ recover<F>(fn: (error: E) => ResultAsync<T, F> | Result<T, F>): ResultAsync<T, F>;
215
+ /**
216
+ * neverthrow-compat alias of {@link recover}. Kept as the documented compat
217
+ * tier; prefer `recover` in new code.
218
+ *
219
+ * @example
220
+ * ```ts
221
+ * loadFromCache(id).orElse(() => loadFromOrigin(id));
222
+ * ```
223
+ */
224
+ orElse<F>(fn: (error: E) => ResultAsync<T, F> | Result<T, F>): ResultAsync<T, F>;
225
+ /**
226
+ * Runs a side effect on the `Ok` value and passes the result through
227
+ * unchanged; a no-op on `Err`. Mirrors {@link tapErr}.
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * saveUser(u).tap((saved) => analytics.track("user_saved", saved.id));
232
+ * ```
233
+ */
234
+ tap(fn: (value: T) => void): ResultAsync<T, E>;
235
+ /**
236
+ * Runs a side effect on the `Err` value and passes the result through
237
+ * unchanged; a no-op on `Ok`. The error-track mirror of {@link tap}.
238
+ *
239
+ * @example
240
+ * ```ts
241
+ * saveUser(u).tapErr((e) => logger.warn("save failed", e));
242
+ * ```
243
+ */
244
+ tapErr(fn: (error: E) => void): ResultAsync<T, E>;
245
+ /**
246
+ * Settles to the `Ok` value, or `defaultValue` if `Err`. Terminal — returns a
247
+ * plain `Promise`, not a `ResultAsync`.
248
+ *
249
+ * @example
250
+ * ```ts
251
+ * const profile = await loadProfile(id).unwrapOr(guestProfile);
252
+ * ```
253
+ */
254
+ unwrapOr<U>(defaultValue: U): Promise<T | U>;
255
+ /**
256
+ * Terminal collapse — folds both tracks into a single awaited value. Returns
257
+ * a plain `Promise`, settling the carrier exactly once.
258
+ *
259
+ * @param onOk - handles the `Ok` value
260
+ * @param onErr - handles the `Err` value
261
+ *
262
+ * @example
263
+ * ```ts
264
+ * const status = await save(row).match(
265
+ * () => 200,
266
+ * (e) => (e.kind === "conflict" ? 409 : 500),
267
+ * );
268
+ * ```
269
+ */
270
+ match<U1, U2 = U1>(onOk: (value: T) => U1, onErr: (error: E) => U2): Promise<U1 | U2>;
271
+ /**
272
+ * Settles the carrier to a bare sync {@link Result}, memoizing so the
273
+ * underlying factory runs at most once. Prefer `await ra` (the thenable) or
274
+ * {@link match} in app code; use `resolve` when you need the tagged union back.
275
+ *
276
+ * @example
277
+ * ```ts
278
+ * const r = await load(id).resolve(); // Result<Data, LoadError>
279
+ * if (isOk(r)) use(r.value);
280
+ * ```
281
+ */
282
+ resolve(): Promise<Result<T, E>>;
283
+ /**
284
+ * Thenable shim — `await ra` resolves to a bare tagged-union `Result<T, E>`.
285
+ * Narrow with `isOk(r)` / `isErr(r)` to read `.value` / `.error`.
286
+ */
287
+ 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>;
288
+ }
289
+
290
+ export { ResultAsync as R };