@evolu/common 8.0.0-next.4 → 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 (237) 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 +44 -44
  5. package/dist/src/Assert.d.ts.map +1 -1
  6. package/dist/src/Assert.js +44 -32
  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 +5 -3
  29. package/dist/src/Error.d.ts.map +1 -1
  30. package/dist/src/Error.js +16 -0
  31. package/dist/src/Function.d.ts +119 -58
  32. package/dist/src/Function.d.ts.map +1 -1
  33. package/dist/src/Function.js +86 -43
  34. package/dist/src/Http.d.ts +394 -0
  35. package/dist/src/Http.d.ts.map +1 -0
  36. package/dist/src/Http.js +102 -0
  37. package/dist/src/Identicon.d.ts +16 -7
  38. package/dist/src/Identicon.d.ts.map +1 -1
  39. package/dist/src/Identicon.js +14 -5
  40. package/dist/src/LeakDetector.d.ts +88 -0
  41. package/dist/src/LeakDetector.d.ts.map +1 -0
  42. package/dist/src/LeakDetector.js +86 -0
  43. package/dist/src/LockManager.d.ts +18 -9
  44. package/dist/src/LockManager.d.ts.map +1 -1
  45. package/dist/src/LockManager.js +7 -8
  46. package/dist/src/Lookup.d.ts +43 -13
  47. package/dist/src/Lookup.d.ts.map +1 -1
  48. package/dist/src/Lookup.js +28 -8
  49. package/dist/src/Microtask.d.ts +1 -1
  50. package/dist/src/Microtask.d.ts.map +1 -1
  51. package/dist/src/Microtask.js +3 -7
  52. package/dist/src/Number.d.ts +49 -13
  53. package/dist/src/Number.d.ts.map +1 -1
  54. package/dist/src/Number.js +34 -5
  55. package/dist/src/Object.d.ts +160 -40
  56. package/dist/src/Object.d.ts.map +1 -1
  57. package/dist/src/Object.js +92 -46
  58. package/dist/src/Option.d.ts +10 -7
  59. package/dist/src/Option.d.ts.map +1 -1
  60. package/dist/src/Option.js +9 -6
  61. package/dist/src/Order.d.ts +21 -21
  62. package/dist/src/Order.d.ts.map +1 -1
  63. package/dist/src/Order.js +18 -21
  64. package/dist/src/Platform.d.ts +31 -0
  65. package/dist/src/Platform.d.ts.map +1 -1
  66. package/dist/src/Platform.js +98 -0
  67. package/dist/src/Random.d.ts +32 -17
  68. package/dist/src/Random.d.ts.map +1 -1
  69. package/dist/src/Random.js +12 -8
  70. package/dist/src/Redacted.d.ts +43 -29
  71. package/dist/src/Redacted.d.ts.map +1 -1
  72. package/dist/src/Redacted.js +14 -3
  73. package/dist/src/Ref.d.ts +11 -3
  74. package/dist/src/Ref.d.ts.map +1 -1
  75. package/dist/src/Ref.js +2 -2
  76. package/dist/src/RefCount.d.ts +2 -6
  77. package/dist/src/RefCount.d.ts.map +1 -1
  78. package/dist/src/RefCount.js +33 -90
  79. package/dist/src/Relation.d.ts +70 -5
  80. package/dist/src/Relation.d.ts.map +1 -1
  81. package/dist/src/Relation.js +77 -2
  82. package/dist/src/Resource.d.ts +590 -155
  83. package/dist/src/Resource.d.ts.map +1 -1
  84. package/dist/src/Resource.js +592 -343
  85. package/dist/src/Result.d.ts +838 -191
  86. package/dist/src/Result.d.ts.map +1 -1
  87. package/dist/src/Result.js +163 -102
  88. package/dist/src/Schedule.d.ts +684 -231
  89. package/dist/src/Schedule.d.ts.map +1 -1
  90. package/dist/src/Schedule.js +766 -295
  91. package/dist/src/Set.d.ts +89 -40
  92. package/dist/src/Set.d.ts.map +1 -1
  93. package/dist/src/Set.js +50 -29
  94. package/dist/src/Sqlite.d.ts +29 -72
  95. package/dist/src/Sqlite.d.ts.map +1 -1
  96. package/dist/src/Sqlite.js +41 -46
  97. package/dist/src/StackTrace.d.ts +40 -0
  98. package/dist/src/StackTrace.d.ts.map +1 -0
  99. package/dist/src/StackTrace.js +140 -0
  100. package/dist/src/Store.d.ts +5 -4
  101. package/dist/src/Store.d.ts.map +1 -1
  102. package/dist/src/Store.js +5 -4
  103. package/dist/src/String.d.ts +7 -1
  104. package/dist/src/String.d.ts.map +1 -1
  105. package/dist/src/String.js +20 -7
  106. package/dist/src/Task.d.ts +3572 -1770
  107. package/dist/src/Task.d.ts.map +1 -1
  108. package/dist/src/Task.js +1879 -1274
  109. package/dist/src/Test.d.ts +17 -125
  110. package/dist/src/Test.d.ts.map +1 -1
  111. package/dist/src/Test.js +17 -68
  112. package/dist/src/Time.d.ts +108 -49
  113. package/dist/src/Time.d.ts.map +1 -1
  114. package/dist/src/Time.js +172 -73
  115. package/dist/src/Type.d.ts +3542 -1926
  116. package/dist/src/Type.d.ts.map +1 -1
  117. package/dist/src/Type.js +3070 -1784
  118. package/dist/src/Types.d.ts +200 -108
  119. package/dist/src/Types.d.ts.map +1 -1
  120. package/dist/src/Types.js +39 -8
  121. package/dist/src/WebSocket.d.ts +48 -23
  122. package/dist/src/WebSocket.d.ts.map +1 -1
  123. package/dist/src/WebSocket.js +15 -6
  124. package/dist/src/Worker.d.ts +94 -36
  125. package/dist/src/Worker.d.ts.map +1 -1
  126. package/dist/src/Worker.js +58 -24
  127. package/dist/src/index.d.ts +55 -52
  128. package/dist/src/index.d.ts.map +1 -1
  129. package/dist/src/index.js +3 -1
  130. package/dist/src/intl/cs.d.ts +94 -0
  131. package/dist/src/intl/cs.d.ts.map +1 -0
  132. package/dist/src/intl/cs.js +220 -0
  133. package/dist/src/intl/index.d.ts +7 -0
  134. package/dist/src/intl/index.d.ts.map +1 -0
  135. package/dist/src/intl/index.js +6 -0
  136. package/dist/src/local-first/Db.d.ts +12 -8
  137. package/dist/src/local-first/Db.d.ts.map +1 -1
  138. package/dist/src/local-first/Db.js +109 -132
  139. package/dist/src/local-first/Error.d.ts +4 -4
  140. package/dist/src/local-first/Evolu.d.ts +338 -92
  141. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  142. package/dist/src/local-first/Evolu.js +47 -55
  143. package/dist/src/local-first/LocalAuth.d.ts +4 -4
  144. package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
  145. package/dist/src/local-first/Owner.d.ts +61 -24
  146. package/dist/src/local-first/Owner.d.ts.map +1 -1
  147. package/dist/src/local-first/Owner.js +45 -8
  148. package/dist/src/local-first/Protocol.d.ts +44 -31
  149. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  150. package/dist/src/local-first/Protocol.js +36 -13
  151. package/dist/src/local-first/Query.d.ts +147 -62
  152. package/dist/src/local-first/Query.d.ts.map +1 -1
  153. package/dist/src/local-first/Query.js +112 -58
  154. package/dist/src/local-first/Relay.d.ts +44 -14
  155. package/dist/src/local-first/Relay.d.ts.map +1 -1
  156. package/dist/src/local-first/Schema.d.ts +92 -41
  157. package/dist/src/local-first/Schema.d.ts.map +1 -1
  158. package/dist/src/local-first/Schema.js +25 -8
  159. package/dist/src/local-first/Shared.d.ts +23 -21
  160. package/dist/src/local-first/Shared.d.ts.map +1 -1
  161. package/dist/src/local-first/Shared.js +100 -57
  162. package/dist/src/local-first/Storage.d.ts +53 -32
  163. package/dist/src/local-first/Storage.d.ts.map +1 -1
  164. package/dist/src/local-first/Storage.js +194 -234
  165. package/dist/src/local-first/Timestamp.d.ts +21 -21
  166. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  167. package/dist/src/local-first/Timestamp.js +2 -2
  168. package/dist/src/local-first/index.d.ts +11 -11
  169. package/package.json +14 -13
  170. package/src/Array.ts +306 -205
  171. package/src/Assert.ts +54 -69
  172. package/src/BigInt.ts +6 -3
  173. package/src/Brand.ts +27 -40
  174. package/src/Buffer.ts +28 -12
  175. package/src/Cache.ts +9 -4
  176. package/src/Callbacks.ts +30 -19
  177. package/src/Console.ts +156 -84
  178. package/src/Crypto.ts +83 -41
  179. package/src/Eq.ts +95 -22
  180. package/src/Error.ts +8 -4
  181. package/src/Function.ts +167 -59
  182. package/src/Http.ts +549 -0
  183. package/src/Identicon.ts +16 -7
  184. package/src/LeakDetector.ts +182 -0
  185. package/src/LockManager.ts +43 -39
  186. package/src/Lookup.ts +62 -28
  187. package/src/Microtask.ts +19 -23
  188. package/src/Number.ts +64 -12
  189. package/src/Object.ts +173 -49
  190. package/src/Option.ts +10 -7
  191. package/src/Order.ts +23 -21
  192. package/src/Platform.ts +136 -0
  193. package/src/Polyfills.ts +6 -12
  194. package/src/Random.ts +32 -19
  195. package/src/Redacted.ts +44 -30
  196. package/src/Ref.ts +14 -6
  197. package/src/RefCount.ts +38 -80
  198. package/src/Relation.ts +202 -10
  199. package/src/Resource.ts +1199 -591
  200. package/src/Result.ts +930 -271
  201. package/src/Schedule.ts +859 -343
  202. package/src/Set.ts +89 -40
  203. package/src/Sqlite.ts +144 -138
  204. package/src/StackTrace.ts +230 -0
  205. package/src/Store.ts +8 -7
  206. package/src/String.ts +22 -5
  207. package/src/Task.ts +5101 -2832
  208. package/src/Test.ts +20 -168
  209. package/src/Time.ts +293 -90
  210. package/src/Type.ts +10034 -4117
  211. package/src/Types.ts +227 -110
  212. package/src/WebSocket.ts +64 -36
  213. package/src/Worker.ts +180 -72
  214. package/src/index.ts +55 -52
  215. package/src/intl/cs.ts +388 -0
  216. package/src/intl/index.ts +7 -0
  217. package/src/local-first/Db.ts +152 -179
  218. package/src/local-first/Error.ts +4 -4
  219. package/src/local-first/Evolu.ts +495 -250
  220. package/src/local-first/LocalAuth.ts +6 -12
  221. package/src/local-first/Owner.ts +58 -21
  222. package/src/local-first/Protocol.ts +49 -31
  223. package/src/local-first/Query.ts +146 -61
  224. package/src/local-first/Relay.ts +51 -21
  225. package/src/local-first/Schema.ts +109 -51
  226. package/src/local-first/Shared.ts +266 -226
  227. package/src/local-first/Storage.ts +266 -264
  228. package/src/local-first/Timestamp.ts +25 -18
  229. package/src/local-first/index.ts +11 -11
  230. package/dist/src/Skiplist.d.ts +0 -28
  231. package/dist/src/Skiplist.d.ts.map +0 -1
  232. package/dist/src/Skiplist.js +0 -64
  233. package/dist/src/Tracer.d.ts +0 -48
  234. package/dist/src/Tracer.d.ts.map +0 -1
  235. package/dist/src/Tracer.js +0 -6
  236. package/src/Skiplist.ts +0 -108
  237. 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,96 +244,87 @@ 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
  );
323
+ const runWithSqlite = disposer.use(run.create({ ...run.deps, sqlite }));
328
324
  const disposables = disposer.move();
329
325
 
330
326
  return {
331
- run: run.addDeps({ sqlite }),
327
+ run: runWithSqlite,
332
328
  sqlite,
333
329
  [Symbol.asyncDispose]: () => disposables.disposeAsync(),
334
330
  };
@@ -393,23 +389,21 @@ export const createPreparedStatementsCache = <P>(
393
389
  }
394
390
  });
395
391
 
396
- const disposables = disposer.move();
397
-
398
- return {
399
- get: (query, alwaysPrepare) => {
400
- assertNotDisposed(disposables);
401
- if (alwaysPrepare !== true && !query.options?.prepare)
402
- return null as never;
403
- let statement = statementsBySql.get(query.sql);
404
- if (!statement) {
405
- statement = factory(query.sql);
406
- statementsBySql.set(query.sql, statement);
407
- }
408
- 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
+ },
409
404
  },
410
-
411
- [Symbol.dispose]: () => disposables.dispose(),
412
- };
405
+ disposer,
406
+ );
413
407
  };
414
408
 
415
409
  /** A double-quoted SQL identifier for safe column or table name interpolation. */
@@ -438,27 +432,32 @@ export type SqlTemplateParam = SqliteValue | SqlIdentifier | RawSql;
438
432
  * ### Example
439
433
  *
440
434
  * ```ts
435
+ * import { sql } from "@evolu/common";
436
+ *
441
437
  * const id = 42;
442
438
  * const name = "Alice";
443
- *
444
- * const result = sqlite.exec(sql`
445
- * select *
446
- * from users
447
- * where id = ${id} and name = ${name};
448
- * `);
449
- *
450
- * // For identifiers
439
+ * const select = sql`
440
+ * select * from users where id = ${id} and name = ${name};
441
+ * `;
451
442
  * const tableName = "users";
452
- * sqlite.exec(sql`
443
+ * const createTable = sql`
453
444
  * create table ${sql.identifier(tableName)} (
454
445
  * "id" text primary key,
455
446
  * "name" text not null
456
447
  * );
457
- * `);
458
- *
459
- * // For raw SQL (use with caution)
448
+ * `;
449
+ * // Use sql.raw only with trusted SQL fragments.
460
450
  * const orderBy = "created_at desc";
461
- * 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
+ * );
462
461
  * ```
463
462
  *
464
463
  * ## TIP
@@ -514,7 +513,10 @@ sql.prepared = (
514
513
  };
515
514
 
516
515
  /** Index metadata stored in `sqlite_master` for a {@link Sqlite} database. */
517
- 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 });
518
520
  export interface SqliteIndex extends InferType<typeof SqliteIndex> {}
519
521
 
520
522
  /** {@link Eq} instance for {@link SqliteIndex}. */
@@ -528,7 +530,10 @@ export const eqSqliteIndex: Eq<SqliteIndex> = /*#__PURE__*/ createEqObject({
528
530
  *
529
531
  * Includes table-column mappings and user-visible indexes.
530
532
  */
531
- 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({
532
537
  tables: /*#__PURE__*/ record(String, /*#__PURE__*/ set(String)),
533
538
  indexes: /*#__PURE__*/ array(SqliteIndex),
534
539
  });
@@ -546,7 +551,7 @@ export const getSqliteSchema =
546
551
  */
547
552
  excludeIndexNamePrefix?: string;
548
553
  } = {}): SqliteSchema => {
549
- const tables = createRecord<string, Set<string>>();
554
+ const tables = createMutableRecord<string, Set<string>>();
550
555
 
551
556
  const tableAndColumnInfoRows = deps.sqlite.exec<{
552
557
  tableName: string;
@@ -569,6 +574,7 @@ export const getSqliteSchema =
569
574
  ? ` and name not like '${excludeIndexNamePrefix.replaceAll("'", "''")}%'`
570
575
  : "";
571
576
 
577
+ // prettier-ignore
572
578
  const indexesRows = deps.sqlite.exec<{ name: string; sql: string }>(
573
579
  sql`
574
580
  select name, sql
@@ -580,19 +586,17 @@ export const getSqliteSchema =
580
586
  `,
581
587
  );
582
588
 
583
- const indexes = indexesRows.rows.map(
584
- (row): SqliteIndex => ({
585
- name: row.name,
586
- /**
587
- * SQLite returns "CREATE INDEX" for "create index" for some reason.
588
- * Other keywords remain unchanged. We have to normalize the casing for
589
- * schema comparison manually.
590
- */
591
- sql: row.sql
592
- .replace("CREATE INDEX", "create index")
593
- .replace("CREATE UNIQUE INDEX", "create unique index"),
594
- }),
595
- );
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
+ }));
596
600
 
597
601
  return { tables, indexes };
598
602
  };
@@ -648,7 +652,7 @@ export const getSqliteSnapshot = (deps: SqliteDep): SqliteSnapshot => {
648
652
  * converting between JavaScript booleans and SQLite boolean values.
649
653
  */
650
654
  export const SqliteBoolean = /*#__PURE__*/ union(0, 1);
651
- export type SqliteBoolean = typeof SqliteBoolean.Type;
655
+ export type SqliteBoolean = typeof SqliteBoolean.Output;
652
656
 
653
657
  /**
654
658
  * Represents the {@link SqliteBoolean} value for `true`.
@@ -670,8 +674,9 @@ export const sqliteFalse = 0;
670
674
  * ### Example
671
675
  *
672
676
  * ```ts
673
- * const isActive = true;
674
- * const sqlValue = booleanToSqliteBoolean(isActive); // Returns 1
677
+ * import { booleanToSqliteBoolean } from "@evolu/common";
678
+ *
679
+ * expect(booleanToSqliteBoolean(true)).toBe(1);
675
680
  * ```
676
681
  */
677
682
  export const booleanToSqliteBoolean = (value: boolean): SqliteBoolean =>
@@ -683,8 +688,9 @@ export const booleanToSqliteBoolean = (value: boolean): SqliteBoolean =>
683
688
  * ### Example
684
689
  *
685
690
  * ```ts
686
- * const sqlValue: SqliteBoolean = 1;
687
- * const bool = sqliteBooleanToBoolean(sqlValue); // Returns true
691
+ * import { sqliteBooleanToBoolean } from "@evolu/common";
692
+ *
693
+ * expect(sqliteBooleanToBoolean(1)).toBe(true);
688
694
  * ```
689
695
  */
690
696
  export const sqliteBooleanToBoolean = (value: SqliteBoolean): boolean =>