@evolu/common 8.7.0 → 8.8.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 (300) 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 +478 -141
  4. package/dist/src/Assert.d.ts.map +1 -1
  5. package/dist/src/Assert.js +644 -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.js +4 -1
  40. package/dist/src/Random.d.ts +1 -1
  41. package/dist/src/Result.d.ts +49 -54
  42. package/dist/src/Result.d.ts.map +1 -1
  43. package/dist/src/Result.js +17 -22
  44. package/dist/src/Set.d.ts +6 -6
  45. package/dist/src/Set.js +2 -2
  46. package/dist/src/Sqlite.js +1 -1
  47. package/dist/src/String.js +3 -2
  48. package/dist/src/Task.d.ts +32 -32
  49. package/dist/src/Task.d.ts.map +1 -1
  50. package/dist/src/Task.js +20 -22
  51. package/dist/src/Test.d.ts +31 -3
  52. package/dist/src/Test.d.ts.map +1 -1
  53. package/dist/src/Test.js +46 -3
  54. package/dist/src/Time.d.ts +3 -11
  55. package/dist/src/Time.d.ts.map +1 -1
  56. package/dist/src/Time.js +10 -18
  57. package/dist/src/Type.d.ts +87 -85
  58. package/dist/src/Type.d.ts.map +1 -1
  59. package/dist/src/Type.js +70 -63
  60. package/dist/src/Types.d.ts +12 -12
  61. package/dist/src/Types.js +1 -1
  62. package/dist/src/WebSocket.d.ts +4 -7
  63. package/dist/src/WebSocket.d.ts.map +1 -1
  64. package/dist/src/Worker.d.ts +1 -1
  65. package/dist/src/Worker.js +7 -7
  66. package/dist/src/index.d.ts +1 -1
  67. package/dist/src/index.js +1 -1
  68. package/dist/src/intl/_en.d.ts.map +1 -1
  69. package/dist/src/intl/_en.js +1 -1
  70. package/dist/src/intl/ar.d.ts.map +1 -1
  71. package/dist/src/intl/ar.js +1 -1
  72. package/dist/src/intl/bn.d.ts.map +1 -1
  73. package/dist/src/intl/bn.js +1 -1
  74. package/dist/src/intl/ca.d.ts.map +1 -1
  75. package/dist/src/intl/ca.js +1 -1
  76. package/dist/src/intl/cs.d.ts.map +1 -1
  77. package/dist/src/intl/cs.js +1 -1
  78. package/dist/src/intl/da.d.ts.map +1 -1
  79. package/dist/src/intl/da.js +1 -1
  80. package/dist/src/intl/de.d.ts.map +1 -1
  81. package/dist/src/intl/de.js +1 -1
  82. package/dist/src/intl/el.d.ts.map +1 -1
  83. package/dist/src/intl/el.js +1 -1
  84. package/dist/src/intl/es.d.ts.map +1 -1
  85. package/dist/src/intl/es.js +1 -1
  86. package/dist/src/intl/fa.d.ts.map +1 -1
  87. package/dist/src/intl/fa.js +1 -1
  88. package/dist/src/intl/fi.d.ts.map +1 -1
  89. package/dist/src/intl/fi.js +1 -1
  90. package/dist/src/intl/fil.d.ts.map +1 -1
  91. package/dist/src/intl/fil.js +1 -1
  92. package/dist/src/intl/fr.d.ts.map +1 -1
  93. package/dist/src/intl/fr.js +1 -1
  94. package/dist/src/intl/he.d.ts.map +1 -1
  95. package/dist/src/intl/he.js +1 -1
  96. package/dist/src/intl/hi.d.ts.map +1 -1
  97. package/dist/src/intl/hi.js +1 -1
  98. package/dist/src/intl/hr.d.ts.map +1 -1
  99. package/dist/src/intl/hr.js +1 -1
  100. package/dist/src/intl/hu.d.ts.map +1 -1
  101. package/dist/src/intl/hu.js +1 -1
  102. package/dist/src/intl/id.d.ts.map +1 -1
  103. package/dist/src/intl/id.js +1 -1
  104. package/dist/src/intl/it.d.ts.map +1 -1
  105. package/dist/src/intl/it.js +1 -1
  106. package/dist/src/intl/ja.d.ts.map +1 -1
  107. package/dist/src/intl/ja.js +1 -1
  108. package/dist/src/intl/ko.d.ts.map +1 -1
  109. package/dist/src/intl/ko.js +1 -1
  110. package/dist/src/intl/ml.d.ts.map +1 -1
  111. package/dist/src/intl/ml.js +1 -1
  112. package/dist/src/intl/mr.d.ts.map +1 -1
  113. package/dist/src/intl/mr.js +1 -1
  114. package/dist/src/intl/ms.d.ts.map +1 -1
  115. package/dist/src/intl/ms.js +1 -1
  116. package/dist/src/intl/nb.d.ts.map +1 -1
  117. package/dist/src/intl/nb.js +1 -1
  118. package/dist/src/intl/nl.d.ts.map +1 -1
  119. package/dist/src/intl/nl.js +1 -1
  120. package/dist/src/intl/pa.d.ts.map +1 -1
  121. package/dist/src/intl/pa.js +1 -1
  122. package/dist/src/intl/pl.d.ts.map +1 -1
  123. package/dist/src/intl/pl.js +1 -1
  124. package/dist/src/intl/pt-BR.d.ts.map +1 -1
  125. package/dist/src/intl/pt-BR.js +1 -1
  126. package/dist/src/intl/pt.d.ts.map +1 -1
  127. package/dist/src/intl/pt.js +1 -1
  128. package/dist/src/intl/ro.d.ts.map +1 -1
  129. package/dist/src/intl/ro.js +1 -1
  130. package/dist/src/intl/sk.d.ts.map +1 -1
  131. package/dist/src/intl/sk.js +1 -1
  132. package/dist/src/intl/sl.d.ts.map +1 -1
  133. package/dist/src/intl/sl.js +1 -1
  134. package/dist/src/intl/sv.d.ts.map +1 -1
  135. package/dist/src/intl/sv.js +1 -1
  136. package/dist/src/intl/sw.d.ts.map +1 -1
  137. package/dist/src/intl/sw.js +1 -1
  138. package/dist/src/intl/ta.d.ts.map +1 -1
  139. package/dist/src/intl/ta.js +1 -1
  140. package/dist/src/intl/te.d.ts.map +1 -1
  141. package/dist/src/intl/te.js +1 -1
  142. package/dist/src/intl/th.d.ts.map +1 -1
  143. package/dist/src/intl/th.js +1 -1
  144. package/dist/src/intl/tr.d.ts.map +1 -1
  145. package/dist/src/intl/tr.js +1 -1
  146. package/dist/src/intl/uk.d.ts.map +1 -1
  147. package/dist/src/intl/uk.js +1 -1
  148. package/dist/src/intl/ur.d.ts.map +1 -1
  149. package/dist/src/intl/ur.js +1 -1
  150. package/dist/src/intl/vi.d.ts.map +1 -1
  151. package/dist/src/intl/vi.js +1 -1
  152. package/dist/src/intl/zh-CN.d.ts.map +1 -1
  153. package/dist/src/intl/zh-CN.js +1 -1
  154. package/dist/src/intl/zh-TW.d.ts.map +1 -1
  155. package/dist/src/intl/zh-TW.js +1 -1
  156. package/dist/src/local-first/Evolu.d.ts +12 -12
  157. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  158. package/dist/src/local-first/Evolu.js +2 -2
  159. package/dist/src/local-first/Protocol.d.ts +14 -76
  160. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  161. package/dist/src/local-first/Protocol.js +29 -206
  162. package/dist/src/local-first/Query.d.ts +11 -11
  163. package/dist/src/local-first/Query.js +6 -6
  164. package/dist/src/local-first/Relay.d.ts +2 -2
  165. package/dist/src/local-first/Schema.d.ts +3 -5
  166. package/dist/src/local-first/Schema.d.ts.map +1 -1
  167. package/dist/src/local-first/Schema.js +2 -4
  168. package/dist/src/local-first/Shared.d.ts.map +1 -1
  169. package/dist/src/local-first/Shared.js +5 -5
  170. package/dist/src/local-first/Storage.js +4 -4
  171. package/dist/src/local-first/Timestamp.d.ts +9 -0
  172. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  173. package/dist/src/local-first/Timestamp.js +13 -10
  174. package/package.json +7 -4
  175. package/src/Array.test.ts +809 -0
  176. package/src/Array.ts +21 -21
  177. package/src/Assert.test.ts +1181 -0
  178. package/src/Assert.ts +971 -227
  179. package/src/BigInt.test.ts +28 -0
  180. package/src/Binary.test.ts +1128 -0
  181. package/src/{Buffer.ts → Binary.ts} +273 -46
  182. package/src/Brand.test.ts +90 -0
  183. package/src/Cache.test.ts +183 -0
  184. package/src/Callbacks.test.ts +66 -0
  185. package/src/Console.test.ts +809 -0
  186. package/src/Console.ts +9 -17
  187. package/src/Crypto.test.ts +138 -0
  188. package/src/Crypto.ts +5 -5
  189. package/src/Eq.test.ts +333 -0
  190. package/src/Eq.ts +110 -176
  191. package/src/Error.test.ts +118 -0
  192. package/src/Function.test.ts +204 -0
  193. package/src/Function.ts +10 -15
  194. package/src/Http.test.ts +816 -0
  195. package/src/Http.ts +6 -7
  196. package/src/LeakDetector.test.ts +210 -0
  197. package/src/LeakDetector.ts +1 -0
  198. package/src/LockManager.ts +2 -2
  199. package/src/Lookup.test.ts +418 -0
  200. package/src/Lookup.ts +4 -3
  201. package/src/Microtask.test.ts +140 -0
  202. package/src/Number.test.ts +218 -0
  203. package/src/Number.ts +2 -2
  204. package/src/Object.test.ts +293 -0
  205. package/src/Object.ts +5 -4
  206. package/src/Option.test.ts +104 -0
  207. package/src/Option.ts +1 -2
  208. package/src/Order.test.ts +56 -0
  209. package/src/Platform.ts +2 -0
  210. package/src/Polyfills.ts +3 -4
  211. package/src/Random.test.ts +89 -0
  212. package/src/Random.ts +1 -1
  213. package/src/Ref.test.ts +139 -0
  214. package/src/RefCount.test.ts +184 -0
  215. package/src/Relation.test.ts +505 -0
  216. package/src/Resource.test.ts +4623 -0
  217. package/src/Result.test.ts +1829 -0
  218. package/src/Result.ts +49 -54
  219. package/src/Schedule.test.ts +1554 -0
  220. package/src/Set.test.ts +279 -0
  221. package/src/Set.ts +6 -6
  222. package/src/Sqlite.ts +1 -1
  223. package/src/Store.test.ts +288 -0
  224. package/src/String.test.ts +54 -0
  225. package/src/String.ts +3 -3
  226. package/src/Task.test.ts +10820 -0
  227. package/src/Task.ts +38 -43
  228. package/src/Test.test.ts +88 -0
  229. package/src/Test.ts +49 -3
  230. package/src/Time.test.ts +926 -0
  231. package/src/Time.ts +13 -21
  232. package/src/Type.base64.test.ts +88 -0
  233. package/src/Type.test.ts +20465 -0
  234. package/src/Type.ts +114 -111
  235. package/src/Types.test.ts +324 -0
  236. package/src/Types.ts +13 -13
  237. package/src/WebSocket.ts +4 -7
  238. package/src/Worker.test.ts +755 -0
  239. package/src/Worker.ts +13 -18
  240. package/src/index.ts +1 -1
  241. package/src/intl/_en.ts +1 -1
  242. package/src/intl/ar.ts +1 -1
  243. package/src/intl/bn.ts +1 -1
  244. package/src/intl/ca.ts +1 -1
  245. package/src/intl/cs.ts +1 -1
  246. package/src/intl/da.ts +1 -1
  247. package/src/intl/de.ts +1 -1
  248. package/src/intl/el.ts +1 -1
  249. package/src/intl/es.ts +1 -1
  250. package/src/intl/fa.ts +1 -1
  251. package/src/intl/fi.ts +1 -1
  252. package/src/intl/fil.ts +1 -1
  253. package/src/intl/fr.ts +1 -1
  254. package/src/intl/he.ts +1 -1
  255. package/src/intl/hi.ts +1 -1
  256. package/src/intl/hr.ts +1 -1
  257. package/src/intl/hu.ts +1 -1
  258. package/src/intl/id.ts +1 -1
  259. package/src/intl/intl.test.ts +3106 -0
  260. package/src/intl/it.ts +1 -1
  261. package/src/intl/ja.ts +1 -1
  262. package/src/intl/ko.ts +1 -1
  263. package/src/intl/ml.ts +1 -1
  264. package/src/intl/mr.ts +1 -1
  265. package/src/intl/ms.ts +1 -1
  266. package/src/intl/nb.ts +1 -1
  267. package/src/intl/nl.ts +1 -1
  268. package/src/intl/pa.ts +1 -1
  269. package/src/intl/pl.ts +1 -1
  270. package/src/intl/pt-BR.ts +1 -1
  271. package/src/intl/pt.ts +1 -1
  272. package/src/intl/ro.ts +1 -1
  273. package/src/intl/sk.ts +1 -1
  274. package/src/intl/sl.ts +1 -1
  275. package/src/intl/sv.ts +1 -1
  276. package/src/intl/sw.ts +1 -1
  277. package/src/intl/ta.ts +1 -1
  278. package/src/intl/te.ts +1 -1
  279. package/src/intl/th.ts +1 -1
  280. package/src/intl/tr.ts +1 -1
  281. package/src/intl/uk.ts +1 -1
  282. package/src/intl/ur.ts +1 -1
  283. package/src/intl/vi.ts +1 -1
  284. package/src/intl/zh-CN.ts +1 -1
  285. package/src/intl/zh-TW.ts +1 -1
  286. package/src/local-first/Evolu.test.ts +1921 -0
  287. package/src/local-first/Evolu.ts +18 -14
  288. package/src/local-first/Owner.test.ts +83 -0
  289. package/src/local-first/Protocol.test.ts +1037 -0
  290. package/src/local-first/Protocol.ts +58 -261
  291. package/src/local-first/Query.test.ts +349 -0
  292. package/src/local-first/Query.ts +11 -11
  293. package/src/local-first/Relay.ts +2 -2
  294. package/src/local-first/Schema.ts +3 -5
  295. package/src/local-first/Shared.test.ts +1828 -0
  296. package/src/local-first/Shared.ts +9 -8
  297. package/src/local-first/Storage.ts +4 -4
  298. package/src/local-first/Timestamp.test.ts +374 -0
  299. package/src/local-first/Timestamp.ts +22 -10
  300. 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
@@ -125,6 +125,7 @@ const appendDisposeError = (
125
125
  currentError: unknown,
126
126
  previousError: unknown,
127
127
  ): unknown =>
128
+ // 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
129
  new globalThis.SuppressedError(
129
130
  currentError,
130
131
  previousError,
@@ -158,6 +159,7 @@ const getOrInstallSymbol = (
158
159
  key: "dispose" | "asyncDispose",
159
160
  description: string,
160
161
  ): symbol => {
162
+ // oxlint-disable-next-line evolu/no-unnecessary-global-this -- Patch the global object constructor even if a realm lexical binding shadows it.
161
163
  const SymbolCtor = globalThis.Symbol as SymbolWithDisposable;
162
164
  const installedValue = Object.getOwnPropertyDescriptor(SymbolCtor, key)
163
165
  ?.value as unknown;
@@ -178,10 +180,7 @@ const getOrInstallSymbol = (
178
180
  const installSuppressedError = (): void => {
179
181
  if (typeof globalThis.SuppressedError === "function") return;
180
182
 
181
- class SuppressedErrorPolyfill
182
- extends Error
183
- implements globalThis.SuppressedError
184
- {
183
+ class SuppressedErrorPolyfill extends Error implements SuppressedError {
185
184
  readonly error: unknown;
186
185
  readonly suppressed: unknown;
187
186
 
@@ -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
+ });