@evolu/common 8.5.0 → 8.6.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 (328) hide show
  1. package/dist/src/Array.d.ts +170 -97
  2. package/dist/src/Array.d.ts.map +1 -1
  3. package/dist/src/Array.js +67 -42
  4. package/dist/src/Assert.d.ts +233 -30
  5. package/dist/src/Assert.d.ts.map +1 -1
  6. package/dist/src/Assert.js +150 -30
  7. package/dist/src/BigInt.d.ts +3 -3
  8. package/dist/src/BigInt.js +3 -3
  9. package/dist/src/Brand.d.ts +7 -7
  10. package/dist/src/Buffer.d.ts +12 -4
  11. package/dist/src/Buffer.d.ts.map +1 -1
  12. package/dist/src/Cache.d.ts +8 -3
  13. package/dist/src/Cache.d.ts.map +1 -1
  14. package/dist/src/Cache.js +8 -3
  15. package/dist/src/Callbacks.d.ts +8 -4
  16. package/dist/src/Callbacks.d.ts.map +1 -1
  17. package/dist/src/Console.d.ts +45 -30
  18. package/dist/src/Console.d.ts.map +1 -1
  19. package/dist/src/Console.js +32 -21
  20. package/dist/src/Crypto.d.ts +13 -8
  21. package/dist/src/Crypto.d.ts.map +1 -1
  22. package/dist/src/Crypto.js +6 -3
  23. package/dist/src/Eq.d.ts +107 -58
  24. package/dist/src/Eq.d.ts.map +1 -1
  25. package/dist/src/Eq.js +308 -112
  26. package/dist/src/Function.d.ts +54 -24
  27. package/dist/src/Function.d.ts.map +1 -1
  28. package/dist/src/Function.js +34 -17
  29. package/dist/src/Http.d.ts +52 -19
  30. package/dist/src/Http.d.ts.map +1 -1
  31. package/dist/src/Identicon.d.ts +9 -4
  32. package/dist/src/Identicon.d.ts.map +1 -1
  33. package/dist/src/Identicon.js +9 -4
  34. package/dist/src/LockManager.d.ts +12 -10
  35. package/dist/src/LockManager.d.ts.map +1 -1
  36. package/dist/src/Lookup.d.ts +4 -2
  37. package/dist/src/Lookup.d.ts.map +1 -1
  38. package/dist/src/Lookup.js +4 -2
  39. package/dist/src/Number.d.ts +28 -24
  40. package/dist/src/Number.d.ts.map +1 -1
  41. package/dist/src/Number.js +16 -17
  42. package/dist/src/Object.d.ts +89 -47
  43. package/dist/src/Object.d.ts.map +1 -1
  44. package/dist/src/Object.js +88 -41
  45. package/dist/src/Option.d.ts +14 -5
  46. package/dist/src/Option.d.ts.map +1 -1
  47. package/dist/src/Option.js +14 -5
  48. package/dist/src/Order.d.ts +12 -12
  49. package/dist/src/Order.js +12 -12
  50. package/dist/src/Platform.d.ts +2 -2
  51. package/dist/src/Random.d.ts +7 -4
  52. package/dist/src/Random.d.ts.map +1 -1
  53. package/dist/src/Redacted.d.ts +13 -6
  54. package/dist/src/Redacted.d.ts.map +1 -1
  55. package/dist/src/Redacted.js +4 -2
  56. package/dist/src/Ref.d.ts +4 -4
  57. package/dist/src/Relation.d.ts +5 -7
  58. package/dist/src/Relation.d.ts.map +1 -1
  59. package/dist/src/Resource.d.ts +27 -12
  60. package/dist/src/Resource.d.ts.map +1 -1
  61. package/dist/src/Result.d.ts +258 -114
  62. package/dist/src/Result.d.ts.map +1 -1
  63. package/dist/src/Result.js +71 -34
  64. package/dist/src/Schedule.d.ts +233 -116
  65. package/dist/src/Schedule.d.ts.map +1 -1
  66. package/dist/src/Schedule.js +204 -110
  67. package/dist/src/Set.d.ts +43 -24
  68. package/dist/src/Set.d.ts.map +1 -1
  69. package/dist/src/Set.js +25 -13
  70. package/dist/src/Sqlite.d.ts +4 -4
  71. package/dist/src/Sqlite.d.ts.map +1 -1
  72. package/dist/src/Sqlite.js +9 -8
  73. package/dist/src/Task.d.ts +441 -212
  74. package/dist/src/Task.d.ts.map +1 -1
  75. package/dist/src/Task.js +238 -115
  76. package/dist/src/Test.d.ts +10 -4
  77. package/dist/src/Test.d.ts.map +1 -1
  78. package/dist/src/Test.js +10 -4
  79. package/dist/src/Time.d.ts +27 -17
  80. package/dist/src/Time.d.ts.map +1 -1
  81. package/dist/src/Time.js +16 -6
  82. package/dist/src/Type.d.ts +1096 -387
  83. package/dist/src/Type.d.ts.map +1 -1
  84. package/dist/src/Type.js +1046 -372
  85. package/dist/src/Types.d.ts +95 -40
  86. package/dist/src/Types.d.ts.map +1 -1
  87. package/dist/src/Types.js +13 -4
  88. package/dist/src/WebSocket.d.ts +14 -6
  89. package/dist/src/WebSocket.d.ts.map +1 -1
  90. package/dist/src/Worker.d.ts +10 -6
  91. package/dist/src/Worker.d.ts.map +1 -1
  92. package/dist/src/intl/_en.d.ts +3 -1
  93. package/dist/src/intl/_en.d.ts.map +1 -1
  94. package/dist/src/intl/_en.js +17 -3
  95. package/dist/src/intl/ar.d.ts +3 -1
  96. package/dist/src/intl/ar.d.ts.map +1 -1
  97. package/dist/src/intl/ar.js +15 -2
  98. package/dist/src/intl/bn.d.ts +3 -1
  99. package/dist/src/intl/bn.d.ts.map +1 -1
  100. package/dist/src/intl/bn.js +16 -3
  101. package/dist/src/intl/ca.d.ts +3 -1
  102. package/dist/src/intl/ca.d.ts.map +1 -1
  103. package/dist/src/intl/ca.js +16 -3
  104. package/dist/src/intl/cs.d.ts +3 -1
  105. package/dist/src/intl/cs.d.ts.map +1 -1
  106. package/dist/src/intl/cs.js +16 -3
  107. package/dist/src/intl/da.d.ts +3 -1
  108. package/dist/src/intl/da.d.ts.map +1 -1
  109. package/dist/src/intl/da.js +16 -3
  110. package/dist/src/intl/de.d.ts +3 -1
  111. package/dist/src/intl/de.d.ts.map +1 -1
  112. package/dist/src/intl/de.js +16 -3
  113. package/dist/src/intl/el.d.ts +3 -1
  114. package/dist/src/intl/el.d.ts.map +1 -1
  115. package/dist/src/intl/el.js +16 -3
  116. package/dist/src/intl/es.d.ts +3 -1
  117. package/dist/src/intl/es.d.ts.map +1 -1
  118. package/dist/src/intl/es.js +16 -3
  119. package/dist/src/intl/fa.d.ts +3 -1
  120. package/dist/src/intl/fa.d.ts.map +1 -1
  121. package/dist/src/intl/fa.js +16 -3
  122. package/dist/src/intl/fi.d.ts +3 -1
  123. package/dist/src/intl/fi.d.ts.map +1 -1
  124. package/dist/src/intl/fi.js +16 -3
  125. package/dist/src/intl/fil.d.ts +3 -1
  126. package/dist/src/intl/fil.d.ts.map +1 -1
  127. package/dist/src/intl/fil.js +16 -3
  128. package/dist/src/intl/fr.d.ts +3 -1
  129. package/dist/src/intl/fr.d.ts.map +1 -1
  130. package/dist/src/intl/fr.js +16 -3
  131. package/dist/src/intl/he.d.ts +3 -1
  132. package/dist/src/intl/he.d.ts.map +1 -1
  133. package/dist/src/intl/he.js +16 -3
  134. package/dist/src/intl/hi.d.ts +3 -1
  135. package/dist/src/intl/hi.d.ts.map +1 -1
  136. package/dist/src/intl/hi.js +16 -3
  137. package/dist/src/intl/hr.d.ts +3 -1
  138. package/dist/src/intl/hr.d.ts.map +1 -1
  139. package/dist/src/intl/hr.js +16 -3
  140. package/dist/src/intl/hu.d.ts +2 -1
  141. package/dist/src/intl/hu.d.ts.map +1 -1
  142. package/dist/src/intl/hu.js +15 -3
  143. package/dist/src/intl/id.d.ts +3 -1
  144. package/dist/src/intl/id.d.ts.map +1 -1
  145. package/dist/src/intl/id.js +16 -3
  146. package/dist/src/intl/it.d.ts +3 -1
  147. package/dist/src/intl/it.d.ts.map +1 -1
  148. package/dist/src/intl/it.js +16 -3
  149. package/dist/src/intl/ja.d.ts +3 -1
  150. package/dist/src/intl/ja.d.ts.map +1 -1
  151. package/dist/src/intl/ja.js +16 -3
  152. package/dist/src/intl/ko.d.ts +3 -1
  153. package/dist/src/intl/ko.d.ts.map +1 -1
  154. package/dist/src/intl/ko.js +16 -3
  155. package/dist/src/intl/ml.d.ts +3 -1
  156. package/dist/src/intl/ml.d.ts.map +1 -1
  157. package/dist/src/intl/ml.js +16 -3
  158. package/dist/src/intl/mr.d.ts +3 -1
  159. package/dist/src/intl/mr.d.ts.map +1 -1
  160. package/dist/src/intl/mr.js +16 -3
  161. package/dist/src/intl/ms.d.ts +3 -1
  162. package/dist/src/intl/ms.d.ts.map +1 -1
  163. package/dist/src/intl/ms.js +15 -2
  164. package/dist/src/intl/nb.d.ts +2 -1
  165. package/dist/src/intl/nb.d.ts.map +1 -1
  166. package/dist/src/intl/nb.js +14 -2
  167. package/dist/src/intl/nl.d.ts +3 -1
  168. package/dist/src/intl/nl.d.ts.map +1 -1
  169. package/dist/src/intl/nl.js +16 -3
  170. package/dist/src/intl/pa.d.ts +3 -1
  171. package/dist/src/intl/pa.d.ts.map +1 -1
  172. package/dist/src/intl/pa.js +16 -3
  173. package/dist/src/intl/pl.d.ts +2 -0
  174. package/dist/src/intl/pl.d.ts.map +1 -1
  175. package/dist/src/intl/pl.js +15 -2
  176. package/dist/src/intl/pt-BR.d.ts +3 -1
  177. package/dist/src/intl/pt-BR.d.ts.map +1 -1
  178. package/dist/src/intl/pt-BR.js +16 -3
  179. package/dist/src/intl/pt.d.ts +3 -1
  180. package/dist/src/intl/pt.d.ts.map +1 -1
  181. package/dist/src/intl/pt.js +15 -2
  182. package/dist/src/intl/ro.d.ts +3 -1
  183. package/dist/src/intl/ro.d.ts.map +1 -1
  184. package/dist/src/intl/ro.js +16 -3
  185. package/dist/src/intl/sk.d.ts +3 -1
  186. package/dist/src/intl/sk.d.ts.map +1 -1
  187. package/dist/src/intl/sk.js +15 -2
  188. package/dist/src/intl/sl.d.ts +3 -1
  189. package/dist/src/intl/sl.d.ts.map +1 -1
  190. package/dist/src/intl/sl.js +16 -3
  191. package/dist/src/intl/sv.d.ts +3 -1
  192. package/dist/src/intl/sv.d.ts.map +1 -1
  193. package/dist/src/intl/sv.js +16 -3
  194. package/dist/src/intl/sw.d.ts +1 -0
  195. package/dist/src/intl/sw.d.ts.map +1 -1
  196. package/dist/src/intl/sw.js +14 -2
  197. package/dist/src/intl/ta.d.ts +3 -1
  198. package/dist/src/intl/ta.d.ts.map +1 -1
  199. package/dist/src/intl/ta.js +16 -3
  200. package/dist/src/intl/te.d.ts +3 -1
  201. package/dist/src/intl/te.d.ts.map +1 -1
  202. package/dist/src/intl/te.js +16 -3
  203. package/dist/src/intl/th.d.ts +3 -1
  204. package/dist/src/intl/th.d.ts.map +1 -1
  205. package/dist/src/intl/th.js +16 -3
  206. package/dist/src/intl/tr.d.ts +3 -1
  207. package/dist/src/intl/tr.d.ts.map +1 -1
  208. package/dist/src/intl/tr.js +16 -3
  209. package/dist/src/intl/uk.d.ts +3 -1
  210. package/dist/src/intl/uk.d.ts.map +1 -1
  211. package/dist/src/intl/uk.js +16 -3
  212. package/dist/src/intl/ur.d.ts +3 -1
  213. package/dist/src/intl/ur.d.ts.map +1 -1
  214. package/dist/src/intl/ur.js +15 -2
  215. package/dist/src/intl/vi.d.ts +3 -1
  216. package/dist/src/intl/vi.d.ts.map +1 -1
  217. package/dist/src/intl/vi.js +16 -3
  218. package/dist/src/intl/zh-CN.d.ts +3 -1
  219. package/dist/src/intl/zh-CN.d.ts.map +1 -1
  220. package/dist/src/intl/zh-CN.js +16 -3
  221. package/dist/src/intl/zh-TW.d.ts +3 -1
  222. package/dist/src/intl/zh-TW.d.ts.map +1 -1
  223. package/dist/src/intl/zh-TW.js +16 -3
  224. package/dist/src/local-first/Evolu.d.ts +47 -24
  225. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  226. package/dist/src/local-first/Owner.d.ts +10 -7
  227. package/dist/src/local-first/Owner.d.ts.map +1 -1
  228. package/dist/src/local-first/Owner.js +10 -7
  229. package/dist/src/local-first/Protocol.d.ts +5 -5
  230. package/dist/src/local-first/Protocol.js +5 -5
  231. package/dist/src/local-first/Query.d.ts +34 -21
  232. package/dist/src/local-first/Query.d.ts.map +1 -1
  233. package/dist/src/local-first/Query.js +25 -13
  234. package/dist/src/local-first/Relay.d.ts +12 -5
  235. package/dist/src/local-first/Relay.d.ts.map +1 -1
  236. package/dist/src/local-first/Schema.d.ts +19 -11
  237. package/dist/src/local-first/Schema.d.ts.map +1 -1
  238. package/dist/src/local-first/Schema.js +11 -6
  239. package/dist/src/local-first/Storage.d.ts +4 -2
  240. package/dist/src/local-first/Storage.d.ts.map +1 -1
  241. package/package.json +3 -4
  242. package/src/Array.ts +170 -97
  243. package/src/Assert.ts +336 -31
  244. package/src/BigInt.ts +3 -3
  245. package/src/Brand.ts +7 -7
  246. package/src/Buffer.ts +12 -4
  247. package/src/Cache.ts +8 -3
  248. package/src/Callbacks.ts +8 -4
  249. package/src/Console.ts +45 -30
  250. package/src/Crypto.ts +13 -8
  251. package/src/Eq.ts +450 -122
  252. package/src/Function.ts +54 -24
  253. package/src/Http.ts +52 -19
  254. package/src/Identicon.ts +9 -4
  255. package/src/LockManager.ts +12 -10
  256. package/src/Lookup.ts +4 -2
  257. package/src/Number.ts +28 -24
  258. package/src/Object.ts +115 -49
  259. package/src/Option.ts +14 -5
  260. package/src/Order.ts +12 -12
  261. package/src/Platform.ts +2 -2
  262. package/src/Random.ts +7 -4
  263. package/src/Redacted.ts +13 -6
  264. package/src/Ref.ts +4 -4
  265. package/src/Relation.ts +5 -7
  266. package/src/Resource.ts +27 -12
  267. package/src/Result.ts +258 -114
  268. package/src/Schedule.ts +233 -116
  269. package/src/Set.ts +43 -24
  270. package/src/Sqlite.ts +9 -8
  271. package/src/Task.ts +441 -212
  272. package/src/Test.ts +10 -4
  273. package/src/Time.ts +27 -17
  274. package/src/Type.ts +2077 -620
  275. package/src/Types.ts +108 -40
  276. package/src/WebSocket.ts +14 -6
  277. package/src/Worker.ts +10 -6
  278. package/src/intl/_en.ts +20 -4
  279. package/src/intl/ar.ts +17 -2
  280. package/src/intl/bn.ts +19 -4
  281. package/src/intl/ca.ts +19 -4
  282. package/src/intl/cs.ts +19 -4
  283. package/src/intl/da.ts +19 -4
  284. package/src/intl/de.ts +19 -4
  285. package/src/intl/el.ts +19 -4
  286. package/src/intl/es.ts +19 -4
  287. package/src/intl/fa.ts +19 -4
  288. package/src/intl/fi.ts +19 -4
  289. package/src/intl/fil.ts +19 -4
  290. package/src/intl/fr.ts +19 -4
  291. package/src/intl/he.ts +19 -4
  292. package/src/intl/hi.ts +19 -4
  293. package/src/intl/hr.ts +19 -4
  294. package/src/intl/hu.ts +18 -4
  295. package/src/intl/id.ts +19 -4
  296. package/src/intl/it.ts +19 -4
  297. package/src/intl/ja.ts +19 -4
  298. package/src/intl/ko.ts +19 -4
  299. package/src/intl/ml.ts +19 -4
  300. package/src/intl/mr.ts +19 -4
  301. package/src/intl/ms.ts +17 -2
  302. package/src/intl/nb.ts +16 -2
  303. package/src/intl/nl.ts +19 -4
  304. package/src/intl/pa.ts +19 -4
  305. package/src/intl/pl.ts +18 -2
  306. package/src/intl/pt-BR.ts +19 -4
  307. package/src/intl/pt.ts +17 -2
  308. package/src/intl/ro.ts +19 -4
  309. package/src/intl/sk.ts +17 -2
  310. package/src/intl/sl.ts +19 -4
  311. package/src/intl/sv.ts +19 -4
  312. package/src/intl/sw.ts +15 -2
  313. package/src/intl/ta.ts +19 -4
  314. package/src/intl/te.ts +19 -4
  315. package/src/intl/th.ts +19 -4
  316. package/src/intl/tr.ts +19 -4
  317. package/src/intl/uk.ts +19 -4
  318. package/src/intl/ur.ts +17 -2
  319. package/src/intl/vi.ts +19 -3
  320. package/src/intl/zh-CN.ts +19 -4
  321. package/src/intl/zh-TW.ts +19 -4
  322. package/src/local-first/Evolu.ts +47 -24
  323. package/src/local-first/Owner.ts +10 -7
  324. package/src/local-first/Protocol.ts +5 -5
  325. package/src/local-first/Query.ts +34 -21
  326. package/src/local-first/Relay.ts +12 -5
  327. package/src/local-first/Schema.ts +19 -11
  328. package/src/local-first/Storage.ts +4 -2
package/src/Eq.ts CHANGED
@@ -5,12 +5,9 @@
5
5
  */
6
6
 
7
7
  import type { Order } from "./Order.ts";
8
- import type {
9
- JsonArray,
10
- JsonObject,
11
- JsonValue,
12
- JsonValueInput,
13
- } from "./Type.ts";
8
+ import { getObjectKind } from "./Object.ts";
9
+ import type { Data, IsData, JsonValue, JsonValueInput } from "./Type.ts";
10
+ import type { CompileTimeError } from "./Types.ts";
14
11
 
15
12
  /**
16
13
  * Compares two values of the same type `A` for equality.
@@ -29,54 +26,68 @@ import type {
29
26
  * ### Example
30
27
  *
31
28
  * ```ts
32
- * import { createEqObject, eqNumber } from "@evolu/common";
29
+ * import {
30
+ * assertFalse,
31
+ * assertTrue,
32
+ * createEqObject,
33
+ * eqNumber,
34
+ * } from "@evolu/common";
33
35
  *
34
36
  * const eqPoint = createEqObject({ x: eqNumber, y: eqNumber });
35
37
  *
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
+ * assertTrue(eqPoint({ x: 1, y: 2 }, { x: 1, y: 2 }));
39
+ * assertFalse(eqPoint({ x: 1, y: 2 }, { x: 2, y: 1 }));
38
40
  * ```
39
41
  */
40
42
  export type Eq<in A> = (x: A, y: A) => boolean;
41
43
 
42
44
  /**
43
- * Compares two values with strict equality (`===`).
45
+ * Compares two values using SameValue equality (`Object.is`).
44
46
  *
45
- * Strict equality considers `NaN` unequal to itself. Use {@link eqSameValueZero}
46
- * when values can contain `NaN` and equality must be reflexive.
47
+ * SameValue considers `NaN` equal to itself, distinguishes `0` from `-0`, and
48
+ * compares objects by reference identity.
49
+ *
50
+ * ### Example
51
+ *
52
+ * ```ts
53
+ * import { assertFalse, assertTrue, eqStrict } from "@evolu/common";
54
+ *
55
+ * assertTrue(eqStrict(NaN, NaN));
56
+ * assertFalse(eqStrict(0, -0));
57
+ * ```
47
58
  */
48
- export const eqStrict = <A>(x: A, y: A): boolean => x === y;
59
+ export const eqStrict = <A>(x: A, y: A): boolean => Object.is(x, y);
49
60
 
50
61
  /**
51
62
  * Compares two values using SameValueZero equality.
52
63
  *
53
64
  * 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.
65
+ * `Array.prototype.includes`. Like SameValue, it considers `NaN` equal to
66
+ * itself, but unlike SameValue, it considers `0` and `-0` equal.
56
67
  *
57
68
  * ### Example
58
69
  *
59
70
  * ```ts
60
- * import { eqSameValueZero } from "@evolu/common";
71
+ * import { assertFalse, assertTrue, eqSameValueZero } from "@evolu/common";
61
72
  *
62
- * expect(eqSameValueZero(NaN, NaN)).toBe(true);
63
- * expect(eqSameValueZero(0, -0)).toBe(true);
64
- * expect(eqSameValueZero({}, {})).toBe(false);
73
+ * assertTrue(eqSameValueZero(NaN, NaN));
74
+ * assertTrue(eqSameValueZero(0, -0));
75
+ * assertFalse(eqSameValueZero({}, {}));
65
76
  * ```
66
77
  */
67
78
  export const eqSameValueZero = <A>(x: A, y: A): boolean =>
68
79
  x === y || Object.is(x, y);
69
80
 
70
- /** An {@link Eq} for strings using strict equality. */
81
+ /** An {@link Eq} for strings using {@link eqStrict}. */
71
82
  export const eqString: Eq<string> = eqStrict;
72
83
 
73
84
  /** An {@link Eq} for numbers using {@link eqSameValueZero}. */
74
85
  export const eqNumber: Eq<number> = eqSameValueZero;
75
86
 
76
- /** An {@link Eq} for bigints using strict equality. */
87
+ /** An {@link Eq} for bigints using {@link eqStrict}. */
77
88
  export const eqBigInt: Eq<bigint> = eqStrict;
78
89
 
79
- /** An {@link Eq} for booleans using strict equality. */
90
+ /** An {@link Eq} for booleans using {@link eqStrict}. */
80
91
  export const eqBoolean: Eq<boolean> = eqStrict;
81
92
 
82
93
  /** An {@link Eq} for `undefined`. */
@@ -98,15 +109,20 @@ export const eqFromOrder =
98
109
  * ### Example
99
110
  *
100
111
  * ```ts
101
- * import { createEqArrayLike, eqNumber } from "@evolu/common";
112
+ * import {
113
+ * assertFalse,
114
+ * assertTrue,
115
+ * createEqArrayLike,
116
+ * eqNumber,
117
+ * } from "@evolu/common";
102
118
  *
103
119
  * const eqArrayNumber = createEqArrayLike(eqNumber);
104
120
  *
105
- * expect(eqArrayNumber([1, 2, 3], [1, 2, 3])).toBe(true);
106
- * expect(
121
+ * assertTrue(eqArrayNumber([1, 2, 3], [1, 2, 3]));
122
+ * assertTrue(
107
123
  * 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);
124
+ * );
125
+ * assertFalse(eqArrayNumber([1, 2, 3], [1, 2, 4]));
110
126
  * ```
111
127
  */
112
128
  export const createEqArrayLike =
@@ -123,19 +139,20 @@ export const createEqArrayLike =
123
139
  };
124
140
 
125
141
  /**
126
- * Compares two array-like structures by strict reference equality (`===`).
142
+ * Compares two array-like structures element by element using {@link eqStrict}.
127
143
  *
128
- * Useful for structural sharing checks where elements are compared by identity.
144
+ * Useful for structural sharing checks where objects are compared by reference
145
+ * identity. `NaN` elements are equal, while `0` and `-0` elements differ.
129
146
  *
130
147
  * ### Example
131
148
  *
132
149
  * ```ts
133
- * import { eqArrayStrict } from "@evolu/common";
150
+ * import { assertFalse, assertTrue, eqArrayStrict } from "@evolu/common";
134
151
  *
135
152
  * const a = { x: 1 };
136
153
  * const b = { x: 1 };
137
- * expect(eqArrayStrict([a, a], [a, a])).toBe(true);
138
- * expect(eqArrayStrict([a], [b])).toBe(false);
154
+ * assertTrue(eqArrayStrict([a, a], [a, a]));
155
+ * assertFalse(eqArrayStrict([a], [b]));
139
156
  * ```
140
157
  */
141
158
  export const eqArrayStrict = /*#__PURE__*/ createEqArrayLike(eqStrict);
@@ -146,13 +163,29 @@ export const eqArrayStrict = /*#__PURE__*/ createEqArrayLike(eqStrict);
146
163
  * ### Example
147
164
  *
148
165
  * ```ts
149
- * import { eqArrayNumber } from "@evolu/common";
166
+ * import { assertTrue, eqArrayNumber } from "@evolu/common";
150
167
  *
151
- * expect(eqArrayNumber([1, NaN], [1, NaN])).toBe(true);
168
+ * assertTrue(eqArrayNumber([1, NaN], [1, NaN]));
152
169
  * ```
153
170
  */
154
171
  export const eqArrayNumber = /*#__PURE__*/ createEqArrayLike(eqNumber);
155
172
 
173
+ /**
174
+ * Compares two Uint8Arrays by byte value.
175
+ *
176
+ * ### Example
177
+ *
178
+ * ```ts
179
+ * import { assertFalse, assertTrue, eqUint8Array } from "@evolu/common";
180
+ *
181
+ * assertTrue(eqUint8Array(new Uint8Array([1, 2]), new Uint8Array([1, 2])));
182
+ * assertFalse(
183
+ * eqUint8Array(new Uint8Array([1, 2]), new Uint8Array([1, 3])),
184
+ * );
185
+ * ```
186
+ */
187
+ export const eqUint8Array: Eq<Uint8Array> = eqArrayNumber;
188
+
156
189
  /**
157
190
  * Creates an equivalence function for objects based on an equivalence for their
158
191
  * fields.
@@ -160,12 +193,17 @@ export const eqArrayNumber = /*#__PURE__*/ createEqArrayLike(eqNumber);
160
193
  * ### Example
161
194
  *
162
195
  * ```ts
163
- * import { createEqObject, eqNumber } from "@evolu/common";
196
+ * import {
197
+ * assertFalse,
198
+ * assertTrue,
199
+ * createEqObject,
200
+ * eqNumber,
201
+ * } from "@evolu/common";
164
202
  *
165
203
  * const eqObjectNumber = createEqObject({ a: eqNumber });
166
204
  *
167
- * expect(eqObjectNumber({ a: 1 }, { a: 1 })).toBe(true);
168
- * expect(eqObjectNumber({ a: 1 }, { a: 2 })).toBe(false);
205
+ * assertTrue(eqObjectNumber({ a: 1 }, { a: 1 }));
206
+ * assertFalse(eqObjectNumber({ a: 1 }, { a: 2 }));
169
207
  * ```
170
208
  */
171
209
  export const createEqObject =
@@ -183,122 +221,412 @@ export const createEqObject =
183
221
  };
184
222
 
185
223
  /**
186
- * Deeply compares two {@link JsonValue} values for equality.
224
+ * Compares two {@link Data} values.
187
225
  *
188
- * - Uses an iterative approach with a stack to handle large or deeply nested
189
- * objects without risking stack overflow.
190
- * - Defensively handles circular references in runtime values without looping,
191
- * although cyclic values are not valid JSON.
192
- * - Unlike JSON.stringify, this function directly compares values, avoiding
193
- * serialization overhead and leveraging short-circuit evaluation for faster
194
- * failure on mismatched structures.
226
+ * Primitive values use SameValueZero, so `NaN` equals itself and `0` equals
227
+ * `-0`. Arrays and Object properties are ordered and keyed respectively. Sets
228
+ * and Maps compare their elements and entries without regard to insertion
229
+ * order. Dates compare their time values, while Uint8Arrays compare their
230
+ * bytes. Cyclic and shared data graphs are supported.
231
+ *
232
+ * The compile-time validation recursively accepts ordinary interfaces whose
233
+ * properties consist only of Data. Runtime representation details are trusted;
234
+ * use the {@link Data} Type at an unknown boundary.
195
235
  *
196
236
  * ### Example
197
237
  *
198
238
  * ```ts
199
- * import { eqJsonValue, type JsonValue } from "@evolu/common";
239
+ * import { assertTrue, eqData } from "@evolu/common";
240
+ *
241
+ * interface User {
242
+ * readonly name: string;
243
+ * readonly roles: ReadonlySet<string>;
244
+ * }
245
+ *
246
+ * const first: User = { name: "Ada", roles: new Set(["admin", "author"]) };
247
+ * const second: User = {
248
+ * name: "Ada",
249
+ * roles: new Set(["author", "admin"]),
250
+ * };
251
+ *
252
+ * assertTrue(eqData(first, second));
253
+ * ```
254
+ */
255
+ export function eqData<Actual, Expected>(
256
+ actual: Actual,
257
+ expected: Expected,
258
+ ...dataError: EqDataError<Actual | Expected>
259
+ ): boolean;
260
+ export function eqData(actual: Data, expected: Data): boolean {
261
+ return eqDataInternal(actual, expected, {
262
+ pairs: [],
263
+ rightsByLeft: new Map(),
264
+ });
265
+ }
266
+
267
+ /**
268
+ * Compares two {@link JsonValue} values using {@link eqData}.
269
+ *
270
+ * ### Example
271
+ *
272
+ * ```ts
273
+ * import { assertTrue, eqJsonValue, type JsonValue } from "@evolu/common";
200
274
  *
201
275
  * const first: JsonValue = { profile: { name: "Ada" } };
202
276
  * const second: JsonValue = { profile: { name: "Ada" } };
203
277
  *
204
- * expect(eqJsonValue(first, second)).toBe(true);
278
+ * assertTrue(eqJsonValue(first, second));
205
279
  * ```
206
280
  */
207
- export const eqJsonValue = (a: JsonValue, b: JsonValue): boolean => {
208
- const stack: Array<[JsonValue, JsonValue]> = [[a, b]];
209
-
210
- const seen = new WeakMap<object, WeakSet<object>>();
211
-
212
- while (stack.length > 0) {
213
- const [x, y] = stack.pop()!;
281
+ export const eqJsonValue: Eq<JsonValue> = eqData;
282
+
283
+ /** Compares two {@link JsonValueInput} values using {@link eqData}. */
284
+ export const eqJsonValueInput: Eq<JsonValueInput> = eqData;
285
+
286
+ type EqDataError<Value> =
287
+ IsData<Value> extends true
288
+ ? []
289
+ : [
290
+ error: CompileTimeError<
291
+ "eqData",
292
+ "Actual and expected values must consist only of Data."
293
+ >,
294
+ ];
295
+
296
+ interface EqDataContext {
297
+ readonly pairs: Array<readonly [object, object]>;
298
+ readonly rightsByLeft: Map<object, Set<object>>;
299
+ }
300
+
301
+ interface EqDataCompareFrame {
302
+ readonly kind: "Compare";
303
+ readonly actual: Data;
304
+ readonly expected: Data;
305
+ }
306
+
307
+ interface EqDataSetFrame {
308
+ readonly kind: "Set";
309
+ readonly actual: ReadonlyArray<Data>;
310
+ readonly actualIndex: number;
311
+ readonly unmatched: Array<Data>;
312
+ readonly candidateIndex: number;
313
+ }
314
+
315
+ interface EqDataMapFrame {
316
+ readonly kind: "Map";
317
+ readonly actual: ReadonlyArray<readonly [Data, Data]>;
318
+ readonly actualIndex: number;
319
+ readonly unmatched: Array<readonly [Data, Data]>;
320
+ readonly candidateIndex: number;
321
+ }
322
+
323
+ interface EqDataCommitSetFrame {
324
+ readonly kind: "CommitSet";
325
+ readonly match: EqDataSetFrame;
326
+ }
327
+
328
+ interface EqDataCommitMapFrame {
329
+ readonly kind: "CommitMap";
330
+ readonly match: EqDataMapFrame;
331
+ }
332
+
333
+ interface EqDataFailFrame {
334
+ readonly kind: "Fail";
335
+ }
336
+
337
+ type EqDataFrame =
338
+ | EqDataCompareFrame
339
+ | EqDataSetFrame
340
+ | EqDataMapFrame
341
+ | EqDataCommitSetFrame
342
+ | EqDataCommitMapFrame
343
+ | EqDataFailFrame;
344
+
345
+ type EqDataCollectionFrame = EqDataSetFrame | EqDataMapFrame;
346
+
347
+ interface EqDataCandidateAttempt {
348
+ readonly contextCheckpoint: number;
349
+ readonly frameCheckpoint: number;
350
+ readonly retry: EqDataCollectionFrame;
351
+ }
352
+
353
+ const eqDataInternal = (
354
+ actual: Data,
355
+ expected: Data,
356
+ context: EqDataContext,
357
+ ): boolean => {
358
+ const frames: Array<EqDataFrame> = [{ kind: "Compare", actual, expected }];
359
+ const attempts: Array<EqDataCandidateAttempt> = [];
360
+
361
+ const retryFailedCandidate = (_frame: EqDataFailFrame): boolean => {
362
+ const attempt = attempts.pop();
363
+ if (attempt === undefined) return false;
364
+
365
+ rollbackEqDataContext(context, attempt.contextCheckpoint);
366
+ frames.length = attempt.frameCheckpoint;
367
+ frames.push(attempt.retry);
368
+ return true;
369
+ };
214
370
 
215
- if (x === y) continue;
371
+ while (frames.length > 0) {
372
+ const frame = frames.pop()!;
216
373
 
217
- const typeX = typeof x;
218
- const typeY = typeof y;
374
+ if (frame.kind === "Compare") {
375
+ const x = frame.actual;
376
+ const y = frame.expected;
219
377
 
220
- if (typeX !== typeY || x === null || y === null) return false;
378
+ if (eqSameValueZero(x, y)) continue;
379
+ if (typeof x !== typeof y) {
380
+ frames.push({ kind: "Fail" });
381
+ continue;
382
+ }
383
+ if (
384
+ typeof x !== "object" ||
385
+ x === null ||
386
+ typeof y !== "object" ||
387
+ y === null
388
+ ) {
389
+ frames.push({ kind: "Fail" });
390
+ continue;
391
+ }
221
392
 
222
- if (typeX === "number" && isNaN(x as number) && isNaN(y as number)) {
223
- continue;
224
- }
393
+ const rights = context.rightsByLeft.get(x);
394
+ if (rights?.has(y)) continue;
395
+ if (rights === undefined) {
396
+ context.rightsByLeft.set(x, new Set([y]));
397
+ } else {
398
+ rights.add(y);
399
+ }
400
+ context.pairs.push([x, y]);
225
401
 
226
- if (typeX === "object") {
227
- const isArrayX = Array.isArray(x);
228
- const isArrayY = Array.isArray(y);
402
+ const xKind = getObjectKind(x);
403
+ const yKind = getObjectKind(y);
404
+ if (xKind !== yKind) {
405
+ frames.push({ kind: "Fail" });
406
+ continue;
407
+ }
229
408
 
230
- if (isArrayX !== isArrayY) return false;
409
+ if (xKind === "Array") {
410
+ const xArray = x as ReadonlyArray<Data>;
411
+ const yArray = y as ReadonlyArray<Data>;
412
+ if (
413
+ xArray.length !== yArray.length ||
414
+ Reflect.ownKeys(xArray).length !== xArray.length + 1 ||
415
+ Reflect.ownKeys(yArray).length !== yArray.length + 1
416
+ ) {
417
+ frames.push({ kind: "Fail" });
418
+ continue;
419
+ }
231
420
 
232
- const xObj = x as object;
233
- const yObj = y as object;
421
+ let valid = true;
422
+ for (let index = 0; index < xArray.length; index++) {
423
+ const xDescriptor = Object.getOwnPropertyDescriptor(xArray, index);
424
+ const yDescriptor = Object.getOwnPropertyDescriptor(yArray, index);
425
+ if (
426
+ xDescriptor === undefined ||
427
+ yDescriptor === undefined ||
428
+ !("value" in xDescriptor) ||
429
+ !("value" in yDescriptor)
430
+ ) {
431
+ valid = false;
432
+ break;
433
+ }
434
+ frames.push({
435
+ kind: "Compare",
436
+ actual: xDescriptor.value as Data,
437
+ expected: yDescriptor.value as Data,
438
+ });
439
+ }
440
+ if (!valid) frames.push({ kind: "Fail" });
441
+ continue;
442
+ }
234
443
 
235
- if (seen.has(xObj)) {
236
- const ySet = seen.get(xObj)!;
237
- if (ySet.has(yObj)) {
444
+ switch (xKind) {
445
+ case "Date":
446
+ if (
447
+ !Object.is(
448
+ Date.prototype.getTime.call(x),
449
+ Date.prototype.getTime.call(y),
450
+ )
451
+ ) {
452
+ frames.push({ kind: "Fail" });
453
+ }
454
+ continue;
455
+ case "Uint8Array":
456
+ if (!eqUint8Array(x as Uint8Array, y as Uint8Array)) {
457
+ frames.push({ kind: "Fail" });
458
+ }
459
+ continue;
460
+ case "Set": {
461
+ const xSet = x as ReadonlySet<Data>;
462
+ const ySet = y as ReadonlySet<Data>;
463
+ if (
464
+ Reflect.ownKeys(x).length !== 0 ||
465
+ Reflect.ownKeys(y).length !== 0 ||
466
+ xSet.size !== ySet.size
467
+ ) {
468
+ frames.push({ kind: "Fail" });
469
+ continue;
470
+ }
471
+ frames.push({
472
+ kind: "Set",
473
+ actual: Array.from(xSet),
474
+ actualIndex: 0,
475
+ unmatched: Array.from(ySet),
476
+ candidateIndex: 0,
477
+ });
238
478
  continue;
239
479
  }
240
- ySet.add(yObj);
241
- } else {
242
- const ySet = new WeakSet<object>();
243
- ySet.add(yObj);
244
- seen.set(xObj, ySet);
480
+ case "Map": {
481
+ const xMap = x as ReadonlyMap<Data, Data>;
482
+ const yMap = y as ReadonlyMap<Data, Data>;
483
+ if (
484
+ Reflect.ownKeys(x).length !== 0 ||
485
+ Reflect.ownKeys(y).length !== 0 ||
486
+ xMap.size !== yMap.size
487
+ ) {
488
+ frames.push({ kind: "Fail" });
489
+ continue;
490
+ }
491
+ frames.push({
492
+ kind: "Map",
493
+ actual: Array.from(xMap),
494
+ actualIndex: 0,
495
+ unmatched: Array.from(yMap),
496
+ candidateIndex: 0,
497
+ });
498
+ continue;
499
+ }
500
+ case "Object": {
501
+ const xKeys = Reflect.ownKeys(x);
502
+ const yKeys = Reflect.ownKeys(y);
503
+ if (xKeys.length !== yKeys.length) {
504
+ frames.push({ kind: "Fail" });
505
+ continue;
506
+ }
507
+
508
+ let valid = true;
509
+ for (const key of xKeys) {
510
+ if (typeof key !== "string") {
511
+ valid = false;
512
+ break;
513
+ }
514
+ const xDescriptor = Object.getOwnPropertyDescriptor(x, key);
515
+ const yDescriptor = Object.getOwnPropertyDescriptor(y, key);
516
+ if (
517
+ xDescriptor === undefined ||
518
+ yDescriptor === undefined ||
519
+ !("value" in xDescriptor) ||
520
+ !("value" in yDescriptor) ||
521
+ !xDescriptor.enumerable ||
522
+ !yDescriptor.enumerable
523
+ ) {
524
+ valid = false;
525
+ break;
526
+ }
527
+ frames.push({
528
+ kind: "Compare",
529
+ actual: xDescriptor.value as Data,
530
+ expected: yDescriptor.value as Data,
531
+ });
532
+ }
533
+ if (!valid) frames.push({ kind: "Fail" });
534
+ continue;
535
+ }
536
+ case "Unsupported":
537
+ frames.push({ kind: "Fail" });
538
+ continue;
245
539
  }
540
+ }
246
541
 
247
- if (isArrayX && isArrayY) {
248
- const xArr = x as JsonArray;
249
- const yArr = y as JsonArray;
542
+ if (frame.kind === "Set") {
543
+ if (frame.actualIndex === frame.actual.length) continue;
544
+ if (frame.candidateIndex === frame.unmatched.length) {
545
+ frames.push({ kind: "Fail" });
546
+ continue;
547
+ }
250
548
 
251
- if (xArr.length !== yArr.length) return false;
252
- for (let i = 0; i < xArr.length; i++) {
253
- stack.push([xArr[i], yArr[i]]);
254
- }
255
- } else {
256
- const xObjTyped = x as JsonObject;
257
- const yObjTyped = y as JsonObject;
549
+ const attempt: EqDataCandidateAttempt = {
550
+ contextCheckpoint: context.pairs.length,
551
+ frameCheckpoint: frames.length,
552
+ retry: { ...frame, candidateIndex: frame.candidateIndex + 1 },
553
+ };
554
+ attempts.push(attempt);
555
+ frames.push({ kind: "CommitSet", match: frame });
556
+ frames.push({
557
+ kind: "Compare",
558
+ actual: frame.actual[frame.actualIndex],
559
+ expected: frame.unmatched[frame.candidateIndex],
560
+ });
561
+ continue;
562
+ }
258
563
 
259
- const xKeys = Object.keys(xObjTyped);
260
- const yKeys = Object.keys(yObjTyped);
564
+ if (frame.kind === "Map") {
565
+ if (frame.actualIndex === frame.actual.length) continue;
566
+ if (frame.candidateIndex === frame.unmatched.length) {
567
+ frames.push({ kind: "Fail" });
568
+ continue;
569
+ }
261
570
 
262
- if (xKeys.length !== yKeys.length) return false;
571
+ const [actualKey, actualValue] = frame.actual[frame.actualIndex];
572
+ const [expectedKey, expectedValue] =
573
+ frame.unmatched[frame.candidateIndex];
574
+ const attempt: EqDataCandidateAttempt = {
575
+ contextCheckpoint: context.pairs.length,
576
+ frameCheckpoint: frames.length,
577
+ retry: { ...frame, candidateIndex: frame.candidateIndex + 1 },
578
+ };
579
+ attempts.push(attempt);
580
+ frames.push({ kind: "CommitMap", match: frame });
581
+ frames.push({
582
+ kind: "Compare",
583
+ actual: actualValue,
584
+ expected: expectedValue,
585
+ });
586
+ frames.push({
587
+ kind: "Compare",
588
+ actual: actualKey,
589
+ expected: expectedKey,
590
+ });
591
+ continue;
592
+ }
263
593
 
264
- const yKeySet = new Set(yKeys);
594
+ if (frame.kind === "CommitSet") {
595
+ attempts.pop();
596
+ frame.match.unmatched.splice(frame.match.candidateIndex, 1);
597
+ frames.push({
598
+ ...frame.match,
599
+ actualIndex: frame.match.actualIndex + 1,
600
+ candidateIndex: 0,
601
+ });
602
+ continue;
603
+ }
265
604
 
266
- for (const key of xKeys) {
267
- if (!yKeySet.has(key)) return false;
268
- stack.push([xObjTyped[key], yObjTyped[key]]);
269
- }
270
- }
271
- } else {
272
- return false;
605
+ if (frame.kind === "CommitMap") {
606
+ attempts.pop();
607
+ frame.match.unmatched.splice(frame.match.candidateIndex, 1);
608
+ frames.push({
609
+ ...frame.match,
610
+ actualIndex: frame.match.actualIndex + 1,
611
+ candidateIndex: 0,
612
+ });
613
+ continue;
273
614
  }
615
+
616
+ if (!retryFailedCandidate(frame)) return false;
274
617
  }
275
618
 
276
619
  return true;
277
620
  };
278
621
 
279
- /**
280
- * Deeply compares two {@link JsonValueInput} values for equality.
281
- *
282
- * - Uses an iterative approach with a stack to handle large or deeply nested
283
- * objects without risking stack overflow.
284
- * - Defensively handles circular references in runtime values without looping,
285
- * although cyclic values are not valid JSON.
286
- * - Unlike JSON.stringify, this function directly compares values, avoiding
287
- * serialization overhead and leveraging short-circuit evaluation for faster
288
- * failure on mismatched structures.
289
- *
290
- * ### Example
291
- *
292
- * ```ts
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);
299
- * ```
300
- */
301
- export const eqJsonValueInput = (
302
- a: JsonValueInput,
303
- b: JsonValueInput,
304
- ): boolean => eqJsonValue(a as JsonValue, b as JsonValue);
622
+ const rollbackEqDataContext = (
623
+ context: EqDataContext,
624
+ checkpoint: number,
625
+ ): void => {
626
+ while (context.pairs.length > checkpoint) {
627
+ const [left, right] = context.pairs.pop()!;
628
+ const rights = context.rightsByLeft.get(left)!;
629
+ rights.delete(right);
630
+ if (rights.size === 0) context.rightsByLeft.delete(left);
631
+ }
632
+ };