@evolu/common 6.0.1-preview.9 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -29
- package/dist/src/Array.d.ts +317 -12
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +219 -9
- package/dist/src/Assert.d.ts +0 -13
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +0 -15
- 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 +39 -0
- package/dist/src/Cache.d.ts.map +1 -0
- package/dist/src/Cache.js +47 -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 +32 -7
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +73 -10
- package/dist/src/Crypto.d.ts +56 -42
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +45 -54
- package/dist/src/Evolu/Db.d.ts +162 -74
- package/dist/src/Evolu/Db.d.ts.map +1 -1
- package/dist/src/Evolu/Db.js +284 -702
- 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 +213 -133
- package/dist/src/Evolu/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu/Evolu.js +189 -180
- package/dist/src/Evolu/Internal.d.ts +2 -4
- package/dist/src/Evolu/Internal.d.ts.map +1 -1
- package/dist/src/Evolu/Internal.js +2 -4
- 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 +268 -240
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +595 -447
- package/dist/src/Evolu/Public.d.ts +7 -9
- package/dist/src/Evolu/Public.d.ts.map +1 -1
- package/dist/src/Evolu/Public.js +3 -4
- package/dist/src/Evolu/PublicKysely.d.ts.map +1 -1
- package/dist/src/Evolu/PublicKysely.js +3 -4
- package/dist/src/Evolu/Query.d.ts +2 -1
- package/dist/src/Evolu/Query.d.ts.map +1 -1
- package/dist/src/Evolu/Query.js +1 -1
- package/dist/src/Evolu/Relay.d.ts +91 -8
- package/dist/src/Evolu/Relay.d.ts.map +1 -1
- package/dist/src/Evolu/Relay.js +214 -88
- package/dist/src/Evolu/Schema.d.ts +125 -47
- package/dist/src/Evolu/Schema.d.ts.map +1 -1
- package/dist/src/Evolu/Schema.js +175 -31
- package/dist/src/Evolu/Storage.d.ts +249 -27
- package/dist/src/Evolu/Storage.d.ts.map +1 -1
- package/dist/src/Evolu/Storage.js +198 -92
- package/dist/src/Evolu/Sync.d.ts +68 -13
- package/dist/src/Evolu/Sync.d.ts.map +1 -1
- package/dist/src/Evolu/Sync.js +469 -20
- package/dist/src/Evolu/Timestamp.d.ts +83 -30
- package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
- package/dist/src/Evolu/Timestamp.js +79 -34
- 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/Number.d.ts +4 -3
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +5 -4
- package/dist/src/Object.d.ts +10 -4
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +9 -3
- package/dist/src/Order.d.ts +0 -5
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +0 -5
- package/dist/src/Platform.d.ts +15 -0
- package/dist/src/Platform.d.ts.map +1 -0
- package/dist/src/Platform.js +17 -0
- package/dist/src/Random.d.ts +3 -7
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Random.js +0 -5
- package/dist/src/Ref.d.ts.map +1 -1
- package/dist/src/Relation.d.ts +79 -0
- package/dist/src/Relation.d.ts.map +1 -0
- package/dist/src/Relation.js +127 -0
- 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 +189 -59
- 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 +89 -5
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +136 -9
- package/dist/src/Store.d.ts +0 -5
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +0 -5
- package/dist/src/Task.d.ts +580 -0
- package/dist/src/Task.d.ts.map +1 -0
- package/dist/src/Task.js +464 -0
- package/dist/src/Time.d.ts +66 -6
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +96 -7
- package/dist/src/Type.d.ts +677 -344
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +718 -467
- package/dist/src/Types.d.ts +38 -75
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/WebSocket.d.ts +5 -7
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +12 -23
- package/dist/src/Worker.d.ts +39 -16
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +22 -9
- package/dist/src/index.d.ts +8 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -3
- package/package.json +14 -13
- package/src/Array.ts +396 -19
- package/src/Assert.ts +0 -35
- package/src/Brand.ts +75 -0
- package/src/Buffer.ts +7 -7
- package/src/Cache.ts +79 -0
- package/src/Callbacks.ts +62 -22
- package/src/Console.ts +92 -12
- package/src/Crypto.ts +99 -91
- package/src/Evolu/Db.ts +519 -1026
- package/src/Evolu/Diff.ts +7 -5
- package/src/Evolu/Evolu.ts +476 -360
- package/src/Evolu/Internal.ts +2 -4
- 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 +846 -742
- package/src/Evolu/Public.ts +8 -15
- package/src/Evolu/PublicKysely.ts +4 -5
- package/src/Evolu/Query.ts +3 -2
- package/src/Evolu/Relay.ts +406 -103
- package/src/Evolu/Schema.ts +323 -91
- package/src/Evolu/Storage.ts +559 -137
- package/src/Evolu/Sync.ts +819 -36
- package/src/Evolu/Timestamp.ts +90 -58
- package/src/Identicon.ts +197 -0
- package/src/Instances.ts +90 -0
- package/src/Number.ts +6 -10
- package/src/Object.ts +13 -5
- package/src/Order.ts +0 -6
- package/src/Platform.ts +20 -0
- package/src/Random.ts +3 -8
- package/src/Ref.ts +0 -5
- package/src/Relation.ts +234 -0
- package/src/Resources.ts +367 -0
- package/src/Result.ts +196 -61
- package/src/Skiplist.ts +1 -1
- package/src/Sqlite.ts +152 -17
- package/src/Store.ts +0 -5
- package/src/Task.ts +894 -0
- package/src/Time.ts +179 -10
- package/src/Type.ts +1136 -731
- package/src/Types.ts +43 -77
- package/src/WebSocket.ts +27 -31
- package/src/Worker.ts +72 -29
- package/src/index.ts +8 -3
- 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/ManyToManyMap.d.ts +0 -26
- package/dist/src/ManyToManyMap.d.ts.map +0 -1
- package/dist/src/ManyToManyMap.js +0 -92
- 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/ManyToManyMap.ts +0 -140
- package/src/NanoId.ts +0 -39
- package/src/Promise.ts +0 -295
package/src/Result.ts
CHANGED
|
@@ -1,36 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 🛡️ Type-safe errors
|
|
3
|
-
*
|
|
4
|
-
* ## Intro
|
|
5
|
-
*
|
|
6
2
|
* The problem with throwing an exception in JavaScript is that the caught error
|
|
7
3
|
* is always of an unknown type. The unknown type is a problem because we can't
|
|
8
4
|
* 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:
|
|
17
|
-
*
|
|
18
|
-
* ```ts
|
|
19
|
-
* type Result<T, E> = Ok<T> | Err<E>;
|
|
5
|
+
* tell us.
|
|
20
6
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* interface Err<E> {
|
|
27
|
-
* readonly ok: false;
|
|
28
|
-
* readonly error: E;
|
|
29
|
-
* }
|
|
7
|
+
* Languages like Rust or Haskell use a type-safe approach to error handling,
|
|
8
|
+
* where errors are explicitly represented as part of the return type, such as
|
|
9
|
+
* Result or Either, allowing the developer to handle errors safely. TypeScript
|
|
10
|
+
* can have this too via the `Result` type.
|
|
30
11
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
12
|
+
* The `Result` type can be either {@link Ok} (success) or {@link Err} (error).
|
|
13
|
+
* Use {@link ok} to create a successful result and {@link err} to create an error
|
|
14
|
+
* result.
|
|
34
15
|
*
|
|
35
16
|
* Now let's look at how `Result` can be used for safe JSON parsing:
|
|
36
17
|
*
|
|
@@ -77,12 +58,10 @@
|
|
|
77
58
|
*
|
|
78
59
|
* Let's summarize it:
|
|
79
60
|
*
|
|
80
|
-
* - For
|
|
61
|
+
* - For safe code, use `ok` and `err`.
|
|
81
62
|
* - For unsafe code, use `trySync` or `tryAsync`.
|
|
82
|
-
* - For asynchronous safe code, use `Promise` with {@link Result}.
|
|
83
63
|
*
|
|
84
|
-
*
|
|
85
|
-
* straightforward:
|
|
64
|
+
* Safe asynchronous code (using Result with a Promise):
|
|
86
65
|
*
|
|
87
66
|
* ```ts
|
|
88
67
|
* const fetchUser = async (
|
|
@@ -103,9 +82,40 @@
|
|
|
103
82
|
* };
|
|
104
83
|
* ```
|
|
105
84
|
*
|
|
106
|
-
*
|
|
85
|
+
* ### Naming convention
|
|
107
86
|
*
|
|
108
|
-
*
|
|
87
|
+
* - For values: `const user = getUser()`
|
|
88
|
+
* - For a single void operation: `const result = foo()`
|
|
89
|
+
* - For multiple void operations: use descriptive names for all
|
|
90
|
+
*
|
|
91
|
+
* ```ts
|
|
92
|
+
* const processUser = () => {
|
|
93
|
+
* // we have a value
|
|
94
|
+
* const user = getUser();
|
|
95
|
+
* if (!user.ok) return user;
|
|
96
|
+
*
|
|
97
|
+
* // single void operation
|
|
98
|
+
* const result = saveToDatabase(user.value);
|
|
99
|
+
* if (!result.ok) return result;
|
|
100
|
+
*
|
|
101
|
+
* return ok();
|
|
102
|
+
* };
|
|
103
|
+
*
|
|
104
|
+
* const setupDatabase = () => {
|
|
105
|
+
* // multiple void operations - use descriptive names
|
|
106
|
+
* const baseTables = createBaseTables();
|
|
107
|
+
* if (!baseTables.ok) return baseTables;
|
|
108
|
+
*
|
|
109
|
+
* const relayTables = createRelayTables();
|
|
110
|
+
* if (!relayTables.ok) return relayTables;
|
|
111
|
+
*
|
|
112
|
+
* return ok();
|
|
113
|
+
* };
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* ### Examples
|
|
117
|
+
*
|
|
118
|
+
* #### Sequential operations with short-circuiting
|
|
109
119
|
*
|
|
110
120
|
* When performing a sequence of operations where any failure should stop
|
|
111
121
|
* further processing, use the `Result` type with early returns.
|
|
@@ -155,37 +165,127 @@
|
|
|
155
165
|
* This approach ensures type-safe error handling, avoids nested try/catch
|
|
156
166
|
* blocks, and clearly communicates the control flow.
|
|
157
167
|
*
|
|
158
|
-
*
|
|
168
|
+
* #### A function with two different errors:
|
|
159
169
|
*
|
|
160
170
|
* ```ts
|
|
161
171
|
* const example = (value: string): Result<number, FooError | BarError> => {
|
|
162
172
|
* const foo = getFoo(value);
|
|
163
173
|
* if (!foo.ok) return foo;
|
|
164
174
|
*
|
|
165
|
-
* const bar =
|
|
175
|
+
* const bar = getBar(foo.value);
|
|
166
176
|
* if (!bar.ok) return bar;
|
|
167
177
|
*
|
|
168
178
|
* return ok(barToNumber(bar.value));
|
|
169
179
|
* };
|
|
170
180
|
* ```
|
|
171
181
|
*
|
|
172
|
-
*
|
|
182
|
+
* ### Handling unexpected errors
|
|
183
|
+
*
|
|
184
|
+
* Even with disciplined use of `trySync` and `tryAsync`, unexpected errors can
|
|
185
|
+
* still occur due to programming mistakes, third-party library bugs, or edge
|
|
186
|
+
* cases. These should be logged for debugging, but **unexpected errors are not
|
|
187
|
+
* recoverable** - they represent bugs that must be fixed.
|
|
188
|
+
*
|
|
189
|
+
* **Important**: "Graceful shutdown" and error recovery can only come from
|
|
190
|
+
* expected errors handled via the `Result` type. Unexpected errors should fail
|
|
191
|
+
* fast - the operation fails immediately and the error bubbles up.
|
|
192
|
+
*
|
|
193
|
+
* #### In browser environments
|
|
194
|
+
*
|
|
195
|
+
* ```ts
|
|
196
|
+
* // Global error handler for unexpected errors
|
|
197
|
+
* window.addEventListener("error", (event) => {
|
|
198
|
+
* console.error("Uncaught error:", event.error);
|
|
199
|
+
* // Send to error reporting service
|
|
200
|
+
* errorReportingService.report(event.error);
|
|
201
|
+
* });
|
|
202
|
+
*
|
|
203
|
+
* // For unhandled promise rejections
|
|
204
|
+
* window.addEventListener("unhandledrejection", (event) => {
|
|
205
|
+
* console.error("Unhandled promise rejection:", event.reason);
|
|
206
|
+
* errorReportingService.report(event.reason);
|
|
207
|
+
* });
|
|
208
|
+
* ```
|
|
209
|
+
*
|
|
210
|
+
* #### In Node.js environments
|
|
211
|
+
*
|
|
212
|
+
* ```ts
|
|
213
|
+
* // Handle uncaught exceptions - log and fail fast
|
|
214
|
+
* process.on("uncaughtException", (error) => {
|
|
215
|
+
* console.error("Uncaught exception:", error);
|
|
216
|
+
* errorReportingService.report(error);
|
|
217
|
+
* // Exit immediately - unexpected errors are not recoverable
|
|
218
|
+
* process.exit(1);
|
|
219
|
+
* });
|
|
220
|
+
*
|
|
221
|
+
* // Handle unhandled promise rejections
|
|
222
|
+
* process.on("unhandledRejection", (reason) => {
|
|
223
|
+
* console.error("Unhandled promise rejection:", reason);
|
|
224
|
+
* errorReportingService.report(reason);
|
|
225
|
+
* });
|
|
226
|
+
* ```
|
|
227
|
+
*
|
|
228
|
+
* These global handlers serve as a safety net to log and report unexpected
|
|
229
|
+
* errors for debugging purposes. They do not attempt recovery - unexpected
|
|
230
|
+
* errors represent bugs that must be fixed. The discipline of explicit error
|
|
231
|
+
* handling through the `Result` pattern remains the primary approach for all
|
|
232
|
+
* recoverable scenarios.
|
|
233
|
+
*
|
|
234
|
+
* ### FAQ
|
|
235
|
+
*
|
|
236
|
+
* #### When should a function return a plain value instead of `Result<T, E>`?
|
|
173
237
|
*
|
|
174
|
-
*
|
|
238
|
+
* Use `Result<T, E>` only when a function can fail with **known, expected
|
|
239
|
+
* errors** that callers need to handle. If a function cannot fail with a known
|
|
240
|
+
* error, return the value directly.
|
|
241
|
+
*
|
|
242
|
+
* - ✅ Return `Result<User, UserNotFoundError>` - can fail with a known error
|
|
243
|
+
* - ✅ Return `User` - cannot fail with a known error
|
|
244
|
+
* - ❌ Don't return `Result<User, never>` - unnecessary wrapper
|
|
245
|
+
*
|
|
246
|
+
* This keeps the codebase clean and makes error handling intentional. The type
|
|
247
|
+
* system communicates which operations can fail and which cannot.
|
|
248
|
+
*
|
|
249
|
+
* Unsafe code from external libraries (not under our control) should be wrapped
|
|
250
|
+
* with `trySync` or `tryAsync` at the boundaries. Once wrapped, if the error is
|
|
251
|
+
* not important to callers, functions can safely return plain values. If the
|
|
252
|
+
* error matters, use `Result` with a typed error.
|
|
253
|
+
*
|
|
254
|
+
* ```ts
|
|
255
|
+
* // ✅ Safe to return void - unsafe code is wrapped and error is handled
|
|
256
|
+
* const processData = (data: string): void => {
|
|
257
|
+
* const parseResult = trySync(
|
|
258
|
+
* () => JSON.parse(data),
|
|
259
|
+
* (error) => ({ type: "ParseError", message: String(error) }),
|
|
260
|
+
* );
|
|
261
|
+
*
|
|
262
|
+
* if (!parseResult.ok) {
|
|
263
|
+
* logError(parseResult.error);
|
|
264
|
+
* return;
|
|
265
|
+
* }
|
|
266
|
+
*
|
|
267
|
+
* // Continue with safe operations...
|
|
268
|
+
* };
|
|
269
|
+
*
|
|
270
|
+
* // ✅ Can call without try-catch since it returns void
|
|
271
|
+
* processData(jsonString);
|
|
272
|
+
* ```
|
|
273
|
+
*
|
|
274
|
+
* #### What if my function doesn't return a value on success?
|
|
175
275
|
*
|
|
176
276
|
* If your function performs an operation but doesn't need to return a value on
|
|
177
277
|
* success, you can use `Result<void, E>`. Using `Result<void, E>` is clearer
|
|
178
278
|
* than using `Result<true, E>` or `Result<null, E>` because it communicates
|
|
179
279
|
* that the function doesn't produce a value but can produce errors.
|
|
180
280
|
*
|
|
181
|
-
*
|
|
281
|
+
* #### How do I short-circuit processing of an array on the first error?
|
|
182
282
|
*
|
|
183
283
|
* If you want to stop processing as soon as an error occurs (short-circuit),
|
|
184
284
|
* you should produce and check each `Result` inside a loop:
|
|
185
285
|
*
|
|
186
286
|
* ```ts
|
|
187
287
|
* for (const query of [
|
|
188
|
-
* sql`drop table
|
|
288
|
+
* sql`drop table evolu_config;`,
|
|
189
289
|
* sql`drop table evolu_message;`,
|
|
190
290
|
* ]) {
|
|
191
291
|
* const result = deps.sqlite.exec(query);
|
|
@@ -194,12 +294,11 @@
|
|
|
194
294
|
* // All queries succeeded
|
|
195
295
|
* ```
|
|
196
296
|
*
|
|
197
|
-
*
|
|
297
|
+
* #### How do I handle an array of operations and short-circuit on the first error?
|
|
198
298
|
*
|
|
199
299
|
* 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:
|
|
300
|
+
* _lazy_—that is, represent each operation as a function. This way, you only
|
|
301
|
+
* execute each operation as needed, and can stop on the first error:
|
|
203
302
|
*
|
|
204
303
|
* ```ts
|
|
205
304
|
* import type { LazyValue } from "./Function";
|
|
@@ -232,17 +331,7 @@
|
|
|
232
331
|
* above) over monadic helpers. Imperative code is generally more readable,
|
|
233
332
|
* easier to debug, and more familiar to most JavaScript and TypeScript
|
|
234
333
|
* 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
|
-
/**
|
|
242
|
-
* A `Result` can be either {@link Ok} (success) or {@link Err} (error).
|
|
243
|
-
*
|
|
244
|
-
* Use {@link ok} to create a successful result and {@link err} to create an error
|
|
245
|
-
* result.
|
|
334
|
+
* obscure control flow and make debugging harder.
|
|
246
335
|
*/
|
|
247
336
|
export type Result<T, E> = Ok<T> | Err<E>;
|
|
248
337
|
|
|
@@ -256,16 +345,16 @@ export interface Ok<T> {
|
|
|
256
345
|
* An error {@link Result}.
|
|
257
346
|
*
|
|
258
347
|
* The `error` property can be any type that describes the error. For normal
|
|
259
|
-
*
|
|
348
|
+
* domain logic, use a plain object. This allows us to structure errors with
|
|
260
349
|
* custom fields (e.g., `{ type: "MyError", code: 123 }`). Messages for users
|
|
261
350
|
* belong to translations, not to error objects.
|
|
262
351
|
*
|
|
263
|
-
* If you need a
|
|
352
|
+
* If you need a stack trace for debugging, use an `Error` instance or a custom
|
|
264
353
|
* error class to include additional metadata.
|
|
265
354
|
*
|
|
266
355
|
* ### Examples
|
|
267
356
|
*
|
|
268
|
-
* ####
|
|
357
|
+
* #### Domain logic error (plain object, recommended)
|
|
269
358
|
*
|
|
270
359
|
* ```ts
|
|
271
360
|
* const failure = err({
|
|
@@ -275,13 +364,13 @@ export interface Ok<T> {
|
|
|
275
364
|
* });
|
|
276
365
|
* ```
|
|
277
366
|
*
|
|
278
|
-
* #### Debugging with
|
|
367
|
+
* #### Debugging with stack trace (error instance)
|
|
279
368
|
*
|
|
280
369
|
* ```ts
|
|
281
370
|
* const failure = err(new Error("Something went wrong"));
|
|
282
371
|
* ```
|
|
283
372
|
*
|
|
284
|
-
* #### Custom
|
|
373
|
+
* #### Custom error class
|
|
285
374
|
*
|
|
286
375
|
* ```ts
|
|
287
376
|
* class MyCustomError extends Error {
|
|
@@ -301,6 +390,22 @@ export interface Err<E> {
|
|
|
301
390
|
readonly error: E;
|
|
302
391
|
}
|
|
303
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Extracts the value type from a {@link Result}.
|
|
395
|
+
*
|
|
396
|
+
* @category Utilities
|
|
397
|
+
*/
|
|
398
|
+
export type InferOk<R extends Result<any, any>> =
|
|
399
|
+
R extends Ok<infer T> ? T : never;
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Extracts the error type from a {@link Result}.
|
|
403
|
+
*
|
|
404
|
+
* @category Utilities
|
|
405
|
+
*/
|
|
406
|
+
export type InferErr<R extends Result<any, any>> =
|
|
407
|
+
R extends Err<infer E> ? E : never;
|
|
408
|
+
|
|
304
409
|
/**
|
|
305
410
|
* Creates an {@link Ok} result.
|
|
306
411
|
*
|
|
@@ -355,15 +460,42 @@ export const err = <E>(error: E): Err<E> => ({ ok: false, error });
|
|
|
355
460
|
* const config = getOrThrow(loadConfig());
|
|
356
461
|
* // Safe to use config here
|
|
357
462
|
* ```
|
|
463
|
+
*
|
|
464
|
+
* Throws: `Error` with the original error attached as `cause`.
|
|
358
465
|
*/
|
|
359
466
|
export const getOrThrow = <T, E>(result: Result<T, E>): T => {
|
|
360
467
|
if (result.ok) {
|
|
361
468
|
return result.value;
|
|
362
469
|
} else {
|
|
363
|
-
throw new Error(
|
|
470
|
+
throw new Error("getOrThrow", { cause: result.error });
|
|
364
471
|
}
|
|
365
472
|
};
|
|
366
473
|
|
|
474
|
+
/**
|
|
475
|
+
* Extracts the value from a {@link Result} if it is an `Ok`, or returns `null`
|
|
476
|
+
* if it is an `Err`.
|
|
477
|
+
*
|
|
478
|
+
* **Intended usage:**
|
|
479
|
+
*
|
|
480
|
+
* - When you need to convert a `Result` to a nullable value for APIs that expect
|
|
481
|
+
* `T | null`.
|
|
482
|
+
* - When the error is not important and you just want the value or nothing.
|
|
483
|
+
*
|
|
484
|
+
* ### Example
|
|
485
|
+
*
|
|
486
|
+
* ```ts
|
|
487
|
+
* const parseResult = parseJson('{"key": "value"}');
|
|
488
|
+
* const value = getOrNull(parseResult);
|
|
489
|
+
* // value is unknown | null
|
|
490
|
+
*
|
|
491
|
+
* if (value != null) {
|
|
492
|
+
* console.log("Parsed value:", value);
|
|
493
|
+
* }
|
|
494
|
+
* ```
|
|
495
|
+
*/
|
|
496
|
+
export const getOrNull = <T, E>(result: Result<T, E>): T | null =>
|
|
497
|
+
result.ok ? result.value : null;
|
|
498
|
+
|
|
367
499
|
/**
|
|
368
500
|
* Wraps synchronous functions that may throw exceptions, returning a
|
|
369
501
|
* {@link Result}.
|
|
@@ -384,7 +516,10 @@ export const getOrThrow = <T, E>(result: Result<T, E>): T => {
|
|
|
384
516
|
* const parseJson = (value: string): Result<unknown, ParseJsonError> =>
|
|
385
517
|
* trySync(
|
|
386
518
|
* () => JSON.parse(value) as unknown,
|
|
387
|
-
* (error)
|
|
519
|
+
* (error): ParseJsonError => ({
|
|
520
|
+
* type: "ParseJsonError",
|
|
521
|
+
* message: String(error),
|
|
522
|
+
* }),
|
|
388
523
|
* );
|
|
389
524
|
* ```
|
|
390
525
|
*/
|
package/src/Skiplist.ts
CHANGED
package/src/Sqlite.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
+
import { Brand } from "./Brand.js";
|
|
2
|
+
import { createLruCache } from "./Cache.js";
|
|
1
3
|
import { ConsoleDep } from "./Console.js";
|
|
4
|
+
import { EncryptionKey } from "./Crypto.js";
|
|
2
5
|
import { createTransferableError, TransferableError } from "./Error.js";
|
|
3
6
|
import { err, ok, Result, tryAsync, trySync } from "./Result.js";
|
|
4
7
|
import {
|
|
5
|
-
Boolean,
|
|
6
8
|
Null,
|
|
7
9
|
Number,
|
|
10
|
+
PositiveInt,
|
|
8
11
|
SimpleName,
|
|
9
12
|
String,
|
|
10
|
-
transform,
|
|
11
13
|
Uint8Array,
|
|
12
14
|
union,
|
|
13
15
|
} from "./Type.js";
|
|
14
|
-
import {
|
|
16
|
+
import { IntentionalNever, Predicate } from "./Types.js";
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* SQLite driver interface. This is the minimal interface that platform-specific
|
|
@@ -33,6 +35,7 @@ export interface CreateSqliteDriverDep {
|
|
|
33
35
|
|
|
34
36
|
export interface SqliteDriverOptions {
|
|
35
37
|
memory?: boolean;
|
|
38
|
+
encryptionKey?: EncryptionKey | undefined;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
/**
|
|
@@ -148,13 +151,13 @@ export const createSqlite =
|
|
|
148
151
|
exec: (query) =>
|
|
149
152
|
trySync(
|
|
150
153
|
() => {
|
|
151
|
-
deps.console?.log("[sql]", query);
|
|
154
|
+
deps.console?.log("[sql]", { query });
|
|
152
155
|
|
|
153
156
|
const result = maybeLogSqliteQueryExecutionTime(query, () =>
|
|
154
157
|
driver.exec(query, isSqlMutation(query.sql)),
|
|
155
158
|
);
|
|
156
159
|
|
|
157
|
-
deps.console?.log("[sql]", result);
|
|
160
|
+
deps.console?.log("[sql]", { result });
|
|
158
161
|
|
|
159
162
|
return result as IntentionalNever;
|
|
160
163
|
},
|
|
@@ -300,7 +303,44 @@ export interface RawSql {
|
|
|
300
303
|
|
|
301
304
|
export type SqlTemplateParam = SqliteValue | SqlIdentifier | RawSql;
|
|
302
305
|
|
|
303
|
-
/**
|
|
306
|
+
/**
|
|
307
|
+
* Creates a safe SQL query using a tagged template literal.
|
|
308
|
+
*
|
|
309
|
+
* Parameters are automatically escaped and bound as SQLite values. Use
|
|
310
|
+
* `sql.identifier` for column/table names and `sql.raw` for unescaped SQL.
|
|
311
|
+
*
|
|
312
|
+
* ### Example
|
|
313
|
+
*
|
|
314
|
+
* ```ts
|
|
315
|
+
* const id = 42;
|
|
316
|
+
* const name = "Alice";
|
|
317
|
+
*
|
|
318
|
+
* const result = sqlite.exec(sql`
|
|
319
|
+
* select *
|
|
320
|
+
* from users
|
|
321
|
+
* where id = ${id} and name = ${name};
|
|
322
|
+
* `);
|
|
323
|
+
*
|
|
324
|
+
* // For identifiers
|
|
325
|
+
* const tableName = "users";
|
|
326
|
+
* sqlite.exec(sql`
|
|
327
|
+
* create table ${sql.identifier(tableName)} (
|
|
328
|
+
* "id" text primary key,
|
|
329
|
+
* "name" text not null
|
|
330
|
+
* );
|
|
331
|
+
* `);
|
|
332
|
+
*
|
|
333
|
+
* // For raw SQL (use with caution)
|
|
334
|
+
* const orderBy = "created_at desc";
|
|
335
|
+
* sqlite.exec(sql`select * from users order by ${sql.raw(orderBy)};`);
|
|
336
|
+
* ```
|
|
337
|
+
*
|
|
338
|
+
* ### TIP
|
|
339
|
+
*
|
|
340
|
+
* Use `prettier-plugin-sql-cst` for SQL formatting. Like Prettier for
|
|
341
|
+
* JavaScript, this plugin formats SQL expressions differently depending on
|
|
342
|
+
* their length.
|
|
343
|
+
*/
|
|
304
344
|
export const sql = (
|
|
305
345
|
strings: TemplateStringsArray,
|
|
306
346
|
...parameters: Array<SqlTemplateParam>
|
|
@@ -348,6 +388,29 @@ sql.prepared = (
|
|
|
348
388
|
return { ...query, options: { prepare: true } };
|
|
349
389
|
};
|
|
350
390
|
|
|
391
|
+
/**
|
|
392
|
+
* Checks if a SQL string contains mutation keywords (insert, update, delete,
|
|
393
|
+
* etc.). Results are cached for performance.
|
|
394
|
+
*/
|
|
395
|
+
export const isSqlMutation: Predicate<string> = (sql) => {
|
|
396
|
+
/**
|
|
397
|
+
* Without cache, "insert 1_000_000" Storage test dropped from 57742
|
|
398
|
+
* inserts/sec to 34k. Regex we used was fast, but CodeQL flagged it as a
|
|
399
|
+
* potential ReDoS vulnerability, so manual comment removal was the only
|
|
400
|
+
* option. LRU cache restores performance.
|
|
401
|
+
*/
|
|
402
|
+
const cached = isSqlMutationCache.get(sql);
|
|
403
|
+
if (cached !== undefined) return cached;
|
|
404
|
+
|
|
405
|
+
const result = isSqlMutationRegEx.test(removeSqlComments(sql));
|
|
406
|
+
isSqlMutationCache.set(sql, result);
|
|
407
|
+
return result;
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const isSqlMutationCache = createLruCache<string, boolean>(
|
|
411
|
+
PositiveInt.orThrow(10_000),
|
|
412
|
+
);
|
|
413
|
+
|
|
351
414
|
const isSqlMutationRegEx = new RegExp(
|
|
352
415
|
`\\b(${[
|
|
353
416
|
"alter",
|
|
@@ -366,8 +429,38 @@ const isSqlMutationRegEx = new RegExp(
|
|
|
366
429
|
"i",
|
|
367
430
|
);
|
|
368
431
|
|
|
369
|
-
|
|
370
|
-
|
|
432
|
+
/**
|
|
433
|
+
* Removes SQL line comments (--) from a SQL string without using regex to avoid
|
|
434
|
+
* ReDoS vulnerabilities.
|
|
435
|
+
*/
|
|
436
|
+
const removeSqlComments = (sql: string): string => {
|
|
437
|
+
// Fast path: if there are no comments, return the original string
|
|
438
|
+
if (!sql.includes("--")) return sql;
|
|
439
|
+
|
|
440
|
+
let result = "";
|
|
441
|
+
let i = 0;
|
|
442
|
+
|
|
443
|
+
while (i < sql.length) {
|
|
444
|
+
// Check for comment start
|
|
445
|
+
if (i < sql.length - 1 && sql[i] === "-" && sql[i + 1] === "-") {
|
|
446
|
+
// Skip until end of line or end of string
|
|
447
|
+
i += 2;
|
|
448
|
+
while (i < sql.length && sql[i] !== "\n") {
|
|
449
|
+
i++;
|
|
450
|
+
}
|
|
451
|
+
// Keep the newline if present
|
|
452
|
+
if (i < sql.length && sql[i] === "\n") {
|
|
453
|
+
result += "\n";
|
|
454
|
+
i++;
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
result += sql[i];
|
|
458
|
+
i++;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return result;
|
|
463
|
+
};
|
|
371
464
|
|
|
372
465
|
export interface SqliteQueryPlanRow {
|
|
373
466
|
id: number;
|
|
@@ -385,13 +478,14 @@ export const explainSqliteQueryPlan =
|
|
|
385
478
|
if (!result.ok) return result;
|
|
386
479
|
|
|
387
480
|
// eslint-disable-next-line no-console
|
|
388
|
-
console.log("
|
|
481
|
+
console.log("[explainSqliteQueryPlan]", query);
|
|
389
482
|
// eslint-disable-next-line no-console
|
|
390
483
|
console.log(
|
|
391
484
|
drawSqliteQueryPlan(
|
|
392
485
|
result.value.rows as unknown as Array<SqliteQueryPlanRow>,
|
|
393
486
|
),
|
|
394
487
|
);
|
|
488
|
+
|
|
395
489
|
return ok();
|
|
396
490
|
};
|
|
397
491
|
|
|
@@ -415,15 +509,56 @@ const drawSqliteQueryPlan = (rows: Array<SqliteQueryPlanRow>): string =>
|
|
|
415
509
|
|
|
416
510
|
/**
|
|
417
511
|
* SQLite represents boolean values using `0` (false) and `1` (true) instead of
|
|
418
|
-
* a dedicated boolean type.
|
|
419
|
-
* SQLite's boolean representation.
|
|
512
|
+
* a dedicated boolean type.
|
|
420
513
|
*
|
|
421
514
|
* See: https://www.sqlite.org/quirks.html#no_separate_boolean_datatype
|
|
515
|
+
*
|
|
516
|
+
* ### Tips
|
|
517
|
+
*
|
|
518
|
+
* - Use {@link sqliteTrue} and {@link sqliteFalse} constants for better
|
|
519
|
+
* readability.
|
|
520
|
+
* - Use {@link booleanToSqliteBoolean} and {@link sqliteBooleanToBoolean} for
|
|
521
|
+
* converting between JavaScript booleans and SQLite boolean values.
|
|
422
522
|
*/
|
|
423
|
-
export const SqliteBoolean =
|
|
424
|
-
Boolean,
|
|
425
|
-
union(0, 1),
|
|
426
|
-
(value) => ok(value ? (1 as const) : (0 as const)),
|
|
427
|
-
(value) => value === 1,
|
|
428
|
-
);
|
|
523
|
+
export const SqliteBoolean = union(0, 1);
|
|
429
524
|
export type SqliteBoolean = typeof SqliteBoolean.Type;
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Represents the {@link SqliteBoolean} value for `true`.
|
|
528
|
+
*
|
|
529
|
+
* See {@link SqliteBoolean}.
|
|
530
|
+
*/
|
|
531
|
+
export const sqliteTrue = 1;
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Represents the {@link SqliteBoolean} value for `false`.
|
|
535
|
+
*
|
|
536
|
+
* See {@link SqliteBoolean}.
|
|
537
|
+
*/
|
|
538
|
+
export const sqliteFalse = 0;
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Converts a JavaScript boolean to a {@link SqliteBoolean}.
|
|
542
|
+
*
|
|
543
|
+
* ### Example
|
|
544
|
+
*
|
|
545
|
+
* ```ts
|
|
546
|
+
* const isActive = true;
|
|
547
|
+
* const sqlValue = booleanToSqliteBoolean(isActive); // Returns 1
|
|
548
|
+
* ```
|
|
549
|
+
*/
|
|
550
|
+
export const booleanToSqliteBoolean = (value: boolean): SqliteBoolean =>
|
|
551
|
+
value ? sqliteTrue : sqliteFalse;
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Converts a {@link SqliteBoolean} to a JavaScript boolean.
|
|
555
|
+
*
|
|
556
|
+
* ### Example
|
|
557
|
+
*
|
|
558
|
+
* ```ts
|
|
559
|
+
* const sqlValue: SqliteBoolean = 1;
|
|
560
|
+
* const bool = sqliteBooleanToBoolean(sqlValue); // Returns true
|
|
561
|
+
* ```
|
|
562
|
+
*/
|
|
563
|
+
export const sqliteBooleanToBoolean = (value: SqliteBoolean): boolean =>
|
|
564
|
+
value === sqliteTrue;
|