@evolu/common 7.4.0 → 8.0.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -61
- package/dist/src/Array.d.ts +343 -102
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +181 -85
- package/dist/src/Assert.d.ts +57 -11
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +47 -11
- package/dist/src/BigInt.d.ts +6 -1
- package/dist/src/BigInt.d.ts.map +1 -1
- package/dist/src/BigInt.js +5 -0
- package/dist/src/Brand.d.ts +40 -12
- package/dist/src/Brand.d.ts.map +1 -1
- package/dist/src/Brand.js +5 -0
- package/dist/src/Buffer.d.ts +6 -1
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Buffer.js +7 -2
- package/dist/src/Cache.d.ts +10 -2
- package/dist/src/Cache.d.ts.map +1 -1
- package/dist/src/Cache.js +6 -0
- package/dist/src/Callbacks.d.ts +10 -7
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Callbacks.js +23 -13
- package/dist/src/Console.d.ts +361 -69
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +217 -117
- package/dist/src/Crypto.d.ts +70 -21
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +72 -27
- package/dist/src/Eq.d.ts +22 -2
- package/dist/src/Eq.d.ts.map +1 -1
- package/dist/src/Eq.js +21 -1
- package/dist/src/Error.d.ts +23 -12
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +27 -11
- package/dist/src/Function.d.ts +128 -56
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +118 -14
- package/dist/src/Identicon.d.ts +5 -0
- package/dist/src/Identicon.d.ts.map +1 -1
- package/dist/src/Identicon.js +6 -1
- package/dist/src/Lookup.d.ts +160 -0
- package/dist/src/Lookup.d.ts.map +1 -0
- package/dist/src/Lookup.js +192 -0
- package/dist/src/Microtask.d.ts +21 -0
- package/dist/src/Microtask.d.ts.map +1 -0
- package/dist/src/Microtask.js +37 -0
- package/dist/src/Number.d.ts +18 -4
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +23 -1
- package/dist/src/Object.d.ts +110 -5
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +108 -5
- package/dist/src/Option.d.ts +58 -0
- package/dist/src/Option.d.ts.map +1 -0
- package/dist/src/Option.js +43 -0
- package/dist/src/Order.d.ts +5 -0
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +8 -3
- package/dist/src/Platform.d.ts +36 -2
- package/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +10 -6
- package/dist/src/Polyfills.d.ts +27 -0
- package/dist/src/Polyfills.d.ts.map +1 -0
- package/dist/src/Polyfills.js +299 -0
- package/dist/src/Random.d.ts +18 -7
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Random.js +9 -7
- package/dist/src/Redacted.d.ts +7 -4
- package/dist/src/Redacted.d.ts.map +1 -1
- package/dist/src/Redacted.js +5 -0
- package/dist/src/Ref.d.ts +31 -16
- package/dist/src/Ref.d.ts.map +1 -1
- package/dist/src/Ref.js +35 -7
- package/dist/src/RefCount.d.ts +62 -0
- package/dist/src/RefCount.d.ts.map +1 -0
- package/dist/src/RefCount.js +83 -0
- package/dist/src/Relation.d.ts +84 -29
- package/dist/src/Relation.d.ts.map +1 -1
- package/dist/src/Relation.js +83 -105
- package/dist/src/Resource.d.ts +263 -0
- package/dist/src/Resource.d.ts.map +1 -0
- package/dist/src/Resource.js +389 -0
- package/dist/src/Result.d.ts +390 -374
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +142 -70
- package/dist/src/Schedule.d.ts +953 -0
- package/dist/src/Schedule.d.ts.map +1 -0
- package/dist/src/Schedule.js +1199 -0
- package/dist/src/Set.d.ts +181 -0
- package/dist/src/Set.d.ts.map +1 -0
- package/dist/src/Set.js +137 -0
- package/dist/src/Skiplist.d.ts +6 -1
- package/dist/src/Skiplist.d.ts.map +1 -1
- package/dist/src/Skiplist.js +5 -0
- package/dist/src/Sqlite.d.ts +160 -60
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +290 -190
- package/dist/src/Store.d.ts +36 -28
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +55 -15
- package/dist/src/String.d.ts +5 -0
- package/dist/src/String.d.ts.map +1 -1
- package/dist/src/String.js +5 -0
- package/dist/src/Task.d.ts +2263 -423
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +1488 -348
- package/dist/src/Test.d.ts +122 -0
- package/dist/src/Test.d.ts.map +1 -0
- package/dist/src/Test.js +66 -0
- package/dist/src/Time.d.ts +201 -48
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +190 -86
- package/dist/src/Tracer.d.ts +48 -0
- package/dist/src/Tracer.d.ts.map +1 -0
- package/dist/src/Tracer.js +6 -0
- package/dist/src/Type.d.ts +582 -209
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +362 -240
- package/dist/src/Types.d.ts +160 -6
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/Types.js +22 -2
- package/dist/src/WebSocket.d.ts +113 -58
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +210 -118
- package/dist/src/Worker.d.ts +245 -60
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +220 -64
- package/dist/src/index.d.ts +25 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +19 -3
- package/dist/src/local-first/Db.d.ts +24 -213
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +622 -293
- package/dist/src/local-first/Error.d.ts +12 -0
- package/dist/src/local-first/Error.d.ts.map +1 -0
- package/dist/src/local-first/Error.js +6 -0
- package/dist/src/local-first/Evolu.d.ts +330 -285
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +472 -402
- package/dist/src/local-first/LocalAuth.d.ts +10 -10
- package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
- package/dist/src/local-first/LocalAuth.js +6 -1
- package/dist/src/local-first/Owner.d.ts +70 -27
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +27 -14
- package/dist/src/local-first/Protocol.d.ts +58 -50
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +253 -155
- package/dist/src/local-first/Query.d.ts +133 -43
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Query.js +139 -90
- package/dist/src/local-first/Relay.d.ts +18 -38
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Relay.js +59 -171
- package/dist/src/local-first/Schema.d.ts +139 -144
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +78 -163
- package/dist/src/local-first/Shared.d.ts +125 -0
- package/dist/src/local-first/Shared.d.ts.map +1 -0
- package/dist/src/local-first/Shared.js +569 -0
- package/dist/src/local-first/Storage.d.ts +50 -53
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +121 -173
- package/dist/src/local-first/Timestamp.d.ts +28 -35
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +24 -25
- package/dist/src/local-first/index.d.ts +2 -15
- package/dist/src/local-first/index.d.ts.map +1 -1
- package/dist/src/local-first/index.js +2 -15
- package/package.json +36 -26
- package/src/Array.ts +467 -106
- package/src/Assert.ts +87 -12
- package/src/BigInt.ts +7 -1
- package/src/Brand.ts +41 -12
- package/src/Buffer.ts +10 -4
- package/src/Cache.ts +11 -2
- package/src/Callbacks.ts +33 -23
- package/src/Console.ts +567 -178
- package/src/Crypto.ts +111 -62
- package/src/Eq.ts +30 -3
- package/src/Error.ts +50 -34
- package/src/Function.ts +140 -68
- package/src/Identicon.ts +7 -1
- package/src/Lookup.ts +415 -0
- package/src/Microtask.ts +58 -0
- package/src/Number.ts +40 -7
- package/src/Object.ts +155 -16
- package/src/Option.ts +74 -0
- package/src/Order.ts +11 -3
- package/src/Platform.ts +43 -7
- package/src/Polyfills.ts +465 -0
- package/src/Random.ts +25 -11
- package/src/Redacted.ts +8 -4
- package/src/Ref.ts +72 -21
- package/src/RefCount.ts +170 -0
- package/src/Relation.ts +188 -136
- package/src/Resource.ts +864 -0
- package/src/Result.ts +548 -381
- package/src/Schedule.ts +1471 -0
- package/src/Set.ts +247 -0
- package/src/Skiplist.ts +7 -1
- package/src/Sqlite.ts +409 -304
- package/src/Store.ts +94 -48
- package/src/String.ts +6 -0
- package/src/Task.ts +3685 -740
- package/src/Test.ts +162 -0
- package/src/Time.ts +348 -145
- package/src/Tracer.ts +54 -0
- package/src/Type.ts +1064 -630
- package/src/Types.ts +202 -7
- package/src/WebSocket.ts +311 -178
- package/src/Worker.ts +527 -147
- package/src/index.ts +74 -3
- package/src/local-first/Db.ts +871 -597
- package/src/local-first/Error.ts +17 -0
- package/src/local-first/Evolu.ts +884 -823
- package/src/local-first/LocalAuth.ts +10 -10
- package/src/local-first/Owner.ts +94 -42
- package/src/local-first/Protocol.ts +440 -303
- package/src/local-first/Query.ts +243 -194
- package/src/local-first/Relay.ts +91 -252
- package/src/local-first/Schema.ts +284 -394
- package/src/local-first/Shared.ts +932 -0
- package/src/local-first/Storage.ts +209 -282
- package/src/local-first/Timestamp.ts +38 -45
- package/src/local-first/index.ts +2 -16
- package/LICENSE +0 -21
- package/dist/src/Instances.d.ts +0 -34
- package/dist/src/Instances.d.ts.map +0 -1
- package/dist/src/Instances.js +0 -44
- package/dist/src/Resources.d.ts +0 -118
- package/dist/src/Resources.d.ts.map +0 -1
- package/dist/src/Resources.js +0 -197
- package/dist/src/local-first/Platform.d.ts +0 -25
- package/dist/src/local-first/Platform.d.ts.map +0 -1
- package/dist/src/local-first/Platform.js +0 -1
- package/dist/src/local-first/Public.d.ts +0 -22
- package/dist/src/local-first/Public.d.ts.map +0 -1
- package/dist/src/local-first/Public.js +0 -15
- package/dist/src/local-first/PublicKysely.d.ts +0 -148
- package/dist/src/local-first/PublicKysely.d.ts.map +0 -1
- package/dist/src/local-first/PublicKysely.js +0 -184
- package/dist/src/local-first/Sync.d.ts +0 -112
- package/dist/src/local-first/Sync.d.ts.map +0 -1
- package/dist/src/local-first/Sync.js +0 -529
- package/src/Instances.ts +0 -90
- package/src/Resources.ts +0 -367
- package/src/local-first/Platform.ts +0 -27
- package/src/local-first/Public.ts +0 -42
- package/src/local-first/PublicKysely.ts +0 -239
- package/src/local-first/Sync.ts +0 -960
package/src/Result.ts
CHANGED
|
@@ -1,25 +1,77 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Type-safe error handling with Result types.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
arrayFrom,
|
|
9
|
+
emptyArray,
|
|
10
|
+
isNonEmptyArray,
|
|
11
|
+
type NonEmptyReadonlyArray,
|
|
12
|
+
} from "./Array.js";
|
|
13
|
+
import { assert } from "./Assert.js";
|
|
14
|
+
import type { Lazy } from "./Function.js";
|
|
15
|
+
import { exhaustiveCheck } from "./Function.js";
|
|
16
|
+
import { createRecord, emptyRecord, isIterable } from "./Object.js";
|
|
17
|
+
import type { Typed } from "./Type.js";
|
|
2
18
|
|
|
3
19
|
/**
|
|
4
|
-
* The problem with
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
20
|
+
* The problem with `throw` in JavaScript is that the caught value is always of
|
|
21
|
+
* unknown type. We can't be sure all errors have been handled because the
|
|
22
|
+
* TypeScript compiler can't tell us what was thrown — we can't use
|
|
23
|
+
* {@link exhaustiveCheck}.
|
|
8
24
|
*
|
|
9
|
-
* Languages like Rust
|
|
10
|
-
* where errors are
|
|
11
|
-
*
|
|
12
|
-
* can have this too via the `Result` type.
|
|
25
|
+
* Languages like Rust and Haskell solve this with types like `Result` or
|
|
26
|
+
* `Either` where errors are part of the return type. TypeScript can do the
|
|
27
|
+
* same.
|
|
13
28
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* // With throw - caught value is unknown, can't use exhaustiveCheck
|
|
31
|
+
* try {
|
|
32
|
+
* doSomething();
|
|
33
|
+
* } catch (error) {
|
|
34
|
+
* // error is unknown - TypeScript can't help us here
|
|
35
|
+
* }
|
|
17
36
|
*
|
|
18
|
-
*
|
|
37
|
+
* // With Result - error type is known, exhaustiveCheck works
|
|
38
|
+
* const result = doSomething();
|
|
39
|
+
* if (!result.ok) {
|
|
40
|
+
* switch (result.error.type) {
|
|
41
|
+
* case "NotFound":
|
|
42
|
+
* // handle not found
|
|
43
|
+
* break;
|
|
44
|
+
* case "InvalidInput":
|
|
45
|
+
* // handle invalid input
|
|
46
|
+
* break;
|
|
47
|
+
* default:
|
|
48
|
+
* exhaustiveCheck(result.error); // TypeScript ensures all cases handled
|
|
49
|
+
* }
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* A `Result` is either {@link Ok} (success with a value) or {@link Err} (failure
|
|
54
|
+
* with an error). Create them with {@link ok} and {@link err}.
|
|
19
55
|
*
|
|
20
56
|
* ```ts
|
|
21
|
-
*
|
|
22
|
-
*
|
|
57
|
+
* type Result<T, E = never> = Ok<T> | Err<E>;
|
|
58
|
+
*
|
|
59
|
+
* interface Ok<T> {
|
|
60
|
+
* readonly ok: true;
|
|
61
|
+
* readonly value: T;
|
|
62
|
+
* }
|
|
63
|
+
*
|
|
64
|
+
* interface Err<E> {
|
|
65
|
+
* readonly ok: false;
|
|
66
|
+
* readonly error: E;
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* ## Example
|
|
71
|
+
*
|
|
72
|
+
* ```ts
|
|
73
|
+
* // Typed<T> adds { type: T } for discriminated unions
|
|
74
|
+
* interface ParseJsonError extends Typed<"ParseJsonError"> {
|
|
23
75
|
* readonly message: string;
|
|
24
76
|
* }
|
|
25
77
|
*
|
|
@@ -31,21 +83,21 @@ import type { Task } from "./Task.js";
|
|
|
31
83
|
* }
|
|
32
84
|
* };
|
|
33
85
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
86
|
+
* const json = parseJson('{"name": "Alice"}');
|
|
87
|
+
* if (!json.ok) return json; // short-circuit on error
|
|
36
88
|
*
|
|
37
|
-
* //
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* // Now, we have access to the json.value.
|
|
41
|
-
* expectTypeOf(json.value).toBeUnknown();
|
|
89
|
+
* // Now we have access to json.value (type: unknown)
|
|
90
|
+
* console.log(json.value);
|
|
42
91
|
* ```
|
|
43
92
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
93
|
+
* The caller doesn't need `try/catch`, just `if (!json.ok)`, and the error is
|
|
94
|
+
* `ParseJsonError`, not `unknown`.
|
|
95
|
+
*
|
|
96
|
+
* Use this pattern when the caller can recover or choose a different flow.
|
|
97
|
+
* Parsing unknown JSON is a good fit because invalid input is expected and
|
|
98
|
+
* actionable.
|
|
46
99
|
*
|
|
47
|
-
*
|
|
48
|
-
* wrapping unsafe code, Evolu provides the {@link trySync} helper:
|
|
100
|
+
* To avoid `try/catch` inside `parseJson` too, use {@link trySync}:
|
|
49
101
|
*
|
|
50
102
|
* ```ts
|
|
51
103
|
* const parseJson = (value: string): Result<unknown, ParseJsonError> =>
|
|
@@ -55,297 +107,137 @@ import type { Task } from "./Task.js";
|
|
|
55
107
|
* );
|
|
56
108
|
* ```
|
|
57
109
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
110
|
+
* `trySync` and {@link tryAsync} are for intentionally converting thrown errors
|
|
111
|
+
* into typed, recoverable {@link Result} values.
|
|
60
112
|
*
|
|
61
|
-
*
|
|
113
|
+
* Do not wrap every throwing API in {@link Result}. If an error is unrecoverable
|
|
114
|
+
* and the caller has no meaningful fallback, let it throw and handle it at the
|
|
115
|
+
* app boundary.
|
|
62
116
|
*
|
|
63
|
-
*
|
|
64
|
-
* - For unsafe code, use `trySync` or `tryAsync`.
|
|
117
|
+
* Since `Result` is a plain object, imperative code works naturally:
|
|
65
118
|
*
|
|
66
|
-
*
|
|
119
|
+
* ### Stop on error, map on success
|
|
67
120
|
*
|
|
68
121
|
* ```ts
|
|
69
|
-
* const
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* // Simulate an API call
|
|
73
|
-
* return new Promise((resolve) => {
|
|
74
|
-
* setTimeout(() => {
|
|
75
|
-
* if (userId === "1") {
|
|
76
|
-
* resolve(ok({ id: "1", name: "Alice" }));
|
|
77
|
-
* } else {
|
|
78
|
-
* resolve(
|
|
79
|
-
* err({ type: "FetchUserError", reason: "user not found" }),
|
|
80
|
-
* );
|
|
81
|
-
* }
|
|
82
|
-
* }, 1000);
|
|
83
|
-
* });
|
|
84
|
-
* };
|
|
122
|
+
* const users = getActiveUsers();
|
|
123
|
+
* if (!users.ok) return users;
|
|
124
|
+
* const usernames = mapArray(users.value, (u) => u.username);
|
|
85
125
|
* ```
|
|
86
126
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* ### Naming convention
|
|
90
|
-
*
|
|
91
|
-
* - **For values you need:** use a name without Result suffix (`user`, `config`)
|
|
92
|
-
* - **For void operations:** use `result` (no value to name)
|
|
93
|
-
*
|
|
94
|
-
* For multiple void operations, use block scopes to avoid potentially long
|
|
95
|
-
* names like `createBaseTablesResult`, `createRelayTablesResult`, or counters
|
|
96
|
-
* like `result1`, `result2`:
|
|
127
|
+
* ### Iterate array, stop on first error
|
|
97
128
|
*
|
|
98
129
|
* ```ts
|
|
99
|
-
* const
|
|
100
|
-
* const
|
|
101
|
-
* if (!user.ok) return user;
|
|
102
|
-
*
|
|
103
|
-
* const result = saveToDatabase(user.value);
|
|
130
|
+
* for (const user of users) {
|
|
131
|
+
* const result = validateUser(user); // Result<ValidUser, ValidateUserError>
|
|
104
132
|
* if (!result.ok) return result;
|
|
105
|
-
*
|
|
106
|
-
* return ok();
|
|
107
|
-
* };
|
|
108
|
-
*
|
|
109
|
-
* const setupDatabase = () => {
|
|
110
|
-
* // Multiple void operations - use block scopes to avoid name clash
|
|
111
|
-
* {
|
|
112
|
-
* const result = createBaseTables();
|
|
113
|
-
* if (!result.ok) return result;
|
|
114
|
-
* }
|
|
115
|
-
* {
|
|
116
|
-
* const result = createRelayTables();
|
|
117
|
-
* if (!result.ok) return result;
|
|
118
|
-
* }
|
|
119
|
-
*
|
|
120
|
-
* return ok();
|
|
121
|
-
* };
|
|
133
|
+
* }
|
|
122
134
|
* ```
|
|
123
135
|
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* #### Sequential operations with short-circuiting
|
|
127
|
-
*
|
|
128
|
-
* When performing a sequence of operations where any failure should stop
|
|
129
|
-
* further processing, use the `Result` type with early returns.
|
|
136
|
+
* ## Style
|
|
130
137
|
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
138
|
+
* Imperative code is the preferred way to compose sequential {@link Result}
|
|
139
|
+
* operations.
|
|
133
140
|
*
|
|
134
141
|
* ```ts
|
|
135
|
-
* const
|
|
136
|
-
*
|
|
137
|
-
* if (!result.ok) return result;
|
|
142
|
+
* const user = getUser();
|
|
143
|
+
* if (!user.ok) return user;
|
|
138
144
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* if (!dbSchema.ok) return dbSchema;
|
|
142
|
-
*
|
|
143
|
-
* {
|
|
144
|
-
* const result = ensureDbSchema(deps)(
|
|
145
|
-
* message.restore.dbSchema,
|
|
146
|
-
* dbSchema.value,
|
|
147
|
-
* );
|
|
148
|
-
* if (!result.ok) return result;
|
|
149
|
-
* }
|
|
150
|
-
* {
|
|
151
|
-
* const result = initializeDb(deps)(message.restore.mnemonic);
|
|
152
|
-
* if (!result.ok) return result;
|
|
153
|
-
* }
|
|
154
|
-
* }
|
|
155
|
-
* return ok();
|
|
156
|
-
* });
|
|
145
|
+
* const profile = getProfile(user.value.id);
|
|
146
|
+
* if (!profile.ok) return profile;
|
|
157
147
|
*
|
|
158
|
-
*
|
|
159
|
-
* deps.postMessage({ type: "onError", error: result.error });
|
|
160
|
-
* return;
|
|
161
|
-
* }
|
|
148
|
+
* return ok({ user: user.value, profile: profile.value });
|
|
162
149
|
* ```
|
|
163
150
|
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
* - If all operations succeed, return `ok()` (or another value if needed).
|
|
171
|
-
* - Outside the transaction, handle the final `Result` to report success or
|
|
172
|
-
* failure.
|
|
151
|
+
* This is an intentional style choice. Evolu does not provide helper
|
|
152
|
+
* combinators for every sequential pattern because that would duplicate plain
|
|
153
|
+
* control flow and create API ambiguity. Use helpers when they add semantics
|
|
154
|
+
* over ordinary control flow, such as operating on collections of results.
|
|
155
|
+
* While it may seem verbose, it is explicit, transparent, and avoids pipes and
|
|
156
|
+
* nested helpers, which are harder to debug.
|
|
173
157
|
*
|
|
174
|
-
*
|
|
175
|
-
* blocks, and clearly communicates the control flow.
|
|
158
|
+
* ## Composition
|
|
176
159
|
*
|
|
177
|
-
*
|
|
160
|
+
* Some patterns are common enough that deserve helpers. The previous example
|
|
161
|
+
* can be written with {@link mapResult}:
|
|
178
162
|
*
|
|
179
163
|
* ```ts
|
|
180
|
-
* const
|
|
181
|
-
*
|
|
182
|
-
* if (!foo.ok) return foo;
|
|
183
|
-
*
|
|
184
|
-
* const bar = getBar(foo.value);
|
|
185
|
-
* if (!bar.ok) return bar;
|
|
186
|
-
*
|
|
187
|
-
* return ok(barToNumber(bar.value));
|
|
188
|
-
* };
|
|
164
|
+
* const result = mapResult(users, validateUser);
|
|
165
|
+
* // Result<ValidUser[], ValidateUserError>
|
|
189
166
|
* ```
|
|
190
167
|
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
* Even with disciplined use of `trySync` and `tryAsync`, unexpected errors can
|
|
194
|
-
* still occur due to programming mistakes, third-party library bugs, or edge
|
|
195
|
-
* cases. These should be logged for debugging, but **unexpected errors are not
|
|
196
|
-
* recoverable** - they represent bugs that must be fixed.
|
|
197
|
-
*
|
|
198
|
-
* **Important**: "Graceful shutdown" and error recovery can only come from
|
|
199
|
-
* expected errors handled via the `Result` type. Unexpected errors should fail
|
|
200
|
-
* fast - the operation fails immediately and the error bubbles up.
|
|
201
|
-
*
|
|
202
|
-
* #### In browser environments
|
|
168
|
+
* For an array of results, {@link allResult} extracts all values or returns the
|
|
169
|
+
* first error:
|
|
203
170
|
*
|
|
204
171
|
* ```ts
|
|
205
|
-
*
|
|
206
|
-
* window.addEventListener("error", (event) => {
|
|
207
|
-
* console.error("Uncaught error:", event.error);
|
|
208
|
-
* // Send to error reporting service
|
|
209
|
-
* errorReportingService.report(event.error);
|
|
210
|
-
* });
|
|
211
|
-
*
|
|
212
|
-
* // For unhandled promise rejections
|
|
213
|
-
* window.addEventListener("unhandledrejection", (event) => {
|
|
214
|
-
* console.error("Unhandled promise rejection:", event.reason);
|
|
215
|
-
* errorReportingService.report(event.reason);
|
|
216
|
-
* });
|
|
172
|
+
* const result = allResult(validationResults);
|
|
217
173
|
* ```
|
|
218
174
|
*
|
|
219
|
-
*
|
|
175
|
+
* For the first success, {@link anyResult} returns the first Ok or the last
|
|
176
|
+
* error if all fail:
|
|
220
177
|
*
|
|
221
178
|
* ```ts
|
|
222
|
-
*
|
|
223
|
-
* process.on("uncaughtException", (error) => {
|
|
224
|
-
* console.error("Uncaught exception:", error);
|
|
225
|
-
* errorReportingService.report(error);
|
|
226
|
-
* // Exit immediately - unexpected errors are not recoverable
|
|
227
|
-
* process.exit(1);
|
|
228
|
-
* });
|
|
229
|
-
*
|
|
230
|
-
* // Handle unhandled promise rejections
|
|
231
|
-
* process.on("unhandledRejection", (reason) => {
|
|
232
|
-
* console.error("Unhandled promise rejection:", reason);
|
|
233
|
-
* errorReportingService.report(reason);
|
|
234
|
-
* });
|
|
179
|
+
* const result = anyResult(parserResults);
|
|
235
180
|
* ```
|
|
236
181
|
*
|
|
237
|
-
*
|
|
238
|
-
* errors for debugging purposes. They do not attempt recovery - unexpected
|
|
239
|
-
* errors represent bugs that must be fixed. The discipline of explicit error
|
|
240
|
-
* handling through the `Result` pattern remains the primary approach for all
|
|
241
|
-
* recoverable scenarios.
|
|
242
|
-
*
|
|
243
|
-
* ### FAQ
|
|
244
|
-
*
|
|
245
|
-
* #### When should a function return a plain value instead of `Result<T, E>`?
|
|
246
|
-
*
|
|
247
|
-
* Use `Result<T, E>` only when a function can fail with **known, expected
|
|
248
|
-
* errors** that callers need to handle. If a function cannot fail with a known
|
|
249
|
-
* error, return the value directly.
|
|
182
|
+
* ## Naming convention
|
|
250
183
|
*
|
|
251
|
-
* -
|
|
252
|
-
* -
|
|
253
|
-
* - ❌ Don't return `Result<User, never>` - unnecessary wrapper
|
|
254
|
-
*
|
|
255
|
-
* This keeps the codebase clean and makes error handling intentional. The type
|
|
256
|
-
* system communicates which operations can fail and which cannot.
|
|
257
|
-
*
|
|
258
|
-
* Unsafe code from external libraries (not under our control) should be wrapped
|
|
259
|
-
* with `trySync` or `tryAsync` at the boundaries. Once wrapped, if the error is
|
|
260
|
-
* not important to callers, functions can safely return plain values. If the
|
|
261
|
-
* error matters, use `Result` with a typed error.
|
|
184
|
+
* - Result with a value: name it after the value (`user`, `config`)
|
|
185
|
+
* - Result without a value: name it `result`
|
|
262
186
|
*
|
|
263
187
|
* ```ts
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
* );
|
|
188
|
+
* const processUser = (): Result<
|
|
189
|
+
* void,
|
|
190
|
+
* GetUserError | SaveToDatabaseError | SendWelcomeEmailError
|
|
191
|
+
* > => {
|
|
192
|
+
* const user = getUser();
|
|
193
|
+
* if (!user.ok) return user;
|
|
194
|
+
*
|
|
195
|
+
* const result = saveToDatabase(user.value);
|
|
196
|
+
* if (!result.ok) return result;
|
|
270
197
|
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
198
|
+
* // To avoid a clash with the previous `result`, use a block scope.
|
|
199
|
+
* {
|
|
200
|
+
* const result = sendWelcomeEmail(user.value);
|
|
201
|
+
* if (!result.ok) return result;
|
|
274
202
|
* }
|
|
275
203
|
*
|
|
276
|
-
*
|
|
204
|
+
* return ok();
|
|
277
205
|
* };
|
|
278
|
-
*
|
|
279
|
-
* // ✅ Can call without try-catch since it returns void
|
|
280
|
-
* processData(jsonString);
|
|
281
|
-
* ```
|
|
282
|
-
*
|
|
283
|
-
* #### What if my function doesn't return a value on success?
|
|
284
|
-
*
|
|
285
|
-
* If your function performs an operation but doesn't need to return a value on
|
|
286
|
-
* success, you can use `Result<void, E>`. Using `Result<void, E>` is clearer
|
|
287
|
-
* than using `Result<true, E>` or `Result<null, E>` because it communicates
|
|
288
|
-
* that the function doesn't produce a value but can produce errors.
|
|
289
|
-
*
|
|
290
|
-
* #### How do I short-circuit processing of an array on the first error?
|
|
291
|
-
*
|
|
292
|
-
* If you want to stop processing as soon as an error occurs (short-circuit),
|
|
293
|
-
* you should produce and check each `Result` inside a loop:
|
|
294
|
-
*
|
|
295
|
-
* ```ts
|
|
296
|
-
* for (const query of [
|
|
297
|
-
* sql`drop table evolu_config;`,
|
|
298
|
-
* sql`drop table evolu_message;`,
|
|
299
|
-
* ]) {
|
|
300
|
-
* const result = deps.sqlite.exec(query);
|
|
301
|
-
* if (!result.ok) return result;
|
|
302
|
-
* }
|
|
303
|
-
* // All queries succeeded
|
|
304
206
|
* ```
|
|
305
207
|
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
* If you have an array of operations (not results), you should make them
|
|
309
|
-
* _lazy_—that is, represent each operation as a function. This way, you only
|
|
310
|
-
* execute each operation as needed, and can stop on the first error:
|
|
208
|
+
* ## Unrecoverable errors
|
|
311
209
|
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
210
|
+
* Some errors can't be handled locally — they must propagate to the top level.
|
|
211
|
+
* These are unrecoverable errors: expected (you know they can happen) but only
|
|
212
|
+
* handleable at the app level.
|
|
314
213
|
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
* ];
|
|
214
|
+
* Do not force these errors into {@link Result} just because the underlying API
|
|
215
|
+
* throws. If the local caller cannot recover, let the error propagate to a
|
|
216
|
+
* global handler or other app boundary.
|
|
319
217
|
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
* }
|
|
324
|
-
* // All operations succeeded
|
|
325
|
-
* ```
|
|
218
|
+
* In Evolu apps, that boundary is typically a platform `createRun` adapter such
|
|
219
|
+
* as `@evolu/web`, `@evolu/nodejs`, or `@evolu/react-native`, which add
|
|
220
|
+
* platform-specific global error handling.
|
|
326
221
|
*
|
|
327
|
-
*
|
|
328
|
-
* happened, so you can't short-circuit. In that case, you can check for the
|
|
329
|
-
* first error:
|
|
222
|
+
* ## FAQ
|
|
330
223
|
*
|
|
331
|
-
*
|
|
332
|
-
* const firstError = results.find((r) => !r.ok);
|
|
333
|
-
* if (firstError) return firstError;
|
|
334
|
-
* // All results are Ok
|
|
335
|
-
* ```
|
|
224
|
+
* ### What if a function doesn't return a value on success?
|
|
336
225
|
*
|
|
337
|
-
*
|
|
226
|
+
* Use `Result<void, E>` and return `ok()` (no argument). Don't return
|
|
227
|
+
* `ok(true)`, `ok("success")`, or `ok("done")` — `ok()` already signals
|
|
228
|
+
* success; redundant values add noise.
|
|
229
|
+
*/
|
|
230
|
+
export type Result<T, E = never> = Ok<T> | Err<E>;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Shorthand for a {@link Result} with `any` type parameters.
|
|
338
234
|
*
|
|
339
|
-
*
|
|
340
|
-
* above) over monadic helpers. Imperative code is generally more readable,
|
|
341
|
-
* easier to debug, and more familiar to most JavaScript and TypeScript
|
|
342
|
-
* developers. While monads and functional helpers can be powerful, they often
|
|
343
|
-
* obscure control flow and make debugging harder.
|
|
235
|
+
* @group Utilities
|
|
344
236
|
*/
|
|
345
|
-
export type
|
|
237
|
+
export type AnyResult = Result<any, any>;
|
|
346
238
|
|
|
347
239
|
/** A successful {@link Result}. */
|
|
348
|
-
export interface Ok<T> {
|
|
240
|
+
export interface Ok<out T> {
|
|
349
241
|
readonly ok: true;
|
|
350
242
|
readonly value: T;
|
|
351
243
|
}
|
|
@@ -353,48 +245,24 @@ export interface Ok<T> {
|
|
|
353
245
|
/**
|
|
354
246
|
* An error {@link Result}.
|
|
355
247
|
*
|
|
356
|
-
* The `error` property can be any type that describes the error. For
|
|
357
|
-
*
|
|
358
|
-
* custom fields (e.g., `{ type: "MyError", code: 123 }`). Messages for users
|
|
359
|
-
* belong to translations, not to error objects.
|
|
360
|
-
*
|
|
361
|
-
* If you need a stack trace for debugging, use an `Error` instance or a custom
|
|
362
|
-
* error class to include additional metadata.
|
|
363
|
-
*
|
|
364
|
-
* ### Examples
|
|
365
|
-
*
|
|
366
|
-
* #### Domain logic error (plain object, recommended)
|
|
248
|
+
* The `error` property can be any type that describes the error. For domain
|
|
249
|
+
* errors, use a plain object with a `type` field for discrimination.
|
|
367
250
|
*
|
|
368
|
-
*
|
|
369
|
-
* const failure = err({
|
|
370
|
-
* type: "ParseJsonError",
|
|
371
|
-
* code: 1001,
|
|
372
|
-
* input: "foo",
|
|
373
|
-
* });
|
|
374
|
-
* ```
|
|
375
|
-
*
|
|
376
|
-
* #### Debugging with stack trace (error instance)
|
|
377
|
-
*
|
|
378
|
-
* ```ts
|
|
379
|
-
* const failure = err(new Error("Something went wrong"));
|
|
380
|
-
* ```
|
|
381
|
-
*
|
|
382
|
-
* #### Custom error class
|
|
251
|
+
* ### Example
|
|
383
252
|
*
|
|
384
253
|
* ```ts
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
* public code: number,
|
|
388
|
-
* public input: string,
|
|
389
|
-
* ) {
|
|
390
|
-
* super(`Error ${code} on input: ${input}`);
|
|
391
|
-
* this.name = "MyCustomError";
|
|
392
|
-
* }
|
|
254
|
+
* interface NotFoundError extends Typed<"NotFoundError"> {
|
|
255
|
+
* readonly id: string;
|
|
393
256
|
* }
|
|
394
|
-
*
|
|
257
|
+
*
|
|
258
|
+
* const findUser = (id: string): Result<User, NotFoundError> => {
|
|
259
|
+
* const user = users.get(id);
|
|
260
|
+
* if (user == null) return err({ type: "NotFoundError", id });
|
|
261
|
+
* return ok(user);
|
|
262
|
+
* };
|
|
395
263
|
* ```
|
|
396
264
|
*/
|
|
397
|
-
export interface Err<E> {
|
|
265
|
+
export interface Err<out E> {
|
|
398
266
|
readonly ok: false;
|
|
399
267
|
readonly error: E;
|
|
400
268
|
}
|
|
@@ -402,7 +270,7 @@ export interface Err<E> {
|
|
|
402
270
|
/**
|
|
403
271
|
* Extracts the value type from a {@link Result}.
|
|
404
272
|
*
|
|
405
|
-
* @
|
|
273
|
+
* @group Utilities
|
|
406
274
|
*/
|
|
407
275
|
export type InferOk<R extends Result<any, any>> =
|
|
408
276
|
R extends Ok<infer T> ? T : never;
|
|
@@ -410,7 +278,7 @@ export type InferOk<R extends Result<any, any>> =
|
|
|
410
278
|
/**
|
|
411
279
|
* Extracts the error type from a {@link Result}.
|
|
412
280
|
*
|
|
413
|
-
* @
|
|
281
|
+
* @group Utilities
|
|
414
282
|
*/
|
|
415
283
|
export type InferErr<R extends Result<any, any>> =
|
|
416
284
|
R extends Err<infer E> ? E : never;
|
|
@@ -418,9 +286,9 @@ export type InferErr<R extends Result<any, any>> =
|
|
|
418
286
|
/**
|
|
419
287
|
* Creates an {@link Ok} result.
|
|
420
288
|
*
|
|
421
|
-
* - `ok()` creates
|
|
422
|
-
* value.
|
|
423
|
-
* - `ok(value)` creates
|
|
289
|
+
* - `ok()` creates a `Result<void, never>` for operations that succeed without
|
|
290
|
+
* producing a value.
|
|
291
|
+
* - `ok(value)` creates a `Result<T, never>` containing the specified value.
|
|
424
292
|
*
|
|
425
293
|
* ### Example
|
|
426
294
|
*
|
|
@@ -432,35 +300,43 @@ export type InferErr<R extends Result<any, any>> =
|
|
|
432
300
|
* console.log(success); // { ok: true, value: 42 }
|
|
433
301
|
* ```
|
|
434
302
|
*/
|
|
435
|
-
export function ok():
|
|
303
|
+
export function ok(): Result<void>;
|
|
436
304
|
/** Creates an {@link Ok} result with a specified value. */
|
|
437
|
-
export function ok<T>(value: T):
|
|
438
|
-
export function ok<T>(value
|
|
305
|
+
export function ok<T>(value: T): Result<T>;
|
|
306
|
+
export function ok<T>(value?: T): Result<T> {
|
|
307
|
+
if (value === undefined) return okVoid as Result<T>;
|
|
439
308
|
return { ok: true, value: value as T };
|
|
440
309
|
}
|
|
441
310
|
|
|
311
|
+
/** Cache ok() and ok(undefined) to avoid repeated allocations. */
|
|
312
|
+
const okVoid: Result<void> = { ok: true, value: undefined };
|
|
313
|
+
|
|
314
|
+
/** Creates an {@link Err} result. */
|
|
315
|
+
export const err = <E>(error: E): Result<never, E> => ({ ok: false, error });
|
|
316
|
+
|
|
317
|
+
/** Type guard for {@link Ok} results. */
|
|
318
|
+
export const isOk = <T, E>(result: Result<T, E>): result is Ok<T> => result.ok;
|
|
319
|
+
|
|
320
|
+
/** Type guard for {@link Err} results. */
|
|
321
|
+
export const isErr = <T, E>(result: Result<T, E>): result is Err<E> =>
|
|
322
|
+
!result.ok;
|
|
323
|
+
|
|
442
324
|
/**
|
|
443
|
-
*
|
|
325
|
+
* Returns the value from an `Ok` {@link Result}, or throws if it is an `Err`.
|
|
444
326
|
*
|
|
445
|
-
*
|
|
327
|
+
* Use this where failure should crash the current flow instead of being handled
|
|
328
|
+
* locally.
|
|
446
329
|
*
|
|
447
|
-
*
|
|
448
|
-
* const failure = err("Something went wrong");
|
|
449
|
-
* console.log(failure); // { ok: false, error: "Something went wrong" }
|
|
450
|
-
* ```
|
|
451
|
-
*/
|
|
452
|
-
export const err = <E>(error: E): Err<E> => ({ ok: false, error });
|
|
453
|
-
|
|
454
|
-
/**
|
|
455
|
-
* Extracts the value from a {@link Result} if it is an `Ok`, or throws an error
|
|
456
|
-
* if it is an `Err`.
|
|
330
|
+
* **When to use:**
|
|
457
331
|
*
|
|
458
|
-
*
|
|
332
|
+
* - Application startup or composition-root setup where errors must stop the
|
|
333
|
+
* program immediately. In Evolu apps, errors are handled by platform-specific
|
|
334
|
+
* `createRun` adapters at the app boundary.
|
|
335
|
+
* - Module-level constants
|
|
336
|
+
* - Test setup with values that are expected to be valid
|
|
459
337
|
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
* - Not recommended for general error handling in application logic—prefer
|
|
463
|
-
* explicit checks.
|
|
338
|
+
* Prefer an explicit `if (!result.ok)` check in ordinary application logic
|
|
339
|
+
* where the caller can recover, retry, or choose a different flow.
|
|
464
340
|
*
|
|
465
341
|
* ### Example
|
|
466
342
|
*
|
|
@@ -493,42 +369,39 @@ export const getOrThrow = <T, E>(result: Result<T, E>): T => {
|
|
|
493
369
|
* ### Example
|
|
494
370
|
*
|
|
495
371
|
* ```ts
|
|
496
|
-
*
|
|
497
|
-
* const
|
|
498
|
-
* // value is unknown | null
|
|
499
|
-
*
|
|
500
|
-
* if (value != null) {
|
|
501
|
-
* console.log("Parsed value:", value);
|
|
502
|
-
* }
|
|
372
|
+
* // For APIs that expect T | null
|
|
373
|
+
* const user = getOrNull(findUser(id));
|
|
503
374
|
* ```
|
|
504
375
|
*/
|
|
505
376
|
export const getOrNull = <T, E>(result: Result<T, E>): T | null =>
|
|
506
377
|
result.ok ? result.value : null;
|
|
507
378
|
|
|
508
379
|
/**
|
|
509
|
-
*
|
|
510
|
-
*
|
|
380
|
+
* Extracts the value from a {@link Result} whose error type is `never`.
|
|
381
|
+
*
|
|
382
|
+
* This is useful when the type system guarantees the result cannot fail (for
|
|
383
|
+
* example `Result<T, never>`), avoiding impossible `if (!result.ok)` branches
|
|
384
|
+
* at call sites.
|
|
385
|
+
*/
|
|
386
|
+
export const getOk = <T>(result: Result<T>): T => {
|
|
387
|
+
assert(result.ok, "Expected Ok result.");
|
|
388
|
+
return result.value;
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Wraps a synchronous function that may throw, returning a {@link Result}.
|
|
511
393
|
*
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
*
|
|
515
|
-
* custom type and returns an `Err` result.
|
|
394
|
+
* Use this when the thrown value should become a typed, recoverable error for
|
|
395
|
+
* the caller. Do not use it for failures that should terminate the current flow
|
|
396
|
+
* and propagate to a global handler.
|
|
516
397
|
*
|
|
517
398
|
* ### Example
|
|
518
399
|
*
|
|
519
400
|
* ```ts
|
|
520
|
-
* interface ParseJsonError {
|
|
521
|
-
* readonly type: "ParseJsonError";
|
|
522
|
-
* readonly message: string;
|
|
523
|
-
* }
|
|
524
|
-
*
|
|
525
401
|
* const parseJson = (value: string): Result<unknown, ParseJsonError> =>
|
|
526
402
|
* trySync(
|
|
527
403
|
* () => JSON.parse(value) as unknown,
|
|
528
|
-
* (error): ParseJsonError
|
|
529
|
-
* type: "ParseJsonError",
|
|
530
|
-
* message: String(error),
|
|
531
|
-
* }),
|
|
404
|
+
* (error) => ({ type: "ParseJsonError", message: String(error) }),
|
|
532
405
|
* );
|
|
533
406
|
* ```
|
|
534
407
|
*/
|
|
@@ -544,54 +417,348 @@ export const trySync = <T, E>(
|
|
|
544
417
|
};
|
|
545
418
|
|
|
546
419
|
/**
|
|
547
|
-
* Wraps async
|
|
548
|
-
* {@link Result}.
|
|
420
|
+
* Wraps an async function that may throw, returning a {@link Result}.
|
|
549
421
|
*
|
|
550
|
-
*
|
|
551
|
-
*
|
|
552
|
-
*
|
|
553
|
-
* the error and returns an `Err` result.
|
|
422
|
+
* Use this when the rejection should become a typed, recoverable error for the
|
|
423
|
+
* caller. Do not use it for failures that should terminate the current flow and
|
|
424
|
+
* propagate to a global handler.
|
|
554
425
|
*
|
|
555
426
|
* ### Example
|
|
556
427
|
*
|
|
557
428
|
* ```ts
|
|
558
|
-
*
|
|
559
|
-
* readonly type: "FetchError";
|
|
560
|
-
* readonly message: string;
|
|
561
|
-
* }
|
|
562
|
-
*
|
|
563
|
-
* const tryFetch = async (
|
|
564
|
-
* url: string,
|
|
565
|
-
* ): Promise<Result<unknown, FetchError>> =>
|
|
429
|
+
* const fetchJson = (url: string): Promise<Result<unknown, FetchError>> =>
|
|
566
430
|
* tryAsync(
|
|
567
431
|
* async () => {
|
|
568
432
|
* const response = await fetch(url);
|
|
569
|
-
* if (!response.ok) {
|
|
570
|
-
* throw new Error(`Request failed with status ${response.status}`);
|
|
571
|
-
* }
|
|
433
|
+
* if (!response.ok) throw new Error(`Status ${response.status}`);
|
|
572
434
|
* return response.json();
|
|
573
435
|
* },
|
|
574
|
-
* (error) => ({
|
|
575
|
-
* type: "FetchError",
|
|
576
|
-
* message: String(error),
|
|
577
|
-
* }),
|
|
436
|
+
* (error) => ({ type: "FetchError", message: String(error) }),
|
|
578
437
|
* );
|
|
579
|
-
*
|
|
580
|
-
* const result = await tryFetch(
|
|
581
|
-
* "https://jsonplaceholder.typicode.com/posts/1",
|
|
582
|
-
* );
|
|
583
|
-
* if (result.ok) {
|
|
584
|
-
* console.log("Data:", result.value);
|
|
585
|
-
* } else {
|
|
586
|
-
* console.error("Error:", result.error);
|
|
587
|
-
* }
|
|
588
438
|
* ```
|
|
589
439
|
*/
|
|
590
|
-
export const tryAsync =
|
|
591
|
-
|
|
440
|
+
export const tryAsync = <T, E>(
|
|
441
|
+
lazyPromise: Lazy<Promise<T>>,
|
|
592
442
|
mapError: (error: unknown) => E,
|
|
593
443
|
): Promise<Result<T, E>> =>
|
|
594
|
-
|
|
444
|
+
Promise.try(lazyPromise).then(
|
|
595
445
|
(value) => ok(value),
|
|
596
446
|
(error: unknown) => err(mapError(error)),
|
|
597
447
|
);
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* A result for a pull-based protocol with three outcomes.
|
|
451
|
+
*
|
|
452
|
+
* The consumer requests the next value (e.g. via `next()`), and the producer
|
|
453
|
+
* responds with one of:
|
|
454
|
+
*
|
|
455
|
+
* - `Ok<A>` — produced a value
|
|
456
|
+
* - `Err<Done<D>>` — completed normally with a done value
|
|
457
|
+
* - `Err<E>` — failed with an error
|
|
458
|
+
*
|
|
459
|
+
* Inspired by JavaScript's `Iterator.next()`, which returns `{ value, done }`.
|
|
460
|
+
*/
|
|
461
|
+
export type NextResult<A, E = never, D = void> = Result<A, E | Done<D>>;
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* A signal indicating normal completion of a pull-based protocol.
|
|
465
|
+
*
|
|
466
|
+
* This is not a failure — it is a control signal that carries an optional
|
|
467
|
+
* "done" value (often `void`, but can be a final summary or leftover).
|
|
468
|
+
*
|
|
469
|
+
* Inspired by JavaScript's `IteratorResult` where `{ done: true }` signals
|
|
470
|
+
* completion.
|
|
471
|
+
*/
|
|
472
|
+
export interface Done<out D = unknown> extends Typed<"Done"> {
|
|
473
|
+
readonly done: D;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Constructs a {@link Done} value.
|
|
478
|
+
*
|
|
479
|
+
* - `done()` creates a `Done<void>` for protocols that don't need a done value.
|
|
480
|
+
* - `done(value)` creates a `Done<D>` containing the specified value.
|
|
481
|
+
*/
|
|
482
|
+
export function done(): Done<void>;
|
|
483
|
+
/** With a done value. */
|
|
484
|
+
export function done<D>(value: D): Done<D>;
|
|
485
|
+
export function done<D>(value?: D): Done<D> {
|
|
486
|
+
return {
|
|
487
|
+
type: "Done",
|
|
488
|
+
done: value as D,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Removes {@link Done} from an error union.
|
|
494
|
+
*
|
|
495
|
+
* Useful for pull-based protocols where completion is encoded in the error
|
|
496
|
+
* channel (for example {@link NextResult}).
|
|
497
|
+
*
|
|
498
|
+
* @group Utilities
|
|
499
|
+
*/
|
|
500
|
+
export type ExcludeDone<E> = Exclude<E, Done<any>>;
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Extracts only {@link Done} from an error union.
|
|
504
|
+
*
|
|
505
|
+
* Useful for pull-based protocols where completion is encoded in the error
|
|
506
|
+
* channel (for example {@link NextResult}).
|
|
507
|
+
*
|
|
508
|
+
* @group Utilities
|
|
509
|
+
*/
|
|
510
|
+
export type OnlyDone<E> = Extract<E, Done<any>>;
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Extracts the done value type from a {@link NextResult}.
|
|
514
|
+
*
|
|
515
|
+
* @group Utilities
|
|
516
|
+
*/
|
|
517
|
+
export type InferDone<R extends Result<any, any>> =
|
|
518
|
+
InferErr<R> extends infer Errors
|
|
519
|
+
? Errors extends Done<infer D>
|
|
520
|
+
? D
|
|
521
|
+
: never
|
|
522
|
+
: never;
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Extracts all values from an array of {@link Result}s.
|
|
526
|
+
*
|
|
527
|
+
* Returns the first error if any result fails.
|
|
528
|
+
*
|
|
529
|
+
* ### Example
|
|
530
|
+
*
|
|
531
|
+
* ```ts
|
|
532
|
+
* const results = [ok(1), ok(2), ok(3)];
|
|
533
|
+
* const all = allResult(results);
|
|
534
|
+
* // ok([1, 2, 3])
|
|
535
|
+
*
|
|
536
|
+
* const withError = [ok(1), err("fail"), ok(3)];
|
|
537
|
+
* const failed = allResult(withError);
|
|
538
|
+
* // err("fail")
|
|
539
|
+
* ```
|
|
540
|
+
*
|
|
541
|
+
* @group Composition
|
|
542
|
+
*/
|
|
543
|
+
export function allResult<
|
|
544
|
+
const T extends readonly [AnyResult, ...ReadonlyArray<AnyResult>],
|
|
545
|
+
>(results: T): Result<{ [K in keyof T]: InferOk<T[K]> }, InferErr<T[number]>>;
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Returns object with same keys.
|
|
549
|
+
*
|
|
550
|
+
* ```ts
|
|
551
|
+
* const result = allResult({ a: ok(1), b: ok(2) });
|
|
552
|
+
* // ok({ a: 1, b: 2 })
|
|
553
|
+
* ```
|
|
554
|
+
*/
|
|
555
|
+
export function allResult<T extends Readonly<Record<string, AnyResult>>>(
|
|
556
|
+
results: T,
|
|
557
|
+
): Result<
|
|
558
|
+
{ [P in keyof T]: InferOk<T[P]> },
|
|
559
|
+
[keyof T] extends [never] ? never : InferErr<T[keyof T]>
|
|
560
|
+
>;
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* For dynamic or generated result lists.
|
|
564
|
+
*
|
|
565
|
+
* ```ts
|
|
566
|
+
* const results: ReadonlyArray<Result<number, Error>> = getResults();
|
|
567
|
+
* const all = allResult(results);
|
|
568
|
+
* // Result<ReadonlyArray<number>, Error>
|
|
569
|
+
* ```
|
|
570
|
+
*/
|
|
571
|
+
export function allResult<T, E>(
|
|
572
|
+
results: Iterable<Result<T, E>>,
|
|
573
|
+
): Result<ReadonlyArray<T>, E>;
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Guarantees non-empty result.
|
|
577
|
+
*
|
|
578
|
+
* ```ts
|
|
579
|
+
* const results: NonEmptyReadonlyArray<Result<number, Error>> = [
|
|
580
|
+
* ok(1),
|
|
581
|
+
* ok(2),
|
|
582
|
+
* ];
|
|
583
|
+
* const all = allResult(results);
|
|
584
|
+
* // Result<NonEmptyReadonlyArray<number>, Error>
|
|
585
|
+
* ```
|
|
586
|
+
*/
|
|
587
|
+
export function allResult<T, E>(
|
|
588
|
+
results: NonEmptyReadonlyArray<Result<T, E>>,
|
|
589
|
+
): Result<NonEmptyReadonlyArray<T>, E>;
|
|
590
|
+
|
|
591
|
+
export function allResult(
|
|
592
|
+
input: Iterable<AnyResult> | Readonly<Record<string, AnyResult>>,
|
|
593
|
+
): AnyResult {
|
|
594
|
+
if (isIterable(input)) {
|
|
595
|
+
const array = arrayFrom(input);
|
|
596
|
+
if (!isNonEmptyArray(array)) return ok(emptyArray);
|
|
597
|
+
|
|
598
|
+
const length = array.length;
|
|
599
|
+
const values = new Array<unknown>(length);
|
|
600
|
+
for (let i = 0; i < length; i++) {
|
|
601
|
+
const result = array[i];
|
|
602
|
+
if (!result.ok) return result;
|
|
603
|
+
values[i] = result.value;
|
|
604
|
+
}
|
|
605
|
+
return ok(values);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const length = Object.keys(input).length;
|
|
609
|
+
if (length === 0) return ok(emptyRecord);
|
|
610
|
+
|
|
611
|
+
const keys = new Array<string>(length);
|
|
612
|
+
const results = new Array<AnyResult>(length);
|
|
613
|
+
let index = 0;
|
|
614
|
+
for (const key in input) {
|
|
615
|
+
keys[index] = key;
|
|
616
|
+
results[index] = (input as Record<string, AnyResult>)[key];
|
|
617
|
+
index++;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const record = createRecord();
|
|
621
|
+
for (let i = 0; i < length; i++) {
|
|
622
|
+
const result = results[i];
|
|
623
|
+
if (!result.ok) return result;
|
|
624
|
+
record[keys[i]] = result.value;
|
|
625
|
+
}
|
|
626
|
+
return ok(record);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Maps items to {@link Result}s and extracts all values.
|
|
631
|
+
*
|
|
632
|
+
* Returns the first error if any result fails.
|
|
633
|
+
*
|
|
634
|
+
* ### Example
|
|
635
|
+
*
|
|
636
|
+
* ```ts
|
|
637
|
+
* const users = [{ id: 1 }, { id: 2 }];
|
|
638
|
+
* const result = mapResult(users, validateUser);
|
|
639
|
+
* // Result<ReadonlyArray<ValidUser>, ValidateUserError>
|
|
640
|
+
* ```
|
|
641
|
+
*
|
|
642
|
+
* @group Composition
|
|
643
|
+
*/
|
|
644
|
+
export function mapResult<
|
|
645
|
+
const A extends readonly [unknown, ...Array<unknown>],
|
|
646
|
+
T,
|
|
647
|
+
E,
|
|
648
|
+
>(
|
|
649
|
+
items: A,
|
|
650
|
+
fn: (a: A[number]) => Result<T, E>,
|
|
651
|
+
): Result<{ [K in keyof A]: T }, E>;
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* For dynamic or generated item lists.
|
|
655
|
+
*
|
|
656
|
+
* ```ts
|
|
657
|
+
* const users = [{ id: 1 }, { id: 2 }];
|
|
658
|
+
* const result = mapResult(users, validateUser);
|
|
659
|
+
* // Result<ReadonlyArray<ValidUser>, ValidateUserError>
|
|
660
|
+
* ```
|
|
661
|
+
*/
|
|
662
|
+
export function mapResult<A, T, E>(
|
|
663
|
+
items: Iterable<A>,
|
|
664
|
+
fn: (a: A) => Result<T, E>,
|
|
665
|
+
): Result<ReadonlyArray<T>, E>;
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Returns object with same keys.
|
|
669
|
+
*
|
|
670
|
+
* ```ts
|
|
671
|
+
* const result = mapResult({ a: 1, b: 2 }, double);
|
|
672
|
+
* // Result<{ a: number, b: number }, DoubleError>
|
|
673
|
+
* ```
|
|
674
|
+
*/
|
|
675
|
+
export function mapResult<A, T, E, K extends string>(
|
|
676
|
+
items: Readonly<Record<K, A>>,
|
|
677
|
+
fn: (a: A) => Result<T, E>,
|
|
678
|
+
): Result<Readonly<Record<K, T>>, E>;
|
|
679
|
+
|
|
680
|
+
export function mapResult(
|
|
681
|
+
input: Iterable<unknown> | Readonly<Record<string, unknown>>,
|
|
682
|
+
fn: (a: unknown) => AnyResult,
|
|
683
|
+
): AnyResult {
|
|
684
|
+
if (isIterable(input)) {
|
|
685
|
+
const array = arrayFrom(input);
|
|
686
|
+
if (!isNonEmptyArray(array)) return ok(emptyArray);
|
|
687
|
+
|
|
688
|
+
const length = array.length;
|
|
689
|
+
const values = new Array<unknown>(length);
|
|
690
|
+
for (let i = 0; i < length; i++) {
|
|
691
|
+
const result = fn(array[i]);
|
|
692
|
+
if (!result.ok) return result;
|
|
693
|
+
values[i] = result.value;
|
|
694
|
+
}
|
|
695
|
+
return ok(values);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const length = Object.keys(input).length;
|
|
699
|
+
if (length === 0) return ok(emptyRecord);
|
|
700
|
+
|
|
701
|
+
const keys = new Array<string>(length);
|
|
702
|
+
const items = new Array<unknown>(length);
|
|
703
|
+
let index = 0;
|
|
704
|
+
for (const key in input) {
|
|
705
|
+
keys[index] = key;
|
|
706
|
+
items[index] = (input as Record<string, unknown>)[key];
|
|
707
|
+
index++;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const record = createRecord();
|
|
711
|
+
for (let i = 0; i < length; i++) {
|
|
712
|
+
const result = fn(items[i]);
|
|
713
|
+
if (!result.ok) return result;
|
|
714
|
+
record[keys[i]] = result.value;
|
|
715
|
+
}
|
|
716
|
+
return ok(record);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Returns the first successful {@link Result}.
|
|
721
|
+
*
|
|
722
|
+
* If all results fail, returns the last error.
|
|
723
|
+
*
|
|
724
|
+
* Requires a non-empty array — there's no "first success" with zero
|
|
725
|
+
* participants. Use {@link isNonEmptyArray} to guard:
|
|
726
|
+
*
|
|
727
|
+
* ```ts
|
|
728
|
+
* if (isNonEmptyArray(results)) {
|
|
729
|
+
* const result = anyResult(results);
|
|
730
|
+
* }
|
|
731
|
+
* ```
|
|
732
|
+
*
|
|
733
|
+
* ### Example
|
|
734
|
+
*
|
|
735
|
+
* ```ts
|
|
736
|
+
* const results = [err("fail1"), ok(42), err("fail2")];
|
|
737
|
+
* if (isNonEmptyArray(results)) {
|
|
738
|
+
* const result = anyResult(results);
|
|
739
|
+
* // ok(42)
|
|
740
|
+
* }
|
|
741
|
+
*
|
|
742
|
+
* const allFailed = [err("a"), err("b"), err("c")];
|
|
743
|
+
* if (isNonEmptyArray(allFailed)) {
|
|
744
|
+
* const result = anyResult(allFailed);
|
|
745
|
+
* // err("c") — last error
|
|
746
|
+
* }
|
|
747
|
+
* ```
|
|
748
|
+
*
|
|
749
|
+
* @group Composition
|
|
750
|
+
*/
|
|
751
|
+
export function anyResult<
|
|
752
|
+
const T extends readonly [AnyResult, ...ReadonlyArray<AnyResult>],
|
|
753
|
+
>(results: T): Result<InferOk<T[number]>, InferErr<T[number]>>;
|
|
754
|
+
|
|
755
|
+
export function anyResult(
|
|
756
|
+
results: NonEmptyReadonlyArray<AnyResult>,
|
|
757
|
+
): AnyResult {
|
|
758
|
+
let lastError: Err<unknown> | null = null;
|
|
759
|
+
for (const result of results) {
|
|
760
|
+
if (result.ok) return result;
|
|
761
|
+
lastError = result;
|
|
762
|
+
}
|
|
763
|
+
return lastError!;
|
|
764
|
+
}
|