@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.
- package/DESIGN.md +0 -1
- package/README.md +10 -17
- package/RECIPES.md +157 -34
- package/dist/$.cjs +40 -0
- package/dist/$.cjs.map +1 -0
- package/dist/$.js +36 -0
- package/dist/$.js.map +1 -0
- package/dist/_.d.cts +2 -0
- package/dist/_.d.ts +2 -0
- package/dist/{async-CCA1yK8q.d.cts → async-CH8Cs8KE.d.cts} +14 -0
- package/dist/{async-DH_-dNIo.d.ts → async-DKISqIBc.d.ts} +14 -0
- package/dist/compat/neverthrow.cjs +30 -12
- package/dist/compat/neverthrow.cjs.map +1 -1
- package/dist/compat/neverthrow.d.cts +1 -1
- package/dist/compat/neverthrow.d.ts +1 -1
- package/dist/compat/neverthrow.js +30 -12
- package/dist/compat/neverthrow.js.map +1 -1
- package/dist/extra.cjs +17 -10
- package/dist/extra.cjs.map +1 -1
- package/dist/extra.d.cts +10 -2
- package/dist/extra.d.ts +10 -2
- package/dist/extra.js +17 -10
- package/dist/extra.js.map +1 -1
- package/dist/fluent.cjs.map +1 -1
- package/dist/fluent.d.cts +3 -1
- package/dist/fluent.d.ts +3 -1
- package/dist/fluent.js.map +1 -1
- package/dist/index.cjs +37 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -5
- package/dist/index.d.ts +21 -5
- package/dist/index.js +37 -13
- package/dist/index.js.map +1 -1
- package/dist/interop.cjs +37 -13
- package/dist/interop.cjs.map +1 -1
- package/dist/interop.d.cts +13 -3
- package/dist/interop.d.ts +13 -3
- package/dist/interop.js +37 -13
- package/dist/interop.js.map +1 -1
- package/dist/railway.cjs +32 -12
- package/dist/railway.cjs.map +1 -1
- package/dist/railway.d.cts +19 -2
- package/dist/railway.d.ts +19 -2
- package/dist/railway.js +32 -13
- package/dist/railway.js.map +1 -1
- package/dist/try-gen.cjs.map +1 -1
- package/dist/try-gen.js.map +1 -1
- package/dist/validation.cjs.map +1 -1
- package/dist/validation.js.map +1 -1
- package/package.json +6 -6
- package/dist/mcp.cjs +0 -273
- package/dist/mcp.cjs.map +0 -1
- package/dist/mcp.d.cts +0 -62
- package/dist/mcp.d.ts +0 -62
- package/dist/mcp.js +0 -265
- 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
|
-
`
|
|
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
|
|
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 `
|
|
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 {
|
|
173
|
+
import { ResultAsync } from "@onrails/result";
|
|
180
174
|
|
|
181
|
-
const combined =
|
|
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
|
-
|
|
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
|
-
(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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 `
|
|
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
|
-
|
|
239
|
+
const requireMin = (min: number) => (s: string) =>
|
|
240
|
+
s.length >= min ? ok(s) : err({ kind: "len" as const, min });
|
|
187
241
|
|
|
188
|
-
const requireAscii =
|
|
189
|
-
(s:
|
|
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(
|
|
198
|
-
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,
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
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
package/dist/_.d.ts
ADDED
|
@@ -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
|
/**
|