@evolu/common 8.6.2 → 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 (303) 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/Binary.d.ts +254 -0
  7. package/dist/src/Binary.d.ts.map +1 -0
  8. package/dist/src/Binary.js +958 -0
  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 +28 -76
  160. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  161. package/dist/src/local-first/Protocol.js +45 -229
  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 -5
  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/Binary.ts +1377 -0
  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 +75 -290
  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 +0 -122
  301. package/dist/src/Buffer.d.ts.map +0 -1
  302. package/dist/src/Buffer.js +0 -69
  303. package/src/Buffer.ts +0 -206
@@ -10,7 +10,11 @@ import {
10
10
  isNonEmptyArray,
11
11
  type NonEmptyReadonlyArray,
12
12
  } from "../Array.ts";
13
- import { assert } from "../Assert.ts";
13
+ import {
14
+ assertNonNullable,
15
+ assertNotSame,
16
+ assertNotUndefined,
17
+ } from "../Assert.ts";
14
18
  import type { Brand } from "../Brand.ts";
15
19
  import { createCallbacks } from "../Callbacks.ts";
16
20
  import type { ConsoleEntry, ConsoleLevel } from "../Console.ts";
@@ -495,7 +499,7 @@ const createEvoluTenant =
495
499
 
496
500
  const initDbWorker = (): void => {
497
501
  const tabLeaderPort = deps.tabLeaderPortStore.get();
498
- assert(tabLeaderPort, "Expected tab leader port.");
502
+ assertNonNullable(tabLeaderPort);
499
503
 
500
504
  const dbWorkerChannel = deps.createMessageChannel<
501
505
  DbWorkerOutput,
@@ -506,10 +510,7 @@ const createEvoluTenant =
506
510
  currentDbWorkerPort.onMessage = (message) => {
507
511
  switch (message.type) {
508
512
  case "LeaderAcquired": {
509
- assert(
510
- dbWorkerPort !== currentDbWorkerPort,
511
- "Expected a new DbWorker port.",
512
- );
513
+ assertNotSame(dbWorkerPort, currentDbWorkerPort);
513
514
  dbWorkerPort?.[Symbol.dispose]();
514
515
  dbWorkerPort = currentDbWorkerPort;
515
516
  queueRequestInFlight = false;
@@ -775,9 +776,9 @@ const createEvoluTenant =
775
776
  } else {
776
777
  instance.usedSyncOwners.decrement(syncOwner);
777
778
  const claimLeases = instance.claimLeasesBySyncOwner.get(syncOwner);
778
- assert(claimLeases, "Expected a ClaimLease for the used owner.");
779
+ assertNotUndefined(claimLeases);
779
780
  const claimLease = claimLeases.pop();
780
- assert(claimLease, "Expected a ClaimLease for the used owner.");
781
+ assertNotUndefined(claimLease);
781
782
  claimLease.release();
782
783
  if (claimLeases.length === 0) {
783
784
  instance.claimLeasesBySyncOwner.delete(syncOwner);
@@ -7,9 +7,9 @@
7
7
  import { sha256 } from "@noble/hashes/sha2.js";
8
8
  import type { NonEmptyReadonlyArray } from "../Array.ts";
9
9
  import { firstInArray, isNonEmptyArray } from "../Array.ts";
10
- import { assert } from "../Assert.ts";
10
+ import { assert, assertNonNullable } from "../Assert.ts";
11
11
  import type { Brand } from "../Brand.ts";
12
- import { concatBytes } from "../Buffer.ts";
12
+ import { concatBytes } from "../Binary.ts";
13
13
  import { decrement } from "../Number.ts";
14
14
  import type { RandomDep } from "../Random.ts";
15
15
  import { err, ok } from "../Result.ts";
@@ -1602,8 +1602,8 @@ export const readOwnerUsageOrDefault =
1602
1602
  }
1603
1603
 
1604
1604
  const row = firstInArray(result.rows);
1605
- assert(row.firstTimestamp, "not null");
1606
- assert(row.lastTimestamp, "not null");
1605
+ assertNonNullable(row.firstTimestamp);
1606
+ assertNonNullable(row.lastTimestamp);
1607
1607
 
1608
1608
  return {
1609
1609
  storedBytes: row.storedBytes,
@@ -0,0 +1,374 @@
1
+ import { describe, it, test } from "node:test";
2
+ import {
3
+ assertEqual,
4
+ assertErr,
5
+ assertFalse,
6
+ assertOk,
7
+ assertTrue,
8
+ } from "../Assert.ts";
9
+ import { increment } from "../Number.ts";
10
+ import { orderNumber } from "../Order.ts";
11
+ import type { Result } from "../Result.ts";
12
+ import { ok } from "../Result.ts";
13
+ import { testCreateDeps } from "../Task.ts";
14
+ import type { Time, TimeDep } from "../Time.ts";
15
+ import { maxMillis, Millis, minMillis, testCreateTime } from "../Time.ts";
16
+ import type {
17
+ Timestamp,
18
+ TimestampBytes,
19
+ TimestampConfigDep,
20
+ TimestampCounterOverflowError,
21
+ TimestampDriftError,
22
+ TimestampTimeOutOfRangeError,
23
+ } from "./Timestamp.ts";
24
+ import {
25
+ Counter,
26
+ createInitialTimestamp,
27
+ createTimestamp,
28
+ defaultTimestampMaxDrift,
29
+ maxCounter,
30
+ minCounter,
31
+ NodeId,
32
+ nodeIdBytesToNodeId,
33
+ nodeIdToNodeIdBytes,
34
+ orderTimestampBytes,
35
+ receiveTimestamp,
36
+ sendTimestamp,
37
+ timestampBytesToTimestamp,
38
+ timestampToDateIso,
39
+ timestampToTimestampBytes,
40
+ } from "./Timestamp.ts";
41
+
42
+ test("validates Millis", () => {
43
+ assertFalse(Millis.fromUnknown(-1).ok);
44
+ assertTrue(Millis.fromUnknown(0).ok);
45
+ assertTrue(Millis.fromUnknown(maxMillis).ok);
46
+ assertFalse(Millis.fromUnknown(maxMillis + 1).ok);
47
+ });
48
+
49
+ test("validates Counter", () => {
50
+ assertFalse(Counter.fromUnknown(-1).ok);
51
+ assertTrue(Counter.fromUnknown(0).ok);
52
+ assertTrue(Counter.fromUnknown(maxCounter).ok);
53
+ assertFalse(Counter.fromUnknown(maxCounter + 1).ok);
54
+ });
55
+
56
+ test("validates NodeId", () => {
57
+ assertFalse(NodeId.fromUnknown("").ok);
58
+ assertTrue(NodeId.fromUnknown("0000000000000000").ok);
59
+ assertTrue(NodeId.fromUnknown("aaaaaaaaaaaaaaaa").ok);
60
+ assertFalse(NodeId.fromUnknown("Aaaaaaaaaaaaaaaa").ok);
61
+ assertFalse(NodeId.fromUnknown("aaaaaaaaaaaaaaaaa").ok);
62
+ });
63
+
64
+ describe("NodeId bytes", () => {
65
+ it("round-trips NodeId", () => {
66
+ const deps = testCreateDeps();
67
+ const nodeIds = Array.from(
68
+ { length: 100 },
69
+ () => createInitialTimestamp(deps).nodeId,
70
+ );
71
+
72
+ for (const nodeId of nodeIds) {
73
+ assertEqual(nodeIdBytesToNodeId(nodeIdToNodeIdBytes(nodeId)), nodeId);
74
+ }
75
+ });
76
+ });
77
+
78
+ test("creates the minimum Timestamp by default", () => {
79
+ assertEqual(createTimestamp(), {
80
+ counter: 0,
81
+ millis: 0,
82
+ nodeId: "0000000000000000",
83
+ });
84
+ });
85
+
86
+ test("creates an initial Timestamp", () => {
87
+ const timestamp = createInitialTimestamp(testCreateDeps());
88
+
89
+ assertEqual(timestamp, {
90
+ counter: 0,
91
+ millis: 0,
92
+ nodeId: "9dca8c435bb0779f",
93
+ });
94
+ });
95
+
96
+ const makeMillis = (millis: number): Millis => Millis.orThrow(millis);
97
+
98
+ const deps0: TimeDep & TimestampConfigDep = {
99
+ time: testCreateTime({ startAt: minMillis }),
100
+ timestampConfig: { maxDrift: defaultTimestampMaxDrift },
101
+ };
102
+
103
+ const deps1: TimeDep & TimestampConfigDep = {
104
+ time: testCreateTime({ startAt: (minMillis + 1) as Millis }),
105
+ timestampConfig: { maxDrift: defaultTimestampMaxDrift },
106
+ };
107
+
108
+ describe("sendTimestamp", () => {
109
+ it("sends monotonically with a monotonic clock", () => {
110
+ assertOk(sendTimestamp(deps1)(createTimestamp()), {
111
+ counter: 0,
112
+ millis: 1,
113
+ nodeId: "0000000000000000",
114
+ });
115
+ });
116
+
117
+ it("sends monotonically with a stuttering clock", () => {
118
+ assertOk(sendTimestamp(deps0)(createTimestamp()), {
119
+ counter: 1,
120
+ millis: 0,
121
+ nodeId: "0000000000000000",
122
+ });
123
+ });
124
+
125
+ it("sends monotonically with a regressing clock", () => {
126
+ assertOk(
127
+ sendTimestamp(deps0)(
128
+ createTimestamp({ millis: makeMillis(minMillis + 1) }),
129
+ ),
130
+ {
131
+ counter: 1,
132
+ millis: 1,
133
+ nodeId: "0000000000000000",
134
+ },
135
+ );
136
+ });
137
+
138
+ it("returns TimestampCounterOverflowError for counter overflow", () => {
139
+ let timestamp: Result<
140
+ Timestamp,
141
+ | TimestampDriftError
142
+ | TimestampCounterOverflowError
143
+ | TimestampTimeOutOfRangeError
144
+ > = ok(createTimestamp());
145
+
146
+ // Note +1 in 65536.
147
+ for (let i = 0; i < 65536; i++) {
148
+ if (timestamp.ok) timestamp = sendTimestamp(deps0)(timestamp.value);
149
+ }
150
+
151
+ assertErr(timestamp, { type: "TimestampCounterOverflowError" });
152
+ });
153
+
154
+ it("returns TimestampDriftError for excessive clock drift", () => {
155
+ assertErr(
156
+ sendTimestamp(deps0)(
157
+ createTimestamp({
158
+ millis: makeMillis(minMillis + defaultTimestampMaxDrift + 1),
159
+ }),
160
+ ),
161
+ {
162
+ next: 300001,
163
+ now: 0,
164
+ type: "TimestampDriftError",
165
+ },
166
+ );
167
+ });
168
+
169
+ it("returns TimestampTimeOutOfRangeError for an invalid clock", () => {
170
+ const time = testCreateTime();
171
+ const invalidTime: Time = {
172
+ ...time,
173
+ now: (() => -1) as Time["now"],
174
+ };
175
+
176
+ assertErr(
177
+ sendTimestamp({
178
+ time: invalidTime,
179
+ timestampConfig: { maxDrift: defaultTimestampMaxDrift },
180
+ })(createTimestamp()),
181
+ { type: "TimestampTimeOutOfRangeError" },
182
+ );
183
+ });
184
+ });
185
+
186
+ describe("receiveTimestamp", () => {
187
+ const makeNode1Timestamp = (
188
+ millis = 0,
189
+ counter = 0,
190
+ nodeId = "0000000000000001",
191
+ ): Timestamp =>
192
+ ({
193
+ millis: makeMillis(minMillis + millis),
194
+ counter,
195
+ nodeId,
196
+ }) as Timestamp;
197
+
198
+ const makeNode2Timestamp = (millis = 0, counter = 0): Timestamp =>
199
+ makeNode1Timestamp(millis, counter, "0000000000000002");
200
+
201
+ it("uses a wall clock later than both timestamps", () => {
202
+ assertOk(
203
+ receiveTimestamp(deps1)(makeNode1Timestamp(), makeNode2Timestamp()),
204
+ {
205
+ counter: 0,
206
+ millis: 1,
207
+ nodeId: "0000000000000001",
208
+ },
209
+ );
210
+ });
211
+
212
+ describe("wall clock is behind", () => {
213
+ it("increments the maximum counter when millis are equal", () => {
214
+ assertOk(
215
+ receiveTimestamp(deps1)(
216
+ makeNode1Timestamp(1, 0),
217
+ makeNode2Timestamp(1, 1),
218
+ ),
219
+ {
220
+ counter: 2,
221
+ millis: 1,
222
+ nodeId: "0000000000000001",
223
+ },
224
+ );
225
+
226
+ assertOk(
227
+ receiveTimestamp(deps0)(
228
+ makeNode1Timestamp(1, 1),
229
+ makeNode2Timestamp(1, 0),
230
+ ),
231
+ {
232
+ counter: 2,
233
+ millis: 1,
234
+ nodeId: "0000000000000001",
235
+ },
236
+ );
237
+ });
238
+
239
+ it("increments the counter when local millis is later", () => {
240
+ assertOk(
241
+ receiveTimestamp(deps0)(makeNode1Timestamp(2), makeNode2Timestamp(1)),
242
+ {
243
+ counter: 1,
244
+ millis: 2,
245
+ nodeId: "0000000000000001",
246
+ },
247
+ );
248
+ });
249
+
250
+ it("increments the counter when remote millis is later", () => {
251
+ assertOk(
252
+ receiveTimestamp(deps0)(makeNode1Timestamp(1), makeNode2Timestamp(2)),
253
+ {
254
+ counter: 1,
255
+ millis: 2,
256
+ nodeId: "0000000000000001",
257
+ },
258
+ );
259
+ });
260
+
261
+ it("returns TimestampDriftError for excessive clock drift", () => {
262
+ const expected = {
263
+ next: 300001,
264
+ now: 0,
265
+ type: "TimestampDriftError",
266
+ } as const;
267
+
268
+ assertErr(
269
+ receiveTimestamp(deps0)(
270
+ createTimestamp({
271
+ millis: makeMillis(minMillis + defaultTimestampMaxDrift + 1),
272
+ }),
273
+ makeNode2Timestamp(),
274
+ ),
275
+ expected,
276
+ );
277
+
278
+ assertErr(
279
+ receiveTimestamp(deps0)(
280
+ makeNode2Timestamp(),
281
+ createTimestamp({
282
+ millis: makeMillis(minMillis + defaultTimestampMaxDrift + 1),
283
+ }),
284
+ ),
285
+ expected,
286
+ );
287
+ });
288
+
289
+ it("returns TimestampCounterOverflowError for counter overflow", () => {
290
+ assertErr(
291
+ receiveTimestamp(deps0)(
292
+ makeNode1Timestamp(0, maxCounter),
293
+ makeNode2Timestamp(0, maxCounter),
294
+ ),
295
+ { type: "TimestampCounterOverflowError" },
296
+ );
297
+ });
298
+ });
299
+ });
300
+
301
+ describe("TimestampBytes", () => {
302
+ it("round-trips and preserves Timestamp order", () => {
303
+ const decodeFromEncoded = (timestamp: TimestampBytes) =>
304
+ timestampBytesToTimestamp(timestamp);
305
+
306
+ const timestamp = createTimestamp();
307
+ assertEqual(
308
+ decodeFromEncoded(timestampToTimestampBytes(timestamp)),
309
+ timestamp,
310
+ );
311
+
312
+ const lastSafeTimestampEncodedDecoded = decodeFromEncoded(
313
+ timestampToTimestampBytes(createTimestamp({ millis: maxMillis })),
314
+ );
315
+ assertEqual(lastSafeTimestampEncodedDecoded.millis, maxMillis);
316
+
317
+ const t1 = timestampToTimestampBytes(
318
+ createTimestamp({ millis: minMillis }),
319
+ );
320
+ const t2 = timestampToTimestampBytes(
321
+ createTimestamp({
322
+ millis: Millis.orThrow(increment(minMillis)),
323
+ }),
324
+ );
325
+ assertEqual(orderTimestampBytes(t1, t2), -1);
326
+ assertEqual(orderTimestampBytes(t2, t1), 1);
327
+ assertEqual(orderTimestampBytes(t1, t1), 0);
328
+
329
+ const t3 = timestampToTimestampBytes(
330
+ createTimestamp({ counter: minCounter }),
331
+ );
332
+ const t4 = timestampToTimestampBytes(
333
+ createTimestamp({
334
+ counter: Counter.orThrow(increment(minCounter)),
335
+ }),
336
+ );
337
+ assertEqual(orderTimestampBytes(t3, t4), -1);
338
+ assertEqual(orderTimestampBytes(t4, t3), 1);
339
+ assertEqual(orderTimestampBytes(t3, t3), 0);
340
+
341
+ const t5 = timestampToTimestampBytes(
342
+ createTimestamp({ nodeId: "0000000000000000" as NodeId }),
343
+ );
344
+ const t6 = timestampToTimestampBytes(
345
+ createTimestamp({ nodeId: "0000000000000001" as NodeId }),
346
+ );
347
+ assertEqual(orderTimestampBytes(t5, t6), -1);
348
+ assertEqual(orderTimestampBytes(t6, t5), 1);
349
+ assertEqual(orderTimestampBytes(t5, t5), 0);
350
+
351
+ const deps = testCreateDeps();
352
+ const randomMillis = new Set<Millis>();
353
+ for (let i = 0; i < 1000; i++) {
354
+ randomMillis.add(deps.randomLib.int(0, 10000) as Millis);
355
+ }
356
+
357
+ const sortedMillis = [...randomMillis].toSorted(orderNumber);
358
+ const sortedEncodedMillis = [...randomMillis]
359
+ .map((millis) => createTimestamp({ millis }))
360
+ .map(timestampToTimestampBytes)
361
+ .toSorted(orderTimestampBytes)
362
+ .map(decodeFromEncoded)
363
+ .map(({ millis }) => millis);
364
+
365
+ assertEqual(sortedEncodedMillis, sortedMillis);
366
+ });
367
+ });
368
+
369
+ test("converts Timestamp to DateIso", () => {
370
+ assertEqual(
371
+ timestampToDateIso(createTimestamp()),
372
+ "1970-01-01T00:00:00.000Z",
373
+ );
374
+ });
@@ -4,7 +4,7 @@
4
4
  * @module
5
5
  */
6
6
 
7
- import { bytesToHex } from "../Buffer.ts";
7
+ import { bytesToHex, hexToBytes } from "../Binary.ts";
8
8
  import type { RandomBytesDep } from "../Crypto.ts";
9
9
  import { createEqObject, eqNumber, eqString } from "../Eq.ts";
10
10
  import { increment } from "../Number.ts";
@@ -99,6 +99,24 @@ export type NodeId = typeof NodeId.Output;
99
99
  export const minNodeId = "0000000000000000" as NodeId;
100
100
  export const maxNodeId = "ffffffffffffffff" as NodeId;
101
101
 
102
+ /** Binary representation of {@link NodeId}. */
103
+ export const NodeIdBytes = /*#__PURE__*/ brand(
104
+ "NodeIdBytes",
105
+ /*#__PURE__*/ length(8)(Uint8Array),
106
+ );
107
+ export type NodeIdBytes = typeof NodeIdBytes.Output;
108
+
109
+ /** Length of {@link NodeIdBytes}. */
110
+ export const nodeIdBytesLength = /*#__PURE__*/ NonNegativeInt.orThrow(8);
111
+
112
+ /** Converts {@link NodeId} to {@link NodeIdBytes}. */
113
+ export const nodeIdToNodeIdBytes = (nodeId: NodeId): NodeIdBytes =>
114
+ hexToBytes(nodeId) as NodeIdBytes;
115
+
116
+ /** Converts {@link NodeIdBytes} to {@link NodeId}. */
117
+ export const nodeIdBytesToNodeId = (nodeIdBytes: NodeIdBytes): NodeId =>
118
+ bytesToHex(nodeIdBytes) as NodeId;
119
+
102
120
  /**
103
121
  * Hybrid Logical Clock timestamp.
104
122
  *
@@ -303,11 +321,8 @@ export const timestampToTimestampBytes = (
303
321
  value[6] = (counter >> 8) & 0xff;
304
322
  value[7] = counter & 0xff;
305
323
 
306
- // Encode `nodeId` (16-character hex string) into the next 8 bytes.
307
- for (let i = 0; i < 8; i++) {
308
- const byte = parseInt(nodeId.slice(i * 2, i * 2 + 2), 16);
309
- value[8 + i] = byte;
310
- }
324
+ // Encode `nodeId` into the next 8 bytes.
325
+ value.set(nodeIdToNodeIdBytes(nodeId), 8);
311
326
 
312
327
  return value as TimestampBytes;
313
328
  };
@@ -328,10 +343,7 @@ export const timestampBytesToTimestamp = (
328
343
  const counter = (timestamp[6] << 8) | timestamp[7];
329
344
 
330
345
  // Decode `nodeId` from the last 8 bytes.
331
- let nodeId = "";
332
- for (let i = 8; i < 16; i++) {
333
- nodeId += timestamp[i].toString(16).padStart(2, "0");
334
- }
346
+ const nodeId = nodeIdBytesToNodeId(timestamp.subarray(8) as NodeIdBytes);
335
347
 
336
348
  return { millis: Number(millis), counter, nodeId } as Timestamp;
337
349
  };
@@ -1,122 +0,0 @@
1
- /**
2
- * Binary data handling and byte array utilities.
3
- *
4
- * @module
5
- */
6
- import { NonNegativeInt } from "./Type.ts";
7
- export { bytesToHex, bytesToUtf8, concatBytes, hexToBytes, utf8ToBytes, } from "@noble/ciphers/utils.js";
8
- /**
9
- * Custom error for {@link Buffer}-related failures like premature end of data.
10
- * Provides better stack traces for debugging binary protocol issues.
11
- */
12
- export declare class BufferError extends Error {
13
- constructor(message: string);
14
- }
15
- /**
16
- * A Buffer is a dynamic, resizable container for binary data, optimized for
17
- * scenarios where the final size is unknown. It grows exponentially (doubling
18
- * its capacity) to minimize memory reallocations and uses `subarray` for
19
- * efficient, copy-free data access in methods like `unwrap` and `shift`.
20
- *
21
- * ## Recommended Usage
22
- *
23
- * Create as few Buffers as possible—typically one main Buffer for the final
24
- * output. Temporary Buffers are allowed when necessary (e.g., for
25
- * variable-length headers), but avoid excessive allocations. Buffers can be
26
- * reused within functions by leveraging `reset` to clear contents while
27
- * preserving capacity, or `truncate` to adjust the length to a specific size,
28
- * reducing the need for new allocations. Pass Buffers to `encode*` functions to
29
- * append serialized data and use `decode*` functions to extract data. Both
30
- * `shift` and `shiftN` throw an {@link BufferError} with message "Buffer parse
31
- * ended prematurely" on failure, as do higher-level `decode*` functions,
32
- * providing stack traces for debugging instead of using {@link Result}. This
33
- * avoids allocation overhead in success cases and leverages exceptions'
34
- * diagnostic benefits.
35
- *
36
- * ### Example
37
- *
38
- * ```ts
39
- * import {
40
- * assert,
41
- * assertEqual,
42
- * assertErr,
43
- * createBuffer,
44
- * createIdFromString,
45
- * IdBytes,
46
- * idBytesToId,
47
- * idBytesTypeValueLength,
48
- * idToIdBytes,
49
- * NonNegativeInt,
50
- * trySync,
51
- * } from "@evolu/common";
52
- * import {
53
- * decodeNonNegativeInt,
54
- * encodeNonNegativeInt,
55
- * } from "@evolu/common/local-first";
56
- *
57
- * const buffer = createBuffer();
58
- * const id = createIdFromString("buffer-example");
59
- * encodeNonNegativeInt(buffer, NonNegativeInt.orThrow(300));
60
- * buffer.extend(idToIdBytes(id));
61
- *
62
- * const decoder = createBuffer(buffer.unwrap());
63
- * assertEqual(decodeNonNegativeInt(decoder), 300);
64
- * const decodedId = idBytesToId(
65
- * IdBytes.orThrow(decoder.shiftN(idBytesTypeValueLength)),
66
- * );
67
- * assertEqual(decodedId, id);
68
- * const result = trySync(() => decodeNonNegativeInt(decoder));
69
- * assertErr(result);
70
- * assert(
71
- * result.error instanceof Error &&
72
- * result.error.message === "Buffer parse ended prematurely",
73
- * "Expected the premature-buffer-end error.",
74
- * );
75
- * ```
76
- *
77
- * For more on exponential growth, see:
78
- * https://blog.mozilla.org/nnethercote/2014/11/04/please-grow-your-buffers-exponentially
79
- */
80
- export interface Buffer {
81
- /** Returns the current capacity of the buffer. */
82
- getCapacity: () => NonNegativeInt;
83
- /** Returns the current number of bytes stored in the buffer. */
84
- getLength: () => NonNegativeInt;
85
- /**
86
- * Appends binary data to the buffer, resizing if necessary. Throws if
87
- * `arg.length` is not a non-negative integer.
88
- */
89
- extend: (arg: Uint8Array | ArrayLike<number>) => void;
90
- /**
91
- * Removes and returns the first byte. Throws an `Error` with message "Buffer
92
- * parse ended prematurely" if the buffer is empty.
93
- */
94
- shift: () => NonNegativeInt;
95
- /**
96
- * Removes and returns the first `n` bytes. Throws an `Error` with message
97
- * "Buffer parse ended prematurely" if fewer than `n` bytes remain.
98
- */
99
- shiftN: (n: NonNegativeInt) => Uint8Array;
100
- /**
101
- * Truncates the buffer to the specified length, discarding data from the end.
102
- * Throws if the new length is greater than the current length.
103
- */
104
- truncate: (length: NonNegativeInt) => void;
105
- /**
106
- * Resets the buffer to its initial empty state, preserving its capacity.
107
- *
108
- * This allows efficient buffer reuse without reallocating memory. Use this
109
- * when you want to clear the buffer and write new data, avoiding unnecessary
110
- * allocations.
111
- */
112
- reset: () => void;
113
- /**
114
- * Returns a view of the buffer’s current data. Do not modify this array, as
115
- * it directly alters the buffer’s internal state, potentially breaking
116
- * subsequent operations.
117
- */
118
- unwrap: () => Uint8Array;
119
- }
120
- /** Creates a {@link Buffer} for efficient byte operations. */
121
- export declare const createBuffer: (arrayLike?: Uint8Array | ArrayLike<number>) => Buffer;
122
- //# sourceMappingURL=Buffer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Buffer.d.ts","sourceRoot":"","sources":["../../src/Buffer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,cAAc,EAAsB,MAAM,WAAW,CAAC;AAC/D,OAAO,EACL,UAAU,EACV,WAAW,EACX,WAAW,EACX,UAAU,EACV,WAAW,GACZ,MAAM,yBAAyB,CAAC;AAEjC;;;GAGG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACpC,YAAY,OAAO,EAAE,MAAM,EAK1B;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AACH,MAAM,WAAW,MAAM;IACrB,kDAAkD;IAClD,WAAW,EAAE,MAAM,cAAc,CAAC;IAElC,gEAAgE;IAChE,SAAS,EAAE,MAAM,cAAc,CAAC;IAEhC;;;OAGG;IACH,MAAM,EAAE,CAAC,GAAG,EAAE,UAAU,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;IAEtD;;;OAGG;IACH,KAAK,EAAE,MAAM,cAAc,CAAC;IAE5B;;;OAGG;IACH,MAAM,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,UAAU,CAAC;IAE1C;;;OAGG;IACH,QAAQ,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;IAE3C;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;IAElB;;;;OAIG;IACH,MAAM,EAAE,MAAM,UAAU,CAAC;CAC1B;AAED,8DAA8D;AAC9D,eAAO,MAAM,YAAY,eACX,UAAU,GAAG,SAAS,CAAC,MAAM,CAAC,KACzC,MA4DF,CAAC"}
@@ -1,69 +0,0 @@
1
- /**
2
- * Binary data handling and byte array utilities.
3
- *
4
- * @module
5
- */
6
- import { NonNegativeInt, zeroNonNegativeInt } from "./Type.js";
7
- export { bytesToHex, bytesToUtf8, concatBytes, hexToBytes, utf8ToBytes, } from "@noble/ciphers/utils.js";
8
- /**
9
- * Custom error for {@link Buffer}-related failures like premature end of data.
10
- * Provides better stack traces for debugging binary protocol issues.
11
- */
12
- export class BufferError extends Error {
13
- constructor(message) {
14
- super(message);
15
- this.name = this.constructor.name;
16
- Error.captureStackTrace(this, this.constructor);
17
- }
18
- }
19
- /** Creates a {@link Buffer} for efficient byte operations. */
20
- export const createBuffer = (arrayLike) => {
21
- let value = arrayLike
22
- ? new globalThis.Uint8Array(arrayLike)
23
- : new globalThis.Uint8Array(512);
24
- let length = NonNegativeInt.orThrow(arrayLike ? arrayLike.length : 0);
25
- const buffer = {
26
- getCapacity: () => NonNegativeInt.orThrow(value.length),
27
- getLength: () => length,
28
- extend: (arg) => {
29
- const targetSize = length + arg.length;
30
- if (value.length < targetSize) {
31
- const oldValue = value;
32
- const newCapacity = Math.max(value.length * 2, targetSize);
33
- value = new globalThis.Uint8Array(newCapacity);
34
- value.set(oldValue);
35
- }
36
- value.set(arg, length);
37
- length = NonNegativeInt.orThrow(length + arg.length);
38
- },
39
- shift: () => {
40
- if (length === 0) {
41
- throw new BufferError("Buffer parse ended prematurely");
42
- }
43
- const first = value[0];
44
- value = value.subarray(1);
45
- length--;
46
- return NonNegativeInt.orThrow(first);
47
- },
48
- shiftN: (n) => {
49
- if (length < n) {
50
- throw new BufferError("Buffer parse ended prematurely");
51
- }
52
- const subarray = value.subarray(0, n);
53
- value = value.subarray(n);
54
- length = NonNegativeInt.orThrow(length - n);
55
- return subarray;
56
- },
57
- truncate: (newLength) => {
58
- if (newLength > length) {
59
- throw new BufferError("Cannot truncate to a length greater than current");
60
- }
61
- length = newLength;
62
- },
63
- reset: () => {
64
- length = zeroNonNegativeInt;
65
- },
66
- unwrap: () => value.subarray(0, length),
67
- };
68
- return buffer;
69
- };