@evolu/common 8.10.0 → 8.12.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.
- package/dist/src/Bytes.d.ts +39 -2
- package/dist/src/Bytes.d.ts.map +1 -1
- package/dist/src/Bytes.js +50 -2
- package/dist/src/Config.d.ts +22 -22
- package/dist/src/Config.d.ts.map +1 -1
- package/dist/src/Console.d.ts +62 -7
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +20 -4
- package/dist/src/Crypto.d.ts +76 -4
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +55 -4
- package/dist/src/Error.d.ts +45 -0
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +69 -0
- package/dist/src/Fs.d.ts +92 -18
- package/dist/src/Fs.d.ts.map +1 -1
- package/dist/src/Fs.js +2 -0
- package/dist/src/Identicon.d.ts +2 -2
- package/dist/src/Identicon.js +2 -2
- package/dist/src/LeakDetector.d.ts +22 -3
- package/dist/src/LeakDetector.d.ts.map +1 -1
- package/dist/src/LeakDetector.js +12 -2
- package/dist/src/LockManager.d.ts +8 -0
- package/dist/src/LockManager.d.ts.map +1 -1
- package/dist/src/LockManager.js +6 -0
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +5 -0
- package/dist/src/Platform.d.ts +47 -7
- package/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +24 -5
- package/dist/src/Random.d.ts +25 -2
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Random.js +14 -2
- package/dist/src/Resource.d.ts +156 -1
- package/dist/src/Resource.d.ts.map +1 -1
- package/dist/src/Resource.js +201 -72
- package/dist/src/Schedule.d.ts +11 -10
- package/dist/src/Schedule.d.ts.map +1 -1
- package/dist/src/Schedule.js +1 -1
- package/dist/src/Sqlite.d.ts +132 -16
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +63 -9
- package/dist/src/Task.d.ts +15 -4
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +41 -15
- package/dist/src/Test.d.ts +9 -0
- package/dist/src/Test.d.ts.map +1 -1
- package/dist/src/Test.js +4 -0
- package/dist/src/Time.d.ts +106 -9
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +55 -4
- package/dist/src/Type.d.ts +1455 -1310
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +1274 -517
- package/dist/src/WebSocket.d.ts +164 -13
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +133 -24
- package/dist/src/Worker.d.ts +90 -8
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +28 -2
- package/dist/src/index.d.ts +6 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -3
- package/dist/src/local-first/Db.d.ts +52 -3
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +412 -137
- package/dist/src/local-first/Evolu.d.ts +412 -213
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +181 -18
- package/dist/src/local-first/Owner.d.ts +13 -30
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +13 -30
- package/dist/src/local-first/Protocol.d.ts +106 -19
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +162 -60
- package/dist/src/local-first/Query.d.ts +8 -15
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Relay.js +4 -2
- package/dist/src/local-first/Schema.d.ts +346 -23
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +214 -17
- package/dist/src/local-first/Shared.d.ts +537 -22
- package/dist/src/local-first/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +1437 -234
- package/dist/src/local-first/Storage.d.ts +195 -17
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +85 -22
- package/dist/src/local-first/Timestamp.d.ts +392 -41
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +403 -81
- package/dist/src/local-first/index.d.ts +0 -1
- package/dist/src/local-first/index.d.ts.map +1 -1
- package/dist/src/local-first/index.js +0 -1
- package/package.json +1 -1
- package/src/Assert.test.ts +2 -5
- package/src/Bytes.test.ts +27 -0
- package/src/Bytes.ts +58 -2
- package/src/Config.test.ts +2 -6
- package/src/Config.ts +133 -133
- package/src/Console.ts +62 -7
- package/src/Crypto.ts +76 -4
- package/src/Eq.test.ts +2 -3
- package/src/Error.test.ts +76 -3
- package/src/Error.ts +71 -0
- package/src/Fs.ts +92 -18
- package/src/Identicon.ts +2 -2
- package/src/LeakDetector.ts +22 -3
- package/src/LockManager.ts +8 -0
- package/src/Object.test.ts +27 -12
- package/src/Object.ts +5 -0
- package/src/Platform.ts +50 -8
- package/src/Random.ts +25 -2
- package/src/Resource.test.ts +837 -0
- package/src/Resource.ts +235 -15
- package/src/Schedule.test.ts +50 -12
- package/src/Schedule.ts +24 -14
- package/src/Sqlite.ts +137 -17
- package/src/Task.test.ts +189 -8
- package/src/Task.ts +56 -17
- package/src/Test.ts +9 -0
- package/src/Time.ts +106 -9
- package/src/Type.test.ts +946 -1028
- package/src/Type.ts +4195 -3136
- package/src/Types.test.ts +4 -14
- package/src/WebSocket.ts +313 -40
- package/src/Worker.ts +90 -8
- package/src/index.ts +20 -6
- package/src/local-first/Db.ts +644 -339
- package/src/local-first/Evolu.test.ts +994 -22
- package/src/local-first/Evolu.ts +625 -232
- package/src/local-first/Owner.ts +13 -30
- package/src/local-first/Protocol.test.ts +634 -10
- package/src/local-first/Protocol.ts +255 -109
- package/src/local-first/Query.ts +8 -15
- package/src/local-first/Relay.ts +4 -2
- package/src/local-first/Schema.test.ts +143 -0
- package/src/local-first/Schema.ts +376 -26
- package/src/local-first/Shared.test.ts +7731 -559
- package/src/local-first/Shared.ts +2036 -267
- package/src/local-first/Storage.ts +224 -36
- package/src/local-first/Timestamp.test.ts +344 -70
- package/src/local-first/Timestamp.ts +434 -118
- package/src/local-first/index.ts +0 -1
- package/dist/src/local-first/Error.d.ts +0 -12
- package/dist/src/local-first/Error.d.ts.map +0 -1
- package/dist/src/local-first/Error.js +0 -6
- package/dist/src/local-first/LocalAuth.d.ts +0 -150
- package/dist/src/local-first/LocalAuth.d.ts.map +0 -1
- package/dist/src/local-first/LocalAuth.js +0 -179
- package/src/local-first/Error.ts +0 -17
- 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
|
-
*
|
|
253
|
+
* How far in ms a timestamp may be ahead of system time.
|
|
15
254
|
*
|
|
16
|
-
* The
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
40
|
-
*
|
|
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
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
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
|
|
90
|
-
* clocks
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
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.
|
|
104
|
-
*
|
|
105
|
-
*
|
|
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
|
-
|
|
152
|
-
|
|
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":"AAAA
|
|
1
|
+
{"version":3,"file":"Timestamp.d.ts","sourceRoot":"","sources":["../../../src/local-first/Timestamp.ts"],"names":[],"mappings":"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"}
|