@evolu/common 5.4.8 → 6.0.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 (242) hide show
  1. package/README.md +30 -34
  2. package/dist/src/Array.d.ts +17 -0
  3. package/dist/src/Array.d.ts.map +1 -0
  4. package/dist/src/Array.js +12 -0
  5. package/dist/src/Assert.d.ts +68 -0
  6. package/dist/src/Assert.d.ts.map +1 -0
  7. package/dist/src/Assert.js +77 -0
  8. package/dist/src/BigInt.d.ts +20 -0
  9. package/dist/src/BigInt.d.ts.map +1 -0
  10. package/dist/src/BigInt.js +18 -0
  11. package/dist/src/Buffer.d.ts +92 -0
  12. package/dist/src/Buffer.d.ts.map +1 -0
  13. package/dist/src/Buffer.js +62 -0
  14. package/dist/src/Callbacks.d.ts +20 -0
  15. package/dist/src/Callbacks.d.ts.map +1 -0
  16. package/dist/src/Callbacks.js +18 -0
  17. package/dist/src/Console.d.ts +78 -0
  18. package/dist/src/Console.d.ts.map +1 -0
  19. package/dist/src/Console.js +103 -0
  20. package/dist/src/Crypto.d.ts +72 -39
  21. package/dist/src/Crypto.d.ts.map +1 -1
  22. package/dist/src/Crypto.js +89 -54
  23. package/dist/src/Eq.d.ts +97 -0
  24. package/dist/src/Eq.d.ts.map +1 -0
  25. package/dist/src/Eq.js +167 -0
  26. package/dist/src/Error.d.ts +14 -10
  27. package/dist/src/Error.d.ts.map +1 -1
  28. package/dist/src/Error.js +43 -11
  29. package/dist/src/Evolu/Config.d.ts +69 -0
  30. package/dist/src/Evolu/Config.d.ts.map +1 -0
  31. package/dist/src/Evolu/Config.js +9 -0
  32. package/dist/src/Evolu/Db.d.ts +126 -0
  33. package/dist/src/Evolu/Db.d.ts.map +1 -0
  34. package/dist/src/Evolu/Db.js +774 -0
  35. package/dist/src/Evolu/Diff.d.ts +43 -0
  36. package/dist/src/Evolu/Diff.d.ts.map +1 -0
  37. package/dist/src/Evolu/Diff.js +95 -0
  38. package/dist/src/Evolu/Evolu.d.ts +334 -0
  39. package/dist/src/Evolu/Evolu.d.ts.map +1 -0
  40. package/dist/src/Evolu/Evolu.js +434 -0
  41. package/dist/src/Evolu/Internal.d.ts +26 -0
  42. package/dist/src/Evolu/Internal.d.ts.map +1 -0
  43. package/dist/src/Evolu/Internal.js +25 -0
  44. package/dist/src/Evolu/Kysely.d.ts +6 -0
  45. package/dist/src/Evolu/Kysely.d.ts.map +1 -0
  46. package/dist/src/Evolu/Kysely.js +21 -0
  47. package/dist/src/Evolu/Owner.d.ts +155 -0
  48. package/dist/src/Evolu/Owner.d.ts.map +1 -0
  49. package/dist/src/Evolu/Owner.js +126 -0
  50. package/dist/src/Evolu/Platform.d.ts +23 -0
  51. package/dist/src/Evolu/Platform.d.ts.map +1 -0
  52. package/dist/src/Evolu/Platform.js +1 -0
  53. package/dist/src/Evolu/Protocol.d.ts +401 -0
  54. package/dist/src/Evolu/Protocol.d.ts.map +1 -0
  55. package/dist/src/Evolu/Protocol.js +1151 -0
  56. package/dist/src/Evolu/Public.d.ts +18 -0
  57. package/dist/src/Evolu/Public.d.ts.map +1 -0
  58. package/dist/src/Evolu/Public.js +11 -0
  59. package/dist/src/Evolu/PublicKysely.d.ts +148 -0
  60. package/dist/src/Evolu/PublicKysely.d.ts.map +1 -0
  61. package/dist/src/Evolu/PublicKysely.js +185 -0
  62. package/dist/src/Evolu/Query.d.ts +63 -0
  63. package/dist/src/Evolu/Query.d.ts.map +1 -0
  64. package/dist/src/Evolu/Query.js +61 -0
  65. package/dist/src/Evolu/Relay.d.ts +13 -0
  66. package/dist/src/Evolu/Relay.d.ts.map +1 -0
  67. package/dist/src/Evolu/Relay.js +109 -0
  68. package/dist/src/Evolu/Schema.d.ts +201 -0
  69. package/dist/src/Evolu/Schema.d.ts.map +1 -0
  70. package/dist/src/Evolu/Schema.js +150 -0
  71. package/dist/src/Evolu/Storage.d.ts +49 -0
  72. package/dist/src/Evolu/Storage.d.ts.map +1 -0
  73. package/dist/src/Evolu/Storage.js +1111 -0
  74. package/dist/src/Evolu/Sync.d.ts +59 -0
  75. package/dist/src/Evolu/Sync.d.ts.map +1 -0
  76. package/dist/src/Evolu/Sync.js +29 -0
  77. package/dist/src/Evolu/Timestamp.d.ts +106 -0
  78. package/dist/src/Evolu/Timestamp.d.ts.map +1 -0
  79. package/dist/src/Evolu/Timestamp.js +179 -0
  80. package/dist/src/Function.d.ts +54 -0
  81. package/dist/src/Function.d.ts.map +1 -0
  82. package/dist/src/Function.js +38 -0
  83. package/dist/src/ManyToManyMap.d.ts +26 -0
  84. package/dist/src/ManyToManyMap.d.ts.map +1 -0
  85. package/dist/src/ManyToManyMap.js +92 -0
  86. package/dist/src/NanoId.d.ts +27 -0
  87. package/dist/src/NanoId.d.ts.map +1 -0
  88. package/dist/src/NanoId.js +6 -0
  89. package/dist/src/Number.d.ts +42 -0
  90. package/dist/src/Number.d.ts.map +1 -0
  91. package/dist/src/Number.js +55 -0
  92. package/dist/src/Object.d.ts +35 -0
  93. package/dist/src/Object.d.ts.map +1 -0
  94. package/dist/src/Object.js +36 -0
  95. package/dist/src/Order.d.ts +90 -0
  96. package/dist/src/Order.d.ts.map +1 -0
  97. package/dist/src/Order.js +85 -0
  98. package/dist/src/Promise.d.ts +180 -0
  99. package/dist/src/Promise.d.ts.map +1 -0
  100. package/dist/src/Promise.js +176 -0
  101. package/dist/src/Random.d.ts +52 -0
  102. package/dist/src/Random.d.ts.map +1 -0
  103. package/dist/src/Random.js +29 -0
  104. package/dist/src/Ref.d.ts +40 -0
  105. package/dist/src/Ref.d.ts.map +1 -0
  106. package/dist/src/Ref.js +13 -0
  107. package/dist/src/Result.d.ts +421 -0
  108. package/dist/src/Result.d.ts.map +1 -0
  109. package/dist/src/Result.js +357 -0
  110. package/dist/src/Skiplist.d.ts +23 -0
  111. package/dist/src/Skiplist.d.ts.map +1 -0
  112. package/dist/src/Skiplist.js +58 -0
  113. package/dist/src/Sqlite.d.ts +116 -52
  114. package/dist/src/Sqlite.d.ts.map +1 -1
  115. package/dist/src/Sqlite.js +183 -67
  116. package/dist/src/Store.d.ts +45 -8
  117. package/dist/src/Store.d.ts.map +1 -1
  118. package/dist/src/Store.js +33 -17
  119. package/dist/src/String.d.ts +2 -0
  120. package/dist/src/String.d.ts.map +1 -0
  121. package/dist/src/String.js +14 -0
  122. package/dist/src/Time.d.ts +20 -0
  123. package/dist/src/Time.d.ts.map +1 -0
  124. package/dist/src/Time.js +25 -0
  125. package/dist/src/Type.d.ts +1937 -0
  126. package/dist/src/Type.d.ts.map +1 -0
  127. package/dist/src/Type.js +2002 -0
  128. package/dist/src/Types.d.ts +188 -0
  129. package/dist/src/Types.d.ts.map +1 -0
  130. package/dist/src/Types.js +6 -0
  131. package/dist/src/WebSocket.d.ts +112 -0
  132. package/dist/src/WebSocket.d.ts.map +1 -0
  133. package/dist/src/WebSocket.js +139 -0
  134. package/dist/src/Worker.d.ts +44 -0
  135. package/dist/src/Worker.d.ts.map +1 -0
  136. package/dist/src/Worker.js +66 -0
  137. package/dist/src/index.d.ts +24 -11
  138. package/dist/src/index.d.ts.map +1 -1
  139. package/dist/src/index.js +24 -11
  140. package/package.json +29 -38
  141. package/src/Array.ts +39 -0
  142. package/src/Assert.ts +116 -0
  143. package/src/BigInt.ts +29 -0
  144. package/src/Buffer.ts +175 -0
  145. package/src/Callbacks.ts +43 -0
  146. package/src/Console.ts +159 -0
  147. package/src/Crypto.ts +169 -115
  148. package/src/Eq.ts +204 -0
  149. package/src/Error.ts +57 -20
  150. package/src/Evolu/Config.ts +83 -0
  151. package/src/Evolu/Db.ts +1275 -0
  152. package/src/Evolu/Diff.ts +142 -0
  153. package/src/Evolu/Evolu.ts +947 -0
  154. package/src/Evolu/Internal.ts +26 -0
  155. package/src/Evolu/Kysely.ts +38 -0
  156. package/src/Evolu/Owner.ts +296 -0
  157. package/src/Evolu/Platform.ts +27 -0
  158. package/src/Evolu/Protocol.ts +1857 -0
  159. package/src/Evolu/Public.ts +43 -0
  160. package/src/Evolu/PublicKysely.ts +240 -0
  161. package/src/Evolu/Query.ts +167 -0
  162. package/src/Evolu/Relay.ts +142 -0
  163. package/src/Evolu/Schema.ts +417 -0
  164. package/src/Evolu/Storage.ts +1281 -0
  165. package/src/Evolu/Sync.ts +105 -0
  166. package/src/Evolu/Timestamp.ts +311 -0
  167. package/src/Function.ts +58 -0
  168. package/src/ManyToManyMap.ts +140 -0
  169. package/src/NanoId.ts +39 -0
  170. package/src/Number.ts +90 -0
  171. package/src/Object.ts +64 -0
  172. package/src/Order.ts +113 -0
  173. package/src/Promise.ts +295 -0
  174. package/src/Random.ts +68 -0
  175. package/src/Ref.ts +63 -0
  176. package/src/Result.ts +453 -0
  177. package/src/Skiplist.ts +102 -0
  178. package/src/Sqlite.ts +366 -153
  179. package/src/Store.ts +79 -36
  180. package/src/String.ts +10 -0
  181. package/src/Time.ts +36 -0
  182. package/src/Type.ts +3978 -0
  183. package/src/Types.ts +209 -0
  184. package/src/WebSocket.ts +273 -0
  185. package/src/Worker.ts +129 -0
  186. package/src/index.ts +24 -11
  187. package/dist/src/Config.d.ts +0 -56
  188. package/dist/src/Config.d.ts.map +0 -1
  189. package/dist/src/Config.js +0 -39
  190. package/dist/src/Crdt.d.ts +0 -89
  191. package/dist/src/Crdt.d.ts.map +0 -1
  192. package/dist/src/Crdt.js +0 -181
  193. package/dist/src/Db.d.ts +0 -107
  194. package/dist/src/Db.d.ts.map +0 -1
  195. package/dist/src/Db.js +0 -443
  196. package/dist/src/Diff.d.ts +0 -27
  197. package/dist/src/Diff.d.ts.map +0 -1
  198. package/dist/src/Diff.js +0 -84
  199. package/dist/src/Evolu.d.ts +0 -426
  200. package/dist/src/Evolu.d.ts.map +0 -1
  201. package/dist/src/Evolu.js +0 -333
  202. package/dist/src/Model.d.ts +0 -141
  203. package/dist/src/Model.d.ts.map +0 -1
  204. package/dist/src/Model.js +0 -125
  205. package/dist/src/Murmurhash.d.ts +0 -2
  206. package/dist/src/Murmurhash.d.ts.map +0 -1
  207. package/dist/src/Murmurhash.js +0 -60
  208. package/dist/src/Owner.d.ts +0 -33
  209. package/dist/src/Owner.d.ts.map +0 -1
  210. package/dist/src/Owner.js +0 -26
  211. package/dist/src/Platform.d.ts +0 -37
  212. package/dist/src/Platform.d.ts.map +0 -1
  213. package/dist/src/Platform.js +0 -11
  214. package/dist/src/Protobuf.d.ts +0 -81
  215. package/dist/src/Protobuf.d.ts.map +0 -1
  216. package/dist/src/Protobuf.js +0 -92
  217. package/dist/src/Public.d.ts +0 -13
  218. package/dist/src/Public.d.ts.map +0 -1
  219. package/dist/src/Public.js +0 -6
  220. package/dist/src/Socket.d.ts +0 -8
  221. package/dist/src/Socket.d.ts.map +0 -1
  222. package/dist/src/Socket.js +0 -51
  223. package/dist/src/Sql.d.ts +0 -12
  224. package/dist/src/Sql.d.ts.map +0 -1
  225. package/dist/src/Sql.js +0 -30
  226. package/dist/src/Sync.d.ts +0 -70
  227. package/dist/src/Sync.d.ts.map +0 -1
  228. package/dist/src/Sync.js +0 -127
  229. package/src/Config.ts +0 -119
  230. package/src/Crdt.ts +0 -361
  231. package/src/Db.ts +0 -955
  232. package/src/Diff.ts +0 -114
  233. package/src/Evolu.ts +0 -1016
  234. package/src/Model.ts +0 -233
  235. package/src/Murmurhash.ts +0 -70
  236. package/src/Owner.ts +0 -69
  237. package/src/Platform.ts +0 -47
  238. package/src/Protobuf.ts +0 -155
  239. package/src/Public.ts +0 -12
  240. package/src/Socket.ts +0 -83
  241. package/src/Sql.ts +0 -41
  242. package/src/Sync.ts +0 -315
@@ -0,0 +1,105 @@
1
+ import { ConsoleConfig, ConsoleDep } from "../Console.js";
2
+ import { createWebSocket } from "../WebSocket.js";
3
+ import { ProtocolMessage } from "./Protocol.js";
4
+ import { Millis } from "./Timestamp.js";
5
+
6
+ export interface Sync {
7
+ readonly send: (message: ProtocolMessage) => void;
8
+ }
9
+
10
+ export interface SyncDep {
11
+ readonly sync: Sync;
12
+ }
13
+
14
+ export type CreateSync = (deps: ConsoleDep) => (config: SyncConfig) => Sync;
15
+
16
+ export interface CreateSyncDep {
17
+ readonly createSync: CreateSync;
18
+ }
19
+
20
+ export interface SyncConfig extends ConsoleConfig {
21
+ readonly syncUrl: string;
22
+ readonly onOpen: (send: Sync["send"]) => void;
23
+ readonly onMessage: (message: Uint8Array, send: Sync["send"]) => void;
24
+ }
25
+
26
+ export const createWebSocketSync: CreateSync = (_deps) => (config) => {
27
+ const sync: Sync = {
28
+ send: (message) => {
29
+ /**
30
+ * We don't need an in-memory queue; apps can be offline for a long time,
31
+ * and mutations are stored in SQLite. Dropped CRDT messages are synced
32
+ * when the web socket connection is open.
33
+ */
34
+ if (socket.getReadyState() !== "open") return;
35
+ socket.send(message);
36
+ },
37
+ };
38
+
39
+ const socket = createWebSocket(config.syncUrl, {
40
+ binaryType: "arraybuffer",
41
+ onOpen: () => {
42
+ config.onOpen(sync.send);
43
+ },
44
+ onMessage: (data) => {
45
+ if (data instanceof ArrayBuffer) {
46
+ const messages = new Uint8Array(data);
47
+ config.onMessage(messages, sync.send);
48
+ }
49
+ },
50
+ });
51
+
52
+ return sync;
53
+ };
54
+
55
+ /**
56
+ * The possible states of a synchronization process. The `SyncState` can be one
57
+ * of the following:
58
+ *
59
+ * - {@link SyncStateInitial}
60
+ * - {@link SyncStateIsSyncing}
61
+ * - {@link SyncStateIsSynced}
62
+ * - {@link SyncStateIsNotSynced}
63
+ */
64
+ export type SyncState =
65
+ | SyncStateInitial
66
+ | SyncStateIsSyncing
67
+ | SyncStateIsSynced
68
+ | SyncStateIsNotSynced;
69
+
70
+ /**
71
+ * The initial synchronization state when the app starts. In this state, the app
72
+ * needs to determine whether the data is synced.
73
+ */
74
+ export interface SyncStateInitial {
75
+ readonly type: "SyncStateInitial";
76
+ }
77
+
78
+ export interface SyncStateIsSyncing {
79
+ readonly type: "SyncStateIsSyncing";
80
+ }
81
+
82
+ export interface SyncStateIsSynced {
83
+ readonly type: "SyncStateIsSynced";
84
+ readonly time: Millis;
85
+ }
86
+
87
+ export interface SyncStateIsNotSynced {
88
+ readonly type: "SyncStateIsNotSynced";
89
+ readonly error: NetworkError | ServerError | PaymentRequiredError;
90
+ }
91
+
92
+ export interface NetworkError {
93
+ readonly type: "NetworkError";
94
+ }
95
+
96
+ export interface ServerError {
97
+ readonly type: "ServerError";
98
+ readonly status: number;
99
+ }
100
+
101
+ export interface PaymentRequiredError {
102
+ readonly type: "PaymentRequiredError";
103
+ }
104
+
105
+ export const initialSyncState: SyncStateInitial = { type: "SyncStateInitial" };
@@ -0,0 +1,311 @@
1
+ import { assert } from "../Assert.js";
2
+ import { NanoIdLibDep } from "../NanoId.js";
3
+ import { increment } from "../Number.js";
4
+ import { Order, orderUint8Array } from "../Order.js";
5
+ import { err, getOrThrow, ok, Result } from "../Result.js";
6
+ import { TimeDep } from "../Time.js";
7
+ import {
8
+ brand,
9
+ lessThanOrEqualTo,
10
+ NonNegativeInt,
11
+ object,
12
+ regex,
13
+ String,
14
+ } from "../Type.js";
15
+ import { Brand } from "../Types.js";
16
+
17
+ export interface TimestampConfig {
18
+ /**
19
+ * Maximum physical clock drift allowed in ms.
20
+ *
21
+ * The default value is 5 * 60 * 1000 (5 minutes).
22
+ */
23
+ readonly maxDrift: number;
24
+ }
25
+
26
+ export interface TimestampConfigDep {
27
+ readonly timestampConfig: TimestampConfig;
28
+ }
29
+
30
+ export type TimestampError =
31
+ | TimestampDriftError
32
+ | TimestampCounterOverflowError
33
+ | TimestampDuplicateNodeError
34
+ | TimestampTimeOutOfRangeError;
35
+
36
+ export interface TimestampDriftError {
37
+ readonly type: "TimestampDriftError";
38
+ readonly next: Millis;
39
+ readonly now: Millis;
40
+ }
41
+
42
+ export interface TimestampCounterOverflowError {
43
+ readonly type: "TimestampCounterOverflowError";
44
+ }
45
+
46
+ export interface TimestampDuplicateNodeError {
47
+ readonly type: "TimestampDuplicateNodeError";
48
+ readonly nodeId: NodeId;
49
+ }
50
+
51
+ export interface TimestampTimeOutOfRangeError {
52
+ readonly type: "TimestampTimeOutOfRangeError";
53
+ }
54
+
55
+ /**
56
+ * Millis is a timestamp in milliseconds, like `Date.now()`, but limited to the
57
+ * maximum value representable in 6 bytes (281474976710655) minus 1 (reserved
58
+ * for infinity). This enables more efficient binary serialization, saving 2
59
+ * bytes compared to the typical 8-byte (64-bit) timestamp representation.
60
+ *
61
+ * This limit is enforced to prevent data corruption. If a device's clock
62
+ * exceeds this range, Evolu will stop saving data until the clock is
63
+ * corrected.
64
+ *
65
+ * `new Date(281474976710654).toString()` = Tue Aug 02 10889 07:31:49
66
+ */
67
+ export const Millis = brand(
68
+ "Millis",
69
+ lessThanOrEqualTo(281474976710655 - 1)(NonNegativeInt),
70
+ );
71
+ export type Millis = typeof Millis.Type;
72
+
73
+ export const minMillis = 0 as Millis;
74
+ export const maxMillis = (281474976710655 - 1) as Millis;
75
+
76
+ export const Counter = brand(
77
+ "Counter",
78
+ lessThanOrEqualTo(65535)(NonNegativeInt),
79
+ );
80
+ export type Counter = typeof Counter.Type;
81
+
82
+ export const minCounter = 0 as Counter;
83
+ export const maxCounter = 65535 as Counter;
84
+
85
+ /**
86
+ * A NodeId uniquely identifies an owner's device. Generated once per device
87
+ * using cryptographic randomness.
88
+ *
89
+ * Collision probability (birthday paradox):
90
+ *
91
+ * - 1,000 devices: ~0.00000000000271% (negligible).
92
+ * - 1M devices: ~0.00000271% (1 in 37M chance).
93
+ * - 135M devices: ~1% chance.
94
+ * - 4.29B devices: ~50% chance.
95
+ *
96
+ * https://lemire.me/blog/2019/12/12/are-64-bit-random-identifiers-free-from-collision
97
+ *
98
+ * What will happen if a different device generates the same NodeId?
99
+ *
100
+ * If the device belongs to a different owner, nothing will happen because
101
+ * different owner have different owner IDs. Timestamps are partitioned by
102
+ * OwnerId.
103
+ *
104
+ * If the device belongs to the same owner, the other device will return
105
+ * {@link TimestampDuplicateNodeError}.
106
+ */
107
+ export const NodeId = regex("NodeId", /^[a-f0-9]{16}$/)(String);
108
+ export type NodeId = typeof NodeId.Type;
109
+
110
+ export const minNodeId = "0000000000000000" as NodeId;
111
+ export const maxNodeId = "ffffffffffffffff" as NodeId;
112
+
113
+ /**
114
+ * Hybrid Logical Clock timestamp.
115
+ *
116
+ * - https://muratbuffalo.blogspot.com/2014/07/hybrid-logical-clocks.html
117
+ * - https://sergeiturukin.com/2017/06/26/hybrid-logical-clocks.html
118
+ * - https://jaredforsyth.com/posts/hybrid-logical-clocks/
119
+ */
120
+ export const Timestamp = object({
121
+ millis: Millis,
122
+ counter: Counter,
123
+ nodeId: NodeId,
124
+ });
125
+ export type Timestamp = typeof Timestamp.Type;
126
+
127
+ export const createTimestamp = ({
128
+ millis = minMillis,
129
+ counter = minCounter,
130
+ nodeId = minNodeId,
131
+ }: Partial<Timestamp> = {}): Timestamp => ({ millis, counter, nodeId });
132
+
133
+ const hexAlphabet = "0123456789abcdef";
134
+
135
+ export const createInitialTimestamp = (deps: NanoIdLibDep): Timestamp => {
136
+ const nodeId = deps.nanoIdLib.customAlphabet(hexAlphabet, 16)() as NodeId;
137
+ return createTimestamp({ nodeId });
138
+ };
139
+
140
+ /** TimestampString is a sortable string version of {@link Timestamp}. */
141
+ export type TimestampString = string & Brand<"TimestampString">;
142
+
143
+ export const timestampToTimestampString = (t: Timestamp): TimestampString =>
144
+ [
145
+ new Date(t.millis).toISOString(),
146
+ t.counter.toString(16).toUpperCase().padStart(4, "0"),
147
+ t.nodeId,
148
+ ].join("-") as TimestampString;
149
+
150
+ export const timestampStringToTimestamp = (
151
+ timestampString: TimestampString,
152
+ ): Timestamp => {
153
+ const array = timestampString.split("-");
154
+ const timestamp = {
155
+ millis: Date.parse(array.slice(0, 3).join("-")).valueOf(),
156
+ counter: parseInt(array[3], 16),
157
+ nodeId: array[4],
158
+ };
159
+ assert(Timestamp.is(timestamp), "timestampString is malformed");
160
+ return timestamp;
161
+ };
162
+
163
+ const getNextMillis =
164
+ (deps: TimeDep & TimestampConfigDep) =>
165
+ (
166
+ millis: ReadonlyArray<Millis>,
167
+ ): Result<Millis, TimestampTimeOutOfRangeError | TimestampDriftError> => {
168
+ const now = Millis.from(deps.time.now());
169
+ if (!now.ok) {
170
+ return err({ type: "TimestampTimeOutOfRangeError" });
171
+ }
172
+ const next = Math.max(now.value, ...millis) as Millis;
173
+ return next - now.value > deps.timestampConfig.maxDrift
174
+ ? err<TimestampDriftError>({
175
+ type: "TimestampDriftError",
176
+ now: now.value,
177
+ next,
178
+ })
179
+ : ok(next);
180
+ };
181
+
182
+ const incrementCounter = (
183
+ counter: Counter,
184
+ ): Result<Counter, TimestampCounterOverflowError> => {
185
+ const next = Counter.from(increment(counter));
186
+ if (!next.ok) return err({ type: "TimestampCounterOverflowError" });
187
+ return ok(next.value);
188
+ };
189
+
190
+ export const sendTimestamp =
191
+ (deps: TimeDep & TimestampConfigDep) =>
192
+ (
193
+ timestamp: Timestamp,
194
+ ): Result<
195
+ Timestamp,
196
+ | TimestampDriftError
197
+ | TimestampCounterOverflowError
198
+ | TimestampTimeOutOfRangeError
199
+ > => {
200
+ const millis = getNextMillis(deps)([timestamp.millis]);
201
+ if (!millis.ok) return millis;
202
+
203
+ const counter =
204
+ millis.value === timestamp.millis
205
+ ? incrementCounter(timestamp.counter)
206
+ : ok(minCounter);
207
+ if (!counter.ok) return counter;
208
+
209
+ return ok({
210
+ millis: millis.value,
211
+ counter: counter.value,
212
+ nodeId: timestamp.nodeId,
213
+ });
214
+ };
215
+
216
+ export const receiveTimestamp =
217
+ (deps: TimeDep & TimestampConfigDep) =>
218
+ (
219
+ local: Timestamp,
220
+ remote: Timestamp,
221
+ ): Result<
222
+ Timestamp,
223
+ | TimestampDriftError
224
+ | TimestampCounterOverflowError
225
+ | TimestampDuplicateNodeError
226
+ | TimestampTimeOutOfRangeError
227
+ > => {
228
+ if (local.nodeId === remote.nodeId) {
229
+ return err({ type: "TimestampDuplicateNodeError", nodeId: local.nodeId });
230
+ }
231
+ const millis = getNextMillis(deps)([local.millis, remote.millis]);
232
+ if (!millis.ok) return millis;
233
+
234
+ const counter =
235
+ millis.value === local.millis && millis.value === remote.millis
236
+ ? incrementCounter(Math.max(local.counter, remote.counter) as Counter)
237
+ : millis.value === local.millis
238
+ ? incrementCounter(local.counter)
239
+ : millis.value === remote.millis
240
+ ? incrementCounter(remote.counter)
241
+ : ok(minCounter);
242
+
243
+ if (!counter.ok) return counter;
244
+
245
+ return ok({
246
+ millis: millis.value,
247
+ counter: counter.value,
248
+ nodeId: local.nodeId,
249
+ });
250
+ };
251
+
252
+ /** BinaryTimestamp is a binary and sortable version of {@link Timestamp} for DB. */
253
+ export type BinaryTimestamp = Uint8Array & Brand<"BinaryTimestamp">;
254
+
255
+ export const binaryTimestampLength = getOrThrow(NonNegativeInt.from(16));
256
+
257
+ export const timestampToBinaryTimestamp = (
258
+ timestamp: Timestamp,
259
+ ): BinaryTimestamp => {
260
+ const { millis, counter, nodeId } = timestamp;
261
+
262
+ // 6 bytes for millis, 2 bytes for counter, 8 bytes for nodeId.
263
+ const value = new Uint8Array(16);
264
+
265
+ // Encode `millis` into the first 6 bytes.
266
+ const millisBigInt = BigInt(millis);
267
+ value[0] = Number((millisBigInt >> 40n) & 0xffn);
268
+ value[1] = Number((millisBigInt >> 32n) & 0xffn);
269
+ value[2] = Number((millisBigInt >> 24n) & 0xffn);
270
+ value[3] = Number((millisBigInt >> 16n) & 0xffn);
271
+ value[4] = Number((millisBigInt >> 8n) & 0xffn);
272
+ value[5] = Number(millisBigInt & 0xffn);
273
+
274
+ // Encode `counter` into the next 2 bytes.
275
+ value[6] = (counter >> 8) & 0xff;
276
+ value[7] = counter & 0xff;
277
+
278
+ // Encode `nodeId` (16-character hex string) into the next 8 bytes.
279
+ for (let i = 0; i < 8; i++) {
280
+ const byte = parseInt(nodeId.slice(i * 2, i * 2 + 2), 16);
281
+ value[8 + i] = byte;
282
+ }
283
+
284
+ return value as BinaryTimestamp;
285
+ };
286
+
287
+ export const binaryTimestampToTimestamp = (
288
+ timestamp: BinaryTimestamp,
289
+ ): Timestamp => {
290
+ // Decode `millis` from the first 6 bytes.
291
+ const millis =
292
+ (BigInt(timestamp[0]) << 40n) |
293
+ (BigInt(timestamp[1]) << 32n) |
294
+ (BigInt(timestamp[2]) << 24n) |
295
+ (BigInt(timestamp[3]) << 16n) |
296
+ (BigInt(timestamp[4]) << 8n) |
297
+ BigInt(timestamp[5]);
298
+
299
+ // Decode `counter` from the next 2 bytes.
300
+ const counter = (timestamp[6] << 8) | timestamp[7];
301
+
302
+ // Decode `nodeId` from the last 8 bytes.
303
+ let nodeId = "";
304
+ for (let i = 8; i < 16; i++) {
305
+ nodeId += timestamp[i].toString(16).padStart(2, "0");
306
+ }
307
+
308
+ return { millis: Number(millis), counter, nodeId } as Timestamp;
309
+ };
310
+
311
+ export const orderBinaryTimestamp: Order<BinaryTimestamp> = orderUint8Array;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Helper function to ensure exhaustive matching in a switch statement. Throws
3
+ * an error if an unhandled case is encountered.
4
+ *
5
+ * Remember, it's useful only when we don't return anything from the switch
6
+ * statement. Otherwise, a return type of a function is enough.
7
+ *
8
+ * ### Example
9
+ *
10
+ * ```ts
11
+ * type Color = "red" | "green" | "blue";
12
+ *
13
+ * function handleColor(color: Color): void {
14
+ * switch (color) {
15
+ * case "red":
16
+ * console.log("Handling red");
17
+ * break;
18
+ * case "green":
19
+ * console.log("Handling green");
20
+ * break;
21
+ * case "blue":
22
+ * console.log("Handling blue");
23
+ * break;
24
+ * default:
25
+ * exhaustiveCheck(color); // Ensures all cases are handled
26
+ * }
27
+ * }
28
+ * ```
29
+ */
30
+ export const exhaustiveCheck = (value: never): never => {
31
+ throw new Error(`exhaustiveCheck unhandled case: ${JSON.stringify(value)}`);
32
+ };
33
+
34
+ export const identity = <A>(a: A): A => a;
35
+
36
+ /**
37
+ * A function that delays computation and returns a value of type T.
38
+ *
39
+ * Useful for:
40
+ *
41
+ * - Lazy evaluation
42
+ * - Returning constant values
43
+ * - Providing default or placeholder behaviors
44
+ *
45
+ * ### Example
46
+ *
47
+ * ```ts
48
+ * const getRandomNumber: LazyValue<number> = () => Math.random();
49
+ * const randomValue = getRandomNumber();
50
+ * ```
51
+ */
52
+ export type LazyValue<T> = () => T;
53
+
54
+ export const constVoid: LazyValue<void> = () => undefined;
55
+ export const constUndefined: LazyValue<undefined> = () => undefined;
56
+ export const constNull: LazyValue<null> = () => null;
57
+ export const constTrue: LazyValue<true> = () => true;
58
+ export const constFalse: LazyValue<false> = () => false;
@@ -0,0 +1,140 @@
1
+ /** Represents a many-to-many mapping between keys and values. */
2
+ export interface ManyToManyMap<K, V> {
3
+ /** Adds a key-value association to the map. */
4
+ add: (key: K, value: V) => ManyToManyMap<K, V>;
5
+
6
+ /** Removes a specific key-value association. */
7
+ remove: (key: K, value: V) => boolean;
8
+
9
+ /** Gets all values associated with a key. */
10
+ getValues: (key: K) => Set<V> | undefined;
11
+
12
+ /** Gets all keys associated with a value. */
13
+ getKeys: (value: V) => Set<K> | undefined;
14
+
15
+ /** Checks if a specific key-value pair exists. */
16
+ hasPair: (key: K, value: V) => boolean;
17
+
18
+ /** Checks if a key exists in the map. */
19
+ hasKey(key: K): boolean;
20
+
21
+ /** Checks if a value exists in the map. */
22
+ hasValue: (value: V) => boolean;
23
+
24
+ /** Deletes all associations for a key. */
25
+ deleteKey: (key: K) => boolean;
26
+
27
+ /** Deletes all associations for a value. */
28
+ deleteValue: (value: V) => boolean;
29
+
30
+ /** Clears all associations in the map. */
31
+ clear(): void;
32
+ }
33
+
34
+ /** Creates a {@link ManyToManyMap}. */
35
+ export const createManyToManyMap = <K, V>(): ManyToManyMap<K, V> => {
36
+ const forwardMap = new Map<K, Set<V>>();
37
+ const reverseMap = new Map<V, Set<K>>();
38
+
39
+ const map: ManyToManyMap<K, V> = {
40
+ add(key: K, value: V) {
41
+ let values = forwardMap.get(key);
42
+ if (!values) {
43
+ values = new Set<V>();
44
+ forwardMap.set(key, values);
45
+ }
46
+ values.add(value);
47
+
48
+ let keys = reverseMap.get(value);
49
+ if (!keys) {
50
+ keys = new Set<K>();
51
+ reverseMap.set(value, keys);
52
+ }
53
+ keys.add(key);
54
+
55
+ return map;
56
+ },
57
+
58
+ remove(key: K, value: V) {
59
+ const values = forwardMap.get(key);
60
+ if (!values?.has(value)) return false;
61
+
62
+ values.delete(value);
63
+ if (values.size === 0) {
64
+ forwardMap.delete(key);
65
+ }
66
+
67
+ const keys = reverseMap.get(value);
68
+ if (keys?.size) {
69
+ keys.delete(key);
70
+ if (keys.size === 0) {
71
+ reverseMap.delete(value);
72
+ }
73
+ }
74
+
75
+ return true;
76
+ },
77
+
78
+ getValues(key: K) {
79
+ return forwardMap.get(key);
80
+ },
81
+
82
+ getKeys(value: V) {
83
+ return reverseMap.get(value);
84
+ },
85
+
86
+ hasPair(key: K, value: V) {
87
+ const values = forwardMap.get(key);
88
+ return values?.has(value) ?? false;
89
+ },
90
+
91
+ hasKey(key: K) {
92
+ return forwardMap.has(key);
93
+ },
94
+
95
+ hasValue(value: V) {
96
+ return reverseMap.has(value);
97
+ },
98
+
99
+ deleteKey(key: K) {
100
+ const values = forwardMap.get(key);
101
+ if (!values) return false;
102
+
103
+ for (const value of values) {
104
+ const keys = reverseMap.get(value);
105
+ if (keys) {
106
+ keys.delete(key);
107
+ if (keys.size === 0) {
108
+ reverseMap.delete(value);
109
+ }
110
+ }
111
+ }
112
+ forwardMap.delete(key);
113
+ return true;
114
+ },
115
+
116
+ deleteValue(value: V) {
117
+ const keys = reverseMap.get(value);
118
+ if (!keys) return false;
119
+
120
+ for (const key of keys) {
121
+ const values = forwardMap.get(key);
122
+ if (values) {
123
+ values.delete(value);
124
+ if (values.size === 0) {
125
+ forwardMap.delete(key);
126
+ }
127
+ }
128
+ }
129
+ reverseMap.delete(value);
130
+ return true;
131
+ },
132
+
133
+ clear() {
134
+ forwardMap.clear();
135
+ reverseMap.clear();
136
+ },
137
+ };
138
+
139
+ return map;
140
+ };
package/src/NanoId.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { customAlphabet, nanoid, urlAlphabet } from "nanoid";
2
+
3
+ /**
4
+ * Interface representing a NanoId library.
5
+ *
6
+ * It's useful for testing by allowing ID generation to be mocked, ensuring
7
+ * consistent results.
8
+ *
9
+ * @see https://github.com/ai/nanoid
10
+ *
11
+ * ### Example
12
+ *
13
+ * ```ts
14
+ * const foo = createFoo({
15
+ * ...createNanoIdLib(),
16
+ * ...createTime(),
17
+ * });
18
+ * ```
19
+ */
20
+ export interface NanoIdLib {
21
+ readonly urlAlphabet: string;
22
+
23
+ readonly customAlphabet: (
24
+ alphabet: string,
25
+ defaultSize?: number,
26
+ ) => (size?: number) => string;
27
+
28
+ readonly nanoid: (size?: number) => string;
29
+ }
30
+
31
+ export interface NanoIdLibDep {
32
+ readonly nanoIdLib: NanoIdLib;
33
+ }
34
+
35
+ export const createNanoIdLib = (): NanoIdLib => ({
36
+ urlAlphabet,
37
+ customAlphabet,
38
+ nanoid,
39
+ });