@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
@@ -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 +0,0 @@
1
- {"version":3,"file":"Buffer.d.ts","sourceRoot":"","sources":["../../src/Buffer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3D,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;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,MAgEF,CAAC;AAqDF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,eAAe,WAAY,MAAM,SAAS,SAAS,KAAG,IAelE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,eAAe,WAAY,MAAM,KAAG,SAwBhD,CAAC"}