@evolu/common 6.0.1-preview.3 → 6.0.1-preview.31
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/Array.d.ts +69 -5
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +64 -5
- package/dist/src/Assert.d.ts +6 -16
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +6 -18
- package/dist/src/Brand.d.ts +75 -0
- package/dist/src/Brand.d.ts.map +1 -0
- package/dist/src/Brand.js +1 -0
- package/dist/src/Buffer.d.ts +1 -1
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Buffer.js +8 -7
- package/dist/src/Cache.d.ts +44 -0
- package/dist/src/Cache.d.ts.map +1 -0
- package/dist/src/Cache.js +52 -0
- package/dist/src/Callbacks.d.ts +45 -12
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Callbacks.js +14 -7
- package/dist/src/Console.d.ts +31 -6
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +72 -9
- package/dist/src/Crypto.d.ts +61 -34
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +32 -45
- package/dist/src/Evolu/Db.d.ts +161 -65
- package/dist/src/Evolu/Db.d.ts.map +1 -1
- package/dist/src/Evolu/Db.js +286 -694
- package/dist/src/Evolu/Diff.d.ts +3 -3
- package/dist/src/Evolu/Diff.d.ts.map +1 -1
- package/dist/src/Evolu/Diff.js +7 -5
- package/dist/src/Evolu/Evolu.d.ts +208 -133
- package/dist/src/Evolu/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu/Evolu.js +188 -183
- package/dist/src/Evolu/Internal.d.ts +0 -2
- package/dist/src/Evolu/Internal.d.ts.map +1 -1
- package/dist/src/Evolu/Internal.js +0 -2
- package/dist/src/Evolu/LocalAuth.d.ts +150 -0
- package/dist/src/Evolu/LocalAuth.d.ts.map +1 -0
- package/dist/src/Evolu/LocalAuth.js +174 -0
- package/dist/src/Evolu/Owner.d.ts +273 -120
- package/dist/src/Evolu/Owner.d.ts.map +1 -1
- package/dist/src/Evolu/Owner.js +130 -104
- package/dist/src/Evolu/Platform.d.ts +9 -7
- package/dist/src/Evolu/Platform.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.d.ts +277 -232
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +603 -378
- package/dist/src/Evolu/Public.d.ts +6 -8
- package/dist/src/Evolu/Public.d.ts.map +1 -1
- package/dist/src/Evolu/Public.js +2 -3
- package/dist/src/Evolu/PublicKysely.js +3 -3
- package/dist/src/Evolu/Query.d.ts +2 -1
- package/dist/src/Evolu/Query.d.ts.map +1 -1
- package/dist/src/Evolu/Relay.d.ts +92 -7
- package/dist/src/Evolu/Relay.d.ts.map +1 -1
- package/dist/src/Evolu/Relay.js +238 -76
- package/dist/src/Evolu/Schema.d.ts +129 -73
- package/dist/src/Evolu/Schema.d.ts.map +1 -1
- package/dist/src/Evolu/Schema.js +169 -89
- package/dist/src/Evolu/Storage.d.ts +240 -26
- package/dist/src/Evolu/Storage.d.ts.map +1 -1
- package/dist/src/Evolu/Storage.js +189 -91
- package/dist/src/Evolu/Sync.d.ts +67 -13
- package/dist/src/Evolu/Sync.d.ts.map +1 -1
- package/dist/src/Evolu/Sync.js +441 -20
- package/dist/src/Evolu/Timestamp.d.ts +85 -27
- package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
- package/dist/src/Evolu/Timestamp.js +77 -18
- package/dist/src/Identicon.d.ts +35 -0
- package/dist/src/Identicon.d.ts.map +1 -0
- package/dist/src/Identicon.js +143 -0
- package/dist/src/Instances.d.ts +34 -0
- package/dist/src/Instances.d.ts.map +1 -0
- package/dist/src/Instances.js +44 -0
- package/dist/src/ManyToManyMap.d.ts +71 -10
- package/dist/src/ManyToManyMap.d.ts.map +1 -1
- package/dist/src/ManyToManyMap.js +41 -6
- package/dist/src/Number.d.ts +4 -3
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +5 -4
- package/dist/src/Platform.d.ts +20 -0
- package/dist/src/Platform.d.ts.map +1 -0
- package/dist/src/Platform.js +22 -0
- package/dist/src/Random.d.ts +3 -2
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Resources.d.ts +118 -0
- package/dist/src/Resources.d.ts.map +1 -0
- package/dist/src/Resources.js +197 -0
- package/dist/src/Result.d.ts +184 -52
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +30 -241
- package/dist/src/Skiplist.js +2 -1
- package/dist/src/Sqlite.d.ts +63 -5
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +110 -9
- package/dist/src/Task.d.ts +586 -0
- package/dist/src/Task.d.ts.map +1 -0
- package/dist/src/Task.js +469 -0
- package/dist/src/Time.d.ts +66 -1
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +99 -5
- package/dist/src/Type.d.ts +622 -340
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +666 -464
- package/dist/src/Types.d.ts +1 -75
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/WebSocket.d.ts +5 -2
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +12 -18
- package/dist/src/Worker.d.ts +39 -11
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +22 -4
- package/dist/src/index.d.ts +7 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -2
- package/package.json +14 -13
- package/src/Array.ts +90 -11
- package/src/Assert.ts +6 -24
- package/src/Brand.ts +75 -0
- package/src/Buffer.ts +7 -7
- package/src/Cache.ts +85 -0
- package/src/Callbacks.ts +62 -22
- package/src/Console.ts +91 -11
- package/src/Crypto.ts +97 -82
- package/src/Evolu/Db.ts +517 -1020
- package/src/Evolu/Diff.ts +7 -5
- package/src/Evolu/Evolu.ts +464 -355
- package/src/Evolu/Internal.ts +0 -2
- package/src/Evolu/LocalAuth.ts +463 -0
- package/src/Evolu/Owner.ts +355 -228
- package/src/Evolu/Platform.ts +9 -9
- package/src/Evolu/Protocol.ts +859 -676
- package/src/Evolu/Public.ts +7 -14
- package/src/Evolu/PublicKysely.ts +3 -3
- package/src/Evolu/Query.ts +2 -1
- package/src/Evolu/Relay.ts +437 -93
- package/src/Evolu/Schema.ts +391 -191
- package/src/Evolu/Storage.ts +532 -135
- package/src/Evolu/Sync.ts +766 -37
- package/src/Evolu/Timestamp.ts +88 -35
- package/src/Identicon.ts +197 -0
- package/src/Instances.ts +90 -0
- package/src/ManyToManyMap.ts +124 -24
- package/src/Number.ts +6 -10
- package/src/Platform.ts +26 -0
- package/src/Random.ts +3 -2
- package/src/Resources.ts +367 -0
- package/src/Result.ts +191 -54
- package/src/Skiplist.ts +1 -1
- package/src/Sqlite.ts +122 -17
- package/src/Task.ts +901 -0
- package/src/Time.ts +180 -5
- package/src/Type.ts +1084 -727
- package/src/Types.ts +1 -77
- package/src/WebSocket.ts +27 -25
- package/src/Worker.ts +72 -23
- package/src/index.ts +7 -2
- package/dist/src/Evolu/Config.d.ts +0 -69
- package/dist/src/Evolu/Config.d.ts.map +0 -1
- package/dist/src/Evolu/Config.js +0 -9
- package/dist/src/Evolu/Kysely.d.ts +0 -6
- package/dist/src/Evolu/Kysely.d.ts.map +0 -1
- package/dist/src/Evolu/Kysely.js +0 -21
- package/dist/src/NanoId.d.ts +0 -27
- package/dist/src/NanoId.d.ts.map +0 -1
- package/dist/src/NanoId.js +0 -6
- package/dist/src/Promise.d.ts +0 -180
- package/dist/src/Promise.d.ts.map +0 -1
- package/dist/src/Promise.js +0 -176
- package/src/Evolu/Config.ts +0 -83
- package/src/Evolu/Kysely.ts +0 -38
- package/src/NanoId.ts +0 -39
- package/src/Promise.ts +0 -295
package/dist/src/Result.d.ts
CHANGED
|
@@ -1,36 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 🛡️ Type-safe errors
|
|
3
3
|
*
|
|
4
|
-
* ## Intro
|
|
5
|
-
*
|
|
6
4
|
* The problem with throwing an exception in JavaScript is that the caught error
|
|
7
5
|
* is always of an unknown type. The unknown type is a problem because we can't
|
|
8
6
|
* be sure all errors have been handled because the TypeScript compiler can't
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* Some other languages like Rust 🦀 or Haskell 📚 use a type-safe approach to
|
|
12
|
-
* error handling, where errors are explicitly represented as part of the return
|
|
13
|
-
* type, such as Result or Either, allowing the developer to handle all errors
|
|
14
|
-
* safely. ✅
|
|
15
|
-
*
|
|
16
|
-
* ✨ Evolu uses {@link Result}, and it looks like this:
|
|
7
|
+
* tell us.
|
|
17
8
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* readonly ok: true;
|
|
23
|
-
* readonly value: T;
|
|
24
|
-
* }
|
|
9
|
+
* Languages like Rust 🦀 or Haskell 📚 use a type-safe approach to error
|
|
10
|
+
* handling, where errors are explicitly represented as part of the return type,
|
|
11
|
+
* such as Result or Either, allowing the developer to handle errors safely.
|
|
12
|
+
* TypeScript can have this too via the `Result` type.
|
|
25
13
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* }
|
|
30
|
-
*
|
|
31
|
-
* const ok = <T>(value: T): Ok<T> => ({ ok: true, value });
|
|
32
|
-
* const err = <E>(error: E): Err<E> => ({ ok: false, error });
|
|
33
|
-
* ```
|
|
14
|
+
* The `Result` type can be either {@link Ok} (success) or {@link Err} (error).
|
|
15
|
+
* Use {@link ok} to create a successful result and {@link err} to create an error
|
|
16
|
+
* result.
|
|
34
17
|
*
|
|
35
18
|
* Now let's look at how `Result` can be used for safe JSON parsing:
|
|
36
19
|
*
|
|
@@ -77,12 +60,10 @@
|
|
|
77
60
|
*
|
|
78
61
|
* Let's summarize it:
|
|
79
62
|
*
|
|
80
|
-
* - For
|
|
63
|
+
* - For safe code, use `ok` and `err`.
|
|
81
64
|
* - For unsafe code, use `trySync` or `tryAsync`.
|
|
82
|
-
* - For asynchronous safe code, use `Promise` with {@link Result}.
|
|
83
65
|
*
|
|
84
|
-
* Asynchronous safe (because of a Promise using Result) code
|
|
85
|
-
* straightforward:
|
|
66
|
+
* Asynchronous safe (because of a Promise using Result) code:
|
|
86
67
|
*
|
|
87
68
|
* ```ts
|
|
88
69
|
* const fetchUser = async (
|
|
@@ -103,9 +84,40 @@
|
|
|
103
84
|
* };
|
|
104
85
|
* ```
|
|
105
86
|
*
|
|
106
|
-
*
|
|
87
|
+
* ### Naming Convention
|
|
88
|
+
*
|
|
89
|
+
* - For values: `const user = getUser()`
|
|
90
|
+
* - For a single void operation: `const result = foo()`
|
|
91
|
+
* - For multiple void operations: use descriptive names for all
|
|
92
|
+
*
|
|
93
|
+
* ```ts
|
|
94
|
+
* const processUser = () => {
|
|
95
|
+
* // we have a value
|
|
96
|
+
* const user = getUser();
|
|
97
|
+
* if (!user.ok) return user;
|
|
98
|
+
*
|
|
99
|
+
* // single void operation
|
|
100
|
+
* const result = saveToDatabase(user.value);
|
|
101
|
+
* if (!result.ok) return result;
|
|
102
|
+
*
|
|
103
|
+
* return ok();
|
|
104
|
+
* };
|
|
105
|
+
*
|
|
106
|
+
* const setupDatabase = () => {
|
|
107
|
+
* // multiple void operations - use descriptive names
|
|
108
|
+
* const baseTables = createBaseTables();
|
|
109
|
+
* if (!baseTables.ok) return baseTables;
|
|
110
|
+
*
|
|
111
|
+
* const relayTables = createRelayTables();
|
|
112
|
+
* if (!relayTables.ok) return relayTables;
|
|
113
|
+
*
|
|
114
|
+
* return ok();
|
|
115
|
+
* };
|
|
116
|
+
* ```
|
|
117
|
+
*
|
|
118
|
+
* ### Examples
|
|
107
119
|
*
|
|
108
|
-
*
|
|
120
|
+
* #### Sequential Operations with Short-Circuiting
|
|
109
121
|
*
|
|
110
122
|
* When performing a sequence of operations where any failure should stop
|
|
111
123
|
* further processing, use the `Result` type with early returns.
|
|
@@ -155,37 +167,127 @@
|
|
|
155
167
|
* This approach ensures type-safe error handling, avoids nested try/catch
|
|
156
168
|
* blocks, and clearly communicates the control flow.
|
|
157
169
|
*
|
|
158
|
-
*
|
|
170
|
+
* #### A function with two different errors:
|
|
159
171
|
*
|
|
160
172
|
* ```ts
|
|
161
173
|
* const example = (value: string): Result<number, FooError | BarError> => {
|
|
162
174
|
* const foo = getFoo(value);
|
|
163
175
|
* if (!foo.ok) return foo;
|
|
164
176
|
*
|
|
165
|
-
* const bar =
|
|
177
|
+
* const bar = getBar(foo.value);
|
|
166
178
|
* if (!bar.ok) return bar;
|
|
167
179
|
*
|
|
168
180
|
* return ok(barToNumber(bar.value));
|
|
169
181
|
* };
|
|
170
182
|
* ```
|
|
171
183
|
*
|
|
172
|
-
*
|
|
184
|
+
* ### Handling Unexpected Errors
|
|
185
|
+
*
|
|
186
|
+
* Even with disciplined use of `trySync` and `tryAsync`, unexpected errors can
|
|
187
|
+
* still occur due to programming mistakes, third-party library bugs, or edge
|
|
188
|
+
* cases. These should be logged for debugging, but **unexpected errors are not
|
|
189
|
+
* recoverable** - they represent bugs that must be fixed.
|
|
190
|
+
*
|
|
191
|
+
* **Important**: "Graceful shutdown" and error recovery can only come from
|
|
192
|
+
* expected errors handled via the `Result` type. Unexpected errors should fail
|
|
193
|
+
* fast - the operation fails immediately and the error bubbles up.
|
|
194
|
+
*
|
|
195
|
+
* #### In Browser Environments
|
|
196
|
+
*
|
|
197
|
+
* ```ts
|
|
198
|
+
* // Global error handler for unexpected errors
|
|
199
|
+
* window.addEventListener("error", (event) => {
|
|
200
|
+
* console.error("Uncaught error:", event.error);
|
|
201
|
+
* // Send to error reporting service
|
|
202
|
+
* errorReportingService.report(event.error);
|
|
203
|
+
* });
|
|
204
|
+
*
|
|
205
|
+
* // For unhandled promise rejections
|
|
206
|
+
* window.addEventListener("unhandledrejection", (event) => {
|
|
207
|
+
* console.error("Unhandled promise rejection:", event.reason);
|
|
208
|
+
* errorReportingService.report(event.reason);
|
|
209
|
+
* });
|
|
210
|
+
* ```
|
|
211
|
+
*
|
|
212
|
+
* #### In Node.js Environments
|
|
213
|
+
*
|
|
214
|
+
* ```ts
|
|
215
|
+
* // Handle uncaught exceptions - log and fail fast
|
|
216
|
+
* process.on("uncaughtException", (error) => {
|
|
217
|
+
* console.error("Uncaught exception:", error);
|
|
218
|
+
* errorReportingService.report(error);
|
|
219
|
+
* // Exit immediately - unexpected errors are not recoverable
|
|
220
|
+
* process.exit(1);
|
|
221
|
+
* });
|
|
222
|
+
*
|
|
223
|
+
* // Handle unhandled promise rejections
|
|
224
|
+
* process.on("unhandledRejection", (reason) => {
|
|
225
|
+
* console.error("Unhandled promise rejection:", reason);
|
|
226
|
+
* errorReportingService.report(reason);
|
|
227
|
+
* });
|
|
228
|
+
* ```
|
|
229
|
+
*
|
|
230
|
+
* These global handlers serve as a safety net to log and report unexpected
|
|
231
|
+
* errors for debugging purposes. They do not attempt recovery - unexpected
|
|
232
|
+
* errors represent bugs that must be fixed. The discipline of explicit error
|
|
233
|
+
* handling through the `Result` pattern remains the primary approach for all
|
|
234
|
+
* recoverable scenarios.
|
|
173
235
|
*
|
|
174
|
-
* ###
|
|
236
|
+
* ### FAQ
|
|
237
|
+
*
|
|
238
|
+
* #### When should a function return a plain value instead of `Result<T, E>`?
|
|
239
|
+
*
|
|
240
|
+
* Use `Result<T, E>` only when a function can fail with **known, expected
|
|
241
|
+
* errors** that callers need to handle. If a function cannot fail with a known
|
|
242
|
+
* error, return the value directly.
|
|
243
|
+
*
|
|
244
|
+
* - ✅ Return `Result<User, UserNotFoundError>` - can fail with a known error
|
|
245
|
+
* - ✅ Return `User` - cannot fail with a known error
|
|
246
|
+
* - ❌ Don't return `Result<User, never>` - unnecessary wrapper
|
|
247
|
+
*
|
|
248
|
+
* This keeps the codebase clean and makes error handling intentional. The type
|
|
249
|
+
* system communicates which operations can fail and which cannot.
|
|
250
|
+
*
|
|
251
|
+
* Unsafe code from external libraries (not under our control) should be wrapped
|
|
252
|
+
* with `trySync` or `tryAsync` at the boundaries. Once wrapped, if the error is
|
|
253
|
+
* not important to callers, functions can safely return plain values. If the
|
|
254
|
+
* error matters, use `Result` with a typed error.
|
|
255
|
+
*
|
|
256
|
+
* ```ts
|
|
257
|
+
* // ✅ Safe to return void - unsafe code is wrapped and error is handled
|
|
258
|
+
* const processData = (data: string): void => {
|
|
259
|
+
* const parseResult = trySync(
|
|
260
|
+
* () => JSON.parse(data),
|
|
261
|
+
* (error) => ({ type: "ParseError", message: String(error) }),
|
|
262
|
+
* );
|
|
263
|
+
*
|
|
264
|
+
* if (!parseResult.ok) {
|
|
265
|
+
* logError(parseResult.error);
|
|
266
|
+
* return;
|
|
267
|
+
* }
|
|
268
|
+
*
|
|
269
|
+
* // Continue with safe operations...
|
|
270
|
+
* };
|
|
271
|
+
*
|
|
272
|
+
* // ✅ Can call without try-catch since it returns void
|
|
273
|
+
* processData(jsonString);
|
|
274
|
+
* ```
|
|
275
|
+
*
|
|
276
|
+
* #### What if my function doesn't return a value on success?
|
|
175
277
|
*
|
|
176
278
|
* If your function performs an operation but doesn't need to return a value on
|
|
177
279
|
* success, you can use `Result<void, E>`. Using `Result<void, E>` is clearer
|
|
178
280
|
* than using `Result<true, E>` or `Result<null, E>` because it communicates
|
|
179
281
|
* that the function doesn't produce a value but can produce errors.
|
|
180
282
|
*
|
|
181
|
-
*
|
|
283
|
+
* #### How do I short-circuit processing of an array on the first error?
|
|
182
284
|
*
|
|
183
285
|
* If you want to stop processing as soon as an error occurs (short-circuit),
|
|
184
286
|
* you should produce and check each `Result` inside a loop:
|
|
185
287
|
*
|
|
186
288
|
* ```ts
|
|
187
289
|
* for (const query of [
|
|
188
|
-
* sql`drop table
|
|
290
|
+
* sql`drop table evolu_config;`,
|
|
189
291
|
* sql`drop table evolu_message;`,
|
|
190
292
|
* ]) {
|
|
191
293
|
* const result = deps.sqlite.exec(query);
|
|
@@ -194,12 +296,11 @@
|
|
|
194
296
|
* // All queries succeeded
|
|
195
297
|
* ```
|
|
196
298
|
*
|
|
197
|
-
*
|
|
299
|
+
* #### How do I handle an array of operations and short-circuit on the first error?
|
|
198
300
|
*
|
|
199
301
|
* If you have an array of operations (not results), you should make them
|
|
200
|
-
* _lazy_—that is, represent each operation as a function
|
|
201
|
-
*
|
|
202
|
-
* stop on the first error:
|
|
302
|
+
* _lazy_—that is, represent each operation as a function. This way, you only
|
|
303
|
+
* execute each operation as needed, and can stop on the first error:
|
|
203
304
|
*
|
|
204
305
|
* ```ts
|
|
205
306
|
* import type { LazyValue } from "./Function";
|
|
@@ -232,16 +333,7 @@
|
|
|
232
333
|
* above) over monadic helpers. Imperative code is generally more readable,
|
|
233
334
|
* easier to debug, and more familiar to most JavaScript and TypeScript
|
|
234
335
|
* developers. While monads and functional helpers can be powerful, they often
|
|
235
|
-
* obscure control flow and make debugging harder.
|
|
236
|
-
* handling explicit and straightforward.
|
|
237
|
-
*
|
|
238
|
-
* @module
|
|
239
|
-
*/
|
|
240
|
-
/**
|
|
241
|
-
* A `Result` can be either {@link Ok} (success) or {@link Err} (error).
|
|
242
|
-
*
|
|
243
|
-
* Use {@link ok} to create a successful result and {@link err} to create an error
|
|
244
|
-
* result.
|
|
336
|
+
* obscure control flow and make debugging harder.
|
|
245
337
|
*/
|
|
246
338
|
export type Result<T, E> = Ok<T> | Err<E>;
|
|
247
339
|
/** A successful {@link Result}. */
|
|
@@ -297,6 +389,18 @@ export interface Err<E> {
|
|
|
297
389
|
readonly ok: false;
|
|
298
390
|
readonly error: E;
|
|
299
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Extracts the value type from a {@link Result}.
|
|
394
|
+
*
|
|
395
|
+
* @category Utilities
|
|
396
|
+
*/
|
|
397
|
+
export type InferOk<R extends Result<any, any>> = R extends Ok<infer T> ? T : never;
|
|
398
|
+
/**
|
|
399
|
+
* Extracts the error type from a {@link Result}.
|
|
400
|
+
*
|
|
401
|
+
* @category Utilities
|
|
402
|
+
*/
|
|
403
|
+
export type InferErr<R extends Result<any, any>> = R extends Err<infer E> ? E : never;
|
|
300
404
|
/**
|
|
301
405
|
* Creates an {@link Ok} result.
|
|
302
406
|
*
|
|
@@ -346,8 +450,33 @@ export declare const err: <E>(error: E) => Err<E>;
|
|
|
346
450
|
* const config = getOrThrow(loadConfig());
|
|
347
451
|
* // Safe to use config here
|
|
348
452
|
* ```
|
|
453
|
+
*
|
|
454
|
+
* Throws: `Error` with the original error attached as `cause`.
|
|
349
455
|
*/
|
|
350
456
|
export declare const getOrThrow: <T, E>(result: Result<T, E>) => T;
|
|
457
|
+
/**
|
|
458
|
+
* Extracts the value from a {@link Result} if it is an `Ok`, or returns `null`
|
|
459
|
+
* if it is an `Err`.
|
|
460
|
+
*
|
|
461
|
+
* **Intended usage:**
|
|
462
|
+
*
|
|
463
|
+
* - When you need to convert a `Result` to a nullable value for APIs that expect
|
|
464
|
+
* `T | null`.
|
|
465
|
+
* - When the error is not important and you just want the value or nothing.
|
|
466
|
+
*
|
|
467
|
+
* ### Example
|
|
468
|
+
*
|
|
469
|
+
* ```ts
|
|
470
|
+
* const parseResult = parseJson('{"key": "value"}');
|
|
471
|
+
* const value = getOrNull(parseResult);
|
|
472
|
+
* // value is unknown | null
|
|
473
|
+
*
|
|
474
|
+
* if (value != null) {
|
|
475
|
+
* console.log("Parsed value:", value);
|
|
476
|
+
* }
|
|
477
|
+
* ```
|
|
478
|
+
*/
|
|
479
|
+
export declare const getOrNull: <T, E>(result: Result<T, E>) => T | null;
|
|
351
480
|
/**
|
|
352
481
|
* Wraps synchronous functions that may throw exceptions, returning a
|
|
353
482
|
* {@link Result}.
|
|
@@ -368,7 +497,10 @@ export declare const getOrThrow: <T, E>(result: Result<T, E>) => T;
|
|
|
368
497
|
* const parseJson = (value: string): Result<unknown, ParseJsonError> =>
|
|
369
498
|
* trySync(
|
|
370
499
|
* () => JSON.parse(value) as unknown,
|
|
371
|
-
* (error)
|
|
500
|
+
* (error): ParseJsonError => ({
|
|
501
|
+
* type: "ParseJsonError",
|
|
502
|
+
* message: String(error),
|
|
503
|
+
* }),
|
|
372
504
|
* );
|
|
373
505
|
* ```
|
|
374
506
|
*/
|
package/dist/src/Result.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Result.d.ts","sourceRoot":"","sources":["../../src/Result.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"Result.d.ts","sourceRoot":"","sources":["../../src/Result.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgVG;AACH,MAAM,MAAM,MAAM,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;AAE1C,mCAAmC;AACnC,MAAM,WAAW,EAAE,CAAC,CAAC;IACnB,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,MAAM,WAAW,GAAG,CAAC,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,MAAM,OAAO,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAC5C,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAEpC;;;;GAIG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAC7C,CAAC,SAAS,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAErC;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC;AAC/B,2DAA2D;AAC3D,wBAAgB,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;AAKvC;;;;;;;;;GASG;AACH,eAAO,MAAM,GAAG,GAAI,CAAC,EAAE,OAAO,CAAC,KAAG,GAAG,CAAC,CAAC,CAA2B,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,KAAG,CAMvD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,EAAE,CAAC,EAAE,QAAQ,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,KAAG,CAAC,GAAG,IAC1B,CAAC;AAElC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,EAAE,CAAC,EAC1B,IAAI,MAAM,CAAC,EACX,UAAU,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC,KAC9B,MAAM,CAAC,CAAC,EAAE,CAAC,CAMb,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,eAAO,MAAM,QAAQ,GAAU,CAAC,EAAE,CAAC,EACjC,WAAW,MAAM,OAAO,CAAC,CAAC,CAAC,EAC3B,UAAU,CAAC,KAAK,EAAE,OAAO,KAAK,CAAC,KAC9B,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAIpB,CAAC"}
|
package/dist/src/Result.js
CHANGED
|
@@ -1,242 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🛡️ Type-safe errors
|
|
3
|
-
*
|
|
4
|
-
* ## Intro
|
|
5
|
-
*
|
|
6
|
-
* The problem with throwing an exception in JavaScript is that the caught error
|
|
7
|
-
* is always of an unknown type. The unknown type is a problem because we can't
|
|
8
|
-
* be sure all errors have been handled because the TypeScript compiler can't
|
|
9
|
-
* help us.
|
|
10
|
-
*
|
|
11
|
-
* Some other languages like Rust 🦀 or Haskell 📚 use a type-safe approach to
|
|
12
|
-
* error handling, where errors are explicitly represented as part of the return
|
|
13
|
-
* type, such as Result or Either, allowing the developer to handle all errors
|
|
14
|
-
* safely. ✅
|
|
15
|
-
*
|
|
16
|
-
* ✨ Evolu uses {@link Result}, and it looks like this:
|
|
17
|
-
*
|
|
18
|
-
* ```ts
|
|
19
|
-
* type Result<T, E> = Ok<T> | Err<E>;
|
|
20
|
-
*
|
|
21
|
-
* interface Ok<T> {
|
|
22
|
-
* readonly ok: true;
|
|
23
|
-
* readonly value: T;
|
|
24
|
-
* }
|
|
25
|
-
*
|
|
26
|
-
* interface Err<E> {
|
|
27
|
-
* readonly ok: false;
|
|
28
|
-
* readonly error: E;
|
|
29
|
-
* }
|
|
30
|
-
*
|
|
31
|
-
* const ok = <T>(value: T): Ok<T> => ({ ok: true, value });
|
|
32
|
-
* const err = <E>(error: E): Err<E> => ({ ok: false, error });
|
|
33
|
-
* ```
|
|
34
|
-
*
|
|
35
|
-
* Now let's look at how `Result` can be used for safe JSON parsing:
|
|
36
|
-
*
|
|
37
|
-
* ```ts
|
|
38
|
-
* interface ParseJsonError {
|
|
39
|
-
* readonly type: "ParseJsonError";
|
|
40
|
-
* readonly message: string;
|
|
41
|
-
* }
|
|
42
|
-
*
|
|
43
|
-
* const parseJson = (value: string): Result<unknown, ParseJsonError> => {
|
|
44
|
-
* try {
|
|
45
|
-
* return ok(JSON.parse(value));
|
|
46
|
-
* } catch (error) {
|
|
47
|
-
* return err({ type: "ParseJsonError", message: String(error) });
|
|
48
|
-
* }
|
|
49
|
-
* };
|
|
50
|
-
*
|
|
51
|
-
* // Result<unknown, ParseJsonError>
|
|
52
|
-
* const json = parseJson('{"key": "value"}');
|
|
53
|
-
*
|
|
54
|
-
* // Fail fast to handle errors early.
|
|
55
|
-
* if (!json.ok) return json; // Err<ParseJsonError>
|
|
56
|
-
*
|
|
57
|
-
* // Now, we have access to the json.value.
|
|
58
|
-
* expectTypeOf(json.value).toBeUnknown();
|
|
59
|
-
* ```
|
|
60
|
-
*
|
|
61
|
-
* Note how we didn't have to use the try/catch, just `if (!json.ok)`, and how
|
|
62
|
-
* the error isn't unknown but has a type.
|
|
63
|
-
*
|
|
64
|
-
* But we had to use `try/catch` in the `parseJson` function. For such a case,
|
|
65
|
-
* wrapping unsafe code, Evolu provides the {@link trySync} helper:
|
|
66
|
-
*
|
|
67
|
-
* ```ts
|
|
68
|
-
* const parseJson = (value: string): Result<unknown, ParseJsonError> =>
|
|
69
|
-
* trySync(
|
|
70
|
-
* () => JSON.parse(value) as unknown,
|
|
71
|
-
* (error) => ({ type: "ParseJsonError", message: String(error) }),
|
|
72
|
-
* );
|
|
73
|
-
* ```
|
|
74
|
-
*
|
|
75
|
-
* ✨ {@link trySync} helper makes unsafe (can throw) synchronous code safe; for
|
|
76
|
-
* unsafe asynchronous code, use {@link tryAsync}.
|
|
77
|
-
*
|
|
78
|
-
* Let's summarize it:
|
|
79
|
-
*
|
|
80
|
-
* - For synchronous safe code, use `ok` and `err`.
|
|
81
|
-
* - For unsafe code, use `trySync` or `tryAsync`.
|
|
82
|
-
* - For asynchronous safe code, use `Promise` with {@link Result}.
|
|
83
|
-
*
|
|
84
|
-
* Asynchronous safe (because of a Promise using Result) code is
|
|
85
|
-
* straightforward:
|
|
86
|
-
*
|
|
87
|
-
* ```ts
|
|
88
|
-
* const fetchUser = async (
|
|
89
|
-
* userId: string,
|
|
90
|
-
* ): Promise<Result<User, FetchUserError>> => {
|
|
91
|
-
* // Simulate an API call
|
|
92
|
-
* return new Promise((resolve) => {
|
|
93
|
-
* setTimeout(() => {
|
|
94
|
-
* if (userId === "1") {
|
|
95
|
-
* resolve(ok({ id: "1", name: "Alice" }));
|
|
96
|
-
* } else {
|
|
97
|
-
* resolve(
|
|
98
|
-
* err({ type: "FetchUserError", reason: "user not found" }),
|
|
99
|
-
* );
|
|
100
|
-
* }
|
|
101
|
-
* }, 1000);
|
|
102
|
-
* });
|
|
103
|
-
* };
|
|
104
|
-
* ```
|
|
105
|
-
*
|
|
106
|
-
* ## Examples
|
|
107
|
-
*
|
|
108
|
-
* ### Sequential Operations with Short-Circuiting
|
|
109
|
-
*
|
|
110
|
-
* When performing a sequence of operations where any failure should stop
|
|
111
|
-
* further processing, use the `Result` type with early returns.
|
|
112
|
-
*
|
|
113
|
-
* Here's an example of a database reset operation that drops tables, restores a
|
|
114
|
-
* schema, and initializes the database, stopping on the first error:
|
|
115
|
-
*
|
|
116
|
-
* ```ts
|
|
117
|
-
* const resetResult = deps.sqlite.transaction(() => {
|
|
118
|
-
* const dropAllTablesResult = dropAllTables(deps);
|
|
119
|
-
* if (!dropAllTablesResult.ok) return dropAllTablesResult;
|
|
120
|
-
*
|
|
121
|
-
* if (message.restore) {
|
|
122
|
-
* const dbSchema = getDbSchema(deps)();
|
|
123
|
-
* if (!dbSchema.ok) return dbSchema;
|
|
124
|
-
*
|
|
125
|
-
* const ensureDbSchemaResult = ensureDbSchema(deps)(
|
|
126
|
-
* message.restore.dbSchema,
|
|
127
|
-
* dbSchema.value,
|
|
128
|
-
* );
|
|
129
|
-
* if (!ensureDbSchemaResult.ok) return ensureDbSchemaResult;
|
|
130
|
-
*
|
|
131
|
-
* const initializeDbResult = initializeDb(deps)(
|
|
132
|
-
* message.restore.mnemonic,
|
|
133
|
-
* );
|
|
134
|
-
* if (!initializeDbResult.ok) return initializeDbResult;
|
|
135
|
-
* }
|
|
136
|
-
* return ok();
|
|
137
|
-
* });
|
|
138
|
-
*
|
|
139
|
-
* if (!resetResult.ok) {
|
|
140
|
-
* deps.postMessage({ type: "onError", error: resetResult.error });
|
|
141
|
-
* return;
|
|
142
|
-
* }
|
|
143
|
-
* ```
|
|
144
|
-
*
|
|
145
|
-
* In this pattern:
|
|
146
|
-
*
|
|
147
|
-
* - Each operation returns a `Result` (e.g., `Result<void, E>` or `Result<T,
|
|
148
|
-
* E>`).
|
|
149
|
-
* - After each operation, check `if (!result.ok)` and return the `Err` to
|
|
150
|
-
* short-circuit.
|
|
151
|
-
* - If all operations succeed, return `ok()` (or another value if needed).
|
|
152
|
-
* - Outside the transaction, handle the final `Result` to report success or
|
|
153
|
-
* failure.
|
|
154
|
-
*
|
|
155
|
-
* This approach ensures type-safe error handling, avoids nested try/catch
|
|
156
|
-
* blocks, and clearly communicates the control flow.
|
|
157
|
-
*
|
|
158
|
-
* ### A function with two different errors:
|
|
159
|
-
*
|
|
160
|
-
* ```ts
|
|
161
|
-
* const example = (value: string): Result<number, FooError | BarError> => {
|
|
162
|
-
* const foo = getFoo(value);
|
|
163
|
-
* if (!foo.ok) return foo;
|
|
164
|
-
*
|
|
165
|
-
* const bar = barize(foo.value);
|
|
166
|
-
* if (!bar.ok) return bar;
|
|
167
|
-
*
|
|
168
|
-
* return ok(barToNumber(bar.value));
|
|
169
|
-
* };
|
|
170
|
-
* ```
|
|
171
|
-
*
|
|
172
|
-
* ## FAQ
|
|
173
|
-
*
|
|
174
|
-
* ### What if my function doesn't return a value on success?
|
|
175
|
-
*
|
|
176
|
-
* If your function performs an operation but doesn't need to return a value on
|
|
177
|
-
* success, you can use `Result<void, E>`. Using `Result<void, E>` is clearer
|
|
178
|
-
* than using `Result<true, E>` or `Result<null, E>` because it communicates
|
|
179
|
-
* that the function doesn't produce a value but can produce errors.
|
|
180
|
-
*
|
|
181
|
-
* ### How do I short-circuit processing of an array on the first error?
|
|
182
|
-
*
|
|
183
|
-
* If you want to stop processing as soon as an error occurs (short-circuit),
|
|
184
|
-
* you should produce and check each `Result` inside a loop:
|
|
185
|
-
*
|
|
186
|
-
* ```ts
|
|
187
|
-
* for (const query of [
|
|
188
|
-
* sql`drop table evolu_owner;`,
|
|
189
|
-
* sql`drop table evolu_message;`,
|
|
190
|
-
* ]) {
|
|
191
|
-
* const result = deps.sqlite.exec(query);
|
|
192
|
-
* if (!result.ok) return result;
|
|
193
|
-
* }
|
|
194
|
-
* // All queries succeeded
|
|
195
|
-
* ```
|
|
196
|
-
*
|
|
197
|
-
* ### How do I handle an array of operations and short-circuit on the first error?
|
|
198
|
-
*
|
|
199
|
-
* If you have an array of operations (not results), you should make them
|
|
200
|
-
* _lazy_—that is, represent each operation as a function (see `LazyValue` in
|
|
201
|
-
* `Function.ts`). This way, you only execute each operation as needed, and can
|
|
202
|
-
* stop on the first error:
|
|
203
|
-
*
|
|
204
|
-
* ```ts
|
|
205
|
-
* import type { LazyValue } from "./Function";
|
|
206
|
-
*
|
|
207
|
-
* const operations: LazyValue<Result<void, MyError>>[] = [
|
|
208
|
-
* () => doSomething(),
|
|
209
|
-
* () => doSomethingElse(),
|
|
210
|
-
* ];
|
|
211
|
-
*
|
|
212
|
-
* for (const op of operations) {
|
|
213
|
-
* const result = op();
|
|
214
|
-
* if (!result.ok) return result;
|
|
215
|
-
* }
|
|
216
|
-
* // All operations succeeded
|
|
217
|
-
* ```
|
|
218
|
-
*
|
|
219
|
-
* If you already have an array of `Result`s, the processing has already
|
|
220
|
-
* happened, so you can't short-circuit. In that case, you can check for the
|
|
221
|
-
* first error:
|
|
222
|
-
*
|
|
223
|
-
* ```ts
|
|
224
|
-
* const firstError = results.find((r) => !r.ok);
|
|
225
|
-
* if (firstError) return firstError;
|
|
226
|
-
* // All results are Ok
|
|
227
|
-
* ```
|
|
228
|
-
*
|
|
229
|
-
* ### Why doesn't Evolu provide "handy helpers"?
|
|
230
|
-
*
|
|
231
|
-
* Evolu intentionally favors imperative patterns (like the `for...of` loop
|
|
232
|
-
* above) over monadic helpers. Imperative code is generally more readable,
|
|
233
|
-
* easier to debug, and more familiar to most JavaScript and TypeScript
|
|
234
|
-
* developers. While monads and functional helpers can be powerful, they often
|
|
235
|
-
* obscure control flow and make debugging harder. Evolu's approach keeps error
|
|
236
|
-
* handling explicit and straightforward.
|
|
237
|
-
*
|
|
238
|
-
* @module
|
|
239
|
-
*/
|
|
240
1
|
export function ok(value = undefined) {
|
|
241
2
|
return { ok: true, value: value };
|
|
242
3
|
}
|
|
@@ -269,15 +30,40 @@ export const err = (error) => ({ ok: false, error });
|
|
|
269
30
|
* const config = getOrThrow(loadConfig());
|
|
270
31
|
* // Safe to use config here
|
|
271
32
|
* ```
|
|
33
|
+
*
|
|
34
|
+
* Throws: `Error` with the original error attached as `cause`.
|
|
272
35
|
*/
|
|
273
36
|
export const getOrThrow = (result) => {
|
|
274
37
|
if (result.ok) {
|
|
275
38
|
return result.value;
|
|
276
39
|
}
|
|
277
40
|
else {
|
|
278
|
-
throw new Error(
|
|
41
|
+
throw new Error("getOrThrow", { cause: result.error });
|
|
279
42
|
}
|
|
280
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* Extracts the value from a {@link Result} if it is an `Ok`, or returns `null`
|
|
46
|
+
* if it is an `Err`.
|
|
47
|
+
*
|
|
48
|
+
* **Intended usage:**
|
|
49
|
+
*
|
|
50
|
+
* - When you need to convert a `Result` to a nullable value for APIs that expect
|
|
51
|
+
* `T | null`.
|
|
52
|
+
* - When the error is not important and you just want the value or nothing.
|
|
53
|
+
*
|
|
54
|
+
* ### Example
|
|
55
|
+
*
|
|
56
|
+
* ```ts
|
|
57
|
+
* const parseResult = parseJson('{"key": "value"}');
|
|
58
|
+
* const value = getOrNull(parseResult);
|
|
59
|
+
* // value is unknown | null
|
|
60
|
+
*
|
|
61
|
+
* if (value != null) {
|
|
62
|
+
* console.log("Parsed value:", value);
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export const getOrNull = (result) => result.ok ? result.value : null;
|
|
281
67
|
/**
|
|
282
68
|
* Wraps synchronous functions that may throw exceptions, returning a
|
|
283
69
|
* {@link Result}.
|
|
@@ -298,7 +84,10 @@ export const getOrThrow = (result) => {
|
|
|
298
84
|
* const parseJson = (value: string): Result<unknown, ParseJsonError> =>
|
|
299
85
|
* trySync(
|
|
300
86
|
* () => JSON.parse(value) as unknown,
|
|
301
|
-
* (error)
|
|
87
|
+
* (error): ParseJsonError => ({
|
|
88
|
+
* type: "ParseJsonError",
|
|
89
|
+
* message: String(error),
|
|
90
|
+
* }),
|
|
302
91
|
* );
|
|
303
92
|
* ```
|
|
304
93
|
*/
|
package/dist/src/Skiplist.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PositiveInt } from "./Type.js";
|
|
1
2
|
// TODO: Use in Storage.
|
|
2
3
|
export const createSkiplistLevel = (deps) => (config = {}) => {
|
|
3
4
|
const { probability = 0.5, maxLevel = 32 } = config;
|
|
@@ -7,7 +8,7 @@ export const createSkiplistLevel = (deps) => (config = {}) => {
|
|
|
7
8
|
while (deps.random.next() <= probability && level < maxLevel) {
|
|
8
9
|
level += 1;
|
|
9
10
|
}
|
|
10
|
-
return level;
|
|
11
|
+
return PositiveInt.orThrow(level);
|
|
11
12
|
},
|
|
12
13
|
};
|
|
13
14
|
};
|