@evolu/common 8.7.0 → 8.9.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 (302) hide show
  1. package/dist/src/Array.d.ts +21 -21
  2. package/dist/src/Array.js +5 -5
  3. package/dist/src/Assert.d.ts +498 -141
  4. package/dist/src/Assert.d.ts.map +1 -1
  5. package/dist/src/Assert.js +648 -99
  6. package/dist/src/{Buffer.d.ts → Binary.d.ts} +85 -12
  7. package/dist/src/Binary.d.ts.map +1 -0
  8. package/dist/src/{Buffer.js → Binary.js} +206 -24
  9. package/dist/src/Console.d.ts +2 -10
  10. package/dist/src/Console.d.ts.map +1 -1
  11. package/dist/src/Console.js +6 -12
  12. package/dist/src/Crypto.d.ts +5 -5
  13. package/dist/src/Crypto.js +1 -1
  14. package/dist/src/Eq.d.ts +35 -40
  15. package/dist/src/Eq.d.ts.map +1 -1
  16. package/dist/src/Eq.js +51 -87
  17. package/dist/src/Function.d.ts +9 -14
  18. package/dist/src/Function.d.ts.map +1 -1
  19. package/dist/src/Function.js +7 -9
  20. package/dist/src/Http.d.ts +6 -7
  21. package/dist/src/Http.d.ts.map +1 -1
  22. package/dist/src/LeakDetector.d.ts.map +1 -1
  23. package/dist/src/LeakDetector.js +1 -0
  24. package/dist/src/LockManager.d.ts +1 -1
  25. package/dist/src/LockManager.js +1 -1
  26. package/dist/src/Lookup.d.ts +3 -2
  27. package/dist/src/Lookup.d.ts.map +1 -1
  28. package/dist/src/Lookup.js +4 -3
  29. package/dist/src/Number.d.ts +1 -1
  30. package/dist/src/Number.d.ts.map +1 -1
  31. package/dist/src/Number.js +1 -1
  32. package/dist/src/Object.d.ts +4 -4
  33. package/dist/src/Object.d.ts.map +1 -1
  34. package/dist/src/Object.js +5 -4
  35. package/dist/src/Option.d.ts +1 -2
  36. package/dist/src/Option.d.ts.map +1 -1
  37. package/dist/src/Option.js +1 -2
  38. package/dist/src/Platform.js +2 -0
  39. package/dist/src/Polyfills.d.ts +6 -0
  40. package/dist/src/Polyfills.d.ts.map +1 -1
  41. package/dist/src/Polyfills.js +10 -1
  42. package/dist/src/Random.d.ts +1 -1
  43. package/dist/src/Result.d.ts +49 -54
  44. package/dist/src/Result.d.ts.map +1 -1
  45. package/dist/src/Result.js +17 -22
  46. package/dist/src/Set.d.ts +6 -6
  47. package/dist/src/Set.js +2 -2
  48. package/dist/src/Sqlite.js +1 -1
  49. package/dist/src/String.js +3 -2
  50. package/dist/src/Task.d.ts +32 -32
  51. package/dist/src/Task.d.ts.map +1 -1
  52. package/dist/src/Task.js +20 -22
  53. package/dist/src/Test.d.ts +31 -3
  54. package/dist/src/Test.d.ts.map +1 -1
  55. package/dist/src/Test.js +46 -3
  56. package/dist/src/Time.d.ts +3 -11
  57. package/dist/src/Time.d.ts.map +1 -1
  58. package/dist/src/Time.js +10 -18
  59. package/dist/src/Type.d.ts +87 -85
  60. package/dist/src/Type.d.ts.map +1 -1
  61. package/dist/src/Type.js +70 -63
  62. package/dist/src/Types.d.ts +12 -12
  63. package/dist/src/Types.js +1 -1
  64. package/dist/src/WebSocket.d.ts +4 -7
  65. package/dist/src/WebSocket.d.ts.map +1 -1
  66. package/dist/src/Worker.d.ts +1 -1
  67. package/dist/src/Worker.js +7 -7
  68. package/dist/src/index.d.ts +1 -1
  69. package/dist/src/index.js +1 -1
  70. package/dist/src/intl/_en.d.ts.map +1 -1
  71. package/dist/src/intl/_en.js +1 -1
  72. package/dist/src/intl/ar.d.ts.map +1 -1
  73. package/dist/src/intl/ar.js +1 -1
  74. package/dist/src/intl/bn.d.ts.map +1 -1
  75. package/dist/src/intl/bn.js +1 -1
  76. package/dist/src/intl/ca.d.ts.map +1 -1
  77. package/dist/src/intl/ca.js +1 -1
  78. package/dist/src/intl/cs.d.ts.map +1 -1
  79. package/dist/src/intl/cs.js +1 -1
  80. package/dist/src/intl/da.d.ts.map +1 -1
  81. package/dist/src/intl/da.js +1 -1
  82. package/dist/src/intl/de.d.ts.map +1 -1
  83. package/dist/src/intl/de.js +1 -1
  84. package/dist/src/intl/el.d.ts.map +1 -1
  85. package/dist/src/intl/el.js +1 -1
  86. package/dist/src/intl/es.d.ts.map +1 -1
  87. package/dist/src/intl/es.js +1 -1
  88. package/dist/src/intl/fa.d.ts.map +1 -1
  89. package/dist/src/intl/fa.js +1 -1
  90. package/dist/src/intl/fi.d.ts.map +1 -1
  91. package/dist/src/intl/fi.js +1 -1
  92. package/dist/src/intl/fil.d.ts.map +1 -1
  93. package/dist/src/intl/fil.js +1 -1
  94. package/dist/src/intl/fr.d.ts.map +1 -1
  95. package/dist/src/intl/fr.js +1 -1
  96. package/dist/src/intl/he.d.ts.map +1 -1
  97. package/dist/src/intl/he.js +1 -1
  98. package/dist/src/intl/hi.d.ts.map +1 -1
  99. package/dist/src/intl/hi.js +1 -1
  100. package/dist/src/intl/hr.d.ts.map +1 -1
  101. package/dist/src/intl/hr.js +1 -1
  102. package/dist/src/intl/hu.d.ts.map +1 -1
  103. package/dist/src/intl/hu.js +1 -1
  104. package/dist/src/intl/id.d.ts.map +1 -1
  105. package/dist/src/intl/id.js +1 -1
  106. package/dist/src/intl/it.d.ts.map +1 -1
  107. package/dist/src/intl/it.js +1 -1
  108. package/dist/src/intl/ja.d.ts.map +1 -1
  109. package/dist/src/intl/ja.js +1 -1
  110. package/dist/src/intl/ko.d.ts.map +1 -1
  111. package/dist/src/intl/ko.js +1 -1
  112. package/dist/src/intl/ml.d.ts.map +1 -1
  113. package/dist/src/intl/ml.js +1 -1
  114. package/dist/src/intl/mr.d.ts.map +1 -1
  115. package/dist/src/intl/mr.js +1 -1
  116. package/dist/src/intl/ms.d.ts.map +1 -1
  117. package/dist/src/intl/ms.js +1 -1
  118. package/dist/src/intl/nb.d.ts.map +1 -1
  119. package/dist/src/intl/nb.js +1 -1
  120. package/dist/src/intl/nl.d.ts.map +1 -1
  121. package/dist/src/intl/nl.js +1 -1
  122. package/dist/src/intl/pa.d.ts.map +1 -1
  123. package/dist/src/intl/pa.js +1 -1
  124. package/dist/src/intl/pl.d.ts.map +1 -1
  125. package/dist/src/intl/pl.js +1 -1
  126. package/dist/src/intl/pt-BR.d.ts.map +1 -1
  127. package/dist/src/intl/pt-BR.js +1 -1
  128. package/dist/src/intl/pt.d.ts.map +1 -1
  129. package/dist/src/intl/pt.js +1 -1
  130. package/dist/src/intl/ro.d.ts.map +1 -1
  131. package/dist/src/intl/ro.js +1 -1
  132. package/dist/src/intl/sk.d.ts.map +1 -1
  133. package/dist/src/intl/sk.js +1 -1
  134. package/dist/src/intl/sl.d.ts.map +1 -1
  135. package/dist/src/intl/sl.js +1 -1
  136. package/dist/src/intl/sv.d.ts.map +1 -1
  137. package/dist/src/intl/sv.js +1 -1
  138. package/dist/src/intl/sw.d.ts.map +1 -1
  139. package/dist/src/intl/sw.js +1 -1
  140. package/dist/src/intl/ta.d.ts.map +1 -1
  141. package/dist/src/intl/ta.js +1 -1
  142. package/dist/src/intl/te.d.ts.map +1 -1
  143. package/dist/src/intl/te.js +1 -1
  144. package/dist/src/intl/th.d.ts.map +1 -1
  145. package/dist/src/intl/th.js +1 -1
  146. package/dist/src/intl/tr.d.ts.map +1 -1
  147. package/dist/src/intl/tr.js +1 -1
  148. package/dist/src/intl/uk.d.ts.map +1 -1
  149. package/dist/src/intl/uk.js +1 -1
  150. package/dist/src/intl/ur.d.ts.map +1 -1
  151. package/dist/src/intl/ur.js +1 -1
  152. package/dist/src/intl/vi.d.ts.map +1 -1
  153. package/dist/src/intl/vi.js +1 -1
  154. package/dist/src/intl/zh-CN.d.ts.map +1 -1
  155. package/dist/src/intl/zh-CN.js +1 -1
  156. package/dist/src/intl/zh-TW.d.ts.map +1 -1
  157. package/dist/src/intl/zh-TW.js +1 -1
  158. package/dist/src/local-first/Evolu.d.ts +12 -12
  159. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  160. package/dist/src/local-first/Evolu.js +2 -2
  161. package/dist/src/local-first/Protocol.d.ts +14 -76
  162. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  163. package/dist/src/local-first/Protocol.js +29 -206
  164. package/dist/src/local-first/Query.d.ts +11 -11
  165. package/dist/src/local-first/Query.js +6 -6
  166. package/dist/src/local-first/Relay.d.ts +2 -2
  167. package/dist/src/local-first/Schema.d.ts +3 -5
  168. package/dist/src/local-first/Schema.d.ts.map +1 -1
  169. package/dist/src/local-first/Schema.js +2 -4
  170. package/dist/src/local-first/Shared.d.ts.map +1 -1
  171. package/dist/src/local-first/Shared.js +5 -5
  172. package/dist/src/local-first/Storage.js +4 -4
  173. package/dist/src/local-first/Timestamp.d.ts +9 -0
  174. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  175. package/dist/src/local-first/Timestamp.js +13 -10
  176. package/package.json +7 -4
  177. package/src/Array.test.ts +809 -0
  178. package/src/Array.ts +21 -21
  179. package/src/Assert.test.ts +1232 -0
  180. package/src/Assert.ts +1023 -227
  181. package/src/BigInt.test.ts +28 -0
  182. package/src/Binary.test.ts +1128 -0
  183. package/src/{Buffer.ts → Binary.ts} +273 -46
  184. package/src/Brand.test.ts +90 -0
  185. package/src/Cache.test.ts +183 -0
  186. package/src/Callbacks.test.ts +66 -0
  187. package/src/Console.test.ts +809 -0
  188. package/src/Console.ts +9 -17
  189. package/src/Crypto.test.ts +138 -0
  190. package/src/Crypto.ts +5 -5
  191. package/src/Eq.test.ts +333 -0
  192. package/src/Eq.ts +110 -176
  193. package/src/Error.test.ts +118 -0
  194. package/src/Function.test.ts +204 -0
  195. package/src/Function.ts +10 -15
  196. package/src/Http.test.ts +816 -0
  197. package/src/Http.ts +6 -7
  198. package/src/LeakDetector.test.ts +210 -0
  199. package/src/LeakDetector.ts +1 -0
  200. package/src/LockManager.ts +2 -2
  201. package/src/Lookup.test.ts +418 -0
  202. package/src/Lookup.ts +4 -3
  203. package/src/Microtask.test.ts +140 -0
  204. package/src/Number.test.ts +218 -0
  205. package/src/Number.ts +2 -2
  206. package/src/Object.test.ts +293 -0
  207. package/src/Object.ts +5 -4
  208. package/src/Option.test.ts +104 -0
  209. package/src/Option.ts +1 -2
  210. package/src/Order.test.ts +56 -0
  211. package/src/Platform.ts +2 -0
  212. package/src/Polyfills.ts +9 -4
  213. package/src/Random.test.ts +89 -0
  214. package/src/Random.ts +1 -1
  215. package/src/Ref.test.ts +139 -0
  216. package/src/RefCount.test.ts +184 -0
  217. package/src/Relation.test.ts +505 -0
  218. package/src/Resource.test.ts +4623 -0
  219. package/src/Result.test.ts +1829 -0
  220. package/src/Result.ts +49 -54
  221. package/src/Schedule.test.ts +1554 -0
  222. package/src/Set.test.ts +279 -0
  223. package/src/Set.ts +6 -6
  224. package/src/Sqlite.ts +1 -1
  225. package/src/Store.test.ts +288 -0
  226. package/src/String.test.ts +54 -0
  227. package/src/String.ts +3 -3
  228. package/src/Task.test.ts +10820 -0
  229. package/src/Task.ts +38 -43
  230. package/src/Test.test.ts +88 -0
  231. package/src/Test.ts +49 -3
  232. package/src/Time.test.ts +926 -0
  233. package/src/Time.ts +13 -21
  234. package/src/Type.base64.test.ts +88 -0
  235. package/src/Type.test.ts +20465 -0
  236. package/src/Type.ts +114 -111
  237. package/src/Types.test.ts +324 -0
  238. package/src/Types.ts +13 -13
  239. package/src/WebSocket.ts +4 -7
  240. package/src/Worker.test.ts +755 -0
  241. package/src/Worker.ts +13 -18
  242. package/src/index.ts +1 -1
  243. package/src/intl/_en.ts +1 -1
  244. package/src/intl/ar.ts +1 -1
  245. package/src/intl/bn.ts +1 -1
  246. package/src/intl/ca.ts +1 -1
  247. package/src/intl/cs.ts +1 -1
  248. package/src/intl/da.ts +1 -1
  249. package/src/intl/de.ts +1 -1
  250. package/src/intl/el.ts +1 -1
  251. package/src/intl/es.ts +1 -1
  252. package/src/intl/fa.ts +1 -1
  253. package/src/intl/fi.ts +1 -1
  254. package/src/intl/fil.ts +1 -1
  255. package/src/intl/fr.ts +1 -1
  256. package/src/intl/he.ts +1 -1
  257. package/src/intl/hi.ts +1 -1
  258. package/src/intl/hr.ts +1 -1
  259. package/src/intl/hu.ts +1 -1
  260. package/src/intl/id.ts +1 -1
  261. package/src/intl/intl.test.ts +3106 -0
  262. package/src/intl/it.ts +1 -1
  263. package/src/intl/ja.ts +1 -1
  264. package/src/intl/ko.ts +1 -1
  265. package/src/intl/ml.ts +1 -1
  266. package/src/intl/mr.ts +1 -1
  267. package/src/intl/ms.ts +1 -1
  268. package/src/intl/nb.ts +1 -1
  269. package/src/intl/nl.ts +1 -1
  270. package/src/intl/pa.ts +1 -1
  271. package/src/intl/pl.ts +1 -1
  272. package/src/intl/pt-BR.ts +1 -1
  273. package/src/intl/pt.ts +1 -1
  274. package/src/intl/ro.ts +1 -1
  275. package/src/intl/sk.ts +1 -1
  276. package/src/intl/sl.ts +1 -1
  277. package/src/intl/sv.ts +1 -1
  278. package/src/intl/sw.ts +1 -1
  279. package/src/intl/ta.ts +1 -1
  280. package/src/intl/te.ts +1 -1
  281. package/src/intl/th.ts +1 -1
  282. package/src/intl/tr.ts +1 -1
  283. package/src/intl/uk.ts +1 -1
  284. package/src/intl/ur.ts +1 -1
  285. package/src/intl/vi.ts +1 -1
  286. package/src/intl/zh-CN.ts +1 -1
  287. package/src/intl/zh-TW.ts +1 -1
  288. package/src/local-first/Evolu.test.ts +1921 -0
  289. package/src/local-first/Evolu.ts +18 -14
  290. package/src/local-first/Owner.test.ts +83 -0
  291. package/src/local-first/Protocol.test.ts +1037 -0
  292. package/src/local-first/Protocol.ts +58 -261
  293. package/src/local-first/Query.test.ts +349 -0
  294. package/src/local-first/Query.ts +11 -11
  295. package/src/local-first/Relay.ts +2 -2
  296. package/src/local-first/Schema.ts +3 -5
  297. package/src/local-first/Shared.test.ts +1828 -0
  298. package/src/local-first/Shared.ts +9 -8
  299. package/src/local-first/Storage.ts +4 -4
  300. package/src/local-first/Timestamp.test.ts +374 -0
  301. package/src/local-first/Timestamp.ts +22 -10
  302. package/dist/src/Buffer.d.ts.map +0 -1
@@ -0,0 +1,104 @@
1
+ import { describe, it, test } from "node:test";
2
+ import { assertEqual, assertSame, assertTrue } from "./Assert.ts";
3
+
4
+ import {
5
+ fromNullable,
6
+ type InferOption,
7
+ isNone,
8
+ isSome,
9
+ none,
10
+ type None,
11
+ type Option,
12
+ some,
13
+ type Some,
14
+ } from "./Option.ts";
15
+ import { assertType } from "./Type.ts";
16
+
17
+ test("some creates Some", () => {
18
+ assertEqual(some(42), { type: "Some", value: 42 });
19
+ });
20
+
21
+ test("none is None", () => {
22
+ assertEqual(none, { type: "None" });
23
+ });
24
+
25
+ test("isSome narrows type", () => {
26
+ const option: Option<string> = some("test");
27
+ if (isSome(option)) {
28
+ assertType<typeof option, Some<string>>();
29
+ assertType<typeof option.value, string>();
30
+ }
31
+ });
32
+
33
+ test("isNone narrows type", () => {
34
+ const option: Option<string> = none;
35
+ if (isNone(option)) {
36
+ assertType<typeof option, None>();
37
+ }
38
+ });
39
+
40
+ test("fromNullable maps null and undefined to none", () => {
41
+ assertTrue(isNone(fromNullable(null)));
42
+ assertTrue(isNone(fromNullable(undefined)));
43
+ {
44
+ const actual = fromNullable(null);
45
+ assertType<typeof actual, Option<never>>();
46
+ }
47
+ });
48
+
49
+ test("fromNullable maps values to some", () => {
50
+ const option = fromNullable("value");
51
+
52
+ assertEqual(option, some("value"));
53
+ });
54
+
55
+ test("fromNullable strips null and undefined from type", () => {
56
+ const value: string | null | undefined = "test";
57
+ const option = fromNullable(value);
58
+ assertType<typeof option, Option<string>>();
59
+ });
60
+
61
+ describe("InferOption", () => {
62
+ it("extracts value type from Option", () => {
63
+ type MyOption = Option<string>;
64
+ assertType<InferOption<MyOption>, string>();
65
+ });
66
+
67
+ it("extracts value type from Some", () => {
68
+ type MySome = Some<number>;
69
+ assertType<InferOption<MySome>, number>();
70
+ });
71
+
72
+ it("returns never for None", () => {
73
+ assertType<InferOption<None>, never>();
74
+ });
75
+
76
+ it("works at runtime", () => {
77
+ type MyOption = Option<string>;
78
+ const value: InferOption<MyOption> = "hello";
79
+ assertEqual(value, "hello");
80
+ });
81
+ });
82
+
83
+ describe("examples", () => {
84
+ it("cache that can store null and undefined", () => {
85
+ const cache = new Map<string, Option<unknown>>();
86
+
87
+ const get = (key: string): Option<unknown> => cache.get(key) ?? none;
88
+
89
+ cache.set("a", some(null));
90
+ cache.set("b", some(undefined));
91
+
92
+ assertTrue(isSome(get("a")));
93
+ assertTrue(isSome(get("b")));
94
+ assertTrue(isNone(get("c")));
95
+
96
+ const a = get("a");
97
+ assertTrue(isSome(a));
98
+ assertSame(a.value, null);
99
+
100
+ const b = get("b");
101
+ assertTrue(isSome(b));
102
+ assertSame(b.value, undefined);
103
+ });
104
+ });
package/src/Option.ts CHANGED
@@ -10,7 +10,6 @@
10
10
  *
11
11
  * ```ts
12
12
  * import {
13
- * assert,
14
13
  * assertSame,
15
14
  * assertTrue,
16
15
  * isNone,
@@ -28,7 +27,7 @@
28
27
  * cache.set("b", some(undefined));
29
28
  *
30
29
  * const a = get("a");
31
- * assert(isSome(a), "Expected Some.");
30
+ * assertTrue(isSome(a));
32
31
  * assertSame(a.value, null);
33
32
  * assertTrue(isSome(get("b")));
34
33
  * assertTrue(isNone(get("c")));
@@ -0,0 +1,56 @@
1
+ import { test } from "node:test";
2
+ import { assertEqual } from "./Assert.ts";
3
+
4
+ import {
5
+ createOrder,
6
+ orderBigInt,
7
+ orderNumber,
8
+ orderString,
9
+ orderUint8Array,
10
+ reverseOrder,
11
+ } from "./Order.ts";
12
+
13
+ test("createOrder", () => {
14
+ const orderNumber = createOrder<number>((x, y) => x < y);
15
+ assertEqual(orderNumber(1, 2), -1);
16
+ assertEqual(orderNumber(2, 1), 1);
17
+ assertEqual(orderNumber(1, 1), 0);
18
+
19
+ const orderString = createOrder<string>((x, y) => x.localeCompare(y) < 0);
20
+ assertEqual(orderString("a", "b"), -1);
21
+ assertEqual(orderString("b", "a"), 1);
22
+ assertEqual(orderString("a", "a"), 0);
23
+ });
24
+
25
+ test("reverseOrder", () => {
26
+ const orderNumberDesc = reverseOrder(orderNumber);
27
+ assertEqual([2, 1, 3].toSorted(orderNumberDesc), [3, 2, 1]);
28
+ });
29
+
30
+ test("orderString", () => {
31
+ assertEqual(["b", "a", "c"].toSorted(orderString), ["a", "b", "c"]);
32
+ });
33
+
34
+ test("orderNumber", () => {
35
+ assertEqual([2, 1, 3].toSorted(orderNumber), [1, 2, 3]);
36
+ });
37
+
38
+ test("orderBigInt", () => {
39
+ assertEqual([2n, 1n, 3n].toSorted(orderBigInt), [1n, 2n, 3n]);
40
+ });
41
+
42
+ test("orderUint8Array", () => {
43
+ const a = new Uint8Array([0x01, 0x02, 0x03]);
44
+ const b = new Uint8Array([0x01, 0x02, 0x04]);
45
+ const c = new Uint8Array([0x01, 0x02, 0x03]);
46
+ // shorter
47
+ const d = new Uint8Array([0x01, 0x02]);
48
+ // longer
49
+ const e = new Uint8Array([0x01, 0x02, 0x03, 0x00]);
50
+
51
+ assertEqual(orderUint8Array(a, b), -1);
52
+ assertEqual(orderUint8Array(b, a), 1);
53
+ assertEqual(orderUint8Array(a, c), 0);
54
+ assertEqual(orderUint8Array(a, d), 1);
55
+ assertEqual(orderUint8Array(a, e), -1);
56
+ });
package/src/Platform.ts CHANGED
@@ -171,8 +171,10 @@ const createTestGlobalErrors = (
171
171
  record((event as Record<typeof webErrorKey, unknown>)[webErrorKey]);
172
172
  };
173
173
 
174
+ // oxlint-disable-next-line evolu/no-unnecessary-global-this -- Call the same global object event API verified by the feature check.
174
175
  globalThis.addEventListener(webEvent, listener);
175
176
  disposableStack.defer(() => {
177
+ // oxlint-disable-next-line evolu/no-unnecessary-global-this -- Remove the listener through the same global object event API used to add it.
176
178
  globalThis.removeEventListener(webEvent, listener);
177
179
  });
178
180
  } else {
package/src/Polyfills.ts CHANGED
@@ -16,6 +16,12 @@
16
16
  * `Map.prototype.getOrInsertComputed`, `WeakMap.prototype.getOrInsert`, and
17
17
  * `WeakMap.prototype.getOrInsertComputed`) for Node 24 and React Native
18
18
  *
19
+ * The polyfills are implemented directly in the Evolu Library instead of being
20
+ * third-party runtime dependencies. We audit their behavior against
21
+ * specifications, conformance tests, and native implementations, and fix issues
22
+ * when necessary. This lets Evolu understand and control every line of this
23
+ * runtime code.
24
+ *
19
25
  * `@evolu/react-native` has its own `Polyfills` module and its
20
26
  * `installPolyfills` calls this function first, then installs React Native
21
27
  * specific polyfills.
@@ -125,6 +131,7 @@ const appendDisposeError = (
125
131
  currentError: unknown,
126
132
  previousError: unknown,
127
133
  ): unknown =>
134
+ // oxlint-disable-next-line evolu/no-unnecessary-global-this -- Use the constructor installed on the global object even if a realm lexical binding shadows it.
128
135
  new globalThis.SuppressedError(
129
136
  currentError,
130
137
  previousError,
@@ -158,6 +165,7 @@ const getOrInstallSymbol = (
158
165
  key: "dispose" | "asyncDispose",
159
166
  description: string,
160
167
  ): symbol => {
168
+ // oxlint-disable-next-line evolu/no-unnecessary-global-this -- Patch the global object constructor even if a realm lexical binding shadows it.
161
169
  const SymbolCtor = globalThis.Symbol as SymbolWithDisposable;
162
170
  const installedValue = Object.getOwnPropertyDescriptor(SymbolCtor, key)
163
171
  ?.value as unknown;
@@ -178,10 +186,7 @@ const getOrInstallSymbol = (
178
186
  const installSuppressedError = (): void => {
179
187
  if (typeof globalThis.SuppressedError === "function") return;
180
188
 
181
- class SuppressedErrorPolyfill
182
- extends Error
183
- implements globalThis.SuppressedError
184
- {
189
+ class SuppressedErrorPolyfill extends Error implements SuppressedError {
185
190
  readonly error: unknown;
186
191
  readonly suppressed: unknown;
187
192
 
@@ -0,0 +1,89 @@
1
+ import { afterEach, describe, it, mock } from "node:test";
2
+ import { assertEqual } from "./Assert.ts";
3
+
4
+ const randomLibConstructor = mock.fn<(seed?: string) => void>();
5
+ const randomLibNext = mock.fn<() => number>();
6
+
7
+ mock.module("random", {
8
+ // @ts-expect-error -- Node.js 24.20 replaces the deprecated namedExports option with exports, which @types/node 24.13 does not declare yet.
9
+ exports: {
10
+ Random: class {
11
+ next = randomLibNext;
12
+
13
+ constructor(seed?: string) {
14
+ if (seed === undefined) randomLibConstructor();
15
+ else randomLibConstructor(seed);
16
+ }
17
+ },
18
+ },
19
+ });
20
+
21
+ const { createRandom, createRandomLib, testCreateRandom, testCreateRandomLib } =
22
+ await import("./Random.ts");
23
+
24
+ describe("Random", () => {
25
+ afterEach(() => {
26
+ mock.restoreAll();
27
+ randomLibConstructor.mock.resetCalls();
28
+ randomLibNext.mock.resetCalls();
29
+ });
30
+
31
+ it("createRandom delegates to Math.random", () => {
32
+ const mathRandom = mock.method(Math, "random", () => 0.25);
33
+
34
+ assertEqual(createRandom().next(), 0.25);
35
+ assertEqual(mathRandom.mock.callCount(), 1);
36
+ });
37
+
38
+ it("testCreateRandom constructs RandomLib with the default seed", () => {
39
+ testCreateRandom();
40
+
41
+ assertEqual(
42
+ randomLibConstructor.mock.calls.map(({ arguments: args }) => args),
43
+ [["evolu"]],
44
+ );
45
+ });
46
+
47
+ it("testCreateRandom constructs RandomLib with a custom seed", () => {
48
+ testCreateRandom("test");
49
+
50
+ assertEqual(
51
+ randomLibConstructor.mock.calls.map(({ arguments: args }) => args),
52
+ [["test"]],
53
+ );
54
+ });
55
+
56
+ it("testCreateRandom delegates next to RandomLib", () => {
57
+ randomLibNext.mock.mockImplementation(() => 0.25);
58
+
59
+ assertEqual(testCreateRandom().next(), 0.25);
60
+ assertEqual(randomLibNext.mock.callCount(), 1);
61
+ });
62
+
63
+ it("createRandomLib constructs RandomLib without a seed", () => {
64
+ createRandomLib();
65
+
66
+ assertEqual(
67
+ randomLibConstructor.mock.calls.map(({ arguments: args }) => args),
68
+ [[]],
69
+ );
70
+ });
71
+
72
+ it("testCreateRandomLib constructs RandomLib with the default seed", () => {
73
+ testCreateRandomLib();
74
+
75
+ assertEqual(
76
+ randomLibConstructor.mock.calls.map(({ arguments: args }) => args),
77
+ [["evolu"]],
78
+ );
79
+ });
80
+
81
+ it("testCreateRandomLib constructs RandomLib with a custom seed", () => {
82
+ testCreateRandomLib("test");
83
+
84
+ assertEqual(
85
+ randomLibConstructor.mock.calls.map(({ arguments: args }) => args),
86
+ [["test"]],
87
+ );
88
+ });
89
+ });
package/src/Random.ts CHANGED
@@ -34,7 +34,7 @@ export type RandomNumber = number & Brand<"RandomNumber">;
34
34
  * // For apps, use the Math.random-backed implementation.
35
35
  * const random = createRandom();
36
36
  * const value = random.next();
37
- * assertType<RandomNumber, typeof value>();
37
+ * assertType<typeof value, RandomNumber>();
38
38
  * assertTrue(value >= 0);
39
39
  * assertTrue(value < 1);
40
40
  *
@@ -0,0 +1,139 @@
1
+ import { describe, it } from "node:test";
2
+ import { assertEqual } from "./Assert.ts";
3
+
4
+ import { createRef } from "./Ref.ts";
5
+
6
+ describe("Ref", () => {
7
+ describe("get", () => {
8
+ it("returns initial value", () => {
9
+ const ref = createRef(42);
10
+ assertEqual(ref.get(), 42);
11
+ });
12
+ });
13
+
14
+ describe("set", () => {
15
+ it("updates value", () => {
16
+ const ref = createRef(0);
17
+ ref.set(1);
18
+ assertEqual(ref.get(), 1);
19
+ });
20
+
21
+ it("accepts a value equal to the current value", () => {
22
+ const ref = createRef(1);
23
+ ref.set(1);
24
+ assertEqual(ref.get(), 1);
25
+ });
26
+ });
27
+
28
+ describe("getAndSet", () => {
29
+ it("returns previous value and updates value", () => {
30
+ const ref = createRef(1);
31
+
32
+ assertEqual(ref.getAndSet(2), 1);
33
+ assertEqual(ref.get(), 2);
34
+ });
35
+
36
+ it("accepts a value equal to the current value", () => {
37
+ const ref = createRef(1);
38
+
39
+ assertEqual(ref.getAndSet(1), 1);
40
+ assertEqual(ref.get(), 1);
41
+ });
42
+ });
43
+
44
+ describe("setAndGet", () => {
45
+ it("returns updated value", () => {
46
+ const ref = createRef(1);
47
+
48
+ assertEqual(ref.setAndGet(2), 2);
49
+ assertEqual(ref.get(), 2);
50
+ });
51
+
52
+ it("accepts a value equal to the current value", () => {
53
+ const ref = createRef(1);
54
+
55
+ assertEqual(ref.setAndGet(1), 1);
56
+ assertEqual(ref.get(), 1);
57
+ });
58
+ });
59
+
60
+ describe("update", () => {
61
+ it("updates value", () => {
62
+ const ref = createRef(1);
63
+
64
+ ref.update((n) => n + 1);
65
+
66
+ assertEqual(ref.get(), 2);
67
+ });
68
+
69
+ it("can keep the same value", () => {
70
+ const ref = createRef(1);
71
+
72
+ ref.update((n) => n);
73
+
74
+ assertEqual(ref.get(), 1);
75
+ });
76
+ });
77
+
78
+ describe("getAndUpdate", () => {
79
+ it("returns previous value and updates value", () => {
80
+ const ref = createRef(1);
81
+
82
+ assertEqual(
83
+ ref.getAndUpdate((n) => n + 1),
84
+ 1,
85
+ );
86
+ assertEqual(ref.get(), 2);
87
+ });
88
+
89
+ it("accepts an identity updater", () => {
90
+ const ref = createRef(1);
91
+
92
+ assertEqual(
93
+ ref.getAndUpdate((n) => n),
94
+ 1,
95
+ );
96
+ assertEqual(ref.get(), 1);
97
+ });
98
+ });
99
+
100
+ describe("updateAndGet", () => {
101
+ it("returns updated value", () => {
102
+ const ref = createRef(1);
103
+
104
+ assertEqual(
105
+ ref.updateAndGet((n) => n + 1),
106
+ 2,
107
+ );
108
+ assertEqual(ref.get(), 2);
109
+ });
110
+
111
+ it("accepts an identity updater", () => {
112
+ const ref = createRef(1);
113
+
114
+ assertEqual(
115
+ ref.updateAndGet((n) => n),
116
+ 1,
117
+ );
118
+ assertEqual(ref.get(), 1);
119
+ });
120
+ });
121
+
122
+ describe("modify", () => {
123
+ it("returns a computed result and updates value", () => {
124
+ const ref = createRef(0);
125
+ const result = ref.modify((current) => [current, current + 1]);
126
+
127
+ assertEqual(result, 0);
128
+ assertEqual(ref.get(), 1);
129
+ });
130
+
131
+ it("can keep the same value while returning a result", () => {
132
+ const ref = createRef(1);
133
+ const result = ref.modify((current) => [`current:${current}`, current]);
134
+
135
+ assertEqual(result, "current:1");
136
+ assertEqual(ref.get(), 1);
137
+ });
138
+ });
139
+ });
@@ -0,0 +1,184 @@
1
+ import { describe, it } from "node:test";
2
+ import {
3
+ assertEqual,
4
+ assertFalse,
5
+ assertThrowsInstanceOf,
6
+ assertTrue,
7
+ } from "./Assert.ts";
8
+
9
+ import { installPolyfills } from "./Polyfills.ts";
10
+ import { createRefCount, createRefCountByKey } from "./RefCount.ts";
11
+
12
+ installPolyfills();
13
+
14
+ describe("createRefCount", () => {
15
+ it("increments and decrements the count", () => {
16
+ const refCount = createRefCount();
17
+
18
+ assertEqual(refCount.getCount(), 0);
19
+ assertEqual(refCount.increment(), 1);
20
+ assertEqual(refCount.increment(), 2);
21
+ assertEqual(refCount.getCount(), 2);
22
+ assertEqual(refCount.decrement(), 1);
23
+ assertEqual(refCount.decrement(), 0);
24
+ assertEqual(refCount.getCount(), 0);
25
+ });
26
+
27
+ it("decrement throws on underflow", () => {
28
+ const refCount = createRefCount();
29
+
30
+ const error = assertThrowsInstanceOf(() => refCount.decrement(), Error);
31
+ assertTrue(
32
+ error.message.includes("RefCount must not be decremented below zero."),
33
+ );
34
+ });
35
+
36
+ it("dispose invalidates the helper", () => {
37
+ const refCount = createRefCount();
38
+
39
+ refCount.increment();
40
+ refCount.increment();
41
+ refCount[Symbol.dispose]();
42
+
43
+ const incrementError = assertThrowsInstanceOf(
44
+ () => refCount.increment(),
45
+ Error,
46
+ );
47
+ assertTrue(
48
+ incrementError.message.includes("Cannot use a disposed object."),
49
+ );
50
+ const decrementError = assertThrowsInstanceOf(
51
+ () => refCount.decrement(),
52
+ Error,
53
+ );
54
+ assertTrue(
55
+ decrementError.message.includes("Cannot use a disposed object."),
56
+ );
57
+ const getCountError = assertThrowsInstanceOf(
58
+ () => refCount.getCount(),
59
+ Error,
60
+ );
61
+ assertTrue(getCountError.message.includes("Cannot use a disposed object."));
62
+ });
63
+ });
64
+
65
+ describe("createRefCountByKey", () => {
66
+ it("tracks counts per key", () => {
67
+ const refCount = createRefCountByKey<string>();
68
+
69
+ assertEqual(refCount.increment("a"), 1);
70
+ assertEqual(refCount.increment("a"), 2);
71
+ assertEqual(refCount.increment("b"), 1);
72
+ assertEqual(refCount.getCount("a"), 2);
73
+ assertEqual(refCount.getCount("b"), 1);
74
+ assertEqual(refCount.keys(), new Set(["a", "b"]));
75
+ });
76
+
77
+ it("decrement removes key at zero", () => {
78
+ const refCount = createRefCountByKey<string>();
79
+
80
+ refCount.increment("a");
81
+
82
+ assertEqual(refCount.decrement("a"), 0);
83
+ assertEqual(refCount.getCount("a"), 0);
84
+ assertFalse(refCount.has("a"));
85
+ assertEqual(refCount.keys(), new Set());
86
+ });
87
+
88
+ it("decrement throws on missing key", () => {
89
+ const refCount = createRefCountByKey<string>();
90
+
91
+ const error = assertThrowsInstanceOf(
92
+ () => refCount.decrement("missing"),
93
+ Error,
94
+ );
95
+ assertTrue(
96
+ error.message.includes(
97
+ "RefCount must not be decremented for an untracked key.",
98
+ ),
99
+ );
100
+ assertFalse(refCount.has("missing"));
101
+ assertEqual(refCount.keys(), new Set());
102
+ });
103
+
104
+ it("decrement keeps key while count stays positive", () => {
105
+ const refCount = createRefCountByKey<string>();
106
+
107
+ refCount.increment("a");
108
+ refCount.increment("a");
109
+
110
+ assertEqual(refCount.decrement("a"), 1);
111
+ assertEqual(refCount.getCount("a"), 1);
112
+ assertTrue(refCount.has("a"));
113
+ });
114
+
115
+ it("keys returns a snapshot set", () => {
116
+ const refCount = createRefCountByKey<string>();
117
+ refCount.increment("a");
118
+
119
+ const keys = refCount.keys();
120
+ refCount.increment("b");
121
+
122
+ assertEqual(keys, new Set(["a"]));
123
+ });
124
+
125
+ it("dispose invalidates the helper", () => {
126
+ const refCount = createRefCountByKey<string>();
127
+
128
+ refCount.increment("a");
129
+ refCount.increment("b");
130
+ refCount[Symbol.dispose]();
131
+
132
+ const incrementError = assertThrowsInstanceOf(
133
+ () => refCount.increment("c"),
134
+ Error,
135
+ );
136
+ assertTrue(
137
+ incrementError.message.includes("Cannot use a disposed object."),
138
+ );
139
+ const decrementError = assertThrowsInstanceOf(
140
+ () => refCount.decrement("a"),
141
+ Error,
142
+ );
143
+ assertTrue(
144
+ decrementError.message.includes("Cannot use a disposed object."),
145
+ );
146
+ const getCountError = assertThrowsInstanceOf(
147
+ () => refCount.getCount("a"),
148
+ Error,
149
+ );
150
+ assertTrue(getCountError.message.includes("Cannot use a disposed object."));
151
+ const hasError = assertThrowsInstanceOf(() => refCount.has("a"), Error);
152
+ assertTrue(hasError.message.includes("Cannot use a disposed object."));
153
+ const keysError = assertThrowsInstanceOf(() => refCount.keys(), Error);
154
+ assertTrue(keysError.message.includes("Cannot use a disposed object."));
155
+ });
156
+
157
+ it("uses reference identity for object keys", () => {
158
+ const refCount = createRefCountByKey<{ readonly id: string }>();
159
+ const keyA = { id: "same" };
160
+ const keyB = { id: "same" };
161
+
162
+ refCount.increment(keyA);
163
+ refCount.increment(keyB);
164
+
165
+ assertEqual(refCount.getCount(keyA), 1);
166
+ assertEqual(refCount.getCount(keyB), 1);
167
+ assertEqual(refCount.keys().size, 2);
168
+ });
169
+
170
+ it("uses lookup for logical key equality", () => {
171
+ const refCount = createRefCountByKey<{ readonly id: string }, string>({
172
+ lookup: (key) => key.id,
173
+ });
174
+
175
+ assertEqual(refCount.increment({ id: "same" }), 1);
176
+ assertEqual(refCount.increment({ id: "same" }), 2);
177
+ assertEqual(refCount.getCount({ id: "same" }), 2);
178
+ assertEqual(refCount.keys().size, 1);
179
+
180
+ assertEqual(refCount.decrement({ id: "same" }), 1);
181
+ assertEqual(refCount.decrement({ id: "same" }), 0);
182
+ assertFalse(refCount.has({ id: "same" }));
183
+ });
184
+ });