@evolu/common 8.10.0 → 8.11.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 (144) hide show
  1. package/dist/src/Config.d.ts +22 -22
  2. package/dist/src/Config.d.ts.map +1 -1
  3. package/dist/src/Console.d.ts +62 -7
  4. package/dist/src/Console.d.ts.map +1 -1
  5. package/dist/src/Console.js +20 -4
  6. package/dist/src/Crypto.d.ts +76 -4
  7. package/dist/src/Crypto.d.ts.map +1 -1
  8. package/dist/src/Crypto.js +55 -4
  9. package/dist/src/Error.d.ts +45 -0
  10. package/dist/src/Error.d.ts.map +1 -1
  11. package/dist/src/Error.js +69 -0
  12. package/dist/src/Fs.d.ts +92 -18
  13. package/dist/src/Fs.d.ts.map +1 -1
  14. package/dist/src/Fs.js +2 -0
  15. package/dist/src/Identicon.d.ts +2 -2
  16. package/dist/src/Identicon.js +2 -2
  17. package/dist/src/LeakDetector.d.ts +22 -3
  18. package/dist/src/LeakDetector.d.ts.map +1 -1
  19. package/dist/src/LeakDetector.js +12 -2
  20. package/dist/src/LockManager.d.ts +8 -0
  21. package/dist/src/LockManager.d.ts.map +1 -1
  22. package/dist/src/LockManager.js +6 -0
  23. package/dist/src/Object.d.ts.map +1 -1
  24. package/dist/src/Object.js +5 -0
  25. package/dist/src/Platform.d.ts +47 -7
  26. package/dist/src/Platform.d.ts.map +1 -1
  27. package/dist/src/Platform.js +24 -5
  28. package/dist/src/Random.d.ts +25 -2
  29. package/dist/src/Random.d.ts.map +1 -1
  30. package/dist/src/Random.js +14 -2
  31. package/dist/src/Resource.d.ts +156 -1
  32. package/dist/src/Resource.d.ts.map +1 -1
  33. package/dist/src/Resource.js +201 -72
  34. package/dist/src/Schedule.d.ts +11 -10
  35. package/dist/src/Schedule.d.ts.map +1 -1
  36. package/dist/src/Schedule.js +1 -1
  37. package/dist/src/Sqlite.d.ts +132 -16
  38. package/dist/src/Sqlite.d.ts.map +1 -1
  39. package/dist/src/Sqlite.js +63 -9
  40. package/dist/src/Task.d.ts +15 -4
  41. package/dist/src/Task.d.ts.map +1 -1
  42. package/dist/src/Task.js +41 -15
  43. package/dist/src/Test.d.ts +9 -0
  44. package/dist/src/Test.d.ts.map +1 -1
  45. package/dist/src/Test.js +4 -0
  46. package/dist/src/Time.d.ts +106 -9
  47. package/dist/src/Time.d.ts.map +1 -1
  48. package/dist/src/Time.js +55 -4
  49. package/dist/src/Type.d.ts +1455 -1310
  50. package/dist/src/Type.d.ts.map +1 -1
  51. package/dist/src/Type.js +1274 -517
  52. package/dist/src/WebSocket.d.ts +164 -13
  53. package/dist/src/WebSocket.d.ts.map +1 -1
  54. package/dist/src/WebSocket.js +133 -24
  55. package/dist/src/Worker.d.ts +90 -8
  56. package/dist/src/Worker.d.ts.map +1 -1
  57. package/dist/src/Worker.js +28 -2
  58. package/dist/src/index.d.ts +6 -7
  59. package/dist/src/index.d.ts.map +1 -1
  60. package/dist/src/index.js +2 -3
  61. package/dist/src/local-first/Db.d.ts +52 -3
  62. package/dist/src/local-first/Db.d.ts.map +1 -1
  63. package/dist/src/local-first/Db.js +412 -137
  64. package/dist/src/local-first/Evolu.d.ts +336 -211
  65. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  66. package/dist/src/local-first/Evolu.js +102 -15
  67. package/dist/src/local-first/Owner.d.ts +13 -30
  68. package/dist/src/local-first/Owner.d.ts.map +1 -1
  69. package/dist/src/local-first/Owner.js +13 -30
  70. package/dist/src/local-first/Protocol.d.ts +94 -16
  71. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  72. package/dist/src/local-first/Protocol.js +118 -38
  73. package/dist/src/local-first/Query.d.ts +8 -15
  74. package/dist/src/local-first/Query.d.ts.map +1 -1
  75. package/dist/src/local-first/Schema.d.ts +335 -21
  76. package/dist/src/local-first/Schema.d.ts.map +1 -1
  77. package/dist/src/local-first/Schema.js +214 -17
  78. package/dist/src/local-first/Shared.d.ts +537 -22
  79. package/dist/src/local-first/Shared.d.ts.map +1 -1
  80. package/dist/src/local-first/Shared.js +1437 -234
  81. package/dist/src/local-first/Storage.d.ts +192 -14
  82. package/dist/src/local-first/Storage.d.ts.map +1 -1
  83. package/dist/src/local-first/Storage.js +81 -20
  84. package/dist/src/local-first/Timestamp.d.ts +392 -41
  85. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  86. package/dist/src/local-first/Timestamp.js +403 -81
  87. package/dist/src/local-first/index.d.ts +0 -1
  88. package/dist/src/local-first/index.d.ts.map +1 -1
  89. package/dist/src/local-first/index.js +0 -1
  90. package/package.json +1 -1
  91. package/src/Assert.test.ts +2 -5
  92. package/src/Config.test.ts +2 -6
  93. package/src/Config.ts +133 -133
  94. package/src/Console.ts +62 -7
  95. package/src/Crypto.ts +76 -4
  96. package/src/Eq.test.ts +2 -3
  97. package/src/Error.test.ts +76 -3
  98. package/src/Error.ts +71 -0
  99. package/src/Fs.ts +92 -18
  100. package/src/Identicon.ts +2 -2
  101. package/src/LeakDetector.ts +22 -3
  102. package/src/LockManager.ts +8 -0
  103. package/src/Object.test.ts +27 -12
  104. package/src/Object.ts +5 -0
  105. package/src/Platform.ts +50 -8
  106. package/src/Random.ts +25 -2
  107. package/src/Resource.test.ts +837 -0
  108. package/src/Resource.ts +235 -15
  109. package/src/Schedule.test.ts +50 -12
  110. package/src/Schedule.ts +24 -14
  111. package/src/Sqlite.ts +137 -17
  112. package/src/Task.test.ts +189 -8
  113. package/src/Task.ts +56 -17
  114. package/src/Test.ts +9 -0
  115. package/src/Time.ts +106 -9
  116. package/src/Type.test.ts +946 -1028
  117. package/src/Type.ts +4195 -3136
  118. package/src/Types.test.ts +4 -14
  119. package/src/WebSocket.ts +313 -40
  120. package/src/Worker.ts +90 -8
  121. package/src/index.ts +15 -6
  122. package/src/local-first/Db.ts +644 -339
  123. package/src/local-first/Evolu.test.ts +686 -21
  124. package/src/local-first/Evolu.ts +450 -228
  125. package/src/local-first/Owner.ts +13 -30
  126. package/src/local-first/Protocol.test.ts +617 -10
  127. package/src/local-first/Protocol.ts +196 -72
  128. package/src/local-first/Query.ts +8 -15
  129. package/src/local-first/Schema.test.ts +143 -0
  130. package/src/local-first/Schema.ts +363 -24
  131. package/src/local-first/Shared.test.ts +7731 -559
  132. package/src/local-first/Shared.ts +2036 -267
  133. package/src/local-first/Storage.ts +218 -32
  134. package/src/local-first/Timestamp.test.ts +344 -70
  135. package/src/local-first/Timestamp.ts +434 -118
  136. package/src/local-first/index.ts +0 -1
  137. package/dist/src/local-first/Error.d.ts +0 -12
  138. package/dist/src/local-first/Error.d.ts.map +0 -1
  139. package/dist/src/local-first/Error.js +0 -6
  140. package/dist/src/local-first/LocalAuth.d.ts +0 -150
  141. package/dist/src/local-first/LocalAuth.d.ts.map +0 -1
  142. package/dist/src/local-first/LocalAuth.js +0 -179
  143. package/src/local-first/Error.ts +0 -17
  144. package/src/local-first/LocalAuth.ts +0 -457
@@ -1,19 +1,259 @@
1
1
  /**
2
2
  * Hybrid logical clock timestamps for CRDT ordering.
3
3
  *
4
+ * Every change to a synced table becomes a CRDT message stamped with a
5
+ * {@link Timestamp}. The timestamp is the message's identity for sync, which
6
+ * reconciles sets of timestamps between devices and relays, and its order for
7
+ * conflicts, which last-writer-wins resolves per column by comparing
8
+ * timestamps. Each database keeps one clock. It advances when stamping local
9
+ * changes to synced tables and accepting incoming messages, so later writes
10
+ * sort after earlier local writes and accepted messages.
11
+ *
12
+ * A device whose system clock is ahead produces future timestamps. They can
13
+ * override edits made later in real time on devices that have not yet accepted
14
+ * them. Accepting one advances the receiver's logical clock, so its later
15
+ * writes sort after the accepted message and carry future timestamps too, even
16
+ * before system time catches up. That propagation preserves ordering, and it
17
+ * does not compound: each device checks an incoming timestamp against its own
18
+ * system time. Counter rollover or a backwards system-clock adjustment can
19
+ * still put such a write in quarantine.
20
+ *
21
+ * ### Clock drift
22
+ *
23
+ * {@link sendTimestamp} and {@link receiveTimestamp} check the resulting clock
24
+ * against {@link TimestampConfig.maxDrift}, returning {@link TimestampDriftError}
25
+ * when it exceeds the limit. Receipt also checks the remote timestamp before
26
+ * arithmetic. The database uses the same {@link isTimestampBeyondMaxDrift}
27
+ * predicate to decide whether a message can be applied. Two situations matter
28
+ * here:
29
+ *
30
+ * - An incoming message has a timestamp too far ahead of the receiving device's
31
+ * system time. It can come from another device of the same owner or from a
32
+ * collaborator, and the check cannot tell whether the sender is ahead or the
33
+ * receiver behind.
34
+ * - The device's own system time is ahead and a write advances its logical clock.
35
+ * Drift is measured against the device's own system time, so the device
36
+ * accepts such writes as normal and stamps them ahead; other devices
37
+ * quarantine them. Only if system time then moves back far enough does the
38
+ * logical clock remain ahead and subsequent local changes go to quarantine.
39
+ * Ordinary incoming messages are still applied when their own timestamps are
40
+ * within the limit, even when the local clock is ahead. The clock is shared
41
+ * by all owners in the database.
42
+ *
43
+ * System time is usually wrong by hours, rarely by years. A person sets the
44
+ * clock by hand, often misreading daylight saving time or the year, or picks a
45
+ * wrong time zone while the clock is set manually. A virtual machine resumes
46
+ * from a snapshot. Time synchronization corrects a clock that ran fast. A
47
+ * device with a dead clock battery boots into the past. Daylight saving time
48
+ * itself changes nothing, because timestamps use epoch milliseconds. Drift of
49
+ * years comes from a clock set to the wrong year, a bug, or, in collaboration,
50
+ * a vandalizing collaborator.
51
+ *
52
+ * The database uses a five-minute limit. It tolerates a few minutes of
53
+ * difference between device clocks and quarantines messages hours or days ahead
54
+ * of system time. The limit is a trade-off: a smaller one quarantines more; a
55
+ * larger one admits more future skew and releases sooner. No limit orders
56
+ * independent offline edits by real time. Five minutes is a policy choice: the
57
+ * [HLC paper, Section 4.2](https://cse.buffalo.edu/tech-reports/2014-04.pdf)
58
+ * leaves the tolerance to application semantics and suggests at most seconds
59
+ * for NTP-synchronized servers, which user devices are not. [Actual
60
+ * Budget](https://github.com/actualbudget/actual/blob/master/packages/crdt/src/crdt/timestamp.ts)
61
+ * uses the same default, a precedent rather than proof.
62
+ *
63
+ * ### Quarantine
64
+ *
65
+ * When a message's own timestamp exceeds the limit, Evolu stores the message in
66
+ * quarantine without applying it to application tables. The database queue and
67
+ * sync continue; other messages and requests are processed normally. Completing
68
+ * a mutation means its changes are stored; some may be quarantined rather than
69
+ * visible in application queries.
70
+ *
71
+ * Quarantine is state, not an error. Nothing is reported through the error
72
+ * channel; the quarantine table records each unapplied message with its reason,
73
+ * whether this database stamped it for a local mutation or received it, and the
74
+ * system time when it was quarantined. Applications watch that table through
75
+ * queries, and a subscribed query reflects a local mutation's quarantined rows
76
+ * before its completion callback runs. Quarantine works offline, independently
77
+ * of sync state, so the application can explain why the user's change is not
78
+ * visible.
79
+ *
80
+ * A local change still receives the next logical timestamp, and that clock
81
+ * advance is persisted with the quarantined message. Further local changes also
82
+ * go to quarantine while their timestamps exceed the drift limit. New mutations
83
+ * apply normally once their timestamps fall within it; existing drift
84
+ * quarantine still waits for database worker startup. An incoming message is
85
+ * quarantined only when its own timestamp exceeds system time by more than the
86
+ * limit. `receiveTimestamp` rejects that timestamp before calculating the next
87
+ * clock, so the message keeps its original timestamp, quarantining it does not
88
+ * advance the local clock. For a message within the limit, the database applies
89
+ * the message and persists the next clock even if an already-ahead local clock
90
+ * or counter rollover produces a timestamp beyond the limit. An ahead local
91
+ * clock surfaces through local changes. A message whose timestamp is already in
92
+ * the owner's set is not written again: it was applied or quarantined before,
93
+ * and a duplicate cannot change that decision. Quarantined messages count as
94
+ * stored for sync and can be forwarded normally; each receiving device decides
95
+ * whether to apply or quarantine them. A quarantined timestamp far in the
96
+ * future also becomes the owner's last stored timestamp on every device and
97
+ * relay that stores it, so their later timestamps take the slower insert path
98
+ * of the timestamp skiplist instead of append until system time passes it. One
99
+ * device whose clock is set ahead is enough to cause this for the whole owner.
100
+ * The cost is a constant factor per stored message, the `insert` versus
101
+ * `append` workloads of the storage benchmark; ordering and sync are
102
+ * unaffected. Relays store and forward messages without checking clock drift:
103
+ * acceptance belongs to clients and must not depend on an honest relay or its
104
+ * system clock.
105
+ *
106
+ * Quarantine does not itself make sync fail: completing sync does not mean
107
+ * every stored message has been applied to application tables.
108
+ *
109
+ * ### Release
110
+ *
111
+ * Drift quarantine is checked only when the database worker starts, as schema
112
+ * quarantine is. At that point, messages are released if their timestamps are
113
+ * no more than the drift limit ahead of system time, matching acceptance on a
114
+ * fresh receipt. Devices can therefore converge on the same visible state after
115
+ * their workers restart, regardless of delivery timing.
116
+ *
117
+ * On the web, the tab holding the leader lock hosts the database worker. When
118
+ * that tab closes, reloads, or enters the browser's back-forward cache, a tab
119
+ * taking over leadership starts a replacement, and other open tabs refresh
120
+ * their subscribed queries. Reloading a non-leader tab does not restart the
121
+ * database worker.
122
+ *
123
+ * Release checks use one captured system time. The logical clock advances over
124
+ * distinct released timestamps in timestamp order, as receipts do, so later
125
+ * local changes sort after them even before system time catches up. Released
126
+ * columns use last-writer-wins, so a future-stamped message overrides edits any
127
+ * device stamped before accepting or releasing it. Columns the schema does not
128
+ * define move to schema quarantine and are applied after a schema update.
129
+ * Duplicate delivery does not release: the timestamp is already in the owner's
130
+ * set. Release during a running database worker's session is not implemented.
131
+ * Correcting system time does not trigger release; eligible messages are
132
+ * released when the database worker next starts. Until then, a later message
133
+ * from one author can be visible while an earlier one is not, so an application
134
+ * can see a row that refers to one still in quarantine. Devices each within the
135
+ * limit can also be up to twice the limit apart, so a message one of them
136
+ * accepted can be quarantined by the other.
137
+ *
138
+ * Release runs before the database worker reports its clock, so fresh requests
139
+ * start from the clock advanced by release. The stored clock never moves
140
+ * backwards. A replacement database worker's clock is adopted only if newer, so
141
+ * an empty `memoryOnly` replacement does not reset the session clock. Pending
142
+ * writes keep their captured inputs, so a replay reproduces the timestamps of
143
+ * the original attempt. Responses report their computed clock, which the
144
+ * SharedWorker adopts only if newer. Acquiring the replacement refreshes
145
+ * subscribed queries, because a startup release or a committed write whose
146
+ * response was lost would otherwise stay invisible.
147
+ *
148
+ * ### Recovery
149
+ *
150
+ * Recovery for messages further ahead than the drift limit is not implemented
151
+ * yet. These constraints shape it. Deleting quarantine rows is not a safe
152
+ * primitive: the timestamp stays in the owner's set and on relays, and a stored
153
+ * timestamp must be able to produce its message. Recovery therefore applies the
154
+ * rows early, re-authors them as a new mutation with a fresh timestamp, or
155
+ * marks them discarded. Re-authoring is only right for a local origin. All
156
+ * three leave the future-stamped message stored for sync with its original
157
+ * timestamp, so on other devices it still overrides edits stamped before they
158
+ * accept it. Applying early is acceptable while the rows are a short time
159
+ * ahead, as after a manual clock change; how short is an application decision,
160
+ * measured as the row's timestamp minus current time. Rows far ahead, from a
161
+ * clock set to the wrong year, a bug, or a vandalizing collaborator, require
162
+ * migrating the owner's visible state to a new owner with fresh timestamps; the
163
+ * old owner is abandoned. How relays treat an abandoned owner is not specified
164
+ * yet.
165
+ *
166
+ * On the device whose clock ran ahead, fresh timestamps first need a clock
167
+ * reset. The database clock is shared by all owners and never moves backwards,
168
+ * so after that device's system time is corrected, every later timestamp is
169
+ * still at least as far ahead, for every owner, including a new one.
170
+ * Re-authored rows and a migration to a new owner would be quarantined too.
171
+ * Recovery there therefore starts by resetting the clock to system time with a
172
+ * fresh random node ID, which keeps timestamps stamped after the reset distinct
173
+ * from the future ones. The SharedWorker must adopt the reset clock although it
174
+ * is older than its session clock, and, like node ID rotation, the reset runs
175
+ * as its own request, never inside a replayed write. The reset is never
176
+ * automatic, because a clock that fell back, as after a dead clock battery,
177
+ * looks the same, and resetting then would stamp changes in the past. Nor is it
178
+ * a standalone action: after it, local edits to columns holding future-stamped
179
+ * values are stored but lose last-writer-wins without being quarantined, so the
180
+ * reset belongs only inside the migration to a new owner. Such a device can be
181
+ * recognized by local-origin drift quarantine whose timestamps exceed their
182
+ * quarantine time by about the skew.
183
+ *
184
+ * ### Range ceiling
185
+ *
186
+ * Counter rollover past the {@link Millis} ceiling, in August 10889, throws. The
187
+ * clock can get there only if the system clock came within the drift limit of
188
+ * the ceiling or the stored clock was tampered with, because `receiveTimestamp`
189
+ * checks remote drift before clock arithmetic. Such a clock is as broken as one
190
+ * past the ceiling, which {@link Millis} already rejects by throwing.
191
+ *
192
+ * ### Duplicate node IDs
193
+ *
194
+ * Detection and recovery are deliberately deferred to separate work. This
195
+ * includes the receive-first collision below, where a local mutation can
196
+ * complete without being stored.
197
+ *
198
+ * The node ID is random per database and persisted in the clock. A database
199
+ * copied to another device, as when an operating system backup is restored to a
200
+ * new phone while the old one stays in use, leaves both independent copies
201
+ * stamping from the same node ID and clock. Tabs and Evolu instances sharing
202
+ * one database coordinate their writes through its shared clock.
203
+ *
204
+ * For the same owner, two changes stamped in the same logical millisecond with
205
+ * the same counter get identical timestamps. If both copies write before
206
+ * receiving the other's change, each keeps its own version, while relays and
207
+ * third devices keep the first arrival. The copies can diverge with no error.
208
+ * This is likely while the copied clock is ahead of both devices' system time,
209
+ * because both stamp counters 1, 2, 3 in the same millisecond.
210
+ *
211
+ * Receiving first can instead lose a local change. A copy quarantines an
212
+ * incoming timestamp beyond the drift limit without advancing its clock. Its
213
+ * next local mutation can then produce that same timestamp. The timestamp is
214
+ * already in the owner's set, so the local change is skipped: it is stored in
215
+ * neither application tables, history, nor quarantine, but `onComplete` still
216
+ * runs. The previously received change remains stored under that timestamp.
217
+ *
218
+ * Detection: a received message whose timestamp is new to the owner's set but
219
+ * carries the local node ID cannot be ours, because every timestamp authored
220
+ * locally is already in the set before it can be sent, and a restored or
221
+ * recreated database mints a fresh node ID. `applyMessages` knows both facts
222
+ * when `insertTimestamp` reports a new timestamp. Messages whose timestamps are
223
+ * already stored are invisible to this rule; a new timestamp from the copy
224
+ * trips it.
225
+ *
226
+ * An empty `memoryOnly` replacement is an exception: it can retain the
227
+ * SharedWorker's previous clock and node ID while losing the timestamp set.
228
+ * Detection must account for this before rotation, or our own earlier messages
229
+ * could be mistaken for another database's changes.
230
+ *
231
+ * Handling: rotate the local node ID to a fresh random one, which changes only
232
+ * future timestamps, and report the copy through sync state or the error store
233
+ * so the application can warn that edits before detection may have diverged or
234
+ * been lost. Both copies detect each other and rotate, after which detection
235
+ * stops because messages with the old ID are no longer ours. Rotation cannot
236
+ * heal past collisions. Deleting and resyncing the database automatically is
237
+ * rejected: it drops unsynced changes and local-only tables, needs a relay, and
238
+ * does not recover the dropped half of a collision; restore from mnemonic is
239
+ * its manual form. The rotation must not happen inside the replayed write: a
240
+ * replay sees nothing new and would save the input clock with the old node ID
241
+ * again. The write's response flags the detection and the SharedWorker enqueues
242
+ * a separate rotation request, which is harmless to replay.
243
+ *
4
244
  * @module
5
245
  */
6
246
  import type { RandomBytesDep } from "../Crypto.ts";
7
247
  import type { Order } from "../Order.ts";
8
248
  import type { Result } from "../Result.ts";
9
- import type { TimeDep } from "../Time.ts";
10
249
  import { Millis } from "../Time.ts";
11
250
  import { type DateIso, type InferType, type ObjectType, type Typed } from "../Type.ts";
12
251
  export interface TimestampConfig {
13
252
  /**
14
- * Maximum physical clock drift allowed in ms.
253
+ * How far in ms a timestamp may be ahead of system time.
15
254
  *
16
- * The default value is 5 * 60 * 1000 (5 minutes).
255
+ * The database uses {@link defaultTimestampMaxDrift}; it is not configurable.
256
+ * A timestamp behind system time is never drift.
17
257
  */
18
258
  readonly maxDrift: number;
19
259
  }
@@ -22,43 +262,39 @@ export declare const defaultTimestampMaxDrift: number;
22
262
  export interface TimestampConfigDep {
23
263
  readonly timestampConfig: TimestampConfig;
24
264
  }
25
- export type TimestampError = TimestampDriftError | TimestampCounterOverflowError | TimestampTimeOutOfRangeError;
265
+ /** Errors from advancing a {@link Timestamp}. */
266
+ export type TimestampError = TimestampDriftError;
267
+ /**
268
+ * A timestamp exceeds {@link TimestampConfig.maxDrift}.
269
+ *
270
+ * For local drift, the failed operation includes its candidate for explicit
271
+ * recovery by the database. For remote drift, it includes the rejected input;
272
+ * the local clock must not advance from it.
273
+ *
274
+ * Local drift stays an error even when the database recovers: callers can rely
275
+ * on successful timestamp operations satisfying the drift limit without a
276
+ * separate check.
277
+ */
26
278
  export interface TimestampDriftError extends Typed<"TimestampDriftError"> {
27
- readonly next: Millis;
279
+ /** The computed candidate for local drift, or the rejected remote input. */
280
+ readonly timestamp: Timestamp;
281
+ readonly cause: "local" | "remote";
282
+ /** Captured system time used for the drift check. */
28
283
  readonly now: Millis;
29
284
  }
30
- export interface TimestampCounterOverflowError extends Typed<"TimestampCounterOverflowError"> {
31
- }
32
- export interface TimestampTimeOutOfRangeError extends Typed<"TimestampTimeOutOfRangeError"> {
33
- }
34
285
  export declare const Counter: import("../Type.ts").BrandType<import("../Type.ts").BrandType<import("../Type.ts").BrandType<import("../Type.ts").BrandType<import("../Type.ts").BrandType<import("../Type.ts").BrandType<import("../Type.ts").Type<"Number", number, number, import("../Type.ts").TypeOfError<"Number">, null, import("../Type.ts").TypeOfError<"Number">, never, number, true>, "NonNaN", import("../Type.ts").NonNaNError>, "Finite", import("../Type.ts").FiniteError>, "Int", import("../Type.ts").IntError>, "NonNegative", import("../Type.ts").NonNegativeError>, "LessThanOrEqualTo65535", import("../Type.ts").LessThanOrEqualToError<65535>>, "Counter", never>;
35
286
  export type Counter = typeof Counter.Output;
36
287
  export declare const minCounter: Counter;
37
288
  export declare const maxCounter: Counter;
38
289
  /**
39
- * A NodeId uniquely identifies an owner's device. Generated once per device
40
- * using cryptographic randomness.
41
- *
42
- * Collision probability (birthday paradox):
43
- *
44
- * - 1,000 devices: ~0.00000000000271% (negligible).
45
- * - 1M devices: ~0.00000271% (1 in 37M chance).
46
- * - 135M devices: ~1% chance.
47
- * - 4.29B devices: ~50% chance.
48
- *
49
- * https://lemire.me/blog/2019/12/12/are-64-bit-random-identifiers-free-from-collision
50
- *
51
- * What happens if different devices generate the same NodeId?
52
- *
53
- * If devices with the same NodeId use different owners, no issues occur.
290
+ * A NodeId identifies the database that stamped a timestamp. It is 64 random
291
+ * bits, generated when the database is created and persisted in its clock.
54
292
  *
55
- * If devices with the same NodeId use the same owner, problems only arise when
56
- * they generate CRDT messages with identical timestamps (same millis, counter,
57
- * and NodeId). In this case, the protocol sync algorithm treats them as the
58
- * same message: the first will be synced with the relay, while the affected
59
- * message will not be delivered. The affected devices will see different data
60
- * yet they will think they are synced. This is extremely rare and can be
61
- * resolved by resetting one device to generate a new NodeId.
293
+ * Only databases that share an owner can collide, so random collisions are
294
+ * negligible. Identical timestamps (same millis, counter, and NodeId) are the
295
+ * same message to sync: one of the changes is lost, and the affected devices
296
+ * see different data while they appear synced. The realistic case is a copied
297
+ * database, described in the Timestamp module's Duplicate node IDs section.
62
298
  */
63
299
  export declare const NodeId: import("../Type.ts").BrandType<import("../Type.ts").Type<"String", string, string, import("../Type.ts").TypeOfError<"String">, null, import("../Type.ts").TypeOfError<"String">, never, string, true>, "NodeId", import("../Type.ts").RegexError<"NodeId">>;
64
300
  export type NodeId = typeof NodeId.Output;
@@ -86,11 +322,20 @@ export declare const nodeIdBytesToNodeId: (nodeIdBytes: NodeIdBytes) => NodeId;
86
322
  * clocks while staying close to physical time for better human
87
323
  * interpretability.
88
324
  *
89
- * The counter component ensures causality is maintained even when physical
90
- * clocks are imperfect. When clocks drift or operations occur concurrently, the
91
- * counter increments to establish a total order. This means Evolu achieves
92
- * well-defined, eventually-consistent behavior regardless of physical clock
93
- * accuracy.
325
+ * The counter gives a total order and preserves causality for accepted
326
+ * messages: when clocks differ within the drift limit or operations occur
327
+ * concurrently, it increments so later writes sort after what the device has
328
+ * seen. Messages beyond the drift limit wait in quarantine, and devices
329
+ * converge once system time is within the limit of those messages and the
330
+ * database worker restarts. See the Timestamp module's Clock drift section.
331
+ *
332
+ * When the 16-bit counter is exhausted, the logical millisecond advances by one
333
+ * and the counter resets to zero. The resulting timestamp must still fit within
334
+ * {@link Millis}. If rollover exceeds {@link TimestampConfig.maxDrift},
335
+ * {@link sendTimestamp} and {@link receiveTimestamp} return
336
+ * {@link TimestampDriftError} with the candidate timestamp for the database to
337
+ * handle. Rollover preserves deterministic ordering even when a batch uses one
338
+ * captured wall time.
94
339
  *
95
340
  * Vector clocks can accurately track causality and detect concurrent
96
341
  * operations, but they require unbounded space in peer-to-peer systems and
@@ -100,12 +345,22 @@ export declare const nodeIdBytesToNodeId: (nodeIdBytes: NodeIdBytes) => NodeId;
100
345
  * malicious actors.
101
346
  *
102
347
  * HLC timestamps work well in practice because modern device clocks accurately
103
- * reflect the order of sequential edits in the common case. Evolu's `maxDrift`
104
- * configuration protects against buggy clocks and prevents problematic
105
- * future-dated entries from propagating through the network.
348
+ * reflect the order of sequential edits in the common case. The database uses
349
+ * the drift limit to quarantine messages whose own timestamps are too far ahead
350
+ * of its system time without applying them to application tables. Quarantined
351
+ * messages remain stored and synchronized; each receiving device checks drift
352
+ * against its own system time.
106
353
  *
107
354
  * ## References
108
355
  *
356
+ * - Kulkarni, Demirbas, Madeppa, Avva, Leone: [Logical Physical Clocks and
357
+ * Consistent Snapshots in Globally Distributed
358
+ * Databases](https://cse.buffalo.edu/tech-reports/2014-04.pdf) (OPODIS 2014,
359
+ * [doi:10.1007/978-3-319-14472-6_2](https://doi.org/10.1007/978-3-319-14472-6_2)).
360
+ * The paper proposes 48 significant bits of an NTP timestamp plus a 16-bit
361
+ * counter and argues that the counter is sufficient under its assumptions.
362
+ * Evolu uses 48-bit milliseconds and rolls counter exhaustion into the next
363
+ * logical millisecond, subject to the timestamp range.
109
364
  * - https://muratbuffalo.blogspot.com/2014/07/hybrid-logical-clocks.html
110
365
  * - https://sergeiturukin.com/2017/06/26/hybrid-logical-clocks.html
111
366
  * - https://jaredforsyth.com/posts/hybrid-logical-clocks/
@@ -146,10 +401,106 @@ export declare const eqTimestamp: import("../Eq.ts").Eq<{
146
401
  readonly millis: number & import("../Brand.ts").Brand<"NonNaN"> & import("../Brand.ts").Brand<"Finite"> & import("../Brand.ts").Brand<"Int"> & import("../Brand.ts").Brand<"NonNegative"> & import("../Brand.ts").Brand<"LessThan281474976710655"> & import("../Brand.ts").Brand<"Millis">;
147
402
  readonly nodeId: string & import("../Brand.ts").Brand<"NodeId">;
148
403
  }>;
404
+ /**
405
+ * Orders {@link Timestamp} by milliseconds, counter, then node ID.
406
+ *
407
+ * Matches {@link orderTimestampBytes} without encoding timestamps. Distinct
408
+ * objects with identical fields compare as equal.
409
+ *
410
+ * ### Example
411
+ *
412
+ * ```ts
413
+ * import { assertEqual } from "@evolu/common";
414
+ * import {
415
+ * createTimestamp,
416
+ * orderTimestamp,
417
+ * } from "@evolu/common/local-first";
418
+ *
419
+ * const timestamp = createTimestamp();
420
+ * assertEqual(orderTimestamp(timestamp, { ...timestamp }), 0);
421
+ * ```
422
+ */
423
+ export declare const orderTimestamp: Order<Timestamp>;
149
424
  export declare const createTimestamp: ({ millis, counter, nodeId, }?: Partial<Timestamp>) => Timestamp;
150
425
  export declare const createInitialTimestamp: (deps: RandomBytesDep) => Timestamp;
151
- export declare const sendTimestamp: (deps: TimeDep & TimestampConfigDep) => (timestamp: Timestamp) => Result<Timestamp, TimestampDriftError | TimestampCounterOverflowError | TimestampTimeOutOfRangeError>;
152
- export declare const receiveTimestamp: (deps: TimeDep & TimestampConfigDep) => (local: Timestamp, remote: Timestamp) => Result<Timestamp, TimestampDriftError | TimestampCounterOverflowError | TimestampTimeOutOfRangeError>;
426
+ /**
427
+ * Advances a {@link Timestamp} for a local event.
428
+ *
429
+ * Counter exhaustion rolls into the next logical millisecond, and rollover past
430
+ * the {@link Millis} ceiling throws; see the module documentation. The resulting
431
+ * timestamp is checked for drift, including after rollover; a failure returns
432
+ * {@link TimestampDriftError} with the candidate and `cause: "local"`. Pass the
433
+ * request's captured system time so replay produces the same result.
434
+ *
435
+ * ### Example
436
+ *
437
+ * ```ts
438
+ * import { assertOk, Millis } from "@evolu/common";
439
+ * import {
440
+ * createTimestamp,
441
+ * maxCounter,
442
+ * sendTimestamp,
443
+ * } from "@evolu/common/local-first";
444
+ *
445
+ * const before = createTimestamp({ counter: maxCounter });
446
+ * const result = sendTimestamp({ timestampConfig: { maxDrift: 1 } })(
447
+ * before,
448
+ * Millis.orThrow(0),
449
+ * );
450
+ * assertOk(result, { ...before, millis: Millis.orThrow(1), counter: 0 });
451
+ * ```
452
+ */
453
+ export declare const sendTimestamp: (deps: TimestampConfigDep) => (timestamp: Timestamp, now: Millis) => Result<Timestamp, TimestampError>;
454
+ /**
455
+ * Advances a {@link Timestamp} for a received one.
456
+ *
457
+ * Rejects remote drift with {@link TimestampDriftError} and `cause: "remote"`
458
+ * before clock arithmetic. Merges the later clock state, preserves the local
459
+ * node ID, and uses {@link sendTimestamp} to advance and validate the result. An
460
+ * already-ahead local clock or rollover beyond the drift limit returns local
461
+ * drift. Pass one captured time for a batch or replay.
462
+ *
463
+ * ### Example
464
+ *
465
+ * ```ts
466
+ * import { assertErr, Millis } from "@evolu/common";
467
+ * import {
468
+ * createTimestamp,
469
+ * receiveTimestamp,
470
+ * } from "@evolu/common/local-first";
471
+ *
472
+ * const receive = receiveTimestamp({ timestampConfig: { maxDrift: 10 } });
473
+ * const local = createTimestamp();
474
+ * const remote = createTimestamp({ millis: Millis.orThrow(111) });
475
+ * assertErr(receive(local, remote, Millis.orThrow(100)), {
476
+ * type: "TimestampDriftError",
477
+ * timestamp: remote,
478
+ * cause: "remote",
479
+ * now: Millis.orThrow(100),
480
+ * });
481
+ * ```
482
+ */
483
+ export declare const receiveTimestamp: (deps: TimestampConfigDep) => (local: Timestamp, remote: Timestamp, now: Millis) => Result<Timestamp, TimestampError>;
484
+ /**
485
+ * Whether timestamp milliseconds exceed {@link TimestampConfig.maxDrift} ahead
486
+ * of the supplied reference time. The exact limit and past timestamps are
487
+ * accepted.
488
+ *
489
+ * ### Example
490
+ *
491
+ * ```ts
492
+ * import { assertFalse, assertTrue, Millis } from "@evolu/common";
493
+ * import { isTimestampBeyondMaxDrift } from "@evolu/common/local-first";
494
+ *
495
+ * const isBeyondMaxDrift = isTimestampBeyondMaxDrift({
496
+ * timestampConfig: { maxDrift: 10 },
497
+ * });
498
+ * const now = Millis.orThrow(100);
499
+ * assertFalse(isBeyondMaxDrift(Millis.orThrow(110), now));
500
+ * assertTrue(isBeyondMaxDrift(Millis.orThrow(111), now));
501
+ * ```
502
+ */
503
+ export declare const isTimestampBeyondMaxDrift: (deps: TimestampConfigDep) => (millis: Millis, now: Millis) => boolean;
153
504
  /** Sortable bytes representation of {@link Timestamp}. */
154
505
  export declare const TimestampBytes: import("../Type.ts").BrandType<import("../Type.ts").BrandType<import("../Type.ts").Type<"Uint8Array", Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>, import("../Type.ts").ObjectTagError<"Uint8Array">, null, import("../Type.ts").ObjectTagError<"Uint8Array">, never, Uint8Array<ArrayBufferLike>, true>, "Length16", import("../Type.ts").LengthError<16>>, "TimestampBytes", never>;
155
506
  export type TimestampBytes = typeof TimestampBytes.Output;
@@ -1 +1 @@
1
- {"version":3,"file":"Timestamp.d.ts","sourceRoot":"","sources":["../../../src/local-first/Timestamp.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Timestamp.d.ts","sourceRoot":"","sources":["../../../src/local-first/Timestamp.ts"],"names":[],"mappings":"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"}