@evolu/common 8.0.0-next.5 → 8.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 (236) hide show
  1. package/dist/src/Array.d.ts +279 -173
  2. package/dist/src/Array.d.ts.map +1 -1
  3. package/dist/src/Array.js +105 -101
  4. package/dist/src/Assert.d.ts +39 -44
  5. package/dist/src/Assert.d.ts.map +1 -1
  6. package/dist/src/Assert.js +38 -31
  7. package/dist/src/BigInt.d.ts +6 -3
  8. package/dist/src/BigInt.d.ts.map +1 -1
  9. package/dist/src/BigInt.js +5 -2
  10. package/dist/src/Brand.d.ts +27 -40
  11. package/dist/src/Brand.d.ts.map +1 -1
  12. package/dist/src/Buffer.d.ts +27 -11
  13. package/dist/src/Buffer.d.ts.map +1 -1
  14. package/dist/src/Cache.d.ts +9 -4
  15. package/dist/src/Cache.d.ts.map +1 -1
  16. package/dist/src/Cache.js +8 -3
  17. package/dist/src/Callbacks.d.ts +30 -19
  18. package/dist/src/Callbacks.d.ts.map +1 -1
  19. package/dist/src/Console.d.ts +145 -66
  20. package/dist/src/Console.d.ts.map +1 -1
  21. package/dist/src/Console.js +91 -43
  22. package/dist/src/Crypto.d.ts +85 -42
  23. package/dist/src/Crypto.d.ts.map +1 -1
  24. package/dist/src/Crypto.js +44 -20
  25. package/dist/src/Eq.d.ts +87 -21
  26. package/dist/src/Eq.d.ts.map +1 -1
  27. package/dist/src/Eq.js +67 -19
  28. package/dist/src/Error.d.ts +3 -3
  29. package/dist/src/Error.d.ts.map +1 -1
  30. package/dist/src/Function.d.ts +119 -58
  31. package/dist/src/Function.d.ts.map +1 -1
  32. package/dist/src/Function.js +86 -43
  33. package/dist/src/Http.d.ts +394 -0
  34. package/dist/src/Http.d.ts.map +1 -0
  35. package/dist/src/Http.js +102 -0
  36. package/dist/src/Identicon.d.ts +16 -7
  37. package/dist/src/Identicon.d.ts.map +1 -1
  38. package/dist/src/Identicon.js +14 -5
  39. package/dist/src/LeakDetector.d.ts +88 -0
  40. package/dist/src/LeakDetector.d.ts.map +1 -0
  41. package/dist/src/LeakDetector.js +86 -0
  42. package/dist/src/LockManager.d.ts +18 -9
  43. package/dist/src/LockManager.d.ts.map +1 -1
  44. package/dist/src/LockManager.js +7 -8
  45. package/dist/src/Lookup.d.ts +43 -13
  46. package/dist/src/Lookup.d.ts.map +1 -1
  47. package/dist/src/Lookup.js +28 -8
  48. package/dist/src/Microtask.d.ts +1 -1
  49. package/dist/src/Microtask.d.ts.map +1 -1
  50. package/dist/src/Microtask.js +3 -7
  51. package/dist/src/Number.d.ts +49 -13
  52. package/dist/src/Number.d.ts.map +1 -1
  53. package/dist/src/Number.js +34 -5
  54. package/dist/src/Object.d.ts +160 -40
  55. package/dist/src/Object.d.ts.map +1 -1
  56. package/dist/src/Object.js +92 -46
  57. package/dist/src/Option.d.ts +10 -7
  58. package/dist/src/Option.d.ts.map +1 -1
  59. package/dist/src/Option.js +9 -6
  60. package/dist/src/Order.d.ts +21 -21
  61. package/dist/src/Order.d.ts.map +1 -1
  62. package/dist/src/Order.js +18 -21
  63. package/dist/src/Platform.d.ts +31 -0
  64. package/dist/src/Platform.d.ts.map +1 -1
  65. package/dist/src/Platform.js +98 -0
  66. package/dist/src/Random.d.ts +32 -17
  67. package/dist/src/Random.d.ts.map +1 -1
  68. package/dist/src/Random.js +12 -8
  69. package/dist/src/Redacted.d.ts +43 -29
  70. package/dist/src/Redacted.d.ts.map +1 -1
  71. package/dist/src/Redacted.js +14 -3
  72. package/dist/src/Ref.d.ts +11 -3
  73. package/dist/src/Ref.d.ts.map +1 -1
  74. package/dist/src/Ref.js +2 -2
  75. package/dist/src/RefCount.d.ts +2 -6
  76. package/dist/src/RefCount.d.ts.map +1 -1
  77. package/dist/src/RefCount.js +33 -90
  78. package/dist/src/Relation.d.ts +70 -5
  79. package/dist/src/Relation.d.ts.map +1 -1
  80. package/dist/src/Relation.js +77 -2
  81. package/dist/src/Resource.d.ts +590 -155
  82. package/dist/src/Resource.d.ts.map +1 -1
  83. package/dist/src/Resource.js +592 -343
  84. package/dist/src/Result.d.ts +838 -191
  85. package/dist/src/Result.d.ts.map +1 -1
  86. package/dist/src/Result.js +163 -102
  87. package/dist/src/Schedule.d.ts +684 -231
  88. package/dist/src/Schedule.d.ts.map +1 -1
  89. package/dist/src/Schedule.js +766 -295
  90. package/dist/src/Set.d.ts +89 -40
  91. package/dist/src/Set.d.ts.map +1 -1
  92. package/dist/src/Set.js +50 -29
  93. package/dist/src/Sqlite.d.ts +29 -72
  94. package/dist/src/Sqlite.d.ts.map +1 -1
  95. package/dist/src/Sqlite.js +39 -45
  96. package/dist/src/StackTrace.d.ts +40 -0
  97. package/dist/src/StackTrace.d.ts.map +1 -0
  98. package/dist/src/StackTrace.js +140 -0
  99. package/dist/src/Store.d.ts +5 -4
  100. package/dist/src/Store.d.ts.map +1 -1
  101. package/dist/src/Store.js +5 -4
  102. package/dist/src/String.d.ts +7 -1
  103. package/dist/src/String.d.ts.map +1 -1
  104. package/dist/src/String.js +20 -7
  105. package/dist/src/Task.d.ts +3428 -1629
  106. package/dist/src/Task.d.ts.map +1 -1
  107. package/dist/src/Task.js +1876 -1279
  108. package/dist/src/Test.d.ts +17 -124
  109. package/dist/src/Test.d.ts.map +1 -1
  110. package/dist/src/Test.js +17 -68
  111. package/dist/src/Time.d.ts +108 -49
  112. package/dist/src/Time.d.ts.map +1 -1
  113. package/dist/src/Time.js +172 -73
  114. package/dist/src/Type.d.ts +3542 -1926
  115. package/dist/src/Type.d.ts.map +1 -1
  116. package/dist/src/Type.js +3070 -1784
  117. package/dist/src/Types.d.ts +200 -100
  118. package/dist/src/Types.d.ts.map +1 -1
  119. package/dist/src/Types.js +39 -8
  120. package/dist/src/WebSocket.d.ts +48 -23
  121. package/dist/src/WebSocket.d.ts.map +1 -1
  122. package/dist/src/WebSocket.js +15 -6
  123. package/dist/src/Worker.d.ts +94 -36
  124. package/dist/src/Worker.d.ts.map +1 -1
  125. package/dist/src/Worker.js +58 -24
  126. package/dist/src/index.d.ts +55 -52
  127. package/dist/src/index.d.ts.map +1 -1
  128. package/dist/src/index.js +3 -1
  129. package/dist/src/intl/cs.d.ts +94 -0
  130. package/dist/src/intl/cs.d.ts.map +1 -0
  131. package/dist/src/intl/cs.js +220 -0
  132. package/dist/src/intl/index.d.ts +7 -0
  133. package/dist/src/intl/index.d.ts.map +1 -0
  134. package/dist/src/intl/index.js +6 -0
  135. package/dist/src/local-first/Db.d.ts +12 -8
  136. package/dist/src/local-first/Db.d.ts.map +1 -1
  137. package/dist/src/local-first/Db.js +107 -130
  138. package/dist/src/local-first/Error.d.ts +4 -4
  139. package/dist/src/local-first/Evolu.d.ts +338 -92
  140. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  141. package/dist/src/local-first/Evolu.js +47 -55
  142. package/dist/src/local-first/LocalAuth.d.ts +4 -4
  143. package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
  144. package/dist/src/local-first/Owner.d.ts +61 -24
  145. package/dist/src/local-first/Owner.d.ts.map +1 -1
  146. package/dist/src/local-first/Owner.js +45 -8
  147. package/dist/src/local-first/Protocol.d.ts +44 -31
  148. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  149. package/dist/src/local-first/Protocol.js +36 -13
  150. package/dist/src/local-first/Query.d.ts +147 -62
  151. package/dist/src/local-first/Query.d.ts.map +1 -1
  152. package/dist/src/local-first/Query.js +112 -58
  153. package/dist/src/local-first/Relay.d.ts +44 -14
  154. package/dist/src/local-first/Relay.d.ts.map +1 -1
  155. package/dist/src/local-first/Schema.d.ts +92 -41
  156. package/dist/src/local-first/Schema.d.ts.map +1 -1
  157. package/dist/src/local-first/Schema.js +25 -8
  158. package/dist/src/local-first/Shared.d.ts +23 -21
  159. package/dist/src/local-first/Shared.d.ts.map +1 -1
  160. package/dist/src/local-first/Shared.js +85 -42
  161. package/dist/src/local-first/Storage.d.ts +53 -32
  162. package/dist/src/local-first/Storage.d.ts.map +1 -1
  163. package/dist/src/local-first/Storage.js +194 -234
  164. package/dist/src/local-first/Timestamp.d.ts +21 -21
  165. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  166. package/dist/src/local-first/Timestamp.js +2 -2
  167. package/dist/src/local-first/index.d.ts +11 -11
  168. package/package.json +14 -13
  169. package/src/Array.ts +306 -205
  170. package/src/Assert.ts +48 -68
  171. package/src/BigInt.ts +6 -3
  172. package/src/Brand.ts +27 -40
  173. package/src/Buffer.ts +28 -12
  174. package/src/Cache.ts +9 -4
  175. package/src/Callbacks.ts +30 -19
  176. package/src/Console.ts +156 -84
  177. package/src/Crypto.ts +83 -41
  178. package/src/Eq.ts +95 -22
  179. package/src/Error.ts +5 -2
  180. package/src/Function.ts +167 -59
  181. package/src/Http.ts +549 -0
  182. package/src/Identicon.ts +16 -7
  183. package/src/LeakDetector.ts +182 -0
  184. package/src/LockManager.ts +43 -39
  185. package/src/Lookup.ts +62 -28
  186. package/src/Microtask.ts +19 -23
  187. package/src/Number.ts +64 -12
  188. package/src/Object.ts +173 -49
  189. package/src/Option.ts +10 -7
  190. package/src/Order.ts +23 -21
  191. package/src/Platform.ts +136 -0
  192. package/src/Polyfills.ts +6 -12
  193. package/src/Random.ts +32 -19
  194. package/src/Redacted.ts +44 -30
  195. package/src/Ref.ts +14 -6
  196. package/src/RefCount.ts +38 -80
  197. package/src/Relation.ts +202 -10
  198. package/src/Resource.ts +1199 -591
  199. package/src/Result.ts +930 -271
  200. package/src/Schedule.ts +859 -343
  201. package/src/Set.ts +89 -40
  202. package/src/Sqlite.ts +142 -137
  203. package/src/StackTrace.ts +230 -0
  204. package/src/Store.ts +8 -7
  205. package/src/String.ts +22 -5
  206. package/src/Task.ts +5109 -2845
  207. package/src/Test.ts +20 -167
  208. package/src/Time.ts +293 -90
  209. package/src/Type.ts +10034 -4117
  210. package/src/Types.ts +227 -101
  211. package/src/WebSocket.ts +64 -36
  212. package/src/Worker.ts +180 -72
  213. package/src/index.ts +55 -52
  214. package/src/intl/cs.ts +388 -0
  215. package/src/intl/index.ts +7 -0
  216. package/src/local-first/Db.ts +161 -190
  217. package/src/local-first/Error.ts +4 -4
  218. package/src/local-first/Evolu.ts +495 -250
  219. package/src/local-first/LocalAuth.ts +6 -12
  220. package/src/local-first/Owner.ts +58 -21
  221. package/src/local-first/Protocol.ts +49 -31
  222. package/src/local-first/Query.ts +146 -61
  223. package/src/local-first/Relay.ts +51 -21
  224. package/src/local-first/Schema.ts +109 -51
  225. package/src/local-first/Shared.ts +253 -214
  226. package/src/local-first/Storage.ts +266 -264
  227. package/src/local-first/Timestamp.ts +25 -18
  228. package/src/local-first/index.ts +11 -11
  229. package/dist/src/Skiplist.d.ts +0 -28
  230. package/dist/src/Skiplist.d.ts.map +0 -1
  231. package/dist/src/Skiplist.js +0 -64
  232. package/dist/src/Tracer.d.ts +0 -48
  233. package/dist/src/Tracer.d.ts.map +0 -1
  234. package/dist/src/Tracer.js +0 -6
  235. package/src/Skiplist.ts +0 -108
  236. package/src/Tracer.ts +0 -54
package/src/Set.ts CHANGED
@@ -8,33 +8,38 @@
8
8
  * Use {@link isNonEmptySet} to narrow to {@link NonEmptyReadonlySet} before
9
9
  * calling functions like {@link firstInSet} that require a non-empty set.
10
10
  *
11
- * ### Example
11
+ * ### Composing immutable and native operations
12
12
  *
13
13
  * ```ts
14
- * // Type guards
15
- * const set: ReadonlySet<number> = new Set([1, 2, 3]);
16
- * if (isNonEmptySet(set)) {
17
- * firstInSet(set);
18
- * }
19
- *
20
- * // Immutable transformations
21
- * const added = addToSet(new Set([1, 2]), 3); // Set {1, 2, 3}
22
- * const removed = deleteFromSet(new Set([1, 2, 3]), 2); // Set {1, 3}
23
- * const mapped = mapSet(new Set([1, 2, 3]), (x) => x * 2); // Set {2, 4, 6}
24
- * const filtered = filterSet(new Set([1, 2, 3, 4]), (x) => x % 2 === 0); // Set {2, 4}
25
- *
26
- * // Set operations
27
- * const union = unionSets(new Set([1, 2]), new Set([2, 3])); // Set {1, 2, 3}
28
- * const intersection = intersectSets(new Set([1, 2]), new Set([2, 3])); // Set {2}
29
- * const difference = differenceSets(new Set([1, 2, 3]), new Set([2])); // Set {1, 3}
14
+ * import {
15
+ * addToSet,
16
+ * deleteFromSet,
17
+ * filterSet,
18
+ * firstInSet,
19
+ * isNonEmptySet,
20
+ * mapSet,
21
+ * } from "@evolu/common";
22
+ *
23
+ * const values = addToSet(new Set([1, 2]), 3);
24
+ * const withoutOne = deleteFromSet(values, 1);
25
+ * const doubled = mapSet(withoutOne, (x) => x * 2);
26
+ * const atLeastFour = filterSet(doubled, (x) => x >= 4);
27
+ *
28
+ * // Evolu's readonly results compose with native Set operations.
29
+ * const union = atLeastFour.union(new Set([6, 8]));
30
+ * const intersection = union.intersection(new Set([4, 6, 8, 10]));
31
+ * const difference = intersection.difference(new Set([4]));
32
+ *
33
+ * if (!isNonEmptySet(difference)) throw new Error("Expected values");
34
+ * expect(firstInSet(difference)).toBe(6);
30
35
  * ```
31
36
  *
32
37
  * @module
33
38
  */
34
39
 
35
- import type { NonEmptyReadonlyArray } from "./Array.js";
36
- import type { Brand } from "./Brand.js";
37
- import type { PredicateWithIndex, RefinementWithIndex } from "./Types.js";
40
+ import type { NonEmptyReadonlyArray } from "./Array.ts";
41
+ import type { Brand } from "./Brand.ts";
42
+ import type { PredicateWithIndex, RefinementWithIndex } from "./Types.ts";
38
43
 
39
44
  /**
40
45
  * An empty readonly set.
@@ -50,11 +55,17 @@ export const emptySet: ReadonlySet<never> = /*#__PURE__*/ new Set();
50
55
  *
51
56
  * Preserves non-empty type when the input array is non-empty.
52
57
  *
53
- * ### Example
58
+ * ### Preserving non-empty inputs
54
59
  *
55
60
  * ```ts
56
- * createSet([1, 2, 3]); // NonEmptyReadonlySet<number>
57
- * createSet([] as ReadonlyArray<number>); // ReadonlySet<number>
61
+ * import { createSet, type NonEmptyReadonlySet } from "@evolu/common";
62
+ *
63
+ * const values = createSet([1, 2, 3]);
64
+ * const empty = createSet([] as ReadonlyArray<number>);
65
+ *
66
+ * expectTypeOf(values).toEqualTypeOf<NonEmptyReadonlySet<number>>();
67
+ * expectTypeOf(empty).toEqualTypeOf<ReadonlySet<number>>();
68
+ * expect(values).toEqual(new Set([1, 2, 3]));
58
69
  * ```
59
70
  *
60
71
  * @group Constructors
@@ -94,13 +105,16 @@ export type NonEmptyReadonlySet<T> = ReadonlySet<T> & Brand<"NonEmpty">;
94
105
  * negated guard is better than `.size === 0` for early returns because
95
106
  * TypeScript narrows the type after the check.
96
107
  *
97
- * ### Example
108
+ * ### Narrowing before access
98
109
  *
99
110
  * ```ts
111
+ * import { isNonEmptySet, type NonEmptyReadonlySet } from "@evolu/common";
112
+ *
100
113
  * const set: ReadonlySet<number> = new Set([1, 2, 3]);
101
- * if (isNonEmptySet(set)) {
102
- * firstInSet(set); // set is NonEmptyReadonlySet<number>
103
- * }
114
+ * if (!isNonEmptySet(set)) throw new Error("Expected a non-empty set");
115
+ *
116
+ * expectTypeOf(set).toEqualTypeOf<NonEmptyReadonlySet<number>>();
117
+ * expect(set.size).toBe(3);
104
118
  * ```
105
119
  *
106
120
  * @group Type guards
@@ -115,11 +129,18 @@ export const isNonEmptySet = <T>(
115
129
  * If the item already exists, returns a new set with the same elements (still a
116
130
  * new reference for change detection).
117
131
  *
118
- * ### Example
132
+ * ### Adding without mutation
119
133
  *
120
134
  * ```ts
121
- * addToSet(new Set([1, 2]), 3); // Set {1, 2, 3}
122
- * addToSet(new Set([1, 2]), 2); // Set {1, 2} (new reference)
135
+ * import { addToSet, type NonEmptyReadonlySet } from "@evolu/common";
136
+ *
137
+ * const original: ReadonlySet<number> = new Set([1, 2]);
138
+ * const added = addToSet(original, 3);
139
+ * const unchanged = addToSet(original, 2);
140
+ *
141
+ * expectTypeOf(added).toEqualTypeOf<NonEmptyReadonlySet<number>>();
142
+ * expect(added).toEqual(new Set([1, 2, 3]));
143
+ * expect(unchanged).not.toBe(original);
123
144
  * ```
124
145
  *
125
146
  * @group Transformations
@@ -139,11 +160,14 @@ export const addToSet = <T>(
139
160
  * If the item doesn't exist, returns a new set with the same elements (still a
140
161
  * new reference for change detection).
141
162
  *
142
- * ### Example
163
+ * ### Deleting without mutation
143
164
  *
144
165
  * ```ts
145
- * deleteFromSet(new Set([1, 2, 3]), 2); // Set {1, 3}
146
- * deleteFromSet(new Set([1, 2]), 5); // Set {1, 2} (new reference)
166
+ * import { deleteFromSet } from "@evolu/common";
167
+ *
168
+ * const original = new Set([1, 2, 3]);
169
+ * expect(deleteFromSet(original, 2)).toEqual(new Set([1, 3]));
170
+ * expect(deleteFromSet(original, 5)).not.toBe(original);
147
171
  * ```
148
172
  *
149
173
  * @group Transformations
@@ -165,11 +189,22 @@ export const deleteFromSet = <T>(
165
189
  * Note: If the mapper produces duplicate values, the resulting set will have
166
190
  * fewer elements.
167
191
  *
168
- * ### Example
192
+ * ### Mapping non-empty sets
169
193
  *
170
194
  * ```ts
171
- * mapSet(new Set([1, 2, 3]), (x) => x * 2); // Set {2, 4, 6}
172
- * mapSet(new Set([1, 2, 3]), (x) => x % 2); // Set {1, 0} (duplicates merged)
195
+ * import {
196
+ * createSet,
197
+ * mapSet,
198
+ * type NonEmptyReadonlySet,
199
+ * } from "@evolu/common";
200
+ *
201
+ * const original = createSet([1, 2, 3]);
202
+ * const doubled = mapSet(original, (x) => x * 2);
203
+ * const parity = mapSet(original, (x) => x % 2);
204
+ *
205
+ * expectTypeOf(doubled).toEqualTypeOf<NonEmptyReadonlySet<number>>();
206
+ * expect(doubled).toEqual(new Set([2, 4, 6]));
207
+ * expect(parity).toEqual(new Set([1, 0]));
173
208
  * ```
174
209
  *
175
210
  * @group Transformations
@@ -201,10 +236,21 @@ export function mapSet<T, U>(
201
236
  * When used with a refinement function (with `value is Type` syntax),
202
237
  * TypeScript will narrow the result type.
203
238
  *
204
- * ### Example
239
+ * ### Filtering and refining
205
240
  *
206
241
  * ```ts
207
- * filterSet(new Set([1, 2, 3, 4, 5]), (x) => x % 2 === 0); // Set {2, 4}
242
+ * import { filterSet } from "@evolu/common";
243
+ *
244
+ * const evens = filterSet(new Set([1, 2, 3, 4, 5]), (x) => x % 2 === 0);
245
+ * expect(evens).toEqual(new Set([2, 4]));
246
+ *
247
+ * const mixed: ReadonlySet<string | number> = new Set([1, "a", 2, "b"]);
248
+ * const strings = filterSet(
249
+ * mixed,
250
+ * (value): value is string => typeof value === "string",
251
+ * );
252
+ * expectTypeOf(strings).toEqualTypeOf<ReadonlySet<string>>();
253
+ * expect(strings).toEqual(new Set(["a", "b"]));
208
254
  * ```
209
255
  *
210
256
  * @group Transformations
@@ -235,10 +281,13 @@ export function filterSet<T>(
235
281
  /**
236
282
  * Returns the first element of a non-empty set (by insertion order).
237
283
  *
238
- * ### Example
284
+ * ### Reading the first value
239
285
  *
240
286
  * ```ts
241
- * firstInSet(new Set(["a", "b", "c"])); // "a"
287
+ * import { createSet, firstInSet } from "@evolu/common";
288
+ *
289
+ * const set = createSet(["a", "b", "c"]);
290
+ * expect(firstInSet(set)).toBe("a");
242
291
  * ```
243
292
  *
244
293
  * @group Accessors
package/src/Sqlite.ts CHANGED
@@ -4,30 +4,35 @@
4
4
  * @module
5
5
  */
6
6
 
7
- import { assertNotDisposed } from "./Assert.js";
8
- import type { Brand } from "./Brand.js";
9
- import { bytesToHex, hexToBytes } from "./Buffer.js";
10
- import type { EncryptionKey } from "./Crypto.js";
11
- import type { Eq } from "./Eq.js";
12
- import { createEqObject, eqArrayNumber, eqString } from "./Eq.js";
13
- import { createRecord, objectToEntries } from "./Object.js";
14
- import type { Result } from "./Result.js";
15
- import { ok } from "./Result.js";
16
- import type { Run, Task } from "./Task.js";
17
- import { testCreateRun, type TestDeps } from "./Test.js";
18
- import type { InferType, Name, Typed } from "./Type.js";
7
+ import type { Brand } from "./Brand.ts";
8
+ import { bytesToHex, hexToBytes } from "./Buffer.ts";
9
+ import type { EncryptionKey } from "./Crypto.ts";
10
+ import type { Eq } from "./Eq.ts";
11
+ import { createEqObject, eqArrayNumber, eqString } from "./Eq.ts";
12
+ import { disposable } from "./Function.ts";
13
+ import { createMutableRecord, objectToEntries } from "./Object.ts";
14
+ import type { Result } from "./Result.ts";
15
+ import { ok } from "./Result.ts";
16
+ import { testCreateRun, type Task, type TestRunDep } from "./Task.ts";
19
17
  import {
20
18
  array,
19
+ type ArrayType,
20
+ type InferType,
21
+ type Name,
21
22
  Null,
22
23
  Number,
23
24
  object,
25
+ type ObjectType,
24
26
  record,
27
+ type RecordType,
25
28
  set,
29
+ type SetType,
26
30
  String,
27
31
  testName,
32
+ type Typed,
28
33
  Uint8Array,
29
34
  union,
30
- } from "./Type.js";
35
+ } from "./Type.ts";
31
36
 
32
37
  /**
33
38
  * Platform-agnostic SQLite wrapping a {@link SqliteDriver}.
@@ -96,7 +101,7 @@ export const SqliteValue = /*#__PURE__*/ union(
96
101
  Number,
97
102
  Uint8Array,
98
103
  );
99
- export type SqliteValue = typeof SqliteValue.Type;
104
+ export type SqliteValue = typeof SqliteValue.Output;
100
105
 
101
106
  /** Equality comparison for {@link SqliteValue}. */
102
107
  export const eqSqliteValue: Eq<SqliteValue> = (x, y) =>
@@ -239,91 +244,81 @@ export const createSqlite =
239
244
  const console = run.deps.console.child("sql");
240
245
  await using disposer = new AsyncDisposableStack();
241
246
 
242
- const driverResult = await run(createSqliteDriver(name, options));
243
- if (!driverResult.ok) return driverResult;
244
- const driver = disposer.use(driverResult.value);
247
+ const driver = disposer.use(
248
+ await run.ok(createSqliteDriver(name, options)),
249
+ );
245
250
  console.debug("SQLite driver created");
246
251
 
247
- const disposables = disposer.move();
248
-
249
- const sqlite: Sqlite = {
250
- exec: <R extends SqliteRow = SqliteRow>(query: SqliteQuery) => {
251
- assertNotDisposed(disposables);
252
- console.debug({ query });
253
-
254
- const label =
255
- query.options?.logQueryExecutionTime &&
256
- `SqliteQueryExecutionTime ${query.sql}`;
257
-
258
- if (label) console.time(label);
259
- const result = driver.exec(query);
260
- if (label) console.timeEnd(label);
261
-
262
- if (query.options?.logExplainQueryPlan) {
263
- const result = driver.exec({
264
- ...query,
265
- sql: `EXPLAIN QUERY PLAN ${query.sql}` as SafeSql,
266
- });
267
- console.log("[logExplainQueryPlan]", query);
268
- console.log(
269
- drawSqliteQueryPlan(
270
- result.rows as unknown as Array<SqliteQueryPlanRow>,
271
- ),
272
- );
273
- }
274
-
275
- console.debug({ result });
276
- return result as SqliteExecResult<R>;
277
- },
278
-
279
- transaction: ((callback: () => Result<unknown, unknown> | void) => {
280
- assertNotDisposed(disposables);
281
- console.debug("begin");
282
- driver.exec(sql`begin;`);
283
-
284
- using rollback = new DisposableStack();
285
- let shouldRollback = true;
286
- rollback.defer(() => {
287
- if (!shouldRollback) return;
288
- console.debug("rollback");
289
- driver.exec(sql`rollback;`);
290
- });
291
-
292
- const result = callback();
293
- if (result != null && !result.ok) return result;
294
-
295
- console.debug("commit");
296
- driver.exec(sql`commit;`);
297
- shouldRollback = false;
298
-
299
- return result;
300
- }) as SqliteTransaction,
301
-
302
- export: () => {
303
- assertNotDisposed(disposables);
304
- return driver.export();
305
- },
306
-
307
- [Symbol.asyncDispose]: () => disposables.disposeAsync(),
308
- };
309
-
310
- return ok(sqlite);
252
+ return ok(
253
+ disposable<Sqlite>(
254
+ {
255
+ exec: <R extends SqliteRow = SqliteRow>(query: SqliteQuery) => {
256
+ console.debug({ query });
257
+
258
+ const label =
259
+ query.options?.logQueryExecutionTime &&
260
+ `SqliteQueryExecutionTime ${query.sql}`;
261
+
262
+ if (label) console.time(label);
263
+ const result = driver.exec(query);
264
+ if (label) console.timeEnd(label);
265
+
266
+ if (query.options?.logExplainQueryPlan) {
267
+ const result = driver.exec({
268
+ ...query,
269
+ sql: `EXPLAIN QUERY PLAN ${query.sql}` as SafeSql,
270
+ });
271
+ console.log("[logExplainQueryPlan]", query);
272
+ console.log(
273
+ drawSqliteQueryPlan(
274
+ result.rows as unknown as Array<SqliteQueryPlanRow>,
275
+ ),
276
+ );
277
+ }
278
+
279
+ console.debug({ result });
280
+ return result as SqliteExecResult<R>;
281
+ },
282
+
283
+ transaction: ((callback: () => Result<unknown, unknown> | void) => {
284
+ console.debug("begin");
285
+ driver.exec(sql`begin;`);
286
+
287
+ using rollback = new DisposableStack();
288
+ let shouldRollback = true;
289
+ rollback.defer(() => {
290
+ if (!shouldRollback) return;
291
+ console.debug("rollback");
292
+ driver.exec(sql`rollback;`);
293
+ });
294
+
295
+ const result = callback();
296
+ if (result != null && !result.ok) return result;
297
+
298
+ console.debug("commit");
299
+ driver.exec(sql`commit;`);
300
+ shouldRollback = false;
301
+
302
+ return result;
303
+ }) as SqliteTransaction,
304
+
305
+ export: () => driver.export(),
306
+ },
307
+ disposer,
308
+ ),
309
+ );
311
310
  };
312
311
 
313
312
  /** Creates a test setup with a in-memory {@link Sqlite}. */
314
313
  export const testSetupSqlite = async (
315
314
  deps: CreateSqliteDriverDep,
316
315
  ): Promise<
317
- AsyncDisposable &
318
- SqliteDep & {
319
- readonly run: Run<TestDeps & CreateSqliteDriverDep & SqliteDep>;
320
- readonly sqlite: Sqlite;
321
- }
316
+ AsyncDisposable & SqliteDep & TestRunDep<CreateSqliteDriverDep & SqliteDep>
322
317
  > => {
323
318
  await using disposer = new AsyncDisposableStack();
324
319
  const run = disposer.use(testCreateRun(deps));
325
320
  const sqlite = disposer.use(
326
- await run.orThrow(createSqlite(testName, { mode: "memory" })),
321
+ await run.ok(createSqlite(testName, { mode: "memory" })),
327
322
  );
328
323
  const runWithSqlite = disposer.use(run.create({ ...run.deps, sqlite }));
329
324
  const disposables = disposer.move();
@@ -394,23 +389,21 @@ export const createPreparedStatementsCache = <P>(
394
389
  }
395
390
  });
396
391
 
397
- const disposables = disposer.move();
398
-
399
- return {
400
- get: (query, alwaysPrepare) => {
401
- assertNotDisposed(disposables);
402
- if (alwaysPrepare !== true && !query.options?.prepare)
403
- return null as never;
404
- let statement = statementsBySql.get(query.sql);
405
- if (!statement) {
406
- statement = factory(query.sql);
407
- statementsBySql.set(query.sql, statement);
408
- }
409
- return statement;
392
+ return disposable<PreparedStatements<P>>(
393
+ {
394
+ get: (query, alwaysPrepare) => {
395
+ if (alwaysPrepare !== true && !query.options?.prepare)
396
+ return null as never;
397
+ let statement = statementsBySql.get(query.sql);
398
+ if (!statement) {
399
+ statement = factory(query.sql);
400
+ statementsBySql.set(query.sql, statement);
401
+ }
402
+ return statement;
403
+ },
410
404
  },
411
-
412
- [Symbol.dispose]: () => disposables.dispose(),
413
- };
405
+ disposer,
406
+ );
414
407
  };
415
408
 
416
409
  /** A double-quoted SQL identifier for safe column or table name interpolation. */
@@ -439,27 +432,32 @@ export type SqlTemplateParam = SqliteValue | SqlIdentifier | RawSql;
439
432
  * ### Example
440
433
  *
441
434
  * ```ts
435
+ * import { sql } from "@evolu/common";
436
+ *
442
437
  * const id = 42;
443
438
  * const name = "Alice";
444
- *
445
- * const result = sqlite.exec(sql`
446
- * select *
447
- * from users
448
- * where id = ${id} and name = ${name};
449
- * `);
450
- *
451
- * // For identifiers
439
+ * const select = sql`
440
+ * select * from users where id = ${id} and name = ${name};
441
+ * `;
452
442
  * const tableName = "users";
453
- * sqlite.exec(sql`
443
+ * const createTable = sql`
454
444
  * create table ${sql.identifier(tableName)} (
455
445
  * "id" text primary key,
456
446
  * "name" text not null
457
447
  * );
458
- * `);
459
- *
460
- * // For raw SQL (use with caution)
448
+ * `;
449
+ * // Use sql.raw only with trusted SQL fragments.
461
450
  * const orderBy = "created_at desc";
462
- * sqlite.exec(sql`select * from users order by ${sql.raw(orderBy)};`);
451
+ * const ordered = sql`select * from users order by ${sql.raw(orderBy)};`;
452
+ *
453
+ * expect(select).toEqual({
454
+ * sql: "select * from users where id = ? and name = ?;",
455
+ * parameters: [42, "Alice"],
456
+ * });
457
+ * expect(createTable.sql).toContain('create table "users"');
458
+ * expect(ordered.sql).toBe(
459
+ * "select * from users order by created_at desc;",
460
+ * );
463
461
  * ```
464
462
  *
465
463
  * ## TIP
@@ -515,7 +513,10 @@ sql.prepared = (
515
513
  };
516
514
 
517
515
  /** Index metadata stored in `sqlite_master` for a {@link Sqlite} database. */
518
- export const SqliteIndex = /*#__PURE__*/ object({ name: String, sql: String });
516
+ export const SqliteIndex: ObjectType<{
517
+ readonly name: typeof String;
518
+ readonly sql: typeof String;
519
+ }> = /*#__PURE__*/ object({ name: String, sql: String });
519
520
  export interface SqliteIndex extends InferType<typeof SqliteIndex> {}
520
521
 
521
522
  /** {@link Eq} instance for {@link SqliteIndex}. */
@@ -529,7 +530,10 @@ export const eqSqliteIndex: Eq<SqliteIndex> = /*#__PURE__*/ createEqObject({
529
530
  *
530
531
  * Includes table-column mappings and user-visible indexes.
531
532
  */
532
- export const SqliteSchema = /*#__PURE__*/ object({
533
+ export const SqliteSchema: ObjectType<{
534
+ readonly tables: RecordType<typeof String, SetType<typeof String>>;
535
+ readonly indexes: ArrayType<typeof SqliteIndex>;
536
+ }> = /*#__PURE__*/ object({
533
537
  tables: /*#__PURE__*/ record(String, /*#__PURE__*/ set(String)),
534
538
  indexes: /*#__PURE__*/ array(SqliteIndex),
535
539
  });
@@ -547,7 +551,7 @@ export const getSqliteSchema =
547
551
  */
548
552
  excludeIndexNamePrefix?: string;
549
553
  } = {}): SqliteSchema => {
550
- const tables = createRecord<string, Set<string>>();
554
+ const tables = createMutableRecord<string, Set<string>>();
551
555
 
552
556
  const tableAndColumnInfoRows = deps.sqlite.exec<{
553
557
  tableName: string;
@@ -570,6 +574,7 @@ export const getSqliteSchema =
570
574
  ? ` and name not like '${excludeIndexNamePrefix.replaceAll("'", "''")}%'`
571
575
  : "";
572
576
 
577
+ // prettier-ignore
573
578
  const indexesRows = deps.sqlite.exec<{ name: string; sql: string }>(
574
579
  sql`
575
580
  select name, sql
@@ -581,19 +586,17 @@ export const getSqliteSchema =
581
586
  `,
582
587
  );
583
588
 
584
- const indexes = indexesRows.rows.map(
585
- (row): SqliteIndex => ({
586
- name: row.name,
587
- /**
588
- * SQLite returns "CREATE INDEX" for "create index" for some reason.
589
- * Other keywords remain unchanged. We have to normalize the casing for
590
- * schema comparison manually.
591
- */
592
- sql: row.sql
593
- .replace("CREATE INDEX", "create index")
594
- .replace("CREATE UNIQUE INDEX", "create unique index"),
595
- }),
596
- );
589
+ const indexes = indexesRows.rows.map((row): SqliteIndex => ({
590
+ name: row.name,
591
+ /**
592
+ * SQLite returns "CREATE INDEX" for "create index" for some reason. Other
593
+ * keywords remain unchanged. We have to normalize the casing for schema
594
+ * comparison manually.
595
+ */
596
+ sql: row.sql
597
+ .replace("CREATE INDEX", "create index")
598
+ .replace("CREATE UNIQUE INDEX", "create unique index"),
599
+ }));
597
600
 
598
601
  return { tables, indexes };
599
602
  };
@@ -649,7 +652,7 @@ export const getSqliteSnapshot = (deps: SqliteDep): SqliteSnapshot => {
649
652
  * converting between JavaScript booleans and SQLite boolean values.
650
653
  */
651
654
  export const SqliteBoolean = /*#__PURE__*/ union(0, 1);
652
- export type SqliteBoolean = typeof SqliteBoolean.Type;
655
+ export type SqliteBoolean = typeof SqliteBoolean.Output;
653
656
 
654
657
  /**
655
658
  * Represents the {@link SqliteBoolean} value for `true`.
@@ -671,8 +674,9 @@ export const sqliteFalse = 0;
671
674
  * ### Example
672
675
  *
673
676
  * ```ts
674
- * const isActive = true;
675
- * const sqlValue = booleanToSqliteBoolean(isActive); // Returns 1
677
+ * import { booleanToSqliteBoolean } from "@evolu/common";
678
+ *
679
+ * expect(booleanToSqliteBoolean(true)).toBe(1);
676
680
  * ```
677
681
  */
678
682
  export const booleanToSqliteBoolean = (value: boolean): SqliteBoolean =>
@@ -684,8 +688,9 @@ export const booleanToSqliteBoolean = (value: boolean): SqliteBoolean =>
684
688
  * ### Example
685
689
  *
686
690
  * ```ts
687
- * const sqlValue: SqliteBoolean = 1;
688
- * const bool = sqliteBooleanToBoolean(sqlValue); // Returns true
691
+ * import { sqliteBooleanToBoolean } from "@evolu/common";
692
+ *
693
+ * expect(sqliteBooleanToBoolean(1)).toBe(true);
689
694
  * ```
690
695
  */
691
696
  export const sqliteBooleanToBoolean = (value: SqliteBoolean): boolean =>