@onrails/result 0.1.2 → 0.1.4

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 (56) hide show
  1. package/DESIGN.md +0 -1
  2. package/README.md +10 -17
  3. package/RECIPES.md +157 -34
  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-CCA1yK8q.d.cts → async-CH8Cs8KE.d.cts} +14 -0
  11. package/dist/{async-DH_-dNIo.d.ts → async-DKISqIBc.d.ts} +14 -0
  12. package/dist/compat/neverthrow.cjs +30 -12
  13. package/dist/compat/neverthrow.cjs.map +1 -1
  14. package/dist/compat/neverthrow.d.cts +1 -1
  15. package/dist/compat/neverthrow.d.ts +1 -1
  16. package/dist/compat/neverthrow.js +30 -12
  17. package/dist/compat/neverthrow.js.map +1 -1
  18. package/dist/extra.cjs +17 -10
  19. package/dist/extra.cjs.map +1 -1
  20. package/dist/extra.d.cts +10 -2
  21. package/dist/extra.d.ts +10 -2
  22. package/dist/extra.js +17 -10
  23. package/dist/extra.js.map +1 -1
  24. package/dist/fluent.cjs.map +1 -1
  25. package/dist/fluent.d.cts +3 -1
  26. package/dist/fluent.d.ts +3 -1
  27. package/dist/fluent.js.map +1 -1
  28. package/dist/index.cjs +37 -13
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +21 -5
  31. package/dist/index.d.ts +21 -5
  32. package/dist/index.js +37 -13
  33. package/dist/index.js.map +1 -1
  34. package/dist/interop.cjs +37 -13
  35. package/dist/interop.cjs.map +1 -1
  36. package/dist/interop.d.cts +13 -3
  37. package/dist/interop.d.ts +13 -3
  38. package/dist/interop.js +37 -13
  39. package/dist/interop.js.map +1 -1
  40. package/dist/railway.cjs +32 -12
  41. package/dist/railway.cjs.map +1 -1
  42. package/dist/railway.d.cts +19 -2
  43. package/dist/railway.d.ts +19 -2
  44. package/dist/railway.js +32 -13
  45. package/dist/railway.js.map +1 -1
  46. package/dist/try-gen.cjs.map +1 -1
  47. package/dist/try-gen.js.map +1 -1
  48. package/dist/validation.cjs.map +1 -1
  49. package/dist/validation.js.map +1 -1
  50. package/package.json +6 -6
  51. package/dist/mcp.cjs +0 -273
  52. package/dist/mcp.cjs.map +0 -1
  53. package/dist/mcp.d.cts +0 -62
  54. package/dist/mcp.d.ts +0 -62
  55. package/dist/mcp.js +0 -265
  56. package/dist/mcp.js.map +0 -1
package/DESIGN.md CHANGED
@@ -96,7 +96,6 @@ Use `flatMap` for dependent checks where later checks need earlier successful va
96
96
  | `@onrails/result/fluent` | Dot-style chains |
97
97
  | `@onrails/result/extra` | Error-type utilities |
98
98
  | `@onrails/result/interop` | Promise/ResultAsync boundary helpers |
99
- | `@onrails/result/mcp` | MCP / HTTP boundary helpers |
100
99
  | `@onrails/result/pipe` | Variadic `flow` for point-free composition |
101
100
  | `@onrails/result/railway` | Named workflow builder and reusable step factories |
102
101
  | `@onrails/result/try-gen` | Sync generator-style `Result` sugar |
package/README.md CHANGED
@@ -132,13 +132,18 @@ else console.error(r.error);
132
132
 
133
133
  ## Match and unwrap helpers
134
134
 
135
- `matchResult` is an alias for `match` for files that also import `match` from `ts-pattern`:
135
+ `match` is the canonical positional fold. If a file imports `match` from `@onrails/pattern` or `ts-pattern`, resolve the collision by using namespace imports:
136
136
 
137
137
  ```ts
138
- import { matchResult } from "@onrails/result";
138
+ import * as R from "@onrails/result";
139
139
  import { match } from "ts-pattern";
140
+
141
+ R.match(result, onOk, onErr);
140
142
  ```
141
143
 
144
+ > [!NOTE]
145
+ > `matchResult` is deprecated and will be removed in the next major version.
146
+
142
147
  `unwrapOk` and `unwrapErr` are test/assertion helpers. Prefer `match`, `isOk`, or `isErr` in production control flow.
143
148
 
144
149
  ```ts
@@ -147,17 +152,6 @@ import { unwrapOk } from "@onrails/result";
147
152
  expect(unwrapOk(parseConfig(raw))).toEqual(expected);
148
153
  ```
149
154
 
150
- ## MCP / HTTP boundaries
151
-
152
- ```ts
153
- import { toToolResponseAsync, unwrapFetchResultAsync } from "@onrails/result/mcp";
154
-
155
- const ra = unwrapFetchResultAsync(
156
- client.GET("/tokens/{id}"),
157
- ({ error, response }) => new PrintrApiError(response.status, detail),
158
- );
159
- return toToolResponseAsync(ra);
160
- ```
161
155
 
162
156
  ## `tryGen` — sync `?`
163
157
 
@@ -173,12 +167,12 @@ const out = tryGen(() => {
173
167
  });
174
168
  ```
175
169
 
176
- Use `sequenceTupleAsync` (or `parallelTupleAsync` when branches should overlap) when combining heterogeneous async results and destructuring the result:
170
+ Use `ResultAsync.combineTuple` (or `parallelTupleAsync` when branches should overlap) when combining heterogeneous async results and destructuring the result:
177
171
 
178
172
  ```ts
179
- import { sequenceTupleAsync } from "@onrails/result";
173
+ import { ResultAsync } from "@onrails/result";
180
174
 
181
- const combined = sequenceTupleAsync([
175
+ const combined = ResultAsync.combineTuple([
182
176
  loadSettings(),
183
177
  loadModelCatalog(),
184
178
  ] as const);
@@ -314,7 +308,6 @@ import { ResultAsync, Result, ok, err, okAsync, errAsync } from "@onrails/result
314
308
  | `@onrails/result/fluent` | `fluent()`, `fluentAsync()` |
315
309
  | `@onrails/result/extra` | Error-type utilities |
316
310
  | `@onrails/result/interop` | `fromAsync`, `fromResult`, `asyncAfter` |
317
- | `@onrails/result/mcp` | MCP / openapi-fetch helpers |
318
311
  | `@onrails/result/pipe` | `flow` (variadic point-free composition) |
319
312
  | `@onrails/result/railway` | `Railway`, `railway`, named workflow helpers |
320
313
  | `@onrails/result/try-gen` | `tryGen`, `yieldResult`, `$` |
package/RECIPES.md CHANGED
@@ -11,6 +11,68 @@ map(fn)(result); // curried, data-last — feeds `pipe(...)` and `flow(...)`
11
11
 
12
12
  See [README.md](./README.md) for the API reference and decision tree.
13
13
 
14
+ ## Composition Guidelines (The Four Tiers)
15
+
16
+ To keep codebases readable and consistent, follow the four-tier decision guideline when choosing how to compose operations. Use the simplest pattern that fits your workflow:
17
+
18
+ | Tier | Use Case | Recommended Pattern |
19
+ | ---- | -------- | ------------------- |
20
+ | **1** | 1–2 steps, linear | direct data-first calls or method chains |
21
+ | **2** | 3+ steps, linear | `pipe` or `flow` |
22
+ | **3** | branchy, value reused | `tryGen` escape hatch |
23
+ | **4** | 4+ named steps, mixed IO | `Railway` or `railway()` steps |
24
+
25
+ `/fluent` is documented as app-edge sugar only — never in library or service internals.
26
+
27
+ ### Tier 1: 1–2 steps, linear (Direct data-first call or method chain)
28
+
29
+ ```ts
30
+ import { map } from "@onrails/result";
31
+
32
+ const normalized = map(rawResult, normalize);
33
+ ```
34
+
35
+ ### Tier 2: 3+ steps, linear (pipe / flow)
36
+
37
+ ```ts
38
+ import { flow, map, flatMap, recover } from "@onrails/result";
39
+
40
+ const process = flow(
41
+ map(normalize),
42
+ flatMap(validate),
43
+ recover(fallback)
44
+ );
45
+ ```
46
+
47
+ ### Tier 3: Branchy, value reused (tryGen)
48
+
49
+ ```ts
50
+ import { tryGen, $ } from "@onrails/result/try-gen";
51
+ import { ok, err } from "@onrails/result";
52
+
53
+ const stepResult = tryGen(() => {
54
+ const user = $(authenticate(req));
55
+ const post = $(fetchPost(postId));
56
+ if (post.authorId !== user.id && !user.isAdmin) {
57
+ return err({ kind: "unauthorized" as const });
58
+ }
59
+ return ok(post);
60
+ });
61
+ ```
62
+
63
+ ### Tier 4: 4+ named steps, mixed IO (Railway context builder)
64
+
65
+ ```ts
66
+ import { Railway } from "@onrails/result/railway";
67
+
68
+ const workflow = Railway
69
+ .fromSync("id", () => IdSchema.parse(raw), toError)
70
+ .fromPromise("row", ({ id }) => db.profiles.find(id), toError)
71
+ .require("profile", "row", ({ id }) => ({ kind: "not_found" as const, id }))
72
+ .derive("normalized", ({ profile }) => normalizeProfile(profile))
73
+ .select(({ normalized }) => normalized);
74
+ ```
75
+
14
76
  ---
15
77
 
16
78
  ## 1. Reusable parser builder via `flow`
@@ -26,16 +88,8 @@ type SchemaError = { kind: "schema"; field: string };
26
88
 
27
89
  const parseJsonWith = <T>(schema: { parse: (x: unknown) => T }) =>
28
90
  flow(
29
- trySync(
30
- (raw: string) => JSON.parse(raw),
31
- (e): ParseError => ({ kind: "parse", message: String(e) }),
32
- ),
33
- flatMap((data: unknown) =>
34
- trySync(
35
- () => schema.parse(data),
36
- (e): SchemaError => ({ kind: "schema", field: String(e) }),
37
- )(),
38
- ),
91
+ trySync(JSON.parse, (e): ParseError => ({ kind: "parse", message: String(e) })),
92
+ flatMap(trySync(schema.parse, (e): SchemaError => ({ kind: "schema", field: String(e) }))),
39
93
  );
40
94
 
41
95
  const parseUser = parseJsonWith(UserSchema);
@@ -64,18 +118,17 @@ const saveOrder = (rawJson: string) =>
64
118
  flatMap((u) => (u.active ? ok(u) : err({ kind: "inactive" as const }))),
65
119
  mapErr((e) => ({ kind: "input" as const, cause: e })),
66
120
  tap((u) => log.info({ msg: "validated", userId: u.id })),
67
- (validated) =>
68
- asyncAfter(validated, (u) =>
69
- tryAsync(
70
- db.orders.insert({ userId: u.id, items: u.cart }),
71
- (e): DbError => ({ kind: "db", cause: e }),
72
- ),
121
+ asyncAfter((u) =>
122
+ tryAsync(
123
+ db.orders.insert({ userId: u.id, items: u.cart }),
124
+ (e): DbError => ({ kind: "db", cause: e }),
73
125
  ),
126
+ ),
74
127
  );
75
128
  // ResultAsync<OrderRow, { kind: "input"; cause: ... } | DbError>
76
129
  ```
77
130
 
78
- 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.
79
132
 
80
133
  ---
81
134
 
@@ -167,7 +220,7 @@ const buildSummary = (userId: string) =>
167
220
  // ResultAsync<Summary, ProfileError | MetricsError>
168
221
  ```
169
222
 
170
- `parallelTupleAsync` preserves tuple positions, so destructuring stays type-safe. Use `sequenceTupleAsync` if branches must run left-to-right.
223
+ `parallelTupleAsync` preserves tuple positions, so destructuring stays type-safe. Use static `ResultAsync.combineTuple` if branches must run left-to-right.
171
224
 
172
225
  ---
173
226
 
@@ -181,24 +234,24 @@ import { flatMap, recover, ok, err } from "@onrails/result";
181
234
 
182
235
  type LengthError = { kind: "len"; min: number };
183
236
  type CharsError = { kind: "chars"; bad: string };
237
+ type TooShortError = { kind: "too_short"; min: number };
184
238
 
185
- const requireMin = (min: number) =>
186
- flow((s: string) => (s.length >= min ? ok(s) : err({ kind: "len" as const, min })));
239
+ const requireMin = (min: number) => (s: string) =>
240
+ s.length >= min ? ok(s) : err({ kind: "len" as const, min });
187
241
 
188
- const requireAscii = flow(
189
- (s: string) =>
190
- /^[\x20-\x7e]*$/.test(s) ? ok(s) : err({ kind: "chars" as const, bad: s }),
191
- );
242
+ const requireAscii = (s: string) =>
243
+ /^[\x20-\x7e]*$/.test(s) ? ok(s) : err({ kind: "chars" as const, bad: s });
192
244
 
193
245
  const validateUsername = flow(
194
246
  (raw: string) => ok(raw.trim()),
195
247
  flatMap(requireMin(3)),
196
248
  flatMap(requireAscii),
197
- recover((e: LengthError | CharsError) =>
198
- e.kind === "len" ? err({ kind: "too_short" as const, min: e.min }) : err(e),
249
+ recover(
250
+ (e: LengthError | CharsError): Result<string, TooShortError | CharsError> =>
251
+ e.kind === "len" ? err({ kind: "too_short" as const, min: e.min }) : err(e),
199
252
  ),
200
253
  );
201
- // (raw: string) => Result<string, { kind: "too_short"; min: number } | CharsError>
254
+ // (raw: string) => Result<string, TooShortError | CharsError>
202
255
  ```
203
256
 
204
257
  `requireMin(3)` is a curried factory — `flow` strings it into the pipeline alongside `requireAscii`. None of the inner steps mention the value.
@@ -252,12 +305,10 @@ const fetchWithBackoff = (url: string) =>
252
305
  pipe(
253
306
  url,
254
307
  fetchSync, // Result<Body, NetworkError | RateLimit | Fatal>
255
- recover((e: NetworkError | RateLimit | Fatal) => {
256
- if (e.kind === "network" && e.retryable) return fetchSync(url);
257
- if (e.kind === "rate_limit") return err(e); // bubble up — caller schedules retry
258
- if (e.kind === "fatal") return err(e); // unrecoverable
259
- return err(e);
260
- }),
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
+ ),
261
312
  );
262
313
  // Result<Body, NetworkError | RateLimit | Fatal>
263
314
  ```
@@ -301,7 +352,12 @@ import { map, recover, ok, err, type Result } from "@onrails/result";
301
352
 
302
353
  type FetchError = { kind: "network" } | { kind: "fatal"; message: string };
303
354
 
304
- const fetchWith = (cfg: { fallback?: Body; rethrow: (e: FetchError) => boolean }) =>
355
+ type FetchConfig = {
356
+ readonly fallback?: Body;
357
+ readonly rethrow: (e: FetchError) => boolean;
358
+ };
359
+
360
+ const fetchWith = (cfg: FetchConfig) =>
305
361
  flow(
306
362
  fetchSync,
307
363
  recover((e: FetchError) =>
@@ -350,6 +406,73 @@ Each sub-flow has a clear purpose; the top-level `ingest` reads as a sentence. E
350
406
 
351
407
  ---
352
408
 
409
+ ## 12. Async pipelines via `ResultAsync` composition
410
+
411
+ When composing functions that return `ResultAsync` values, `flow` chains them by wrapping the async transforms in arrow functions. The resulting pipeline remains point-free over the input.
412
+
413
+ ```ts
414
+ import { flow } from "@onrails/result/pipe";
415
+ import { type ResultAsync } from "@onrails/result";
416
+
417
+ type Profile = { id: string; name: string };
418
+ type Metrics = { score: number };
419
+ type Summary = { name: string; score: number };
420
+
421
+ declare const fetchProfile: (id: string) => ResultAsync<Profile, Error>;
422
+ declare const fetchMetrics: (p: Profile) => ResultAsync<Metrics, Error>;
423
+ declare const formatSummary: (p: Profile, m: Metrics) => Summary;
424
+
425
+ const loadSummary = flow(
426
+ fetchProfile,
427
+ (ra) => ra.flatMap((profile) =>
428
+ fetchMetrics(profile).map((metrics) => formatSummary(profile, metrics))
429
+ ),
430
+ );
431
+ // (id: string) => ResultAsync<Summary, Error>
432
+ ```
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
+
436
+ ---
437
+
438
+ ## 13. Functional Railway pipelines (`railway` + named steps)
439
+
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`.
441
+
442
+ ```ts
443
+ import { railway, parseNamed, fromPromiseNamed, deriveNamed, select } from "@onrails/result/railway";
444
+ import { type ResultAsync } from "@onrails/result";
445
+
446
+ type Dashboard = { title: string };
447
+
448
+ type IdContext = { readonly id: string };
449
+ type ProfileContext = { readonly profile: Profile };
450
+ type TitleContext = { readonly title: string };
451
+
452
+ declare const IdSchema: { parse: (x: unknown) => string };
453
+ declare const fetchProfile: (id: string) => Promise<Profile>;
454
+ declare const toError: (e: unknown) => Error;
455
+
456
+ const loadDashboard = (rawId: unknown): ResultAsync<Dashboard, Error> =>
457
+ railway(
458
+ rawId,
459
+ parseNamed("id", IdSchema, toError),
460
+ fromPromiseNamed(
461
+ "profile",
462
+ ({ id }: IdContext) => fetchProfile(id),
463
+ toError,
464
+ ),
465
+ deriveNamed("title", ({ profile }: ProfileContext) =>
466
+ profile.name.toUpperCase(),
467
+ ),
468
+ select(({ title }: TitleContext) => ({ title })),
469
+ );
470
+ ```
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
+
474
+ ---
475
+
353
476
  ## When NOT to go point-free
354
477
 
355
478
  Pipelines should read top-to-bottom and each step should do one obvious thing. Reach for `pipe` when:
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;AAMO,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 * Fantasy Land `of` — alias of {@link ok}.\n * @deprecated Use {@link ok} instead.\n */\nexport const of = ok;\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`, the\n * collision-free alias {@link matchResult} is identical.\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 * Collision-free alias for files that also import `match` from ts-pattern.\n * @deprecated Namespace import carriers (e.g., `import * as R from \"@onrails/result\"`) and use canonical {@link match} instead.\n */\nexport const matchResult = match;\n\n/**\n * Curried collapse with named slots — escape valve when positional `match`\n * order is unclear at the call site (e.g. when both handlers return the same\n * type and a transposed `match(r, onErr, onOk)` would silently compile).\n * @deprecated Use curried {@link match}(onOk, onErr)(result) instead.\n */\nexport const fold =\n <T, E, U>(handlers: { readonly ok: (value: T) => U; readonly err: (error: E) => U }) =>\n (result: Result<T, E>): U =>\n matchImpl(result, handlers.ok, handlers.err);\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 — throws the original Err value when called on Err.\n *\n * @throws when the result is Err — assertion-tier; use {@link match} / {@link unwrapOr} in business logic.\n * Allowed in `*.spec.ts` / `*.test.ts`; flagged elsewhere by the plugins.\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 — throws TypeError when called on Ok.\n *\n * @throws when the result is Ok — assertion-tier; allowed in `*.spec.ts` / `*.test.ts`; flagged elsewhere by the plugins.\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/** Wrap a throwing sync function — neverthrow `Result.fromThrowable` */\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/** First failure wins; otherwise collects values in order */\nexport const combine = <T, E>(results: readonly Result<T, E>[]): Result<T[], E> => {\n const values: T[] = [];\n for (const result of results) {\n if (isErr(result)) return err(result.error);\n values.push(result.value);\n }\n return ok(values);\n};\n\n/** Tuple-preserving combine (neverthrow-style) */\nexport const combineTuple = <const R extends readonly Result<unknown, unknown>[]>(\n results: R,\n): CombineTuple<R> =>\n // Runtime identical to combine; the cast restores per-index tuple types.\n combine(results as readonly Result<unknown, unknown>[]) as CombineTuple<R>;\n\ntype _OkValue<R> = R extends { _tag: \"Ok\"; readonly value: infer T } ? T : never;\ntype _ErrValue<R> = R extends { _tag: \"Err\"; readonly error: infer E } ? E : never;\n\ntype CombineTuple<R extends readonly Result<unknown, unknown>[]> = Result<\n { [K in keyof R]: _OkValue<R[K]> },\n { [K in keyof R]: _ErrValue<R[K]> }[number]\n>;\n\n/**\n * Variadic value-first pipe — threads `value` through up to nine unary fns.\n *\n * ```ts\n * pipe(\n * parseConfig(raw),\n * map((cfg) => cfg.name),\n * flatMap((name) => name ? ok(name) : err({ kind: \"empty\" })),\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 * Unwrap a {@link Result} inside {@link tryGen} (Rust `?` for sync code).\n * Prefer {@link flatMap} / {@link fluent} for long pipelines.\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\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;AAMO,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 * Fantasy Land `of` — alias of {@link ok}.\n * @deprecated Use {@link ok} instead.\n */\nexport const of = ok;\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`, the\n * collision-free alias {@link matchResult} is identical.\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 * Collision-free alias for files that also import `match` from ts-pattern.\n * @deprecated Namespace import carriers (e.g., `import * as R from \"@onrails/result\"`) and use canonical {@link match} instead.\n */\nexport const matchResult = match;\n\n/**\n * Curried collapse with named slots — escape valve when positional `match`\n * order is unclear at the call site (e.g. when both handlers return the same\n * type and a transposed `match(r, onErr, onOk)` would silently compile).\n * @deprecated Use curried {@link match}(onOk, onErr)(result) instead.\n */\nexport const fold =\n <T, E, U>(handlers: { readonly ok: (value: T) => U; readonly err: (error: E) => U }) =>\n (result: Result<T, E>): U =>\n matchImpl(result, handlers.ok, handlers.err);\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 — throws the original Err value when called on Err.\n *\n * @throws when the result is Err — assertion-tier; use {@link match} / {@link unwrapOr} in business logic.\n * Allowed in `*.spec.ts` / `*.test.ts`; flagged elsewhere by the plugins.\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 — throws TypeError when called on Ok.\n *\n * @throws when the result is Ok — assertion-tier; allowed in `*.spec.ts` / `*.test.ts`; flagged elsewhere by the plugins.\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/** Wrap a throwing sync function — neverthrow `Result.fromThrowable` */\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/** First failure wins; otherwise collects values in order */\nexport const combine = <T, E>(results: readonly Result<T, E>[]): Result<T[], E> => {\n const values: T[] = [];\n for (const result of results) {\n if (isErr(result)) return err(result.error);\n values.push(result.value);\n }\n return ok(values);\n};\n\n/** Tuple-preserving combine (neverthrow-style) */\nexport const combineTuple = <const R extends readonly Result<unknown, unknown>[]>(\n results: R,\n): CombineTuple<R> =>\n // Runtime identical to combine; the cast restores per-index tuple types.\n combine(results as readonly Result<unknown, unknown>[]) as CombineTuple<R>;\n\ntype _OkValue<R> = R extends { _tag: \"Ok\"; readonly value: infer T } ? T : never;\ntype _ErrValue<R> = R extends { _tag: \"Err\"; readonly error: infer E } ? E : never;\n\ntype CombineTuple<R extends readonly Result<unknown, unknown>[]> = Result<\n { [K in keyof R]: _OkValue<R[K]> },\n { [K in keyof R]: _ErrValue<R[K]> }[number]\n>;\n\n/**\n * Variadic value-first pipe — threads `value` through up to nine unary fns.\n *\n * ```ts\n * pipe(\n * parseConfig(raw),\n * map((cfg) => cfg.name),\n * flatMap((name) => name ? ok(name) : err({ kind: \"empty\" })),\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 * Unwrap a {@link Result} inside {@link tryGen} (Rust `?` for sync code).\n * Prefer {@link flatMap} / {@link fluent} for long pipelines.\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\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-C2Dp1d5J.cjs';
package/dist/_.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { $, tryGen, yieldResult } from './try-gen.js';
2
+ import './types-C2Dp1d5J.js';
@@ -14,6 +14,7 @@ type CombineTupleAsync<R extends readonly ResultAsync<unknown, unknown>[]> = Res
14
14
  */
15
15
  declare class ResultAsync<T, E> {
16
16
  protected readonly run: PromiseFactory<T, E>;
17
+ private promise;
17
18
  protected constructor(run: PromiseFactory<T, E>);
18
19
  static fromResult<T, E>(result: Result<T, E>): ResultAsync<T, E>;
19
20
  static fromPromise<T, E>(promise: PromiseLike<T>, onReject: (error: unknown) => E): ResultAsync<T, E>;
@@ -43,6 +44,10 @@ declare class ResultAsync<T, E> {
43
44
  andThen<U, F = E>(fn: (value: T) => ResultAsync<U, F> | Result<U, F> | {
44
45
  inner: Result<U, F>;
45
46
  }): ResultAsync<U, E | F>;
47
+ /**
48
+ * Alias for {@link flatMap}.
49
+ * @deprecated Use {@link flatMap} or keep/use compat {@link andThen} instead.
50
+ */
46
51
  chain<U, F = E>(fn: (value: T) => ResultAsync<U, F> | Result<U, F> | {
47
52
  inner: Result<U, F>;
48
53
  }): ResultAsync<U, E | F>;
@@ -51,7 +56,15 @@ declare class ResultAsync<T, E> {
51
56
  tap(fn: (value: T) => void): ResultAsync<T, E>;
52
57
  tapErr(fn: (error: E) => void): ResultAsync<T, E>;
53
58
  unwrapOr<U>(defaultValue: U): Promise<T | U>;
59
+ /**
60
+ * @deprecated Await the ResultAsync and narrow with {@link isOk} — this method
61
+ * re-executes deferred factories and cannot narrow.
62
+ */
54
63
  isOk(): Promise<boolean>;
64
+ /**
65
+ * @deprecated Await the ResultAsync and narrow with {@link isErr} — this method
66
+ * re-executes deferred factories and cannot narrow.
67
+ */
55
68
  isErr(): Promise<boolean>;
56
69
  match<U1, U2 = U1>(onOk: (value: T) => U1, onErr: (error: E) => U2): Promise<U1 | U2>;
57
70
  resolve(): Promise<Result<T, E>>;
@@ -107,6 +120,7 @@ declare const fromSafePromise: typeof ResultAsync.fromSafePromise;
107
120
  * const combined = sequenceTupleAsync([loadCfg(), loadCatalog()] as const);
108
121
  * // ResultAsync<readonly [Cfg, Catalog], CfgError | CatalogError>
109
122
  * ```
123
+ * @deprecated Use static {@link ResultAsync.combineTuple} instead.
110
124
  */
111
125
  declare const sequenceTupleAsync: typeof ResultAsync.combineTuple;
112
126
  /**
@@ -14,6 +14,7 @@ type CombineTupleAsync<R extends readonly ResultAsync<unknown, unknown>[]> = Res
14
14
  */
15
15
  declare class ResultAsync<T, E> {
16
16
  protected readonly run: PromiseFactory<T, E>;
17
+ private promise;
17
18
  protected constructor(run: PromiseFactory<T, E>);
18
19
  static fromResult<T, E>(result: Result<T, E>): ResultAsync<T, E>;
19
20
  static fromPromise<T, E>(promise: PromiseLike<T>, onReject: (error: unknown) => E): ResultAsync<T, E>;
@@ -43,6 +44,10 @@ declare class ResultAsync<T, E> {
43
44
  andThen<U, F = E>(fn: (value: T) => ResultAsync<U, F> | Result<U, F> | {
44
45
  inner: Result<U, F>;
45
46
  }): ResultAsync<U, E | F>;
47
+ /**
48
+ * Alias for {@link flatMap}.
49
+ * @deprecated Use {@link flatMap} or keep/use compat {@link andThen} instead.
50
+ */
46
51
  chain<U, F = E>(fn: (value: T) => ResultAsync<U, F> | Result<U, F> | {
47
52
  inner: Result<U, F>;
48
53
  }): ResultAsync<U, E | F>;
@@ -51,7 +56,15 @@ declare class ResultAsync<T, E> {
51
56
  tap(fn: (value: T) => void): ResultAsync<T, E>;
52
57
  tapErr(fn: (error: E) => void): ResultAsync<T, E>;
53
58
  unwrapOr<U>(defaultValue: U): Promise<T | U>;
59
+ /**
60
+ * @deprecated Await the ResultAsync and narrow with {@link isOk} — this method
61
+ * re-executes deferred factories and cannot narrow.
62
+ */
54
63
  isOk(): Promise<boolean>;
64
+ /**
65
+ * @deprecated Await the ResultAsync and narrow with {@link isErr} — this method
66
+ * re-executes deferred factories and cannot narrow.
67
+ */
55
68
  isErr(): Promise<boolean>;
56
69
  match<U1, U2 = U1>(onOk: (value: T) => U1, onErr: (error: E) => U2): Promise<U1 | U2>;
57
70
  resolve(): Promise<Result<T, E>>;
@@ -107,6 +120,7 @@ declare const fromSafePromise: typeof ResultAsync.fromSafePromise;
107
120
  * const combined = sequenceTupleAsync([loadCfg(), loadCatalog()] as const);
108
121
  * // ResultAsync<readonly [Cfg, Catalog], CfgError | CatalogError>
109
122
  * ```
123
+ * @deprecated Use static {@link ResultAsync.combineTuple} instead.
110
124
  */
111
125
  declare const sequenceTupleAsync: typeof ResultAsync.combineTuple;
112
126
  /**