@evolu/common 6.0.1-preview.8 → 7.0.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.
Files changed (184) hide show
  1. package/README.md +2 -2
  2. package/dist/src/Array.d.ts +256 -12
  3. package/dist/src/Array.d.ts.map +1 -1
  4. package/dist/src/Array.js +215 -9
  5. package/dist/src/Assert.d.ts +0 -13
  6. package/dist/src/Assert.d.ts.map +1 -1
  7. package/dist/src/Assert.js +0 -15
  8. package/dist/src/Brand.d.ts +75 -0
  9. package/dist/src/Brand.d.ts.map +1 -0
  10. package/dist/src/Brand.js +1 -0
  11. package/dist/src/Buffer.d.ts +1 -1
  12. package/dist/src/Buffer.d.ts.map +1 -1
  13. package/dist/src/Buffer.js +8 -7
  14. package/dist/src/Cache.d.ts +44 -0
  15. package/dist/src/Cache.d.ts.map +1 -0
  16. package/dist/src/Cache.js +52 -0
  17. package/dist/src/Callbacks.d.ts +45 -12
  18. package/dist/src/Callbacks.d.ts.map +1 -1
  19. package/dist/src/Callbacks.js +14 -7
  20. package/dist/src/Console.d.ts +31 -6
  21. package/dist/src/Console.d.ts.map +1 -1
  22. package/dist/src/Console.js +72 -9
  23. package/dist/src/Crypto.d.ts +56 -42
  24. package/dist/src/Crypto.d.ts.map +1 -1
  25. package/dist/src/Crypto.js +40 -53
  26. package/dist/src/Evolu/Db.d.ts +162 -74
  27. package/dist/src/Evolu/Db.d.ts.map +1 -1
  28. package/dist/src/Evolu/Db.js +284 -702
  29. package/dist/src/Evolu/Diff.d.ts +3 -3
  30. package/dist/src/Evolu/Diff.d.ts.map +1 -1
  31. package/dist/src/Evolu/Diff.js +7 -5
  32. package/dist/src/Evolu/Evolu.d.ts +214 -134
  33. package/dist/src/Evolu/Evolu.d.ts.map +1 -1
  34. package/dist/src/Evolu/Evolu.js +189 -180
  35. package/dist/src/Evolu/Internal.d.ts +0 -2
  36. package/dist/src/Evolu/Internal.d.ts.map +1 -1
  37. package/dist/src/Evolu/Internal.js +0 -2
  38. package/dist/src/Evolu/LocalAuth.d.ts +150 -0
  39. package/dist/src/Evolu/LocalAuth.d.ts.map +1 -0
  40. package/dist/src/Evolu/LocalAuth.js +174 -0
  41. package/dist/src/Evolu/Owner.d.ts +273 -120
  42. package/dist/src/Evolu/Owner.d.ts.map +1 -1
  43. package/dist/src/Evolu/Owner.js +130 -104
  44. package/dist/src/Evolu/Platform.d.ts +9 -7
  45. package/dist/src/Evolu/Platform.d.ts.map +1 -1
  46. package/dist/src/Evolu/Protocol.d.ts +268 -240
  47. package/dist/src/Evolu/Protocol.d.ts.map +1 -1
  48. package/dist/src/Evolu/Protocol.js +600 -454
  49. package/dist/src/Evolu/Public.d.ts +6 -8
  50. package/dist/src/Evolu/Public.d.ts.map +1 -1
  51. package/dist/src/Evolu/Public.js +2 -3
  52. package/dist/src/Evolu/PublicKysely.d.ts.map +1 -1
  53. package/dist/src/Evolu/PublicKysely.js +3 -4
  54. package/dist/src/Evolu/Query.d.ts +2 -1
  55. package/dist/src/Evolu/Query.d.ts.map +1 -1
  56. package/dist/src/Evolu/Query.js +1 -1
  57. package/dist/src/Evolu/Relay.d.ts +91 -8
  58. package/dist/src/Evolu/Relay.d.ts.map +1 -1
  59. package/dist/src/Evolu/Relay.js +214 -88
  60. package/dist/src/Evolu/Schema.d.ts +125 -47
  61. package/dist/src/Evolu/Schema.d.ts.map +1 -1
  62. package/dist/src/Evolu/Schema.js +175 -31
  63. package/dist/src/Evolu/Storage.d.ts +249 -27
  64. package/dist/src/Evolu/Storage.d.ts.map +1 -1
  65. package/dist/src/Evolu/Storage.js +198 -92
  66. package/dist/src/Evolu/Sync.d.ts +68 -13
  67. package/dist/src/Evolu/Sync.d.ts.map +1 -1
  68. package/dist/src/Evolu/Sync.js +469 -20
  69. package/dist/src/Evolu/Timestamp.d.ts +83 -30
  70. package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
  71. package/dist/src/Evolu/Timestamp.js +79 -34
  72. package/dist/src/Identicon.d.ts +35 -0
  73. package/dist/src/Identicon.d.ts.map +1 -0
  74. package/dist/src/Identicon.js +143 -0
  75. package/dist/src/Instances.d.ts +34 -0
  76. package/dist/src/Instances.d.ts.map +1 -0
  77. package/dist/src/Instances.js +44 -0
  78. package/dist/src/Number.d.ts +4 -3
  79. package/dist/src/Number.d.ts.map +1 -1
  80. package/dist/src/Number.js +5 -4
  81. package/dist/src/Object.d.ts +10 -4
  82. package/dist/src/Object.d.ts.map +1 -1
  83. package/dist/src/Object.js +9 -3
  84. package/dist/src/Platform.d.ts +20 -0
  85. package/dist/src/Platform.d.ts.map +1 -0
  86. package/dist/src/Platform.js +22 -0
  87. package/dist/src/Random.d.ts +3 -2
  88. package/dist/src/Random.d.ts.map +1 -1
  89. package/dist/src/Relation.d.ts +79 -0
  90. package/dist/src/Relation.d.ts.map +1 -0
  91. package/dist/src/Relation.js +127 -0
  92. package/dist/src/Resources.d.ts +118 -0
  93. package/dist/src/Resources.d.ts.map +1 -0
  94. package/dist/src/Resources.js +197 -0
  95. package/dist/src/Result.d.ts +184 -52
  96. package/dist/src/Result.d.ts.map +1 -1
  97. package/dist/src/Result.js +30 -241
  98. package/dist/src/Skiplist.js +2 -1
  99. package/dist/src/Sqlite.d.ts +89 -5
  100. package/dist/src/Sqlite.d.ts.map +1 -1
  101. package/dist/src/Sqlite.js +136 -9
  102. package/dist/src/Task.d.ts +586 -0
  103. package/dist/src/Task.d.ts.map +1 -0
  104. package/dist/src/Task.js +469 -0
  105. package/dist/src/Time.d.ts +66 -1
  106. package/dist/src/Time.d.ts.map +1 -1
  107. package/dist/src/Time.js +99 -5
  108. package/dist/src/Type.d.ts +676 -343
  109. package/dist/src/Type.d.ts.map +1 -1
  110. package/dist/src/Type.js +718 -467
  111. package/dist/src/Types.d.ts +1 -75
  112. package/dist/src/Types.d.ts.map +1 -1
  113. package/dist/src/WebSocket.d.ts +5 -2
  114. package/dist/src/WebSocket.d.ts.map +1 -1
  115. package/dist/src/WebSocket.js +12 -18
  116. package/dist/src/Worker.d.ts +39 -11
  117. package/dist/src/Worker.d.ts.map +1 -1
  118. package/dist/src/Worker.js +22 -4
  119. package/dist/src/index.d.ts +8 -3
  120. package/dist/src/index.d.ts.map +1 -1
  121. package/dist/src/index.js +8 -3
  122. package/package.json +15 -14
  123. package/src/Array.ts +301 -19
  124. package/src/Assert.ts +0 -21
  125. package/src/Brand.ts +75 -0
  126. package/src/Buffer.ts +7 -7
  127. package/src/Cache.ts +85 -0
  128. package/src/Callbacks.ts +62 -22
  129. package/src/Console.ts +91 -11
  130. package/src/Crypto.ts +94 -90
  131. package/src/Evolu/Db.ts +519 -1026
  132. package/src/Evolu/Diff.ts +7 -5
  133. package/src/Evolu/Evolu.ts +477 -361
  134. package/src/Evolu/Internal.ts +0 -2
  135. package/src/Evolu/LocalAuth.ts +463 -0
  136. package/src/Evolu/Owner.ts +355 -228
  137. package/src/Evolu/Platform.ts +9 -9
  138. package/src/Evolu/Protocol.ts +842 -742
  139. package/src/Evolu/Public.ts +7 -14
  140. package/src/Evolu/PublicKysely.ts +4 -5
  141. package/src/Evolu/Query.ts +3 -2
  142. package/src/Evolu/Relay.ts +406 -103
  143. package/src/Evolu/Schema.ts +323 -91
  144. package/src/Evolu/Storage.ts +559 -137
  145. package/src/Evolu/Sync.ts +819 -36
  146. package/src/Evolu/Timestamp.ts +90 -58
  147. package/src/Identicon.ts +197 -0
  148. package/src/Instances.ts +90 -0
  149. package/src/Number.ts +6 -10
  150. package/src/Object.ts +13 -5
  151. package/src/Platform.ts +26 -0
  152. package/src/Random.ts +3 -2
  153. package/src/Relation.ts +234 -0
  154. package/src/Resources.ts +367 -0
  155. package/src/Result.ts +191 -54
  156. package/src/Skiplist.ts +1 -1
  157. package/src/Sqlite.ts +152 -17
  158. package/src/Task.ts +901 -0
  159. package/src/Time.ts +180 -5
  160. package/src/Type.ts +1135 -730
  161. package/src/Types.ts +1 -77
  162. package/src/WebSocket.ts +27 -25
  163. package/src/Worker.ts +72 -23
  164. package/src/index.ts +8 -3
  165. package/dist/src/Evolu/Config.d.ts +0 -69
  166. package/dist/src/Evolu/Config.d.ts.map +0 -1
  167. package/dist/src/Evolu/Config.js +0 -9
  168. package/dist/src/Evolu/Kysely.d.ts +0 -6
  169. package/dist/src/Evolu/Kysely.d.ts.map +0 -1
  170. package/dist/src/Evolu/Kysely.js +0 -21
  171. package/dist/src/ManyToManyMap.d.ts +0 -26
  172. package/dist/src/ManyToManyMap.d.ts.map +0 -1
  173. package/dist/src/ManyToManyMap.js +0 -92
  174. package/dist/src/NanoId.d.ts +0 -27
  175. package/dist/src/NanoId.d.ts.map +0 -1
  176. package/dist/src/NanoId.js +0 -6
  177. package/dist/src/Promise.d.ts +0 -180
  178. package/dist/src/Promise.d.ts.map +0 -1
  179. package/dist/src/Promise.js +0 -176
  180. package/src/Evolu/Config.ts +0 -83
  181. package/src/Evolu/Kysely.ts +0 -38
  182. package/src/ManyToManyMap.ts +0 -140
  183. package/src/NanoId.ts +0 -39
  184. package/src/Promise.ts +0 -295
package/src/Result.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
- * 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
- * }
7
+ * tell us.
25
8
  *
26
- * interface Err<E> {
27
- * readonly ok: false;
28
- * readonly error: E;
29
- * }
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.
30
13
  *
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 synchronous safe code, use `ok` and `err`.
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 is
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
- * ## Examples
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
- * ### Sequential Operations with Short-Circuiting
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
- * ### A function with two different errors:
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 = barize(foo.value);
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
- * ## FAQ
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.
235
+ *
236
+ * ### FAQ
237
+ *
238
+ * #### When should a function return a plain value instead of `Result<T, E>`?
173
239
  *
174
- * ### What if my function doesn't return a value on success?
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
- * ### How do I short-circuit processing of an array on the first error?
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 evolu_owner;`,
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
- * ### How do I handle an array of operations and short-circuit on the first error?
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 (see `LazyValue` in
201
- * `Function.ts`). This way, you only execute each operation as needed, and can
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,17 +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. Evolu's approach keeps error
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.
336
+ * obscure control flow and make debugging harder.
246
337
  */
247
338
  export type Result<T, E> = Ok<T> | Err<E>;
248
339
 
@@ -301,6 +392,22 @@ export interface Err<E> {
301
392
  readonly error: E;
302
393
  }
303
394
 
395
+ /**
396
+ * Extracts the value type from a {@link Result}.
397
+ *
398
+ * @category Utilities
399
+ */
400
+ export type InferOk<R extends Result<any, any>> =
401
+ R extends Ok<infer T> ? T : never;
402
+
403
+ /**
404
+ * Extracts the error type from a {@link Result}.
405
+ *
406
+ * @category Utilities
407
+ */
408
+ export type InferErr<R extends Result<any, any>> =
409
+ R extends Err<infer E> ? E : never;
410
+
304
411
  /**
305
412
  * Creates an {@link Ok} result.
306
413
  *
@@ -355,15 +462,42 @@ export const err = <E>(error: E): Err<E> => ({ ok: false, error });
355
462
  * const config = getOrThrow(loadConfig());
356
463
  * // Safe to use config here
357
464
  * ```
465
+ *
466
+ * Throws: `Error` with the original error attached as `cause`.
358
467
  */
359
468
  export const getOrThrow = <T, E>(result: Result<T, E>): T => {
360
469
  if (result.ok) {
361
470
  return result.value;
362
471
  } else {
363
- throw new Error(`Result error: ${JSON.stringify(result.error)}`);
472
+ throw new Error("getOrThrow", { cause: result.error });
364
473
  }
365
474
  };
366
475
 
476
+ /**
477
+ * Extracts the value from a {@link Result} if it is an `Ok`, or returns `null`
478
+ * if it is an `Err`.
479
+ *
480
+ * **Intended usage:**
481
+ *
482
+ * - When you need to convert a `Result` to a nullable value for APIs that expect
483
+ * `T | null`.
484
+ * - When the error is not important and you just want the value or nothing.
485
+ *
486
+ * ### Example
487
+ *
488
+ * ```ts
489
+ * const parseResult = parseJson('{"key": "value"}');
490
+ * const value = getOrNull(parseResult);
491
+ * // value is unknown | null
492
+ *
493
+ * if (value != null) {
494
+ * console.log("Parsed value:", value);
495
+ * }
496
+ * ```
497
+ */
498
+ export const getOrNull = <T, E>(result: Result<T, E>): T | null =>
499
+ result.ok ? result.value : null;
500
+
367
501
  /**
368
502
  * Wraps synchronous functions that may throw exceptions, returning a
369
503
  * {@link Result}.
@@ -384,7 +518,10 @@ export const getOrThrow = <T, E>(result: Result<T, E>): T => {
384
518
  * const parseJson = (value: string): Result<unknown, ParseJsonError> =>
385
519
  * trySync(
386
520
  * () => JSON.parse(value) as unknown,
387
- * (error) => ({ type: "ParseJsonError", message: String(error) }),
521
+ * (error): ParseJsonError => ({
522
+ * type: "ParseJsonError",
523
+ * message: String(error),
524
+ * }),
388
525
  * );
389
526
  * ```
390
527
  */
package/src/Skiplist.ts CHANGED
@@ -25,7 +25,7 @@ export const createSkiplistLevel =
25
25
  while (deps.random.next() <= probability && level < maxLevel) {
26
26
  level += 1;
27
27
  }
28
- return level as PositiveInt;
28
+ return PositiveInt.orThrow(level);
29
29
  },
30
30
  };
31
31
  };
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 { Brand, Predicate, IntentionalNever } from "./Types.js";
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
- /** TODO: Docs. */
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
- export const isSqlMutation: Predicate<string> = (sql) =>
370
- isSqlMutationRegEx.test(sql.replace(/--.*$/gm, ""));
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("ExplainQueryPlan", query);
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. This transform Type ensures that values conform to
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 = transform(
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;