@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/Eq.ts CHANGED
@@ -4,30 +4,85 @@
4
4
  * @module
5
5
  */
6
6
 
7
- import type { Order } from "./Order.js";
7
+ import type { Order } from "./Order.ts";
8
8
  import type {
9
9
  JsonArray,
10
10
  JsonObject,
11
11
  JsonValue,
12
12
  JsonValueInput,
13
- } from "./Type.js";
13
+ } from "./Type.ts";
14
14
 
15
15
  /**
16
16
  * Compares two values of the same type `A` for equality.
17
17
  *
18
18
  * Equality functions start with an 'eq' prefix, e.g., `eqString`.
19
19
  *
20
- * TODO: Explain, examples (composition etc.)
20
+ * An `Eq` must define an equivalence relation over its intended domain:
21
+ *
22
+ * - **Reflexive**: `eq(a, a)` is `true`.
23
+ * - **Symmetric**: `eq(a, b)` equals `eq(b, a)`.
24
+ * - **Transitive**: if `eq(a, b)` and `eq(b, c)` are `true`, then `eq(a, c)` is
25
+ * `true`.
26
+ *
27
+ * Use {@link eqFromOrder} to derive equality from an {@link Order}.
28
+ *
29
+ * ### Example
30
+ *
31
+ * ```ts
32
+ * import { createEqObject, eqNumber } from "@evolu/common";
33
+ *
34
+ * const eqPoint = createEqObject({ x: eqNumber, y: eqNumber });
35
+ *
36
+ * expect(eqPoint({ x: 1, y: 2 }, { x: 1, y: 2 })).toBe(true);
37
+ * expect(eqPoint({ x: 1, y: 2 }, { x: 2, y: 1 })).toBe(false);
38
+ * ```
21
39
  */
22
40
  export type Eq<in A> = (x: A, y: A) => boolean;
23
41
 
42
+ /**
43
+ * Compares two values with strict equality (`===`).
44
+ *
45
+ * Strict equality considers `NaN` unequal to itself. Use {@link eqSameValueZero}
46
+ * when values can contain `NaN` and equality must be reflexive.
47
+ */
24
48
  export const eqStrict = <A>(x: A, y: A): boolean => x === y;
25
49
 
50
+ /**
51
+ * Compares two values using SameValueZero equality.
52
+ *
53
+ * SameValueZero is the standard equality algorithm used by `Map`, `Set`, and
54
+ * `Array.prototype.includes`. It behaves like strict equality except that `NaN`
55
+ * equals itself. Both algorithms consider `0` and `-0` equal.
56
+ *
57
+ * ### Example
58
+ *
59
+ * ```ts
60
+ * import { eqSameValueZero } from "@evolu/common";
61
+ *
62
+ * expect(eqSameValueZero(NaN, NaN)).toBe(true);
63
+ * expect(eqSameValueZero(0, -0)).toBe(true);
64
+ * expect(eqSameValueZero({}, {})).toBe(false);
65
+ * ```
66
+ */
67
+ export const eqSameValueZero = <A>(x: A, y: A): boolean =>
68
+ x === y || Object.is(x, y);
69
+
70
+ /** An {@link Eq} for strings using strict equality. */
26
71
  export const eqString: Eq<string> = eqStrict;
27
- export const eqNumber: Eq<number> = eqStrict;
72
+
73
+ /** An {@link Eq} for numbers using {@link eqSameValueZero}. */
74
+ export const eqNumber: Eq<number> = eqSameValueZero;
75
+
76
+ /** An {@link Eq} for bigints using strict equality. */
28
77
  export const eqBigInt: Eq<bigint> = eqStrict;
78
+
79
+ /** An {@link Eq} for booleans using strict equality. */
29
80
  export const eqBoolean: Eq<boolean> = eqStrict;
81
+
82
+ /** An {@link Eq} for `undefined`. */
30
83
  export const eqUndefined: Eq<undefined> = eqStrict;
84
+
85
+ /** An {@link Eq} for `null`. */
31
86
  export const eqNull: Eq<null> = eqStrict;
32
87
 
33
88
  /** Derives an {@link Eq} from an {@link Order}. */
@@ -43,10 +98,15 @@ export const eqFromOrder =
43
98
  * ### Example
44
99
  *
45
100
  * ```ts
101
+ * import { createEqArrayLike, eqNumber } from "@evolu/common";
102
+ *
46
103
  * const eqArrayNumber = createEqArrayLike(eqNumber);
47
- * eqArrayNumber([1, 2, 3], [1, 2, 3]); // true (works with regular arrays)
48
- * eqArrayNumber(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 3])); // true (works with Uint8Array)
49
- * eqArrayNumber([1, 2, 3], [1, 2, 4]); // false
104
+ *
105
+ * expect(eqArrayNumber([1, 2, 3], [1, 2, 3])).toBe(true);
106
+ * expect(
107
+ * eqArrayNumber(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 3])),
108
+ * ).toBe(true);
109
+ * expect(eqArrayNumber([1, 2, 3], [1, 2, 4])).toBe(false);
50
110
  * ```
51
111
  */
52
112
  export const createEqArrayLike =
@@ -70,10 +130,12 @@ export const createEqArrayLike =
70
130
  * ### Example
71
131
  *
72
132
  * ```ts
133
+ * import { eqArrayStrict } from "@evolu/common";
134
+ *
73
135
  * const a = { x: 1 };
74
136
  * const b = { x: 1 };
75
- * eqArrayStrict([a, a], [a, a]); // true (same references)
76
- * eqArrayStrict([a], [b]); // false (different references, even if equal values)
137
+ * expect(eqArrayStrict([a, a], [a, a])).toBe(true);
138
+ * expect(eqArrayStrict([a], [b])).toBe(false);
77
139
  * ```
78
140
  */
79
141
  export const eqArrayStrict = /*#__PURE__*/ createEqArrayLike(eqStrict);
@@ -84,9 +146,9 @@ export const eqArrayStrict = /*#__PURE__*/ createEqArrayLike(eqStrict);
84
146
  * ### Example
85
147
  *
86
148
  * ```ts
87
- * eqArrayNumber([1, 2, 3], [1, 2, 3]); // true (works with regular arrays)
88
- * eqArrayNumber(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 3])); // true (works with Uint8Array)
89
- * eqArrayNumber([1, 2, 3], [1, 2, 4]); // false
149
+ * import { eqArrayNumber } from "@evolu/common";
150
+ *
151
+ * expect(eqArrayNumber([1, NaN], [1, NaN])).toBe(true);
90
152
  * ```
91
153
  */
92
154
  export const eqArrayNumber = /*#__PURE__*/ createEqArrayLike(eqNumber);
@@ -98,9 +160,12 @@ export const eqArrayNumber = /*#__PURE__*/ createEqArrayLike(eqNumber);
98
160
  * ### Example
99
161
  *
100
162
  * ```ts
163
+ * import { createEqObject, eqNumber } from "@evolu/common";
164
+ *
101
165
  * const eqObjectNumber = createEqObject({ a: eqNumber });
102
- * eqObjectNumber({ a: 1 }, { a: 1 }); // true
103
- * eqObjectNumber({ a: 1 }, { a: 2 }); // false
166
+ *
167
+ * expect(eqObjectNumber({ a: 1 }, { a: 1 })).toBe(true);
168
+ * expect(eqObjectNumber({ a: 1 }, { a: 2 })).toBe(false);
104
169
  * ```
105
170
  */
106
171
  export const createEqObject =
@@ -122,7 +187,8 @@ export const createEqObject =
122
187
  *
123
188
  * - Uses an iterative approach with a stack to handle large or deeply nested
124
189
  * objects without risking stack overflow.
125
- * - Handles circular references with a WeakMap to prevent infinite loops.
190
+ * - Defensively handles circular references in runtime values without looping,
191
+ * although cyclic values are not valid JSON.
126
192
  * - Unlike JSON.stringify, this function directly compares values, avoiding
127
193
  * serialization overhead and leveraging short-circuit evaluation for faster
128
194
  * failure on mismatched structures.
@@ -130,9 +196,12 @@ export const createEqObject =
130
196
  * ### Example
131
197
  *
132
198
  * ```ts
133
- * const obj1: Json = { name: "Alice", hobbies: ["reading", "hiking"] };
134
- * const obj2: Json = { name: "Alice", hobbies: ["reading", "hiking"] };
135
- * console.log(eqJson(obj1, obj2)); // true
199
+ * import { eqJsonValue, type JsonValue } from "@evolu/common";
200
+ *
201
+ * const first: JsonValue = { profile: { name: "Ada" } };
202
+ * const second: JsonValue = { profile: { name: "Ada" } };
203
+ *
204
+ * expect(eqJsonValue(first, second)).toBe(true);
136
205
  * ```
137
206
  */
138
207
  export const eqJsonValue = (a: JsonValue, b: JsonValue): boolean => {
@@ -212,7 +281,8 @@ export const eqJsonValue = (a: JsonValue, b: JsonValue): boolean => {
212
281
  *
213
282
  * - Uses an iterative approach with a stack to handle large or deeply nested
214
283
  * objects without risking stack overflow.
215
- * - Handles circular references with a WeakMap to prevent infinite loops.
284
+ * - Defensively handles circular references in runtime values without looping,
285
+ * although cyclic values are not valid JSON.
216
286
  * - Unlike JSON.stringify, this function directly compares values, avoiding
217
287
  * serialization overhead and leveraging short-circuit evaluation for faster
218
288
  * failure on mismatched structures.
@@ -220,9 +290,12 @@ export const eqJsonValue = (a: JsonValue, b: JsonValue): boolean => {
220
290
  * ### Example
221
291
  *
222
292
  * ```ts
223
- * const obj1: Json = { name: "Alice", hobbies: ["reading", "hiking"] };
224
- * const obj2: Json = { name: "Alice", hobbies: ["reading", "hiking"] };
225
- * console.log(eqJson(obj1, obj2)); // true
293
+ * import { eqJsonValueInput, type JsonValueInput } from "@evolu/common";
294
+ *
295
+ * const first: JsonValueInput = { profile: { name: "Ada" } };
296
+ * const second: JsonValueInput = { profile: { name: "Ada" } };
297
+ *
298
+ * expect(eqJsonValueInput(first, second)).toBe(true);
226
299
  * ```
227
300
  */
228
301
  export const eqJsonValueInput = (
package/src/Error.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * @module
5
5
  */
6
6
 
7
- import type { Typed } from "./Type.js";
7
+ import { type InferType, typed, type TypedType, Unknown } from "./Type.ts";
8
8
 
9
9
  /**
10
10
  * A wrapper for unknown errors caught at runtime.
@@ -18,9 +18,13 @@ import type { Typed } from "./Type.js";
18
18
  *
19
19
  * Use {@link createUnknownError} to create instances.
20
20
  */
21
- export interface UnknownError extends Typed<"UnknownError"> {
22
- readonly error: unknown;
23
- }
21
+ export const UnknownError: TypedType<
22
+ "UnknownError",
23
+ { readonly error: typeof Unknown }
24
+ > = /*#__PURE__*/ typed("UnknownError", {
25
+ error: Unknown,
26
+ });
27
+ export interface UnknownError extends InferType<typeof UnknownError> {}
24
28
 
25
29
  /**
26
30
  * Creates an {@link UnknownError} from an unknown error.
package/src/Function.ts CHANGED
@@ -4,6 +4,10 @@
4
4
  * @module
5
5
  */
6
6
 
7
+ import { assertNotDisposed } from "./Assert.ts";
8
+ import { isFunction } from "./Object.ts";
9
+ import type { Writable } from "./Types.ts";
10
+
7
11
  /**
8
12
  * Helper function to ensure exhaustive matching in a switch statement. Throws
9
13
  * an error if an unhandled case is encountered.
@@ -11,32 +15,36 @@
11
15
  * ### Example
12
16
  *
13
17
  * ```ts
18
+ * import { exhaustiveCheck } from "@evolu/common";
19
+ *
14
20
  * type Color = "red" | "green" | "blue";
21
+ * const handled: Array<string> = [];
15
22
  *
16
23
  * const handleColor = (color: Color): void => {
17
24
  * switch (color) {
18
25
  * case "red":
19
- * console.log("Handling red");
26
+ * handled.push("Handling red");
20
27
  * break;
21
28
  * case "green":
22
- * console.log("Handling green");
29
+ * handled.push("Handling green");
23
30
  * break;
24
31
  * case "blue":
25
- * console.log("Handling blue");
32
+ * handled.push("Handling blue");
26
33
  * break;
27
34
  * default:
28
- * exhaustiveCheck(color); // Ensures all cases are handled
35
+ * exhaustiveCheck(color);
29
36
  * }
30
37
  * };
38
+ *
39
+ * handleColor("blue");
40
+ * expect(handled).toEqual(["Handling blue"]);
31
41
  * ```
32
42
  *
33
43
  * Use this primarily in side-effect switches (`void` branches). For
34
44
  * value-producing switches, TypeScript can enforce exhaustiveness without a
35
- * `default` branch.
36
- *
37
- * ### Example
45
+ * `default` branch in either of the following styles.
38
46
  *
39
- * Return from each case for value-producing switches.
47
+ * ### Return from every case
40
48
  *
41
49
  * ```ts
42
50
  * type Color = "red" | "green" | "blue";
@@ -51,11 +59,11 @@
51
59
  * return "#0000ff";
52
60
  * }
53
61
  * };
54
- * ```
55
62
  *
56
- * ### Example
63
+ * expect(colorToHex("green")).toBe("#00ff00");
64
+ * ```
57
65
  *
58
- * Use assignment + no `default` to get exhaustiveness by definite assignment.
66
+ * ### Assign in every case
59
67
  *
60
68
  * ```ts
61
69
  * type Input =
@@ -63,7 +71,7 @@
63
71
  * | { readonly type: "Query" }
64
72
  * | { readonly type: "Export" };
65
73
  *
66
- * const onInput = (input: Input): void => {
74
+ * const inputToKind = (input: Input): "A" | "B" | "C" => {
67
75
  * let result: "A" | "B" | "C";
68
76
  *
69
77
  * switch (input.type) {
@@ -78,12 +86,10 @@
78
86
  * break;
79
87
  * }
80
88
  *
81
- * handleKind(result);
89
+ * return result;
82
90
  * };
83
91
  *
84
- * const handleKind = (kind: "A" | "B" | "C"): void => {
85
- * console.log(kind);
86
- * };
92
+ * expect(inputToKind({ type: "Query" })).toBe("B");
87
93
  * ```
88
94
  */
89
95
  export const exhaustiveCheck = (value: never): never => {
@@ -99,81 +105,181 @@ export const exhaustiveCheck = (value: never): never => {
99
105
  * ### Example
100
106
  *
101
107
  * ```ts
102
- * const values = [1, 2, 3];
103
- * const same = values.map(identity); // [1, 2, 3]
108
+ * import { identity } from "@evolu/common";
104
109
  *
110
+ * const values = [1, 2, 3];
111
+ * const object = { value: 1 };
105
112
  * const getTransform = (shouldDouble: boolean) =>
106
- * shouldDouble ? (x: number) => x * 2 : identity;
113
+ * shouldDouble ? (value: number) => value * 2 : identity;
114
+ *
115
+ * expect(values.map(identity)).toEqual([1, 2, 3]);
116
+ * expect(identity(object)).toBe(object);
117
+ * expect(getTransform(false)(2)).toBe(2);
107
118
  * ```
108
119
  */
109
120
  export const identity = <A>(a: A): A => a;
110
121
 
111
122
  /**
112
- * A function that takes no arguments and returns a value of type T. Also known
113
- * as a thunk.
123
+ * Creates an object that follows JavaScript disposal semantics.
124
+ *
125
+ * The first argument is the object to make disposable. The returned object gets
126
+ * a disposal method and its functions are wrapped with a disposal guard. This
127
+ * is the JavaScript equivalent of .NET `ObjectDisposedException`: once an
128
+ * object has been disposed, calling its methods is a programmer error and
129
+ * should throw immediately instead of continuing with invalid state. Evolu
130
+ * asserts this invariant with the "Cannot use a disposed object." message.
131
+ *
132
+ * The second argument is an optional disposer. When provided, it is
133
+ * [moved](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DisposableStack/move)
134
+ * into the returned object, and the returned object's disposal method disposes
135
+ * it. Omit it when the object has no cleanup resources but still must become
136
+ * unusable after disposal, such as with reference count helpers where disposal
137
+ * enforces correct ownership tracking.
138
+ *
139
+ * ### Example
140
+ *
141
+ * ```ts
142
+ * import { disposable } from "@evolu/common";
143
+ *
144
+ * let cleaned = false;
145
+ * const createResource = () => {
146
+ * using disposer = new DisposableStack();
147
+ * disposer.defer(() => {
148
+ * cleaned = true;
149
+ * });
150
+ * return disposable({ read: () => "ready" }, disposer);
151
+ * };
152
+ *
153
+ * const resource = createResource();
154
+ * expect(resource.read()).toBe("ready");
155
+ * resource[Symbol.dispose]();
156
+ *
157
+ * expect(cleaned).toBe(true);
158
+ * expect(() => resource.read()).toThrow("Cannot use a disposed object.");
159
+ * ```
160
+ */
161
+ export function disposable<T extends object>(
162
+ value: T extends Disposable ? Omit<T, typeof Symbol.dispose> : T,
163
+ disposer?: DisposableStack,
164
+ ): T extends Disposable ? T : T & Disposable;
165
+ export function disposable<T extends object>(
166
+ value: T extends AsyncDisposable ? Omit<T, typeof Symbol.asyncDispose> : T,
167
+ disposer: AsyncDisposableStack,
168
+ ): T extends AsyncDisposable ? T : T & AsyncDisposable;
169
+ export function disposable<T extends object>(
170
+ value: T,
171
+ disposer: DisposableStack | AsyncDisposableStack | null = null,
172
+ ): T & (Disposable | AsyncDisposable) {
173
+ const mutableValue = value as Writable<Record<string, unknown>>;
174
+ const ownedDisposer = disposer?.move() ?? new DisposableStack();
175
+
176
+ for (const [key, property] of Object.entries(value)) {
177
+ if (!isFunction(property)) continue;
178
+
179
+ mutableValue[key] = (...args: Array<unknown>): unknown => {
180
+ assertNotDisposed(ownedDisposer);
181
+ return (property as (...args: Array<unknown>) => unknown)(...args);
182
+ };
183
+ }
184
+
185
+ if (ownedDisposer instanceof AsyncDisposableStack) {
186
+ (value as T & AsyncDisposable)[Symbol.asyncDispose] = () =>
187
+ ownedDisposer.disposeAsync();
188
+ } else {
189
+ (value as T & Disposable)[Symbol.dispose] = () => ownedDisposer.dispose();
190
+ }
191
+
192
+ return value as T & (Disposable | AsyncDisposable);
193
+ }
194
+
195
+ export const isDisposable = (
196
+ value: unknown,
197
+ ): value is Disposable | AsyncDisposable => {
198
+ if (typeof value !== "object" || value === null) return false;
199
+
200
+ return (
201
+ isFunction((value as Partial<Disposable>)[Symbol.dispose]) ||
202
+ isFunction((value as Partial<AsyncDisposable>)[Symbol.asyncDispose])
203
+ );
204
+ };
205
+
206
+ /**
207
+ * A function that takes no arguments and returns a value.
114
208
  *
115
209
  * Useful for:
116
210
  *
117
- * - Providing default callbacks (see {@link lazyVoid}, {@link lazyTrue}, etc.)
211
+ * - Providing default callbacks (see {@link constVoid}, {@link constTrue}, etc.)
118
212
  * - Delaying expensive operations until actually needed
119
213
  * - Deferring side effects so the callee controls when they run
120
214
  *
121
215
  * ### Example
122
216
  *
123
217
  * ```ts
124
- * // Default callback
125
- * const notify = (onDone: Lazy<void> = lazyVoid) => {
126
- * onDone();
127
- * };
218
+ * import { constVoid, type Thunk } from "@evolu/common";
128
219
  *
129
- * // Delay computation
130
- * const getData: Lazy<Data> = () => compute();
131
- * const data = getData();
220
+ * const notify = (onDone: Thunk<void> = constVoid) => onDone();
221
+ * notify();
132
222
  *
133
- * // Defer side effects
134
- * const schedule = (job: Lazy<void>) => {
135
- * queueMicrotask(job);
223
+ * let value = 0;
224
+ * const compute: Thunk<number> = () => ++value;
225
+ * const jobs: Array<Thunk<void>> = [];
226
+ * const schedule = (job: Thunk<void>): void => {
227
+ * jobs.push(job);
136
228
  * };
137
- * schedule(() => logMetric("loaded"));
229
+ * schedule(() => {
230
+ * value += 10;
231
+ * });
232
+ *
233
+ * const computed = compute();
234
+ * jobs.shift()?.();
235
+ * expect(computed).toBe(1);
236
+ * expect(value).toBe(11);
138
237
  * ```
139
238
  */
140
- export type Lazy<T> = () => T;
239
+ export type Thunk<T> = () => T;
141
240
 
142
241
  /**
143
- * Creates a {@link Lazy} from a precomputed value.
242
+ * Creates a {@link Thunk} that always returns a precomputed value.
144
243
  *
145
- * Use when the value is expensive to compute and want to compute it once at
244
+ * Use when the value is expensive to compute and you want to compute it once at
146
245
  * definition time rather than on every call.
147
246
  *
148
247
  * ### Example
149
248
  *
150
249
  * ```ts
151
- * // Computed once at definition, returned on every call
152
- * const getConfig = lazy(parseConfig(rawConfig));
250
+ * import { constant } from "@evolu/common";
251
+ *
252
+ * let version = 0;
253
+ * const readConfig = () => ({ version: ++version });
254
+ * const getConstantConfig = constant(readConfig());
255
+ * const getFreshConfig = () => readConfig();
153
256
  *
154
- * // vs. computed on every call
155
- * const getConfig = () => parseConfig(rawConfig);
257
+ * expect(getConstantConfig()).toBe(getConstantConfig());
258
+ * expect(getConstantConfig().version).toBe(1);
259
+ * expect(getFreshConfig().version).toBe(2);
260
+ * expect(getFreshConfig().version).toBe(3);
156
261
  * ```
157
262
  */
158
- export const lazy =
159
- <T>(value: T): Lazy<T> =>
263
+ export const constant =
264
+ <T>(value: T): Thunk<T> =>
160
265
  () =>
161
266
  value;
162
267
 
163
- /** A {@link Lazy} that returns `true`. */
164
- export const lazyTrue: Lazy<true> = /*#__PURE__*/ lazy(true);
268
+ /** A {@link Thunk} that returns `true`. */
269
+ export const constTrue: Thunk<true> = /*#__PURE__*/ constant(true);
165
270
 
166
- /** A {@link Lazy} that returns `false`. */
167
- export const lazyFalse: Lazy<false> = /*#__PURE__*/ lazy(false);
271
+ /** A {@link Thunk} that returns `false`. */
272
+ export const constFalse: Thunk<false> = /*#__PURE__*/ constant(false);
168
273
 
169
- /** A {@link Lazy} that returns `null`. */
170
- export const lazyNull: Lazy<null> = /*#__PURE__*/ lazy(null);
274
+ /** A {@link Thunk} that returns `null`. */
275
+ export const constNull: Thunk<null> = /*#__PURE__*/ constant(null);
171
276
 
172
- /** A {@link Lazy} that returns `undefined`. */
173
- export const lazyUndefined: Lazy<undefined> = /*#__PURE__*/ lazy(undefined);
277
+ /** A {@link Thunk} that returns `undefined`. */
278
+ export const constUndefined: Thunk<undefined> =
279
+ /*#__PURE__*/ constant(undefined);
174
280
 
175
- /** A {@link Lazy} that returns `undefined` for void callbacks. */
176
- export const lazyVoid: Lazy<void> = lazyUndefined;
281
+ /** A {@link Thunk} that returns `undefined` for void callbacks. */
282
+ export const constVoid: Thunk<void> = constUndefined;
177
283
 
178
284
  /**
179
285
  * Development placeholder that always throws.
@@ -185,17 +291,19 @@ export const lazyVoid: Lazy<void> = lazyUndefined;
185
291
  * ### Example
186
292
  *
187
293
  * ```ts
188
- * // Type inferred from return type annotation
189
- * const fetchUser = (id: UserId): Result<User, FetchError> => todo();
294
+ * import { todo } from "@evolu/common";
190
295
  *
191
- * expectTypeOf(fetchUser).returns.toEqualTypeOf<
192
- * Result<User, FetchError>
193
- * >();
296
+ * interface Config {
297
+ * readonly theme: string;
298
+ * }
194
299
  *
195
- * // Explicit generic when no return type
300
+ * const getCount = (): number => todo();
196
301
  * const getConfig = () => todo<Config>();
197
302
  *
198
- * expectTypeOf(getConfig).returns.toEqualTypeOf<Config>();
303
+ * expectTypeOf<
304
+ * [ReturnType<typeof getCount>, ReturnType<typeof getConfig>]
305
+ * >().toEqualTypeOf<[number, Config]>();
306
+ * expect(getCount).toThrow("not yet implemented");
199
307
  * ```
200
308
  */
201
309
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters