@onrails/result 0.1.1 → 0.1.3
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 +143 -21
- 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 +78 -63
- 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 +78 -63
- 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 +53 -58
- 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 +53 -58
- package/dist/index.js.map +1 -1
- package/dist/interop.cjs +49 -50
- package/dist/interop.cjs.map +1 -1
- package/dist/interop.d.cts +1 -1
- package/dist/interop.d.ts +1 -1
- package/dist/interop.js +49 -50
- package/dist/interop.js.map +1 -1
- package/dist/railway.cjs +58 -59
- package/dist/railway.cjs.map +1 -1
- package/dist/railway.d.cts +7 -6
- package/dist/railway.d.ts +7 -6
- package/dist/railway.js +58 -59
- 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 +8 -25
- package/dist/validation.cjs.map +1 -1
- package/dist/validation.js +8 -25
- package/dist/validation.js.map +1 -1
- package/package.json +1 -6
- package/dist/mcp.cjs +0 -292
- 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 -284
- 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);
|
|
@@ -167,7 +221,7 @@ const buildSummary = (userId: string) =>
|
|
|
167
221
|
// ResultAsync<Summary, ProfileError | MetricsError>
|
|
168
222
|
```
|
|
169
223
|
|
|
170
|
-
`parallelTupleAsync` preserves tuple positions, so destructuring stays type-safe. Use `
|
|
224
|
+
`parallelTupleAsync` preserves tuple positions, so destructuring stays type-safe. Use static `ResultAsync.combineTuple` if branches must run left-to-right.
|
|
171
225
|
|
|
172
226
|
---
|
|
173
227
|
|
|
@@ -181,24 +235,24 @@ import { flatMap, recover, ok, err } from "@onrails/result";
|
|
|
181
235
|
|
|
182
236
|
type LengthError = { kind: "len"; min: number };
|
|
183
237
|
type CharsError = { kind: "chars"; bad: string };
|
|
238
|
+
type TooShortError = { kind: "too_short"; min: number };
|
|
184
239
|
|
|
185
|
-
const requireMin = (min: number) =>
|
|
186
|
-
|
|
240
|
+
const requireMin = (min: number) => (s: string) =>
|
|
241
|
+
s.length >= min ? ok(s) : err({ kind: "len" as const, min });
|
|
187
242
|
|
|
188
|
-
const requireAscii =
|
|
189
|
-
(s:
|
|
190
|
-
/^[\x20-\x7e]*$/.test(s) ? ok(s) : err({ kind: "chars" as const, bad: s }),
|
|
191
|
-
);
|
|
243
|
+
const requireAscii = (s: string) =>
|
|
244
|
+
/^[\x20-\x7e]*$/.test(s) ? ok(s) : err({ kind: "chars" as const, bad: s });
|
|
192
245
|
|
|
193
246
|
const validateUsername = flow(
|
|
194
247
|
(raw: string) => ok(raw.trim()),
|
|
195
248
|
flatMap(requireMin(3)),
|
|
196
249
|
flatMap(requireAscii),
|
|
197
|
-
recover(
|
|
198
|
-
e
|
|
250
|
+
recover(
|
|
251
|
+
(e: LengthError | CharsError): Result<string, TooShortError | CharsError> =>
|
|
252
|
+
e.kind === "len" ? err({ kind: "too_short" as const, min: e.min }) : err(e),
|
|
199
253
|
),
|
|
200
254
|
);
|
|
201
|
-
// (raw: string) => Result<string,
|
|
255
|
+
// (raw: string) => Result<string, TooShortError | CharsError>
|
|
202
256
|
```
|
|
203
257
|
|
|
204
258
|
`requireMin(3)` is a curried factory — `flow` strings it into the pipeline alongside `requireAscii`. None of the inner steps mention the value.
|
|
@@ -301,7 +355,12 @@ import { map, recover, ok, err, type Result } from "@onrails/result";
|
|
|
301
355
|
|
|
302
356
|
type FetchError = { kind: "network" } | { kind: "fatal"; message: string };
|
|
303
357
|
|
|
304
|
-
|
|
358
|
+
type FetchConfig = {
|
|
359
|
+
readonly fallback?: Body;
|
|
360
|
+
readonly rethrow: (e: FetchError) => boolean;
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const fetchWith = (cfg: FetchConfig) =>
|
|
305
364
|
flow(
|
|
306
365
|
fetchSync,
|
|
307
366
|
recover((e: FetchError) =>
|
|
@@ -350,6 +409,69 @@ Each sub-flow has a clear purpose; the top-level `ingest` reads as a sentence. E
|
|
|
350
409
|
|
|
351
410
|
---
|
|
352
411
|
|
|
412
|
+
## 12. Async pipelines via `ResultAsync` composition
|
|
413
|
+
|
|
414
|
+
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.
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
import { flow } from "@onrails/result/pipe";
|
|
418
|
+
import { type ResultAsync } from "@onrails/result";
|
|
419
|
+
|
|
420
|
+
type Profile = { id: string; name: string };
|
|
421
|
+
type Metrics = { score: number };
|
|
422
|
+
type Summary = { name: string; score: number };
|
|
423
|
+
|
|
424
|
+
declare const fetchProfile: (id: string) => ResultAsync<Profile, Error>;
|
|
425
|
+
declare const fetchMetrics: (p: Profile) => ResultAsync<Metrics, Error>;
|
|
426
|
+
declare const formatSummary: (p: Profile, m: Metrics) => Summary;
|
|
427
|
+
|
|
428
|
+
const loadSummary = flow(
|
|
429
|
+
fetchProfile,
|
|
430
|
+
(ra) => ra.flatMap((profile) =>
|
|
431
|
+
fetchMetrics(profile).map((metrics) => formatSummary(profile, metrics))
|
|
432
|
+
),
|
|
433
|
+
);
|
|
434
|
+
// (id: string) => ResultAsync<Summary, Error>
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## 13. Functional Railway pipelines (`railway` + named steps)
|
|
440
|
+
|
|
441
|
+
Use `railway(input, ...steps)` from `@onrails/result/railway` to build a multi-step async pipeline using point-free reusable step wrappers. If any step is async, the entire pipeline resolves to a `ResultAsync`.
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
import { railway, parseWith, fromPromiseNamed, deriveNamed, select } from "@onrails/result/railway";
|
|
445
|
+
import { type ResultAsync } from "@onrails/result";
|
|
446
|
+
|
|
447
|
+
type Dashboard = { title: string };
|
|
448
|
+
|
|
449
|
+
type IdContext = { readonly id: string };
|
|
450
|
+
type ProfileContext = { readonly profile: Profile };
|
|
451
|
+
type TitleContext = { readonly title: string };
|
|
452
|
+
|
|
453
|
+
declare const IdSchema: { parse: (x: unknown) => string };
|
|
454
|
+
declare const fetchProfile: (id: string) => Promise<Profile>;
|
|
455
|
+
declare const toError: (e: unknown) => Error;
|
|
456
|
+
|
|
457
|
+
const loadDashboard = (rawId: unknown): ResultAsync<Dashboard, Error> =>
|
|
458
|
+
railway(
|
|
459
|
+
rawId,
|
|
460
|
+
parseWith(IdSchema, toError).as("id"),
|
|
461
|
+
fromPromiseNamed(
|
|
462
|
+
"profile",
|
|
463
|
+
({ id }: IdContext) => fetchProfile(id),
|
|
464
|
+
toError,
|
|
465
|
+
),
|
|
466
|
+
deriveNamed("title", ({ profile }: ProfileContext) =>
|
|
467
|
+
profile.name.toUpperCase(),
|
|
468
|
+
),
|
|
469
|
+
select(({ title }: TitleContext) => ({ title })),
|
|
470
|
+
);
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
353
475
|
## When NOT to go point-free
|
|
354
476
|
|
|
355
477
|
Pipelines should read top-to-bottom and each step should do one obvious thing. Reach for `pipe` when:
|
|
@@ -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
|
/**
|
|
@@ -39,7 +39,7 @@ function trySync(fn, onThrow) {
|
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
|
-
var
|
|
42
|
+
var combine = (results) => {
|
|
43
43
|
const values = [];
|
|
44
44
|
for (const result of results) {
|
|
45
45
|
if (isErr(result)) return err(result.error);
|
|
@@ -47,6 +47,10 @@ var combineTuple = (results) => {
|
|
|
47
47
|
}
|
|
48
48
|
return ok(values);
|
|
49
49
|
};
|
|
50
|
+
var combineTuple = (results) => (
|
|
51
|
+
// Runtime identical to combine; the cast restores per-index tuple types.
|
|
52
|
+
combine(results)
|
|
53
|
+
);
|
|
50
54
|
|
|
51
55
|
// src/types.ts
|
|
52
56
|
var UnexpectedError = class extends Error {
|
|
@@ -65,22 +69,27 @@ var liftPromiseResult = async (promise, onDefect) => {
|
|
|
65
69
|
if (isErr(result)) {
|
|
66
70
|
return result;
|
|
67
71
|
}
|
|
68
|
-
|
|
69
|
-
if (inner && typeof inner === "object" && "_tag" in inner) {
|
|
70
|
-
if (inner._tag === "Ok" || inner._tag === "Err") {
|
|
71
|
-
return inner;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return result;
|
|
72
|
+
return isResultLike(result.value) ? result.value : result;
|
|
75
73
|
} catch (error) {
|
|
76
74
|
return err(onDefect(error));
|
|
77
75
|
}
|
|
78
76
|
};
|
|
77
|
+
var sequenceSettled = (settled) => {
|
|
78
|
+
const values = [];
|
|
79
|
+
for (const result of settled) {
|
|
80
|
+
if (isErr(result)) {
|
|
81
|
+
return err(result.error);
|
|
82
|
+
}
|
|
83
|
+
values.push(result.value);
|
|
84
|
+
}
|
|
85
|
+
return ok(values);
|
|
86
|
+
};
|
|
79
87
|
var ResultAsync = class _ResultAsync {
|
|
80
88
|
constructor(run) {
|
|
81
89
|
this.run = run;
|
|
82
90
|
}
|
|
83
91
|
run;
|
|
92
|
+
promise = null;
|
|
84
93
|
static fromResult(result) {
|
|
85
94
|
return new _ResultAsync(async () => result);
|
|
86
95
|
}
|
|
@@ -128,46 +137,30 @@ var ResultAsync = class _ResultAsync {
|
|
|
128
137
|
});
|
|
129
138
|
}
|
|
130
139
|
static combineTuple(results) {
|
|
131
|
-
return
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const result = await ra.resolve();
|
|
135
|
-
if (isErr(result)) {
|
|
136
|
-
return err(result.error);
|
|
137
|
-
}
|
|
138
|
-
values.push(result.value);
|
|
139
|
-
}
|
|
140
|
-
return ok(values);
|
|
141
|
-
});
|
|
140
|
+
return _ResultAsync.combine(
|
|
141
|
+
results
|
|
142
|
+
);
|
|
142
143
|
}
|
|
143
144
|
/**
|
|
144
145
|
* Like {@link combineTuple}, but starts every branch before awaiting (wall-clock
|
|
145
146
|
* parallel for independent IO). On failure, returns the first `Err` in input order.
|
|
146
147
|
*/
|
|
147
148
|
static combineTupleParallel(results) {
|
|
148
|
-
return new _ResultAsync(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
for (const result of settled) {
|
|
152
|
-
if (isErr(result)) {
|
|
153
|
-
return err(result.error);
|
|
154
|
-
}
|
|
155
|
-
values.push(result.value);
|
|
156
|
-
}
|
|
157
|
-
return ok(values);
|
|
158
|
-
});
|
|
149
|
+
return new _ResultAsync(
|
|
150
|
+
async () => sequenceSettled(await Promise.all(results.map((ra) => ra.resolve())))
|
|
151
|
+
);
|
|
159
152
|
}
|
|
160
153
|
map(fn) {
|
|
161
|
-
return new _ResultAsync(async () => map(await this.
|
|
154
|
+
return new _ResultAsync(async () => map(await this.resolve(), fn));
|
|
162
155
|
}
|
|
163
156
|
mapErr(fn) {
|
|
164
|
-
return new _ResultAsync(async () => mapErr(await this.
|
|
157
|
+
return new _ResultAsync(async () => mapErr(await this.resolve(), fn));
|
|
165
158
|
}
|
|
166
159
|
flatMap(fn) {
|
|
167
160
|
return new _ResultAsync(async () => {
|
|
168
|
-
const first = await this.
|
|
161
|
+
const first = await this.resolve();
|
|
169
162
|
if (isErr(first)) {
|
|
170
|
-
return
|
|
163
|
+
return first;
|
|
171
164
|
}
|
|
172
165
|
const next = fn(first.value);
|
|
173
166
|
if (next instanceof _ResultAsync) {
|
|
@@ -176,29 +169,27 @@ var ResultAsync = class _ResultAsync {
|
|
|
176
169
|
if (isResultLike(next)) {
|
|
177
170
|
return next;
|
|
178
171
|
}
|
|
179
|
-
|
|
180
|
-
return next.inner;
|
|
181
|
-
}
|
|
182
|
-
return next;
|
|
172
|
+
return isCompatLike(next) ? next.inner : next;
|
|
183
173
|
});
|
|
184
174
|
}
|
|
185
175
|
andThen(fn) {
|
|
186
176
|
return this.flatMap(fn);
|
|
187
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Alias for {@link flatMap}.
|
|
180
|
+
* @deprecated Use {@link flatMap} or keep/use compat {@link andThen} instead.
|
|
181
|
+
*/
|
|
188
182
|
chain(fn) {
|
|
189
183
|
return this.flatMap(fn);
|
|
190
184
|
}
|
|
191
185
|
recover(fn) {
|
|
192
186
|
return new _ResultAsync(async () => {
|
|
193
|
-
const first = await this.
|
|
187
|
+
const first = await this.resolve();
|
|
194
188
|
if (!isErr(first)) {
|
|
195
189
|
return first;
|
|
196
190
|
}
|
|
197
191
|
const next = fn(first.error);
|
|
198
|
-
|
|
199
|
-
return next.resolve();
|
|
200
|
-
}
|
|
201
|
-
return next;
|
|
192
|
+
return next instanceof _ResultAsync ? next.resolve() : next;
|
|
202
193
|
});
|
|
203
194
|
}
|
|
204
195
|
orElse(fn) {
|
|
@@ -206,7 +197,7 @@ var ResultAsync = class _ResultAsync {
|
|
|
206
197
|
}
|
|
207
198
|
tap(fn) {
|
|
208
199
|
return new _ResultAsync(async () => {
|
|
209
|
-
const result = await this.
|
|
200
|
+
const result = await this.resolve();
|
|
210
201
|
if (!isErr(result)) {
|
|
211
202
|
fn(result.value);
|
|
212
203
|
}
|
|
@@ -215,7 +206,7 @@ var ResultAsync = class _ResultAsync {
|
|
|
215
206
|
}
|
|
216
207
|
tapErr(fn) {
|
|
217
208
|
return new _ResultAsync(async () => {
|
|
218
|
-
const result = await this.
|
|
209
|
+
const result = await this.resolve();
|
|
219
210
|
if (isErr(result)) {
|
|
220
211
|
fn(result.error);
|
|
221
212
|
}
|
|
@@ -223,21 +214,32 @@ var ResultAsync = class _ResultAsync {
|
|
|
223
214
|
});
|
|
224
215
|
}
|
|
225
216
|
unwrapOr(defaultValue) {
|
|
226
|
-
return this.
|
|
217
|
+
return this.resolve().then((result) => isErr(result) ? defaultValue : result.value);
|
|
227
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* @deprecated Await the ResultAsync and narrow with {@link isOk} — this method
|
|
221
|
+
* re-executes deferred factories and cannot narrow.
|
|
222
|
+
*/
|
|
228
223
|
isOk() {
|
|
229
|
-
return this.
|
|
224
|
+
return this.resolve().then((result) => !isErr(result));
|
|
230
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* @deprecated Await the ResultAsync and narrow with {@link isErr} — this method
|
|
228
|
+
* re-executes deferred factories and cannot narrow.
|
|
229
|
+
*/
|
|
231
230
|
isErr() {
|
|
232
|
-
return this.
|
|
231
|
+
return this.resolve().then((result) => isErr(result));
|
|
233
232
|
}
|
|
234
233
|
match(onOk, onErr) {
|
|
235
|
-
return this.
|
|
234
|
+
return this.resolve().then(
|
|
236
235
|
(result) => isErr(result) ? onErr(result.error) : onOk(result.value)
|
|
237
236
|
);
|
|
238
237
|
}
|
|
239
238
|
resolve() {
|
|
240
|
-
|
|
239
|
+
if (!this.promise) {
|
|
240
|
+
this.promise = this.run();
|
|
241
|
+
}
|
|
242
|
+
return this.promise;
|
|
241
243
|
}
|
|
242
244
|
/**
|
|
243
245
|
* Thenable shim — `await ra` resolves to a bare tagged-union `Result<T, E>`.
|
|
@@ -245,7 +247,8 @@ var ResultAsync = class _ResultAsync {
|
|
|
245
247
|
*/
|
|
246
248
|
// biome-ignore lint/suspicious/noThenProperty: makes ResultAsync awaitable
|
|
247
249
|
then(onfulfilled, onrejected) {
|
|
248
|
-
return this.
|
|
250
|
+
return this.resolve().then(
|
|
251
|
+
// Safe: without onfulfilled, R1 stays at its Result<T, E> default.
|
|
249
252
|
(r) => onfulfilled ? onfulfilled(r) : r,
|
|
250
253
|
onrejected ?? void 0
|
|
251
254
|
);
|
|
@@ -298,7 +301,7 @@ var CompatResult = class _CompatResult {
|
|
|
298
301
|
if (isErr(this.inner)) {
|
|
299
302
|
return CompatResultAsync.err(this.inner.error);
|
|
300
303
|
}
|
|
301
|
-
return CompatResultAsync.fromInner(
|
|
304
|
+
return CompatResultAsync.fromInner(coerceToCoreAsync(fn(this.inner.value)));
|
|
302
305
|
}
|
|
303
306
|
orElse(fn) {
|
|
304
307
|
if (isOk(this.inner)) {
|
|
@@ -376,9 +379,7 @@ var CompatResultAsync = class _CompatResultAsync {
|
|
|
376
379
|
return new _CompatResultAsync(this.inner.mapErr(fn));
|
|
377
380
|
}
|
|
378
381
|
andThen(fn) {
|
|
379
|
-
return new _CompatResultAsync(
|
|
380
|
-
this.inner.andThen((value) => coerceToCore(fn(value)))
|
|
381
|
-
);
|
|
382
|
+
return new _CompatResultAsync(this.inner.andThen((value) => coerceToCore(fn(value))));
|
|
382
383
|
}
|
|
383
384
|
chain(fn) {
|
|
384
385
|
return this.andThen(fn);
|
|
@@ -406,18 +407,31 @@ var CompatResultAsync = class _CompatResultAsync {
|
|
|
406
407
|
return this.inner.match(onOk, onErr);
|
|
407
408
|
}
|
|
408
409
|
andTee(fn) {
|
|
409
|
-
return
|
|
410
|
-
(
|
|
410
|
+
return new _CompatResultAsync(
|
|
411
|
+
ResultAsync.defer(async () => {
|
|
412
|
+
const r = await this.inner.resolve();
|
|
413
|
+
if (!isErr(r)) {
|
|
414
|
+
try {
|
|
415
|
+
await fn(r.value);
|
|
416
|
+
} catch {
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return r;
|
|
420
|
+
})
|
|
411
421
|
);
|
|
412
422
|
}
|
|
413
423
|
orTee(fn) {
|
|
414
424
|
return new _CompatResultAsync(
|
|
415
|
-
ResultAsync.
|
|
416
|
-
this.inner.resolve()
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
425
|
+
ResultAsync.defer(async () => {
|
|
426
|
+
const r = await this.inner.resolve();
|
|
427
|
+
if (isErr(r)) {
|
|
428
|
+
try {
|
|
429
|
+
await fn(r.error);
|
|
430
|
+
} catch {
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return r;
|
|
434
|
+
})
|
|
421
435
|
);
|
|
422
436
|
}
|
|
423
437
|
};
|
|
@@ -427,6 +441,7 @@ function coerceToCore(next) {
|
|
|
427
441
|
if (next instanceof CompatResult) return next.inner;
|
|
428
442
|
return next;
|
|
429
443
|
}
|
|
444
|
+
var coerceToCoreAsync = (next) => next instanceof CompatResultAsync ? next.toCore() : next;
|
|
430
445
|
var okAsync2 = CompatResultAsync.ok;
|
|
431
446
|
var errAsync2 = CompatResultAsync.err;
|
|
432
447
|
var fromPromise2 = CompatResultAsync.fromPromise;
|