@evolu/common 8.2.0 → 8.3.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/dist/src/Function.d.ts +1 -0
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Http.d.ts +9 -0
- package/dist/src/Http.d.ts.map +1 -1
- package/dist/src/Object.d.ts +1 -0
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/RefCount.d.ts +1 -0
- package/dist/src/RefCount.d.ts.map +1 -1
- package/dist/src/Relation.d.ts +2 -0
- package/dist/src/Relation.d.ts.map +1 -1
- package/dist/src/Resource.d.ts +1 -0
- package/dist/src/Resource.d.ts.map +1 -1
- package/dist/src/Result.d.ts +383 -548
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +296 -46
- package/dist/src/StackTrace.d.ts +1 -0
- package/dist/src/StackTrace.d.ts.map +1 -1
- package/dist/src/Task.d.ts +985 -1069
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +1007 -156
- package/dist/src/Time.d.ts +1 -0
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Type.d.ts +1285 -734
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +1057 -193
- package/dist/src/intl/_en.d.ts +126 -0
- package/dist/src/intl/_en.d.ts.map +1 -0
- package/dist/src/intl/_en.js +267 -0
- package/dist/src/intl/ar.d.ts +117 -0
- package/dist/src/intl/ar.d.ts.map +1 -0
- package/dist/src/intl/ar.js +245 -0
- package/dist/src/intl/bn.d.ts +117 -0
- package/dist/src/intl/bn.d.ts.map +1 -0
- package/dist/src/intl/bn.js +258 -0
- package/dist/src/intl/ca.d.ts +117 -0
- package/dist/src/intl/ca.d.ts.map +1 -0
- package/dist/src/intl/ca.js +258 -0
- package/dist/src/intl/cs.d.ts +20 -7
- package/dist/src/intl/cs.d.ts.map +1 -1
- package/dist/src/intl/cs.js +54 -27
- package/dist/src/intl/da.d.ts +117 -0
- package/dist/src/intl/da.d.ts.map +1 -0
- package/dist/src/intl/da.js +258 -0
- package/dist/src/intl/de.d.ts +117 -0
- package/dist/src/intl/de.d.ts.map +1 -0
- package/dist/src/intl/de.js +258 -0
- package/dist/src/intl/el.d.ts +117 -0
- package/dist/src/intl/el.d.ts.map +1 -0
- package/dist/src/intl/el.js +265 -0
- package/dist/src/intl/es.d.ts +117 -0
- package/dist/src/intl/es.d.ts.map +1 -0
- package/dist/src/intl/es.js +258 -0
- package/dist/src/intl/fa.d.ts +117 -0
- package/dist/src/intl/fa.d.ts.map +1 -0
- package/dist/src/intl/fa.js +258 -0
- package/dist/src/intl/fi.d.ts +117 -0
- package/dist/src/intl/fi.d.ts.map +1 -0
- package/dist/src/intl/fi.js +258 -0
- package/dist/src/intl/fil.d.ts +117 -0
- package/dist/src/intl/fil.d.ts.map +1 -0
- package/dist/src/intl/fil.js +258 -0
- package/dist/src/intl/fr.d.ts +117 -0
- package/dist/src/intl/fr.d.ts.map +1 -0
- package/dist/src/intl/fr.js +258 -0
- package/dist/src/intl/he.d.ts +117 -0
- package/dist/src/intl/he.d.ts.map +1 -0
- package/dist/src/intl/he.js +258 -0
- package/dist/src/intl/hi.d.ts +117 -0
- package/dist/src/intl/hi.d.ts.map +1 -0
- package/dist/src/intl/hi.js +258 -0
- package/dist/src/intl/hr.d.ts +117 -0
- package/dist/src/intl/hr.d.ts.map +1 -0
- package/dist/src/intl/hr.js +265 -0
- package/dist/src/intl/hu.d.ts +63 -0
- package/dist/src/intl/hu.d.ts.map +1 -0
- package/dist/src/intl/hu.js +204 -0
- package/dist/src/intl/id.d.ts +117 -0
- package/dist/src/intl/id.d.ts.map +1 -0
- package/dist/src/intl/id.js +258 -0
- package/dist/src/intl/index.d.ts +42 -0
- package/dist/src/intl/index.d.ts.map +1 -1
- package/dist/src/intl/index.js +42 -0
- package/dist/src/intl/it.d.ts +117 -0
- package/dist/src/intl/it.d.ts.map +1 -0
- package/dist/src/intl/it.js +258 -0
- package/dist/src/intl/ja.d.ts +117 -0
- package/dist/src/intl/ja.d.ts.map +1 -0
- package/dist/src/intl/ja.js +258 -0
- package/dist/src/intl/ko.d.ts +117 -0
- package/dist/src/intl/ko.d.ts.map +1 -0
- package/dist/src/intl/ko.js +258 -0
- package/dist/src/intl/ml.d.ts +117 -0
- package/dist/src/intl/ml.d.ts.map +1 -0
- package/dist/src/intl/ml.js +258 -0
- package/dist/src/intl/mr.d.ts +117 -0
- package/dist/src/intl/mr.d.ts.map +1 -0
- package/dist/src/intl/mr.js +258 -0
- package/dist/src/intl/ms.d.ts +117 -0
- package/dist/src/intl/ms.d.ts.map +1 -0
- package/dist/src/intl/ms.js +245 -0
- package/dist/src/intl/nb.d.ts +63 -0
- package/dist/src/intl/nb.d.ts.map +1 -0
- package/dist/src/intl/nb.js +191 -0
- package/dist/src/intl/nl.d.ts +117 -0
- package/dist/src/intl/nl.d.ts.map +1 -0
- package/dist/src/intl/nl.js +258 -0
- package/dist/src/intl/pa.d.ts +117 -0
- package/dist/src/intl/pa.d.ts.map +1 -0
- package/dist/src/intl/pa.js +258 -0
- package/dist/src/intl/pl.d.ts +117 -0
- package/dist/src/intl/pl.d.ts.map +1 -0
- package/dist/src/intl/pl.js +242 -0
- package/dist/src/intl/pt-BR.d.ts +117 -0
- package/dist/src/intl/pt-BR.d.ts.map +1 -0
- package/dist/src/intl/pt-BR.js +258 -0
- package/dist/src/intl/pt.d.ts +117 -0
- package/dist/src/intl/pt.d.ts.map +1 -0
- package/dist/src/intl/pt.js +246 -0
- package/dist/src/intl/ro.d.ts +117 -0
- package/dist/src/intl/ro.d.ts.map +1 -0
- package/dist/src/intl/ro.js +258 -0
- package/dist/src/intl/sk.d.ts +117 -0
- package/dist/src/intl/sk.d.ts.map +1 -0
- package/dist/src/intl/sk.js +244 -0
- package/dist/src/intl/sl.d.ts +117 -0
- package/dist/src/intl/sl.d.ts.map +1 -0
- package/dist/src/intl/sl.js +258 -0
- package/dist/src/intl/sv.d.ts +117 -0
- package/dist/src/intl/sv.d.ts.map +1 -0
- package/dist/src/intl/sv.js +258 -0
- package/dist/src/intl/sw.d.ts +63 -0
- package/dist/src/intl/sw.d.ts.map +1 -0
- package/dist/src/intl/sw.js +191 -0
- package/dist/src/intl/ta.d.ts +117 -0
- package/dist/src/intl/ta.d.ts.map +1 -0
- package/dist/src/intl/ta.js +258 -0
- package/dist/src/intl/te.d.ts +117 -0
- package/dist/src/intl/te.d.ts.map +1 -0
- package/dist/src/intl/te.js +258 -0
- package/dist/src/intl/th.d.ts +117 -0
- package/dist/src/intl/th.d.ts.map +1 -0
- package/dist/src/intl/th.js +258 -0
- package/dist/src/intl/tr.d.ts +117 -0
- package/dist/src/intl/tr.d.ts.map +1 -0
- package/dist/src/intl/tr.js +255 -0
- package/dist/src/intl/uk.d.ts +117 -0
- package/dist/src/intl/uk.d.ts.map +1 -0
- package/dist/src/intl/uk.js +258 -0
- package/dist/src/intl/ur.d.ts +117 -0
- package/dist/src/intl/ur.d.ts.map +1 -0
- package/dist/src/intl/ur.js +245 -0
- package/dist/src/intl/vi.d.ts +117 -0
- package/dist/src/intl/vi.d.ts.map +1 -0
- package/dist/src/intl/vi.js +255 -0
- package/dist/src/intl/zh-CN.d.ts +117 -0
- package/dist/src/intl/zh-CN.d.ts.map +1 -0
- package/dist/src/intl/zh-CN.js +258 -0
- package/dist/src/intl/zh-TW.d.ts +117 -0
- package/dist/src/intl/zh-TW.d.ts.map +1 -0
- package/dist/src/intl/zh-TW.js +258 -0
- package/dist/src/local-first/Owner.d.ts +156 -62
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +119 -20
- package/package.json +1 -1
- package/src/Function.ts +2 -0
- package/src/Http.ts +9 -0
- package/src/Object.ts +2 -0
- package/src/RefCount.ts +2 -0
- package/src/Relation.ts +4 -0
- package/src/Resource.ts +2 -0
- package/src/Result.ts +397 -560
- package/src/StackTrace.ts +2 -0
- package/src/Task.ts +1143 -1222
- package/src/Time.ts +2 -0
- package/src/Type.ts +1598 -712
- package/src/intl/_en.ts +497 -0
- package/src/intl/ar.ts +417 -0
- package/src/intl/bn.ts +486 -0
- package/src/intl/ca.ts +488 -0
- package/src/intl/cs.ts +95 -31
- package/src/intl/da.ts +488 -0
- package/src/intl/de.ts +488 -0
- package/src/intl/el.ts +494 -0
- package/src/intl/es.ts +488 -0
- package/src/intl/fa.ts +487 -0
- package/src/intl/fi.ts +487 -0
- package/src/intl/fil.ts +488 -0
- package/src/intl/fr.ts +488 -0
- package/src/intl/he.ts +486 -0
- package/src/intl/hi.ts +487 -0
- package/src/intl/hr.ts +494 -0
- package/src/intl/hu.ts +387 -0
- package/src/intl/id.ts +487 -0
- package/src/intl/index.ts +42 -0
- package/src/intl/it.ts +488 -0
- package/src/intl/ja.ts +488 -0
- package/src/intl/ko.ts +487 -0
- package/src/intl/ml.ts +487 -0
- package/src/intl/mr.ts +487 -0
- package/src/intl/ms.ts +410 -0
- package/src/intl/nb.ts +364 -0
- package/src/intl/nl.ts +488 -0
- package/src/intl/pa.ts +487 -0
- package/src/intl/pl.ts +450 -0
- package/src/intl/pt-BR.ts +487 -0
- package/src/intl/pt.ts +424 -0
- package/src/intl/ro.ts +488 -0
- package/src/intl/sk.ts +412 -0
- package/src/intl/sl.ts +487 -0
- package/src/intl/sv.ts +488 -0
- package/src/intl/sw.ts +324 -0
- package/src/intl/ta.ts +487 -0
- package/src/intl/te.ts +487 -0
- package/src/intl/th.ts +484 -0
- package/src/intl/tr.ts +487 -0
- package/src/intl/uk.ts +488 -0
- package/src/intl/ur.ts +410 -0
- package/src/intl/vi.ts +435 -0
- package/src/intl/zh-CN.ts +482 -0
- package/src/intl/zh-TW.ts +482 -0
- package/src/local-first/Owner.ts +156 -62
package/dist/src/Result.d.ts
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* ## Intro
|
|
3
3
|
*
|
|
4
|
-
* @
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { type Thunk } from "./Function.ts";
|
|
8
|
-
import type { Typed } from "./Type.ts";
|
|
9
|
-
import type { Awaitable } from "./Types.ts";
|
|
10
|
-
/**
|
|
11
|
-
* The problem with `throw` in JavaScript is that the caught value is always of
|
|
4
|
+
* Type-safe error handling with {@link Result}.
|
|
5
|
+
*
|
|
6
|
+
* The problem with exceptions in JavaScript is that a caught value is always of
|
|
12
7
|
* unknown type. We can't be sure all errors have been handled because the
|
|
13
|
-
* TypeScript compiler can't tell us what
|
|
8
|
+
* TypeScript compiler can't tell us what might be thrown — we can't use
|
|
14
9
|
* {@link exhaustiveCheck}.
|
|
15
10
|
*
|
|
16
|
-
* Languages like Rust and Haskell
|
|
17
|
-
* `
|
|
18
|
-
* same.
|
|
11
|
+
* Languages like Rust and Haskell model recoverable failures with types such as
|
|
12
|
+
* `Result` and `Either`, making errors part of the return type.
|
|
19
13
|
*
|
|
20
|
-
*
|
|
14
|
+
* TypeScript can express the same pattern with discriminated unions of plain
|
|
15
|
+
* objects. In Evolu, domain error interfaces extend {@link Typed} to avoid
|
|
16
|
+
* repeating the literal `type` discriminant.
|
|
21
17
|
*
|
|
22
18
|
* ```ts
|
|
23
|
-
* import {
|
|
19
|
+
* import {
|
|
20
|
+
* err,
|
|
21
|
+
* exhaustiveCheck,
|
|
22
|
+
* type Result,
|
|
23
|
+
* type Typed,
|
|
24
|
+
* } from "@evolu/common";
|
|
24
25
|
*
|
|
25
26
|
* // TypeScript can't know what was thrown.
|
|
26
27
|
* try {
|
|
@@ -32,16 +33,12 @@ import type { Awaitable } from "./Types.ts";
|
|
|
32
33
|
* // With Result, errors are part of the return type.
|
|
33
34
|
* const doSomething = (): Result<
|
|
34
35
|
* number,
|
|
35
|
-
*
|
|
36
|
+
* InvalidInputError | NotFoundError
|
|
36
37
|
* > => err({ type: "NotFound" });
|
|
37
38
|
*
|
|
38
|
-
* interface
|
|
39
|
-
* readonly type: "NotFound";
|
|
40
|
-
* }
|
|
39
|
+
* interface InvalidInputError extends Typed<"InvalidInput"> {}
|
|
41
40
|
*
|
|
42
|
-
* interface
|
|
43
|
-
* readonly type: "InvalidInput";
|
|
44
|
-
* }
|
|
41
|
+
* interface NotFoundError extends Typed<"NotFound"> {}
|
|
45
42
|
*
|
|
46
43
|
* // With Result, the error type is known and exhaustiveCheck works.
|
|
47
44
|
* // If we add another error type, TypeScript tells us it isn't handled.
|
|
@@ -78,41 +75,37 @@ import type { Awaitable } from "./Types.ts";
|
|
|
78
75
|
* }
|
|
79
76
|
* ```
|
|
80
77
|
*
|
|
81
|
-
* Use {@link trySync} and {@link tryAsync} to intentionally convert thrown
|
|
82
|
-
*
|
|
78
|
+
* Use {@link trySync} and {@link tryAsync} to intentionally convert thrown values
|
|
79
|
+
* and Promise rejections into typed, recoverable errors represented by
|
|
80
|
+
* `Result`.
|
|
83
81
|
*
|
|
84
|
-
* Do not wrap every
|
|
85
|
-
* the caller has no meaningful fallback, let it
|
|
86
|
-
*
|
|
87
|
-
* ## Composition
|
|
82
|
+
* Do not wrap every API that throws or rejects in `Result`. If an error is
|
|
83
|
+
* unrecoverable and the caller has no meaningful fallback, let it propagate.
|
|
88
84
|
*
|
|
89
85
|
* Since `Result` is a plain object, imperative code works naturally.
|
|
90
86
|
*
|
|
91
87
|
* ```ts
|
|
92
|
-
* import { ok, type Result } from "@evolu/common";
|
|
88
|
+
* import { ok, type Result, type Typed } from "@evolu/common";
|
|
93
89
|
*
|
|
94
90
|
* interface User {
|
|
95
91
|
* readonly id: string;
|
|
96
92
|
* }
|
|
97
93
|
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
94
|
+
* const getUser = (): Result<User, UserNotFoundError> =>
|
|
95
|
+
* ok({ id: "user-1" });
|
|
96
|
+
*
|
|
97
|
+
* interface UserNotFoundError extends Typed<"UserNotFound"> {}
|
|
101
98
|
*
|
|
102
99
|
* interface Profile {
|
|
103
100
|
* readonly userId: string;
|
|
104
101
|
* }
|
|
105
102
|
*
|
|
106
|
-
* interface ProfileNotFoundError {
|
|
107
|
-
* readonly type: "ProfileNotFoundError";
|
|
108
|
-
* }
|
|
109
|
-
*
|
|
110
|
-
* const getUser = (): Result<User, UserNotFoundError> =>
|
|
111
|
-
* ok({ id: "user-1" });
|
|
112
103
|
* const getProfile = (
|
|
113
104
|
* userId: string,
|
|
114
105
|
* ): Result<Profile, ProfileNotFoundError> => ok({ userId });
|
|
115
106
|
*
|
|
107
|
+
* interface ProfileNotFoundError extends Typed<"ProfileNotFound"> {}
|
|
108
|
+
*
|
|
116
109
|
* const getCurrentProfile = (): Result<
|
|
117
110
|
* Profile,
|
|
118
111
|
* UserNotFoundError | ProfileNotFoundError
|
|
@@ -130,211 +123,26 @@ import type { Awaitable } from "./Types.ts";
|
|
|
130
123
|
* expectOk(profile, { userId: "user-1" });
|
|
131
124
|
* ```
|
|
132
125
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* ```ts
|
|
136
|
-
* import { flatMapResult, ok, type Result } from "@evolu/common";
|
|
137
|
-
*
|
|
138
|
-
* interface User {
|
|
139
|
-
* readonly id: string;
|
|
140
|
-
* }
|
|
141
|
-
*
|
|
142
|
-
* interface UserNotFoundError {
|
|
143
|
-
* readonly type: "UserNotFoundError";
|
|
144
|
-
* }
|
|
145
|
-
*
|
|
146
|
-
* interface Profile {
|
|
147
|
-
* readonly userId: string;
|
|
148
|
-
* }
|
|
149
|
-
*
|
|
150
|
-
* interface ProfileNotFoundError {
|
|
151
|
-
* readonly type: "ProfileNotFoundError";
|
|
152
|
-
* }
|
|
153
|
-
*
|
|
154
|
-
* const getUser = (): Result<User, UserNotFoundError> =>
|
|
155
|
-
* ok({ id: "user-1" });
|
|
156
|
-
* const getProfile = (
|
|
157
|
-
* userId: string,
|
|
158
|
-
* ): Result<Profile, ProfileNotFoundError> => ok({ userId });
|
|
159
|
-
*
|
|
160
|
-
* const profile = flatMapResult(getUser(), (user) => getProfile(user.id));
|
|
161
|
-
* expectTypeOf(profile).toEqualTypeOf<
|
|
162
|
-
* Result<Profile, UserNotFoundError | ProfileNotFoundError>
|
|
163
|
-
* >();
|
|
164
|
-
* expectOk(profile, { userId: "user-1" });
|
|
165
|
-
* ```
|
|
166
|
-
*
|
|
167
|
-
* Use {@link allResult} to collect the Ok values from a collection of Results,
|
|
168
|
-
* or return the first Err.
|
|
169
|
-
*
|
|
170
|
-
* ```ts
|
|
171
|
-
* import { allResult, err, ok, type Result } from "@evolu/common";
|
|
172
|
-
*
|
|
173
|
-
* interface User {
|
|
174
|
-
* readonly id: string;
|
|
175
|
-
* }
|
|
176
|
-
*
|
|
177
|
-
* interface Preferences {
|
|
178
|
-
* readonly theme: "dark" | "light";
|
|
179
|
-
* }
|
|
180
|
-
*
|
|
181
|
-
* interface LoadAccountError {
|
|
182
|
-
* readonly type: "LoadAccountError";
|
|
183
|
-
* readonly resource: "user" | "preferences";
|
|
184
|
-
* }
|
|
185
|
-
*
|
|
186
|
-
* const user: Result<User, LoadAccountError> = ok({ id: "user-1" });
|
|
187
|
-
* const preferences: Result<Preferences, LoadAccountError> = ok({
|
|
188
|
-
* theme: "dark",
|
|
189
|
-
* });
|
|
190
|
-
* const account = allResult([user, preferences]);
|
|
191
|
-
* expectTypeOf(account).toEqualTypeOf<
|
|
192
|
-
* Result<readonly [User, Preferences], LoadAccountError>
|
|
193
|
-
* >();
|
|
194
|
-
* expectOk(account, [{ id: "user-1" }, { theme: "dark" }]);
|
|
195
|
-
*
|
|
196
|
-
* const userError: Result<never, LoadAccountError> = err({
|
|
197
|
-
* type: "LoadAccountError",
|
|
198
|
-
* resource: "user",
|
|
199
|
-
* });
|
|
200
|
-
* const preferencesError: Result<never, LoadAccountError> = err({
|
|
201
|
-
* type: "LoadAccountError",
|
|
202
|
-
* resource: "preferences",
|
|
203
|
-
* });
|
|
204
|
-
* const failedAccount = allResult([userError, preferencesError]);
|
|
205
|
-
* expectErr(failedAccount, {
|
|
206
|
-
* type: "LoadAccountError",
|
|
207
|
-
* resource: "user",
|
|
208
|
-
* });
|
|
209
|
-
* ```
|
|
210
|
-
*
|
|
211
|
-
* `allResult` can also map collection values to Results before collecting their
|
|
212
|
-
* Ok values. Mapping stops on the first Err.
|
|
213
|
-
*
|
|
214
|
-
* ```ts
|
|
215
|
-
* import { allResult, err, ok, type Result } from "@evolu/common";
|
|
216
|
-
*
|
|
217
|
-
* interface OrderLine {
|
|
218
|
-
* readonly productId: string;
|
|
219
|
-
* readonly quantity: number;
|
|
220
|
-
* }
|
|
221
|
-
*
|
|
222
|
-
* interface ProductUnavailableError {
|
|
223
|
-
* readonly type: "ProductUnavailableError";
|
|
224
|
-
* readonly productId: string;
|
|
225
|
-
* }
|
|
226
|
-
*
|
|
227
|
-
* const priceByProductId = new Map([["book", 20]]);
|
|
228
|
-
* const priceOrderLine = ({
|
|
229
|
-
* productId,
|
|
230
|
-
* quantity,
|
|
231
|
-
* }: OrderLine): Result<number, ProductUnavailableError> => {
|
|
232
|
-
* const price = priceByProductId.get(productId);
|
|
233
|
-
* if (price == null) {
|
|
234
|
-
* return err({ type: "ProductUnavailableError", productId });
|
|
235
|
-
* }
|
|
236
|
-
* return ok(price * quantity);
|
|
237
|
-
* };
|
|
238
|
-
*
|
|
239
|
-
* const orderLines: ReadonlyArray<OrderLine> = [
|
|
240
|
-
* { productId: "book", quantity: 2 },
|
|
241
|
-
* { productId: "sold-out", quantity: 1 },
|
|
242
|
-
* ];
|
|
243
|
-
* const prices = allResult(orderLines, priceOrderLine);
|
|
244
|
-
* expectTypeOf(prices).toEqualTypeOf<
|
|
245
|
-
* Result<ReadonlyArray<number>, ProductUnavailableError>
|
|
246
|
-
* >();
|
|
247
|
-
* expectErr(prices, {
|
|
248
|
-
* type: "ProductUnavailableError",
|
|
249
|
-
* productId: "sold-out",
|
|
250
|
-
* });
|
|
251
|
-
* ```
|
|
252
|
-
*
|
|
253
|
-
* For side effects, or whenever the success values aren't needed, disable
|
|
254
|
-
* collection with `{ collect: false }`. `allResult` then returns `Result<void,
|
|
255
|
-
* E>` without storing the successful values and still stops on the first Err.
|
|
126
|
+
* Note `user` and `profile` are named after their success values, not after the
|
|
127
|
+
* Result (`userResult`, `profileResult`).
|
|
256
128
|
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
* interface User {
|
|
261
|
-
* readonly id: string;
|
|
262
|
-
* }
|
|
263
|
-
*
|
|
264
|
-
* interface SaveUserError {
|
|
265
|
-
* readonly type: "SaveUserError";
|
|
266
|
-
* }
|
|
267
|
-
*
|
|
268
|
-
* interface SendWelcomeEmailError {
|
|
269
|
-
* readonly type: "SendWelcomeEmailError";
|
|
270
|
-
* }
|
|
271
|
-
*
|
|
272
|
-
* const saveUser = (_user: User): Result<number, SaveUserError> =>
|
|
273
|
-
* err({ type: "SaveUserError" });
|
|
274
|
-
*
|
|
275
|
-
* let welcomeEmailSent = false;
|
|
276
|
-
* const sendWelcomeEmail = (
|
|
277
|
-
* _user: User,
|
|
278
|
-
* ): Result<void, SendWelcomeEmailError> => {
|
|
279
|
-
* welcomeEmailSent = true;
|
|
280
|
-
* return ok();
|
|
281
|
-
* };
|
|
282
|
-
*
|
|
283
|
-
* const registerUser = (
|
|
284
|
-
* user: User,
|
|
285
|
-
* ): Result<void, SaveUserError | SendWelcomeEmailError> =>
|
|
286
|
-
* allResult(
|
|
287
|
-
* [saveUser, sendWelcomeEmail],
|
|
288
|
-
* (operation) => operation(user),
|
|
289
|
-
* { collect: false },
|
|
290
|
-
* );
|
|
291
|
-
*
|
|
292
|
-
* const result = registerUser({ id: "user-1" });
|
|
293
|
-
* expectTypeOf(result).toEqualTypeOf<
|
|
294
|
-
* Result<void, SaveUserError | SendWelcomeEmailError>
|
|
295
|
-
* >();
|
|
296
|
-
* expectErr(result, { type: "SaveUserError" });
|
|
297
|
-
* expect(welcomeEmailSent).toBe(false);
|
|
298
|
-
* ```
|
|
299
|
-
*
|
|
300
|
-
* For the first success, {@link anyResult} returns the first Ok or the last
|
|
301
|
-
* error if all fail:
|
|
302
|
-
*
|
|
303
|
-
* ```ts
|
|
304
|
-
* import { anyResult, err, ok, type Result } from "@evolu/common";
|
|
305
|
-
*
|
|
306
|
-
* interface CacheMissError {
|
|
307
|
-
* readonly type: "CacheMissError";
|
|
308
|
-
* }
|
|
309
|
-
*
|
|
310
|
-
* const getCachedPrice = (): Result<number, CacheMissError> =>
|
|
311
|
-
* err({ type: "CacheMissError" });
|
|
312
|
-
*
|
|
313
|
-
* const prices = [getCachedPrice(), ok(20)] as const;
|
|
314
|
-
* const price = anyResult(prices);
|
|
315
|
-
* expectTypeOf(price).toEqualTypeOf<Result<number, CacheMissError>>();
|
|
316
|
-
* expectOk(price, 20);
|
|
317
|
-
* ```
|
|
318
|
-
*
|
|
319
|
-
* ## Naming convention
|
|
320
|
-
*
|
|
321
|
-
* Name a Result after its value (`user`, `config`), not after the wrapper
|
|
322
|
-
* (`userResult`, `configResult`). If it has no success value, name it `result`.
|
|
323
|
-
* For several such operations, use `allResult` with `{ collect: false }` as
|
|
324
|
-
* shown above.
|
|
129
|
+
* If a Result has no success value, name it `result`. For several such
|
|
130
|
+
* operations, use {@link allResult} with `{ collect: false }`.
|
|
325
131
|
*
|
|
326
132
|
* ## Unrecoverable errors
|
|
327
133
|
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
134
|
+
* Unrecoverable errors are failures the application can't handle meaningfully.
|
|
135
|
+
* For example, a database failure is unrecoverable when the application has no
|
|
136
|
+
* useful fallback, retry, or degraded mode.
|
|
331
137
|
*
|
|
332
|
-
* Do not
|
|
333
|
-
* throws.
|
|
334
|
-
*
|
|
138
|
+
* Do not turn such failures into `Result` merely because the underlying API
|
|
139
|
+
* throws or rejects. Let the exception or Promise rejection propagate to the
|
|
140
|
+
* top-level handler so the application stops instead of continuing in a
|
|
141
|
+
* potentially invalid state.
|
|
335
142
|
*
|
|
336
|
-
* In Evolu apps, the root Run reports defects and the platform lifecycle
|
|
337
|
-
* owns application shutdown. For example, `@evolu/nodejs` provides
|
|
143
|
+
* In Evolu apps, the root {@link Run} reports defects and the platform lifecycle
|
|
144
|
+
* API owns application shutdown. For example, `@evolu/nodejs` provides
|
|
145
|
+
* {@link @evolu/nodejs!runMain | runMain}.
|
|
338
146
|
*
|
|
339
147
|
* ## FAQ
|
|
340
148
|
*
|
|
@@ -377,15 +185,46 @@ import type { Awaitable } from "./Types.ts";
|
|
|
377
185
|
* Use `Result<void, E>` and return `ok()` (no argument). Don't return
|
|
378
186
|
* `ok(true)`, `ok("success")`, or `ok("done")` — `ok()` already signals
|
|
379
187
|
* success; redundant values add noise.
|
|
188
|
+
*
|
|
189
|
+
* @module
|
|
190
|
+
*/
|
|
191
|
+
import { type NonEmptyReadonlyArray } from "./Array.ts";
|
|
192
|
+
import { type Thunk } from "./Function.ts";
|
|
193
|
+
import type { Typed } from "./Type.ts";
|
|
194
|
+
import type { Awaitable } from "./Types.ts";
|
|
195
|
+
/**
|
|
196
|
+
* A discriminated success or failure value: either {@link Ok} or {@link Err}.
|
|
197
|
+
*
|
|
198
|
+
* See the {@link @evolu/common!Result | Result overview}.
|
|
199
|
+
*
|
|
200
|
+
* @group Core
|
|
380
201
|
*/
|
|
381
202
|
export type Result<T, E = never> = Ok<T> | Err<E>;
|
|
382
203
|
/**
|
|
383
204
|
* Shorthand for a {@link Result} with `any` type parameters.
|
|
384
205
|
*
|
|
385
|
-
* @group
|
|
206
|
+
* @group Core
|
|
386
207
|
*/
|
|
387
208
|
export type AnyResult = Result<any, any>;
|
|
388
|
-
/**
|
|
209
|
+
/**
|
|
210
|
+
* A successful {@link Result}.
|
|
211
|
+
*
|
|
212
|
+
* ### Example
|
|
213
|
+
*
|
|
214
|
+
* ```ts
|
|
215
|
+
* import { ok, type Result } from "@evolu/common";
|
|
216
|
+
*
|
|
217
|
+
* const result = ok();
|
|
218
|
+
* const count = ok(42);
|
|
219
|
+
*
|
|
220
|
+
* expectTypeOf(result).toEqualTypeOf<Result<void>>();
|
|
221
|
+
* expectTypeOf(count).toEqualTypeOf<Result<number>>();
|
|
222
|
+
* expectOk(result, undefined);
|
|
223
|
+
* expectOk(count, 42);
|
|
224
|
+
* ```
|
|
225
|
+
*
|
|
226
|
+
* @group Core
|
|
227
|
+
*/
|
|
389
228
|
export interface Ok<out T> {
|
|
390
229
|
readonly ok: true;
|
|
391
230
|
readonly value: T;
|
|
@@ -394,7 +233,7 @@ export interface Ok<out T> {
|
|
|
394
233
|
* An error {@link Result}.
|
|
395
234
|
*
|
|
396
235
|
* The `error` property can be any type that describes the error. For domain
|
|
397
|
-
* errors,
|
|
236
|
+
* errors, define a plain interface extending {@link Typed}.
|
|
398
237
|
*
|
|
399
238
|
* ### Example
|
|
400
239
|
*
|
|
@@ -405,23 +244,25 @@ export interface Ok<out T> {
|
|
|
405
244
|
* readonly id: string;
|
|
406
245
|
* }
|
|
407
246
|
*
|
|
408
|
-
* interface NotFoundError extends Typed<"NotFoundError"> {
|
|
409
|
-
* readonly id: string;
|
|
410
|
-
* }
|
|
411
|
-
*
|
|
412
247
|
* const users = new Map<string, User>([["user-1", { id: "user-1" }]]);
|
|
413
248
|
* const findUser = (id: string): Result<User, NotFoundError> => {
|
|
414
249
|
* const user = users.get(id);
|
|
415
|
-
* if (user == null) return err({ type: "
|
|
250
|
+
* if (user == null) return err({ type: "NotFound", id });
|
|
416
251
|
* return ok(user);
|
|
417
252
|
* };
|
|
418
253
|
*
|
|
254
|
+
* interface NotFoundError extends Typed<"NotFound"> {
|
|
255
|
+
* readonly id: string;
|
|
256
|
+
* }
|
|
257
|
+
*
|
|
419
258
|
* expectOk(findUser("user-1"), { id: "user-1" });
|
|
420
259
|
* expectErr(findUser("missing"), {
|
|
421
|
-
* type: "
|
|
260
|
+
* type: "NotFound",
|
|
422
261
|
* id: "missing",
|
|
423
262
|
* });
|
|
424
263
|
* ```
|
|
264
|
+
*
|
|
265
|
+
* @group Core
|
|
425
266
|
*/
|
|
426
267
|
export interface Err<out E> {
|
|
427
268
|
readonly ok: false;
|
|
@@ -430,13 +271,13 @@ export interface Err<out E> {
|
|
|
430
271
|
/**
|
|
431
272
|
* Infers the success value type from a {@link Result}.
|
|
432
273
|
*
|
|
433
|
-
* @group
|
|
274
|
+
* @group Core
|
|
434
275
|
*/
|
|
435
276
|
export type InferOk<R extends Result<any, any>> = R extends Ok<infer T> ? T : never;
|
|
436
277
|
/**
|
|
437
278
|
* Infers the error type from a {@link Result}.
|
|
438
279
|
*
|
|
439
|
-
* @group
|
|
280
|
+
* @group Core
|
|
440
281
|
*/
|
|
441
282
|
export type InferErr<R extends Result<any, any>> = R extends Err<infer E> ? E : never;
|
|
442
283
|
/**
|
|
@@ -446,31 +287,76 @@ export type InferErr<R extends Result<any, any>> = R extends Err<infer E> ? E :
|
|
|
446
287
|
* producing a value.
|
|
447
288
|
* - `ok(value)` creates a `Result<T, never>` containing the specified value.
|
|
448
289
|
*
|
|
290
|
+
* @group Core
|
|
291
|
+
*/
|
|
292
|
+
export declare function ok(): Result<void>;
|
|
293
|
+
/** Creates an {@link Ok} result with a specified value. */
|
|
294
|
+
export declare function ok<T>(value: T): Result<T>;
|
|
295
|
+
/**
|
|
296
|
+
* Creates an {@link Err} result.
|
|
297
|
+
*
|
|
298
|
+
* @group Core
|
|
299
|
+
*/
|
|
300
|
+
export declare const err: <E>(error: E) => Result<never, E>;
|
|
301
|
+
/**
|
|
302
|
+
* Type guard for {@link Ok} results.
|
|
303
|
+
*
|
|
449
304
|
* ### Example
|
|
450
305
|
*
|
|
451
306
|
* ```ts
|
|
452
|
-
* import {
|
|
307
|
+
* import {
|
|
308
|
+
* isOk,
|
|
309
|
+
* ok,
|
|
310
|
+
* type Ok,
|
|
311
|
+
* type Result,
|
|
312
|
+
* type Typed,
|
|
313
|
+
* } from "@evolu/common";
|
|
314
|
+
*
|
|
315
|
+
* const getCount = (): Result<number, CountUnavailableError> => ok(2);
|
|
453
316
|
*
|
|
454
|
-
*
|
|
455
|
-
* const success = ok(42);
|
|
317
|
+
* interface CountUnavailableError extends Typed<"CountUnavailable"> {}
|
|
456
318
|
*
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
*
|
|
319
|
+
* const count = getCount();
|
|
320
|
+
* if (isOk(count)) {
|
|
321
|
+
* expectTypeOf(count).toEqualTypeOf<Ok<number>>();
|
|
322
|
+
* expect(count.value).toBe(2);
|
|
323
|
+
* }
|
|
461
324
|
* ```
|
|
325
|
+
*
|
|
326
|
+
* @group Guards
|
|
462
327
|
*/
|
|
463
|
-
export declare function ok(): Result<void>;
|
|
464
|
-
/** Creates an {@link Ok} result with a specified value. */
|
|
465
|
-
export declare function ok<T>(value: T): Result<T>;
|
|
466
|
-
/** Creates an {@link Err} result. */
|
|
467
|
-
export declare const err: <E>(error: E) => Result<never, E>;
|
|
468
|
-
/** Type guard for {@link Ok} results. */
|
|
469
328
|
export declare const isOk: <T, E>(result: Result<T, E>) => result is Ok<T>;
|
|
470
|
-
/**
|
|
329
|
+
/**
|
|
330
|
+
* Type guard for {@link Err} results.
|
|
331
|
+
*
|
|
332
|
+
* ### Example
|
|
333
|
+
*
|
|
334
|
+
* ```ts
|
|
335
|
+
* import {
|
|
336
|
+
* err,
|
|
337
|
+
* isErr,
|
|
338
|
+
* type Err,
|
|
339
|
+
* type Result,
|
|
340
|
+
* type Typed,
|
|
341
|
+
* } from "@evolu/common";
|
|
342
|
+
*
|
|
343
|
+
* const getCount = (): Result<number, CountUnavailableError> =>
|
|
344
|
+
* err({ type: "CountUnavailable" });
|
|
345
|
+
*
|
|
346
|
+
* interface CountUnavailableError extends Typed<"CountUnavailable"> {}
|
|
347
|
+
*
|
|
348
|
+
* const count = getCount();
|
|
349
|
+
* if (isErr(count)) {
|
|
350
|
+
* expectTypeOf(count).toEqualTypeOf<Err<CountUnavailableError>>();
|
|
351
|
+
* expect(count.error).toEqual({ type: "CountUnavailable" });
|
|
352
|
+
* }
|
|
353
|
+
* ```
|
|
354
|
+
*
|
|
355
|
+
* @group Guards
|
|
356
|
+
*/
|
|
471
357
|
export declare const isErr: <T, E>(result: Result<T, E>) => result is Err<E>;
|
|
472
358
|
/**
|
|
473
|
-
* Gets the value from an
|
|
359
|
+
* Gets the value from an {@link Ok}, or throws for an {@link Err}.
|
|
474
360
|
*
|
|
475
361
|
* Use this where failure should crash the current flow instead of being handled
|
|
476
362
|
* locally.
|
|
@@ -478,8 +364,8 @@ export declare const isErr: <T, E>(result: Result<T, E>) => result is Err<E>;
|
|
|
478
364
|
* **When to use:**
|
|
479
365
|
*
|
|
480
366
|
* - Application startup or composition-root setup where errors must stop the
|
|
481
|
-
* program immediately. In Evolu apps, the root Run reports the defect
|
|
482
|
-
* platform lifecycle API handles shutdown.
|
|
367
|
+
* program immediately. In Evolu apps, the root {@link Run} reports the defect
|
|
368
|
+
* and the platform lifecycle API handles shutdown.
|
|
483
369
|
* - Module-level constants
|
|
484
370
|
* - Test setup with values that are expected to be valid
|
|
485
371
|
*
|
|
@@ -489,38 +375,45 @@ export declare const isErr: <T, E>(result: Result<T, E>) => result is Err<E>;
|
|
|
489
375
|
* ### Example
|
|
490
376
|
*
|
|
491
377
|
* ```ts
|
|
492
|
-
* import {
|
|
378
|
+
* import {
|
|
379
|
+
* err,
|
|
380
|
+
* getOrThrow,
|
|
381
|
+
* ok,
|
|
382
|
+
* type Result,
|
|
383
|
+
* type Typed,
|
|
384
|
+
* } from "@evolu/common";
|
|
493
385
|
*
|
|
494
386
|
* interface Config {
|
|
495
387
|
* readonly port: number;
|
|
496
388
|
* }
|
|
497
|
-
*
|
|
498
|
-
* readonly type: "InvalidConfigError";
|
|
499
|
-
* }
|
|
389
|
+
*
|
|
500
390
|
* const loadConfig = (): Result<Config, InvalidConfigError> =>
|
|
501
391
|
* ok({ port: 3000 });
|
|
502
392
|
*
|
|
393
|
+
* interface InvalidConfigError extends Typed<"InvalidConfig"> {}
|
|
394
|
+
*
|
|
503
395
|
* // At app startup, crash if the config is invalid.
|
|
504
396
|
* const config = getOrThrow(loadConfig());
|
|
505
397
|
* expectTypeOf(config).toEqualTypeOf<Config>();
|
|
506
398
|
* expect(config).toEqual({ port: 3000 });
|
|
507
399
|
*
|
|
508
400
|
* try {
|
|
509
|
-
* getOrThrow(err({ type: "
|
|
401
|
+
* getOrThrow(err({ type: "InvalidConfig" }));
|
|
510
402
|
* assert.fail("Expected getOrThrow to throw");
|
|
511
403
|
* } catch (error) {
|
|
512
404
|
* assert(error instanceof Error);
|
|
513
405
|
* expect(error.message).toBe("getOrThrow");
|
|
514
|
-
* expect(error.cause).toEqual({ type: "
|
|
406
|
+
* expect(error.cause).toEqual({ type: "InvalidConfig" });
|
|
515
407
|
* }
|
|
516
408
|
* ```
|
|
517
409
|
*
|
|
518
410
|
* Throws: `Error` with the original error attached as `cause`.
|
|
411
|
+
*
|
|
412
|
+
* @group Unwrapping
|
|
519
413
|
*/
|
|
520
414
|
export declare const getOrThrow: <T, E>(result: Result<T, E>) => T;
|
|
521
415
|
/**
|
|
522
|
-
* Gets the value from an
|
|
523
|
-
* `Err`.
|
|
416
|
+
* Gets the value from an {@link Ok}, or returns `null` for an {@link Err}.
|
|
524
417
|
*
|
|
525
418
|
* **Intended usage:**
|
|
526
419
|
*
|
|
@@ -531,16 +424,22 @@ export declare const getOrThrow: <T, E>(result: Result<T, E>) => T;
|
|
|
531
424
|
* ### Example
|
|
532
425
|
*
|
|
533
426
|
* ```ts
|
|
534
|
-
* import {
|
|
427
|
+
* import {
|
|
428
|
+
* err,
|
|
429
|
+
* getOrNull,
|
|
430
|
+
* ok,
|
|
431
|
+
* type Result,
|
|
432
|
+
* type Typed,
|
|
433
|
+
* } from "@evolu/common";
|
|
535
434
|
*
|
|
536
435
|
* interface User {
|
|
537
436
|
* readonly id: string;
|
|
538
437
|
* }
|
|
539
|
-
*
|
|
540
|
-
* readonly type: "UserNotFoundError";
|
|
541
|
-
* }
|
|
438
|
+
*
|
|
542
439
|
* const findUser = (id: string): Result<User, UserNotFoundError> =>
|
|
543
|
-
* id === "user-1" ? ok({ id }) : err({ type: "
|
|
440
|
+
* id === "user-1" ? ok({ id }) : err({ type: "UserNotFound" });
|
|
441
|
+
*
|
|
442
|
+
* interface UserNotFoundError extends Typed<"UserNotFound"> {}
|
|
544
443
|
*
|
|
545
444
|
* // For APIs that expect T | null.
|
|
546
445
|
* const user = getOrNull(findUser("user-1"));
|
|
@@ -550,6 +449,8 @@ export declare const getOrThrow: <T, E>(result: Result<T, E>) => T;
|
|
|
550
449
|
* expect(user).toEqual({ id: "user-1" });
|
|
551
450
|
* expect(missingUser).toBeNull();
|
|
552
451
|
* ```
|
|
452
|
+
*
|
|
453
|
+
* @group Unwrapping
|
|
553
454
|
*/
|
|
554
455
|
export declare const getOrNull: <T, E>(result: Result<T, E>) => T | null;
|
|
555
456
|
/**
|
|
@@ -569,108 +470,114 @@ export declare const getOrNull: <T, E>(result: Result<T, E>) => T | null;
|
|
|
569
470
|
* expectTypeOf(count).toEqualTypeOf<number>();
|
|
570
471
|
* expect(count).toBe(2);
|
|
571
472
|
* ```
|
|
473
|
+
*
|
|
474
|
+
* @group Unwrapping
|
|
572
475
|
*/
|
|
573
476
|
export declare const getOk: <T>(result: Result<T>) => T;
|
|
574
477
|
/**
|
|
575
478
|
* Wraps a synchronous function that may throw, returning a {@link Result}.
|
|
576
479
|
*
|
|
577
|
-
* `mapError` converts the caught `unknown` value into a typed
|
|
480
|
+
* When provided, `mapError` converts the caught `unknown` value into a typed
|
|
481
|
+
* domain error.
|
|
578
482
|
*
|
|
579
|
-
* Some APIs
|
|
580
|
-
*
|
|
581
|
-
*
|
|
582
|
-
* to Err.
|
|
483
|
+
* Some APIs throw for both recoverable and unrecoverable errors. In that case,
|
|
484
|
+
* convert only the errors the caller can recover from and rethrow the rest. The
|
|
485
|
+
* `trySync` error mapper propagates rethrown values instead of converting them
|
|
486
|
+
* to {@link Err}.
|
|
583
487
|
*
|
|
584
488
|
* ### Example
|
|
585
489
|
*
|
|
586
490
|
* ```ts
|
|
587
|
-
* import { trySync, type Result } from "@evolu/common";
|
|
588
|
-
*
|
|
589
|
-
* interface ReserveSeatError {
|
|
590
|
-
* readonly type: "ReserveSeatError";
|
|
591
|
-
* readonly seat: string;
|
|
592
|
-
* }
|
|
491
|
+
* import { trySync, type Result, type Typed } from "@evolu/common";
|
|
593
492
|
*
|
|
594
493
|
* class LegacySeatUnavailableError extends Error {}
|
|
595
494
|
*
|
|
596
495
|
* const legacyReserveSeat = (seat: string): void => {
|
|
597
496
|
* if (seat === "A1") throw new LegacySeatUnavailableError();
|
|
598
|
-
* if (seat === "B1") throw new Error("
|
|
497
|
+
* if (seat === "B1") throw new Error("Database error");
|
|
599
498
|
* };
|
|
600
499
|
*
|
|
601
|
-
* const reserveSeat = (seat: string): Result<void,
|
|
500
|
+
* const reserveSeat = (seat: string): Result<void, SeatUnavailableError> =>
|
|
602
501
|
* trySync(
|
|
603
502
|
* () => legacyReserveSeat(seat),
|
|
604
503
|
* (error) => {
|
|
605
504
|
* if (error instanceof LegacySeatUnavailableError) {
|
|
606
|
-
* return { type: "
|
|
505
|
+
* return { type: "SeatUnavailable", seat };
|
|
607
506
|
* }
|
|
608
507
|
* throw error;
|
|
609
508
|
* },
|
|
610
509
|
* );
|
|
611
510
|
*
|
|
511
|
+
* interface SeatUnavailableError extends Typed<"SeatUnavailable"> {
|
|
512
|
+
* readonly seat: string;
|
|
513
|
+
* }
|
|
514
|
+
*
|
|
612
515
|
* const result = reserveSeat("B2");
|
|
613
|
-
* expectTypeOf(result).toEqualTypeOf<Result<void,
|
|
516
|
+
* expectTypeOf(result).toEqualTypeOf<Result<void, SeatUnavailableError>>();
|
|
614
517
|
* expectOk(result, undefined);
|
|
615
|
-
* expectErr(reserveSeat("A1"), { type: "
|
|
616
|
-
* expect(() => reserveSeat("B1")).toThrow("
|
|
518
|
+
* expectErr(reserveSeat("A1"), { type: "SeatUnavailable", seat: "A1" });
|
|
519
|
+
* expect(() => reserveSeat("B1")).toThrow("Database error");
|
|
617
520
|
* ```
|
|
521
|
+
*
|
|
522
|
+
* @group Exception interop
|
|
618
523
|
*/
|
|
619
524
|
export declare function trySync<T>(fn: () => T): Result<T, unknown>;
|
|
525
|
+
/** Maps caught exceptions to a typed error. */
|
|
620
526
|
export declare function trySync<T, E>(fn: () => T, mapError: (error: unknown) => E): Result<T, E>;
|
|
621
527
|
/**
|
|
622
528
|
* Wraps an async function that may throw or reject, returning a {@link Result}.
|
|
623
529
|
*
|
|
624
|
-
* `mapError` converts the caught `unknown` value into a typed
|
|
530
|
+
* When provided, `mapError` converts the caught `unknown` value into a typed
|
|
531
|
+
* domain error.
|
|
625
532
|
*
|
|
626
|
-
* Some APIs
|
|
627
|
-
* that case, convert only the
|
|
628
|
-
* the rest. `tryAsync`
|
|
629
|
-
* them to Err.
|
|
533
|
+
* Some async APIs throw or reject for both recoverable and unrecoverable
|
|
534
|
+
* errors. In that case, convert only the errors the caller can recover from and
|
|
535
|
+
* rethrow the rest. The `tryAsync` error mapper leaves rethrown values as
|
|
536
|
+
* Promise rejections instead of converting them to {@link Err}.
|
|
630
537
|
*
|
|
631
538
|
* ### Example
|
|
632
539
|
*
|
|
633
540
|
* ```ts
|
|
634
|
-
* import { tryAsync, type Result } from "@evolu/common";
|
|
635
|
-
*
|
|
636
|
-
* interface ReserveSeatError {
|
|
637
|
-
* readonly type: "ReserveSeatError";
|
|
638
|
-
* readonly seat: string;
|
|
639
|
-
* }
|
|
541
|
+
* import { tryAsync, type Result, type Typed } from "@evolu/common";
|
|
640
542
|
*
|
|
641
543
|
* class LegacySeatUnavailableError extends Error {}
|
|
642
544
|
*
|
|
643
545
|
* const legacyReserveSeat = async (seat: string): Promise<void> => {
|
|
644
546
|
* if (seat === "A1") throw new LegacySeatUnavailableError();
|
|
645
|
-
* if (seat === "B1") throw new Error("
|
|
547
|
+
* if (seat === "B1") throw new Error("Database error");
|
|
646
548
|
* };
|
|
647
549
|
*
|
|
648
550
|
* const reserveSeat = (
|
|
649
551
|
* seat: string,
|
|
650
|
-
* ): Promise<Result<void,
|
|
552
|
+
* ): Promise<Result<void, SeatUnavailableError>> =>
|
|
651
553
|
* tryAsync(
|
|
652
554
|
* () => legacyReserveSeat(seat),
|
|
653
555
|
* (error) => {
|
|
654
556
|
* if (error instanceof LegacySeatUnavailableError) {
|
|
655
|
-
* return { type: "
|
|
557
|
+
* return { type: "SeatUnavailable", seat };
|
|
656
558
|
* }
|
|
657
559
|
* throw error;
|
|
658
560
|
* },
|
|
659
561
|
* );
|
|
660
562
|
*
|
|
563
|
+
* interface SeatUnavailableError extends Typed<"SeatUnavailable"> {
|
|
564
|
+
* readonly seat: string;
|
|
565
|
+
* }
|
|
566
|
+
*
|
|
661
567
|
* const result = await reserveSeat("B2");
|
|
662
|
-
* expectTypeOf(result).toEqualTypeOf<Result<void,
|
|
568
|
+
* expectTypeOf(result).toEqualTypeOf<Result<void, SeatUnavailableError>>();
|
|
663
569
|
* expectOk(result, undefined);
|
|
664
570
|
* expectErr(await reserveSeat("A1"), {
|
|
665
|
-
* type: "
|
|
571
|
+
* type: "SeatUnavailable",
|
|
666
572
|
* seat: "A1",
|
|
667
573
|
* });
|
|
668
|
-
* await expect(reserveSeat("B1")).rejects.toThrow(
|
|
669
|
-
* "Unexpected database error",
|
|
670
|
-
* );
|
|
574
|
+
* await expect(reserveSeat("B1")).rejects.toThrow("Database error");
|
|
671
575
|
* ```
|
|
576
|
+
*
|
|
577
|
+
* @group Exception interop
|
|
672
578
|
*/
|
|
673
579
|
export declare function tryAsync<T>(promiseThunk: Thunk<Awaitable<T>>): Promise<Result<T, unknown>>;
|
|
580
|
+
/** Maps thrown or rejected values to a typed error. */
|
|
674
581
|
export declare function tryAsync<T, E>(promiseThunk: Thunk<Awaitable<T>>, mapError: (error: unknown) => E): Promise<Result<T, E>>;
|
|
675
582
|
/**
|
|
676
583
|
* A result for a pull-based protocol with three outcomes.
|
|
@@ -678,35 +585,43 @@ export declare function tryAsync<T, E>(promiseThunk: Thunk<Awaitable<T>>, mapErr
|
|
|
678
585
|
* The consumer requests the next value (e.g. via `next()`), and the producer
|
|
679
586
|
* responds with one of:
|
|
680
587
|
*
|
|
681
|
-
* - `
|
|
682
|
-
* -
|
|
683
|
-
* - `
|
|
588
|
+
* - An {@link Ok} containing `A` — produced a value
|
|
589
|
+
* - An {@link Err} containing {@link Done} — completed normally with a done value
|
|
590
|
+
* - An {@link Err} containing `E` — failed with an error
|
|
684
591
|
*
|
|
685
592
|
* Inspired by JavaScript's `Iterator.next()`, which returns `{ value, done }`.
|
|
686
593
|
*
|
|
687
594
|
* ### Example
|
|
688
595
|
*
|
|
689
596
|
* ```ts
|
|
690
|
-
* import {
|
|
691
|
-
*
|
|
692
|
-
*
|
|
693
|
-
*
|
|
694
|
-
*
|
|
597
|
+
* import {
|
|
598
|
+
* done,
|
|
599
|
+
* err,
|
|
600
|
+
* ok,
|
|
601
|
+
* type NextResult,
|
|
602
|
+
* type Typed,
|
|
603
|
+
* } from "@evolu/common";
|
|
695
604
|
*
|
|
696
|
-
* const next = (
|
|
605
|
+
* const next = (
|
|
606
|
+
* index: number,
|
|
607
|
+
* ): NextResult<string, ReadFailedError, number> => {
|
|
697
608
|
* if (index === 0) return ok("first");
|
|
698
|
-
* if (index === 1) return err({ type: "
|
|
609
|
+
* if (index === 1) return err({ type: "ReadFailed" });
|
|
699
610
|
* return err(done(index));
|
|
700
611
|
* };
|
|
701
612
|
*
|
|
613
|
+
* interface ReadFailedError extends Typed<"ReadFailed"> {}
|
|
614
|
+
*
|
|
702
615
|
* const value = next(0);
|
|
703
616
|
* expectTypeOf(value).toEqualTypeOf<
|
|
704
|
-
* NextResult<string,
|
|
617
|
+
* NextResult<string, ReadFailedError, number>
|
|
705
618
|
* >();
|
|
706
619
|
* expectOk(value, "first");
|
|
707
|
-
* expectErr(next(1), { type: "
|
|
620
|
+
* expectErr(next(1), { type: "ReadFailed" });
|
|
708
621
|
* expectErr(next(2), { type: "Done", done: 2 });
|
|
709
622
|
* ```
|
|
623
|
+
*
|
|
624
|
+
* @group Pull
|
|
710
625
|
*/
|
|
711
626
|
export type NextResult<A, E = never, D = void> = Result<A, E | Done<D>>;
|
|
712
627
|
/**
|
|
@@ -717,15 +632,6 @@ export type NextResult<A, E = never, D = void> = Result<A, E | Done<D>>;
|
|
|
717
632
|
*
|
|
718
633
|
* Inspired by JavaScript's `IteratorResult` where `{ done: true }` signals
|
|
719
634
|
* completion.
|
|
720
|
-
*/
|
|
721
|
-
export interface Done<out D = unknown> extends Typed<"Done"> {
|
|
722
|
-
readonly done: D;
|
|
723
|
-
}
|
|
724
|
-
/**
|
|
725
|
-
* Constructs a {@link Done} value.
|
|
726
|
-
*
|
|
727
|
-
* - `done()` creates a `Done<void>` for protocols that don't need a done value.
|
|
728
|
-
* - `done(value)` creates a `Done<D>` containing the specified value.
|
|
729
635
|
*
|
|
730
636
|
* ### Example
|
|
731
637
|
*
|
|
@@ -739,6 +645,19 @@ export interface Done<out D = unknown> extends Typed<"Done"> {
|
|
|
739
645
|
* expect(withoutValue).toEqual({ type: "Done", done: undefined });
|
|
740
646
|
* expect(withValue).toEqual({ type: "Done", done: 42 });
|
|
741
647
|
* ```
|
|
648
|
+
*
|
|
649
|
+
* @group Pull
|
|
650
|
+
*/
|
|
651
|
+
export interface Done<out D = unknown> extends Typed<"Done"> {
|
|
652
|
+
readonly done: D;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Constructs a {@link Done} value.
|
|
656
|
+
*
|
|
657
|
+
* - `done()` creates a `Done<void>` for protocols that don't need a done value.
|
|
658
|
+
* - `done(value)` creates a `Done<D>` containing the specified value.
|
|
659
|
+
*
|
|
660
|
+
* @group Pull
|
|
742
661
|
*/
|
|
743
662
|
export declare function done(): Done<void>;
|
|
744
663
|
/** With a done value. */
|
|
@@ -752,13 +671,16 @@ export declare function done<D>(value: D): Done<D>;
|
|
|
752
671
|
* ### Example
|
|
753
672
|
*
|
|
754
673
|
* ```ts
|
|
755
|
-
* import { type Done, type ExcludeDone } from "@evolu/common";
|
|
674
|
+
* import { type Done, type ExcludeDone, type Typed } from "@evolu/common";
|
|
675
|
+
*
|
|
676
|
+
* type Errors = ReadFailedError | Done<number>;
|
|
756
677
|
*
|
|
757
|
-
*
|
|
758
|
-
*
|
|
678
|
+
* interface ReadFailedError extends Typed<"ReadFailed"> {}
|
|
679
|
+
*
|
|
680
|
+
* expectTypeOf<ExcludeDone<Errors>>().toEqualTypeOf<ReadFailedError>();
|
|
759
681
|
* ```
|
|
760
682
|
*
|
|
761
|
-
* @group
|
|
683
|
+
* @group Pull
|
|
762
684
|
*/
|
|
763
685
|
export type ExcludeDone<E> = Exclude<E, Done<any>>;
|
|
764
686
|
/**
|
|
@@ -770,13 +692,16 @@ export type ExcludeDone<E> = Exclude<E, Done<any>>;
|
|
|
770
692
|
* ### Example
|
|
771
693
|
*
|
|
772
694
|
* ```ts
|
|
773
|
-
* import { type Done, type OnlyDone } from "@evolu/common";
|
|
695
|
+
* import { type Done, type OnlyDone, type Typed } from "@evolu/common";
|
|
696
|
+
*
|
|
697
|
+
* type Errors = ReadFailedError | Done<number>;
|
|
698
|
+
*
|
|
699
|
+
* interface ReadFailedError extends Typed<"ReadFailed"> {}
|
|
774
700
|
*
|
|
775
|
-
* type Errors = "ReadError" | Done<number>;
|
|
776
701
|
* expectTypeOf<OnlyDone<Errors>>().toEqualTypeOf<Done<number>>();
|
|
777
702
|
* ```
|
|
778
703
|
*
|
|
779
|
-
* @group
|
|
704
|
+
* @group Pull
|
|
780
705
|
*/
|
|
781
706
|
export type OnlyDone<E> = Extract<E, Done<any>>;
|
|
782
707
|
/**
|
|
@@ -785,13 +710,20 @@ export type OnlyDone<E> = Extract<E, Done<any>>;
|
|
|
785
710
|
* ### Example
|
|
786
711
|
*
|
|
787
712
|
* ```ts
|
|
788
|
-
* import {
|
|
713
|
+
* import {
|
|
714
|
+
* type InferDone,
|
|
715
|
+
* type NextResult,
|
|
716
|
+
* type Typed,
|
|
717
|
+
* } from "@evolu/common";
|
|
718
|
+
*
|
|
719
|
+
* type ReadResult = NextResult<string, ReadFailedError, number>;
|
|
720
|
+
*
|
|
721
|
+
* interface ReadFailedError extends Typed<"ReadFailed"> {}
|
|
789
722
|
*
|
|
790
|
-
* type ReadResult = NextResult<string, "ReadError", number>;
|
|
791
723
|
* expectTypeOf<InferDone<ReadResult>>().toEqualTypeOf<number>();
|
|
792
724
|
* ```
|
|
793
725
|
*
|
|
794
|
-
* @group
|
|
726
|
+
* @group Pull
|
|
795
727
|
*/
|
|
796
728
|
export type InferDone<R extends Result<any, any>> = InferErr<R> extends infer Errors ? Errors extends Done<infer D> ? D : never : never;
|
|
797
729
|
/**
|
|
@@ -806,85 +738,71 @@ export type InferDone<R extends Result<any, any>> = InferErr<R> extends infer Er
|
|
|
806
738
|
* ### Example
|
|
807
739
|
*
|
|
808
740
|
* ```ts
|
|
809
|
-
* import {
|
|
741
|
+
* import {
|
|
742
|
+
* flatMapResult,
|
|
743
|
+
* ok,
|
|
744
|
+
* type Result,
|
|
745
|
+
* type Typed,
|
|
746
|
+
* } from "@evolu/common";
|
|
810
747
|
*
|
|
811
748
|
* interface User {
|
|
812
749
|
* readonly id: string;
|
|
813
750
|
* }
|
|
814
|
-
*
|
|
815
|
-
*
|
|
816
|
-
* }
|
|
751
|
+
*
|
|
752
|
+
* interface UserNotFoundError extends Typed<"UserNotFound"> {}
|
|
817
753
|
*
|
|
818
754
|
* interface Profile {
|
|
819
755
|
* readonly userId: string;
|
|
820
756
|
* }
|
|
821
|
-
* interface ProfileError {
|
|
822
|
-
* readonly type: "ProfileError";
|
|
823
|
-
* }
|
|
824
|
-
* const getProfile = (userId: string): Result<Profile, ProfileError> =>
|
|
825
|
-
* ok({ userId });
|
|
826
|
-
*
|
|
827
|
-
* let getProfileCalls = 0;
|
|
828
|
-
* const getProfileForUser = (
|
|
829
|
-
* user: User,
|
|
830
|
-
* ): Result<Profile, ProfileError> => {
|
|
831
|
-
* getProfileCalls++;
|
|
832
|
-
* return getProfile(user.id);
|
|
833
|
-
* };
|
|
834
757
|
*
|
|
835
|
-
* const
|
|
836
|
-
*
|
|
758
|
+
* const getProfile = (
|
|
759
|
+
* userId: string,
|
|
760
|
+
* ): Result<Profile, ProfileNotFoundError> => ok({ userId });
|
|
761
|
+
*
|
|
762
|
+
* interface ProfileNotFoundError extends Typed<"ProfileNotFound"> {}
|
|
763
|
+
*
|
|
764
|
+
* const user: Result<User, UserNotFoundError> = ok({ id: "user-1" });
|
|
765
|
+
* const profile = flatMapResult(user, ({ id }) => getProfile(id));
|
|
837
766
|
* expectTypeOf(profile).toEqualTypeOf<
|
|
838
|
-
* Result<Profile,
|
|
767
|
+
* Result<Profile, UserNotFoundError | ProfileNotFoundError>
|
|
839
768
|
* >();
|
|
840
769
|
* expectOk(profile, { userId: "user-1" });
|
|
841
|
-
* expect(getProfileCalls).toBe(1);
|
|
842
|
-
*
|
|
843
|
-
* const missingUser: Result<User, UserError> = err({ type: "UserError" });
|
|
844
|
-
* const missingProfile = flatMapResult(missingUser, getProfileForUser);
|
|
845
|
-
* expectErr(missingProfile, { type: "UserError" });
|
|
846
|
-
* expect(getProfileCalls).toBe(1);
|
|
847
770
|
* ```
|
|
848
771
|
*
|
|
849
772
|
* @group Composition
|
|
850
773
|
*/
|
|
851
774
|
export declare const flatMapResult: <T, E, U, F>(result: Result<T, E>, fn: (value: T) => Result<U, F>) => Result<U, E | F>;
|
|
852
775
|
/**
|
|
853
|
-
* Collects
|
|
854
|
-
* collection values to Results and collects their Ok values.
|
|
776
|
+
* Collects successful values from {@link Result}s.
|
|
855
777
|
*
|
|
856
|
-
*
|
|
778
|
+
* Stops processing the input at the first {@link Err}. Mapping overloads do not
|
|
779
|
+
* call the mapper for the remaining input values.
|
|
857
780
|
*
|
|
858
|
-
* Pass `{ collect: false }` to return `Result<void, E>` without storing the
|
|
859
|
-
* values in an output collection.
|
|
781
|
+
* Pass `{ collect: false }` to return `Result<void, E>` without storing the
|
|
782
|
+
* {@link Ok} values in an output collection.
|
|
860
783
|
*
|
|
861
784
|
* ### Example
|
|
862
785
|
*
|
|
863
786
|
* ```ts
|
|
864
|
-
* import { allResult,
|
|
787
|
+
* import { allResult, ok, type Result, type Typed } from "@evolu/common";
|
|
865
788
|
*
|
|
866
|
-
*
|
|
867
|
-
*
|
|
868
|
-
* }
|
|
869
|
-
*
|
|
870
|
-
*
|
|
871
|
-
*
|
|
789
|
+
* const getCount = (): Result<number, CountUnavailableError> => ok(2);
|
|
790
|
+
*
|
|
791
|
+
* interface CountUnavailableError extends Typed<"CountUnavailable"> {}
|
|
792
|
+
*
|
|
793
|
+
* const getLabel = (): Result<string, LabelUnavailableError> =>
|
|
794
|
+
* ok("books");
|
|
872
795
|
*
|
|
873
|
-
*
|
|
874
|
-
* const getLabel = (): Result<string, LabelError> => ok("books");
|
|
796
|
+
* interface LabelUnavailableError extends Typed<"LabelUnavailable"> {}
|
|
875
797
|
*
|
|
876
798
|
* const values = allResult([getCount(), getLabel()]);
|
|
877
799
|
* expectTypeOf(values).toEqualTypeOf<
|
|
878
|
-
* Result<
|
|
800
|
+
* Result<
|
|
801
|
+
* readonly [number, string],
|
|
802
|
+
* CountUnavailableError | LabelUnavailableError
|
|
803
|
+
* >
|
|
879
804
|
* >();
|
|
880
805
|
* expectOk(values, [2, "books"]);
|
|
881
|
-
*
|
|
882
|
-
* const numbers = allResult([
|
|
883
|
-
* ok(1),
|
|
884
|
-
* err({ type: "CountError" }),
|
|
885
|
-
* err({ type: "LabelError" }),
|
|
886
|
-
* ]);
|
|
887
|
-
* expectErr(numbers, { type: "CountError" });
|
|
888
806
|
* ```
|
|
889
807
|
*
|
|
890
808
|
* @group Composition
|
|
@@ -916,26 +834,15 @@ export declare function allResult<T extends Readonly<Record<string, AnyResult>>>
|
|
|
916
834
|
/**
|
|
917
835
|
* For dynamic or generated result lists.
|
|
918
836
|
*
|
|
919
|
-
* Stops consuming the iterable on the first Err.
|
|
920
|
-
*
|
|
921
837
|
* ### Example
|
|
922
838
|
*
|
|
923
839
|
* ```ts
|
|
924
|
-
* import { allResult,
|
|
925
|
-
*
|
|
926
|
-
* interface LoadNumberError {
|
|
927
|
-
* readonly type: "LoadNumberError";
|
|
928
|
-
* }
|
|
840
|
+
* import { allResult, ok, type Result } from "@evolu/common";
|
|
929
841
|
*
|
|
930
|
-
* const results: ReadonlyArray<Result<number
|
|
931
|
-
* ok(1),
|
|
932
|
-
* err({ type: "LoadNumberError" }),
|
|
933
|
-
* ];
|
|
842
|
+
* const results: ReadonlyArray<Result<number>> = [ok(1), ok(2)];
|
|
934
843
|
* const numbers = allResult(results);
|
|
935
|
-
* expectTypeOf(numbers).toEqualTypeOf<
|
|
936
|
-
*
|
|
937
|
-
* >();
|
|
938
|
-
* expectErr(numbers, { type: "LoadNumberError" });
|
|
844
|
+
* expectTypeOf(numbers).toEqualTypeOf<Result<ReadonlyArray<number>>>();
|
|
845
|
+
* expectOk(numbers, [1, 2]);
|
|
939
846
|
* ```
|
|
940
847
|
*/
|
|
941
848
|
export declare function allResult<T, E>(results: Iterable<Result<T, E>>): Result<ReadonlyArray<T>, E>;
|
|
@@ -961,7 +868,19 @@ export declare function allResult<T, E>(results: Iterable<Result<T, E>>): Result
|
|
|
961
868
|
* ```
|
|
962
869
|
*/
|
|
963
870
|
export declare function allResult<T, E>(results: NonEmptyReadonlyArray<Result<T, E>>): Result<NonEmptyReadonlyArray<T>, E>;
|
|
964
|
-
/**
|
|
871
|
+
/**
|
|
872
|
+
* Does not collect the Ok values from a record of Results.
|
|
873
|
+
*
|
|
874
|
+
* ### Example
|
|
875
|
+
*
|
|
876
|
+
* ```ts
|
|
877
|
+
* import { allResult, ok, type Result } from "@evolu/common";
|
|
878
|
+
*
|
|
879
|
+
* const result = allResult({ a: ok(1), b: ok(2) }, { collect: false });
|
|
880
|
+
* expectTypeOf(result).toEqualTypeOf<Result<void>>();
|
|
881
|
+
* expectOk(result, undefined);
|
|
882
|
+
* ```
|
|
883
|
+
*/
|
|
965
884
|
export declare function allResult<T extends Readonly<Record<string, AnyResult>>>(results: T, options: {
|
|
966
885
|
readonly collect: false;
|
|
967
886
|
}): Result<void, [keyof T] extends [never] ? never : InferErr<T[keyof T]>>;
|
|
@@ -969,25 +888,17 @@ export declare function allResult<T extends Readonly<Record<string, AnyResult>>>
|
|
|
969
888
|
* Does not collect the Ok values from an iterable of Results.
|
|
970
889
|
*
|
|
971
890
|
* Use this overload when the Results already exist and only their collective
|
|
972
|
-
* success or failure matters.
|
|
973
|
-
* use the mapping overload instead.
|
|
891
|
+
* success or failure matters.
|
|
974
892
|
*
|
|
975
893
|
* ### Example
|
|
976
894
|
*
|
|
977
895
|
* ```ts
|
|
978
|
-
* import { allResult,
|
|
979
|
-
*
|
|
980
|
-
* interface SaveError {
|
|
981
|
-
* readonly type: "SaveError";
|
|
982
|
-
* }
|
|
896
|
+
* import { allResult, ok, type Result } from "@evolu/common";
|
|
983
897
|
*
|
|
984
|
-
* const results: ReadonlyArray<Result<number
|
|
985
|
-
* ok(1),
|
|
986
|
-
* err({ type: "SaveError" }),
|
|
987
|
-
* ];
|
|
898
|
+
* const results: ReadonlyArray<Result<number>> = [ok(1), ok(2)];
|
|
988
899
|
* const result = allResult(results, { collect: false });
|
|
989
|
-
* expectTypeOf(result).toEqualTypeOf<Result<void
|
|
990
|
-
*
|
|
900
|
+
* expectTypeOf(result).toEqualTypeOf<Result<void>>();
|
|
901
|
+
* expectOk(result, undefined);
|
|
991
902
|
* ```
|
|
992
903
|
*/
|
|
993
904
|
export declare function allResult<R extends AnyResult>(results: Iterable<R>, options: {
|
|
@@ -1006,35 +917,16 @@ export declare function allResult<R extends AnyResult>(results: Iterable<R>, opt
|
|
|
1006
917
|
* type Result,
|
|
1007
918
|
* } from "@evolu/common";
|
|
1008
919
|
*
|
|
1009
|
-
*
|
|
1010
|
-
*
|
|
1011
|
-
*
|
|
1012
|
-
*
|
|
1013
|
-
* readonly orderId: string;
|
|
1014
|
-
* }
|
|
1015
|
-
* interface CreateInvoiceError {
|
|
1016
|
-
* readonly type: "CreateInvoiceError";
|
|
1017
|
-
* }
|
|
1018
|
-
*
|
|
1019
|
-
* const orders: NonEmptyReadonlyArray<Order> = [{ id: "order-1" }];
|
|
1020
|
-
* const createInvoice = (
|
|
1021
|
-
* order: Order,
|
|
1022
|
-
* ): Result<Invoice, CreateInvoiceError> => ok({ orderId: order.id });
|
|
1023
|
-
*
|
|
1024
|
-
* const invoices = allResult(orders, createInvoice);
|
|
1025
|
-
* expectTypeOf(invoices).toEqualTypeOf<
|
|
1026
|
-
* Result<NonEmptyReadonlyArray<Invoice>, CreateInvoiceError>
|
|
920
|
+
* const numbers: NonEmptyReadonlyArray<number> = [1, 2];
|
|
921
|
+
* const doubled = allResult(numbers, (number) => ok(number * 2));
|
|
922
|
+
* expectTypeOf(doubled).toEqualTypeOf<
|
|
923
|
+
* Result<NonEmptyReadonlyArray<number>>
|
|
1027
924
|
* >();
|
|
1028
|
-
* expectOk(
|
|
925
|
+
* expectOk(doubled, [2, 4]);
|
|
1029
926
|
*
|
|
1030
|
-
* const
|
|
1031
|
-
*
|
|
1032
|
-
*
|
|
1033
|
-
* );
|
|
1034
|
-
* expectTypeOf(invoiceTuple).toEqualTypeOf<
|
|
1035
|
-
* Result<readonly [Invoice, Invoice], CreateInvoiceError>
|
|
1036
|
-
* >();
|
|
1037
|
-
* expectOk(invoiceTuple, [{ orderId: "order-1" }, { orderId: "order-2" }]);
|
|
927
|
+
* const tuple = allResult([1, 2] as const, (number) => ok(number * 2));
|
|
928
|
+
* expectTypeOf(tuple).toEqualTypeOf<Result<readonly [number, number]>>();
|
|
929
|
+
* expectOk(tuple, [2, 4]);
|
|
1038
930
|
* ```
|
|
1039
931
|
*/
|
|
1040
932
|
export declare function allResult<const A extends readonly [unknown, ...Array<unknown>], R extends AnyResult>(values: A, fn: (value: A[number]) => R): Result<{
|
|
@@ -1043,37 +935,15 @@ export declare function allResult<const A extends readonly [unknown, ...Array<un
|
|
|
1043
935
|
/**
|
|
1044
936
|
* Maps a dynamic or generated value collection to Results.
|
|
1045
937
|
*
|
|
1046
|
-
* Stops consuming and mapping the iterable on the first Err.
|
|
1047
|
-
*
|
|
1048
938
|
* ### Example
|
|
1049
939
|
*
|
|
1050
940
|
* ```ts
|
|
1051
|
-
* import { allResult,
|
|
1052
|
-
*
|
|
1053
|
-
* interface ProductNotFoundError {
|
|
1054
|
-
* readonly type: "ProductNotFoundError";
|
|
1055
|
-
* readonly productId: string;
|
|
1056
|
-
* }
|
|
1057
|
-
*
|
|
1058
|
-
* const pricesByProductId = new Map([
|
|
1059
|
-
* ["book", 20],
|
|
1060
|
-
* ["pen", 5],
|
|
1061
|
-
* ]);
|
|
1062
|
-
* const getPrice = (
|
|
1063
|
-
* productId: string,
|
|
1064
|
-
* ): Result<number, ProductNotFoundError> => {
|
|
1065
|
-
* const price = pricesByProductId.get(productId);
|
|
1066
|
-
* return price == null
|
|
1067
|
-
* ? err({ type: "ProductNotFoundError", productId })
|
|
1068
|
-
* : ok(price);
|
|
1069
|
-
* };
|
|
941
|
+
* import { allResult, ok, type Result } from "@evolu/common";
|
|
1070
942
|
*
|
|
1071
|
-
* const
|
|
1072
|
-
* const
|
|
1073
|
-
* expectTypeOf(
|
|
1074
|
-
*
|
|
1075
|
-
* >();
|
|
1076
|
-
* expectOk(prices, [20, 5]);
|
|
943
|
+
* const numbers: Iterable<number> = new Set([1, 2]);
|
|
944
|
+
* const doubled = allResult(numbers, (number) => ok(number * 2));
|
|
945
|
+
* expectTypeOf(doubled).toEqualTypeOf<Result<ReadonlyArray<number>>>();
|
|
946
|
+
* expectOk(doubled, [2, 4]);
|
|
1077
947
|
* ```
|
|
1078
948
|
*/
|
|
1079
949
|
export declare function allResult<A, R extends AnyResult>(values: Iterable<A>, fn: (value: A) => R): Result<ReadonlyArray<InferOk<R>>, InferErr<R>>;
|
|
@@ -1083,32 +953,18 @@ export declare function allResult<A, R extends AnyResult>(values: Iterable<A>, f
|
|
|
1083
953
|
* ### Example
|
|
1084
954
|
*
|
|
1085
955
|
* ```ts
|
|
1086
|
-
* import { allResult,
|
|
956
|
+
* import { allResult, ok, type Result } from "@evolu/common";
|
|
1087
957
|
*
|
|
1088
958
|
* interface User {
|
|
1089
959
|
* readonly id: string;
|
|
1090
960
|
* }
|
|
1091
961
|
*
|
|
1092
|
-
*
|
|
1093
|
-
* readonly type: "UserNotFoundError";
|
|
1094
|
-
* readonly userId: string;
|
|
1095
|
-
* }
|
|
1096
|
-
*
|
|
1097
|
-
* const usersById = new Map<string, User>([
|
|
1098
|
-
* ["user-1", { id: "user-1" }],
|
|
1099
|
-
* ["user-2", { id: "user-2" }],
|
|
1100
|
-
* ]);
|
|
1101
|
-
* const findUser = (userId: string): Result<User, UserNotFoundError> => {
|
|
1102
|
-
* const user = usersById.get(userId);
|
|
1103
|
-
* return user == null
|
|
1104
|
-
* ? err({ type: "UserNotFoundError", userId })
|
|
1105
|
-
* : ok(user);
|
|
1106
|
-
* };
|
|
962
|
+
* const toUser = (id: string): Result<User> => ok({ id });
|
|
1107
963
|
*
|
|
1108
964
|
* const userIdsByRole = { owner: "user-1", reviewer: "user-2" } as const;
|
|
1109
|
-
* const usersByRole = allResult(userIdsByRole,
|
|
965
|
+
* const usersByRole = allResult(userIdsByRole, toUser);
|
|
1110
966
|
* expectTypeOf(usersByRole).toEqualTypeOf<
|
|
1111
|
-
* Result<Readonly<Record<"owner" | "reviewer", User
|
|
967
|
+
* Result<Readonly<Record<"owner" | "reviewer", User>>>
|
|
1112
968
|
* >();
|
|
1113
969
|
* expectOk(usersByRole, {
|
|
1114
970
|
* owner: { id: "user-1" },
|
|
@@ -1128,28 +984,40 @@ export declare function allResult<A, R extends AnyResult, K extends string>(valu
|
|
|
1128
984
|
* ```ts
|
|
1129
985
|
* import { allResult, ok, type Result } from "@evolu/common";
|
|
1130
986
|
*
|
|
1131
|
-
*
|
|
1132
|
-
*
|
|
1133
|
-
*
|
|
987
|
+
* const visited: Array<number> = [];
|
|
988
|
+
* const result = allResult(
|
|
989
|
+
* [1, 2],
|
|
990
|
+
* (number) => {
|
|
991
|
+
* visited.push(number);
|
|
992
|
+
* return ok(number * 2);
|
|
993
|
+
* },
|
|
994
|
+
* {
|
|
995
|
+
* collect: false,
|
|
996
|
+
* },
|
|
997
|
+
* );
|
|
998
|
+
* expectTypeOf(result).toEqualTypeOf<Result<void>>();
|
|
999
|
+
* expectOk(result, undefined);
|
|
1000
|
+
* expect(visited).toEqual([1, 2]);
|
|
1001
|
+
* ```
|
|
1002
|
+
*/
|
|
1003
|
+
export declare function allResult<A, R extends AnyResult>(values: Iterable<A>, fn: (value: A) => R, options: {
|
|
1004
|
+
readonly collect: false;
|
|
1005
|
+
}): Result<void, InferErr<R>>;
|
|
1006
|
+
/**
|
|
1007
|
+
* Maps record values to Results without collecting their Ok values.
|
|
1134
1008
|
*
|
|
1135
|
-
*
|
|
1136
|
-
* const updateItem = (itemId: string): Result<number, UpdateItemError> => {
|
|
1137
|
-
* updatedItemIds.push(itemId);
|
|
1138
|
-
* return ok(1);
|
|
1139
|
-
* };
|
|
1009
|
+
* ### Example
|
|
1140
1010
|
*
|
|
1141
|
-
*
|
|
1011
|
+
* ```ts
|
|
1012
|
+
* import { allResult, ok, type Result } from "@evolu/common";
|
|
1013
|
+
*
|
|
1014
|
+
* const result = allResult({ a: 1, b: 2 }, (number) => ok(number * 2), {
|
|
1142
1015
|
* collect: false,
|
|
1143
1016
|
* });
|
|
1144
|
-
* expectTypeOf(result).toEqualTypeOf<Result<void
|
|
1017
|
+
* expectTypeOf(result).toEqualTypeOf<Result<void>>();
|
|
1145
1018
|
* expectOk(result, undefined);
|
|
1146
|
-
* expect(updatedItemIds).toEqual(["item-1", "item-2"]);
|
|
1147
1019
|
* ```
|
|
1148
1020
|
*/
|
|
1149
|
-
export declare function allResult<A, R extends AnyResult>(values: Iterable<A>, fn: (value: A) => R, options: {
|
|
1150
|
-
readonly collect: false;
|
|
1151
|
-
}): Result<void, InferErr<R>>;
|
|
1152
|
-
/** Maps record values to Results without collecting their Ok values. */
|
|
1153
1021
|
export declare function allResult<A, R extends AnyResult, K extends string>(values: Readonly<Record<K, A>>, fn: (value: A) => R, options: {
|
|
1154
1022
|
readonly collect: false;
|
|
1155
1023
|
}): Result<void, InferErr<R>>;
|
|
@@ -1167,52 +1035,19 @@ export declare function allResult<A, R extends AnyResult, K extends string>(valu
|
|
|
1167
1035
|
* import {
|
|
1168
1036
|
* anyResult,
|
|
1169
1037
|
* err,
|
|
1170
|
-
* isNonEmptyArray,
|
|
1171
1038
|
* ok,
|
|
1172
1039
|
* type Result,
|
|
1040
|
+
* type Typed,
|
|
1173
1041
|
* } from "@evolu/common";
|
|
1174
1042
|
*
|
|
1175
|
-
*
|
|
1176
|
-
*
|
|
1177
|
-
* }
|
|
1178
|
-
*
|
|
1179
|
-
* const results: ReadonlyArray<Result<number, LookupError>> = [
|
|
1180
|
-
* err({ type: "LookupError" }),
|
|
1181
|
-
* ok(42),
|
|
1182
|
-
* ];
|
|
1183
|
-
* if (isNonEmptyArray(results)) {
|
|
1184
|
-
* const number = anyResult(results);
|
|
1185
|
-
* expectTypeOf(number).toEqualTypeOf<Result<number, LookupError>>();
|
|
1186
|
-
* expectOk(number, 42);
|
|
1187
|
-
* }
|
|
1188
|
-
* ```
|
|
1189
|
-
*
|
|
1190
|
-
* A statically non-empty tuple needs no guard:
|
|
1191
|
-
*
|
|
1192
|
-
* ```ts
|
|
1193
|
-
* import { anyResult, err, ok, type Result } from "@evolu/common";
|
|
1194
|
-
*
|
|
1195
|
-
* interface LookupError {
|
|
1196
|
-
* readonly type: "LookupError";
|
|
1197
|
-
* readonly source: string;
|
|
1198
|
-
* }
|
|
1043
|
+
* const getCachedPrice = (): Result<number, CacheMissError> =>
|
|
1044
|
+
* err({ type: "CacheMiss" });
|
|
1199
1045
|
*
|
|
1200
|
-
*
|
|
1201
|
-
* err({ type: "LookupError", source });
|
|
1046
|
+
* interface CacheMissError extends Typed<"CacheMiss"> {}
|
|
1202
1047
|
*
|
|
1203
|
-
* const
|
|
1204
|
-
*
|
|
1205
|
-
* ok(42),
|
|
1206
|
-
* lookupError("second"),
|
|
1207
|
-
* ] as const;
|
|
1208
|
-
* const number = anyResult(results);
|
|
1209
|
-
* expectTypeOf(number).toEqualTypeOf<Result<number, LookupError>>();
|
|
1048
|
+
* const number = anyResult([getCachedPrice(), ok(42)]);
|
|
1049
|
+
* expectTypeOf(number).toEqualTypeOf<Result<number, CacheMissError>>();
|
|
1210
1050
|
* expectOk(number, 42);
|
|
1211
|
-
*
|
|
1212
|
-
* const allFailed = [lookupError("a"), lookupError("b")] as const;
|
|
1213
|
-
* const result = anyResult(allFailed);
|
|
1214
|
-
* expectTypeOf(result).toEqualTypeOf<Result<never, LookupError>>();
|
|
1215
|
-
* expectErr(result, { type: "LookupError", source: "b" });
|
|
1216
1051
|
* ```
|
|
1217
1052
|
*
|
|
1218
1053
|
* @group Composition
|