@evolu/common 8.9.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.
- package/dist/src/Bytes.d.ts +647 -0
- package/dist/src/Bytes.d.ts.map +1 -0
- package/dist/src/{Binary.js → Bytes.js} +266 -16
- package/dist/src/Config.d.ts +142 -0
- package/dist/src/Config.d.ts.map +1 -0
- package/dist/src/Config.js +181 -0
- 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 +376 -0
- package/dist/src/Fs.d.ts.map +1 -0
- package/dist/src/Fs.js +113 -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/Number.d.ts +50 -7
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +47 -8
- package/dist/src/Object.d.ts +32 -0
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +46 -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 +64 -10
- 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 +179 -20
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +95 -6
- package/dist/src/Type.d.ts +3056 -1539
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +2548 -584
- 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 +9 -8
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -4
- package/dist/src/intl/_en.d.ts +24 -1
- package/dist/src/intl/_en.d.ts.map +1 -1
- package/dist/src/intl/_en.js +20 -0
- package/dist/src/intl/ar.d.ts +24 -1
- package/dist/src/intl/ar.d.ts.map +1 -1
- package/dist/src/intl/ar.js +20 -0
- package/dist/src/intl/bn.d.ts +24 -1
- package/dist/src/intl/bn.d.ts.map +1 -1
- package/dist/src/intl/bn.js +20 -0
- package/dist/src/intl/ca.d.ts +24 -1
- package/dist/src/intl/ca.d.ts.map +1 -1
- package/dist/src/intl/ca.js +20 -0
- package/dist/src/intl/cs.d.ts +24 -1
- package/dist/src/intl/cs.d.ts.map +1 -1
- package/dist/src/intl/cs.js +20 -0
- package/dist/src/intl/da.d.ts +24 -1
- package/dist/src/intl/da.d.ts.map +1 -1
- package/dist/src/intl/da.js +20 -0
- package/dist/src/intl/de.d.ts +24 -1
- package/dist/src/intl/de.d.ts.map +1 -1
- package/dist/src/intl/de.js +20 -0
- package/dist/src/intl/el.d.ts +24 -1
- package/dist/src/intl/el.d.ts.map +1 -1
- package/dist/src/intl/el.js +20 -0
- package/dist/src/intl/es.d.ts +24 -1
- package/dist/src/intl/es.d.ts.map +1 -1
- package/dist/src/intl/es.js +20 -0
- package/dist/src/intl/fa.d.ts +24 -1
- package/dist/src/intl/fa.d.ts.map +1 -1
- package/dist/src/intl/fa.js +20 -0
- package/dist/src/intl/fi.d.ts +24 -1
- package/dist/src/intl/fi.d.ts.map +1 -1
- package/dist/src/intl/fi.js +20 -0
- package/dist/src/intl/fil.d.ts +24 -1
- package/dist/src/intl/fil.d.ts.map +1 -1
- package/dist/src/intl/fil.js +20 -0
- package/dist/src/intl/fr.d.ts +24 -1
- package/dist/src/intl/fr.d.ts.map +1 -1
- package/dist/src/intl/fr.js +20 -0
- package/dist/src/intl/he.d.ts +24 -1
- package/dist/src/intl/he.d.ts.map +1 -1
- package/dist/src/intl/he.js +20 -0
- package/dist/src/intl/hi.d.ts +24 -1
- package/dist/src/intl/hi.d.ts.map +1 -1
- package/dist/src/intl/hi.js +20 -0
- package/dist/src/intl/hr.d.ts +24 -1
- package/dist/src/intl/hr.d.ts.map +1 -1
- package/dist/src/intl/hr.js +20 -0
- package/dist/src/intl/hu.d.ts +22 -1
- package/dist/src/intl/hu.d.ts.map +1 -1
- package/dist/src/intl/hu.js +18 -0
- package/dist/src/intl/id.d.ts +24 -1
- package/dist/src/intl/id.d.ts.map +1 -1
- package/dist/src/intl/id.js +20 -0
- package/dist/src/intl/it.d.ts +24 -1
- package/dist/src/intl/it.d.ts.map +1 -1
- package/dist/src/intl/it.js +20 -0
- package/dist/src/intl/ja.d.ts +24 -1
- package/dist/src/intl/ja.d.ts.map +1 -1
- package/dist/src/intl/ja.js +20 -0
- package/dist/src/intl/ko.d.ts +24 -1
- package/dist/src/intl/ko.d.ts.map +1 -1
- package/dist/src/intl/ko.js +20 -0
- package/dist/src/intl/ml.d.ts +24 -1
- package/dist/src/intl/ml.d.ts.map +1 -1
- package/dist/src/intl/ml.js +20 -0
- package/dist/src/intl/mr.d.ts +24 -1
- package/dist/src/intl/mr.d.ts.map +1 -1
- package/dist/src/intl/mr.js +20 -0
- package/dist/src/intl/ms.d.ts +24 -1
- package/dist/src/intl/ms.d.ts.map +1 -1
- package/dist/src/intl/ms.js +20 -0
- package/dist/src/intl/nb.d.ts +22 -1
- package/dist/src/intl/nb.d.ts.map +1 -1
- package/dist/src/intl/nb.js +18 -0
- package/dist/src/intl/nl.d.ts +24 -1
- package/dist/src/intl/nl.d.ts.map +1 -1
- package/dist/src/intl/nl.js +20 -0
- package/dist/src/intl/pa.d.ts +24 -1
- package/dist/src/intl/pa.d.ts.map +1 -1
- package/dist/src/intl/pa.js +20 -0
- package/dist/src/intl/pl.d.ts +23 -0
- package/dist/src/intl/pl.d.ts.map +1 -1
- package/dist/src/intl/pl.js +20 -0
- package/dist/src/intl/pt-BR.d.ts +24 -1
- package/dist/src/intl/pt-BR.d.ts.map +1 -1
- package/dist/src/intl/pt-BR.js +20 -0
- package/dist/src/intl/pt.d.ts +24 -1
- package/dist/src/intl/pt.d.ts.map +1 -1
- package/dist/src/intl/pt.js +20 -0
- package/dist/src/intl/ro.d.ts +24 -1
- package/dist/src/intl/ro.d.ts.map +1 -1
- package/dist/src/intl/ro.js +20 -0
- package/dist/src/intl/sk.d.ts +24 -1
- package/dist/src/intl/sk.d.ts.map +1 -1
- package/dist/src/intl/sk.js +20 -0
- package/dist/src/intl/sl.d.ts +24 -1
- package/dist/src/intl/sl.d.ts.map +1 -1
- package/dist/src/intl/sl.js +20 -0
- package/dist/src/intl/sv.d.ts +24 -1
- package/dist/src/intl/sv.d.ts.map +1 -1
- package/dist/src/intl/sv.js +20 -0
- package/dist/src/intl/sw.d.ts +21 -0
- package/dist/src/intl/sw.d.ts.map +1 -1
- package/dist/src/intl/sw.js +18 -0
- package/dist/src/intl/ta.d.ts +24 -1
- package/dist/src/intl/ta.d.ts.map +1 -1
- package/dist/src/intl/ta.js +20 -0
- package/dist/src/intl/te.d.ts +24 -1
- package/dist/src/intl/te.d.ts.map +1 -1
- package/dist/src/intl/te.js +20 -0
- package/dist/src/intl/th.d.ts +24 -1
- package/dist/src/intl/th.d.ts.map +1 -1
- package/dist/src/intl/th.js +20 -0
- package/dist/src/intl/tr.d.ts +24 -1
- package/dist/src/intl/tr.d.ts.map +1 -1
- package/dist/src/intl/tr.js +20 -0
- package/dist/src/intl/uk.d.ts +80 -57
- package/dist/src/intl/uk.d.ts.map +1 -1
- package/dist/src/intl/uk.js +174 -149
- package/dist/src/intl/ur.d.ts +24 -1
- package/dist/src/intl/ur.d.ts.map +1 -1
- package/dist/src/intl/ur.js +20 -0
- package/dist/src/intl/vi.d.ts +24 -1
- package/dist/src/intl/vi.d.ts.map +1 -1
- package/dist/src/intl/vi.js +20 -0
- package/dist/src/intl/zh-CN.d.ts +24 -1
- package/dist/src/intl/zh-CN.d.ts.map +1 -1
- package/dist/src/intl/zh-CN.js +20 -0
- package/dist/src/intl/zh-TW.d.ts +24 -1
- package/dist/src/intl/zh-TW.d.ts.map +1 -1
- package/dist/src/intl/zh-TW.js +20 -0
- 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 +336 -211
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +102 -15
- 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 +95 -17
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +119 -39
- 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/Schema.d.ts +345 -21
- 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 +192 -14
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +82 -21
- 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 +404 -82
- 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/{Binary.test.ts → Bytes.test.ts} +286 -1
- package/src/{Binary.ts → Bytes.ts} +652 -21
- package/src/Config.test.ts +668 -0
- package/src/Config.ts +410 -0
- 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.test.ts +105 -0
- package/src/Fs.ts +488 -0
- package/src/Identicon.ts +2 -2
- package/src/LeakDetector.ts +22 -3
- package/src/LockManager.ts +8 -0
- package/src/Number.test.ts +82 -18
- package/src/Number.ts +76 -8
- package/src/Object.test.ts +139 -10
- package/src/Object.ts +49 -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 +138 -18
- package/src/Task.test.ts +189 -8
- package/src/Task.ts +56 -17
- package/src/Test.ts +9 -0
- package/src/Time.test.ts +82 -11
- package/src/Time.ts +246 -24
- package/src/Type.test.ts +3994 -1119
- package/src/Type.ts +7258 -3842
- package/src/Types.test.ts +4 -14
- package/src/WebSocket.ts +313 -40
- package/src/Worker.ts +90 -8
- package/src/index.ts +18 -7
- package/src/intl/_en.ts +70 -0
- package/src/intl/ar.ts +71 -0
- package/src/intl/bn.ts +70 -0
- package/src/intl/ca.ts +70 -0
- package/src/intl/cs.ts +70 -0
- package/src/intl/da.ts +70 -0
- package/src/intl/de.ts +70 -0
- package/src/intl/el.ts +70 -0
- package/src/intl/es.ts +70 -0
- package/src/intl/fa.ts +70 -0
- package/src/intl/fi.ts +70 -0
- package/src/intl/fil.ts +70 -0
- package/src/intl/fr.ts +70 -0
- package/src/intl/he.ts +70 -0
- package/src/intl/hi.ts +70 -0
- package/src/intl/hr.ts +70 -0
- package/src/intl/hu.ts +69 -0
- package/src/intl/id.ts +70 -0
- package/src/intl/intl.test.ts +819 -1
- package/src/intl/it.ts +70 -0
- package/src/intl/ja.ts +70 -0
- package/src/intl/ko.ts +68 -0
- package/src/intl/ml.ts +70 -0
- package/src/intl/mr.ts +70 -0
- package/src/intl/ms.ts +71 -0
- package/src/intl/nb.ts +69 -0
- package/src/intl/nl.ts +70 -0
- package/src/intl/pa.ts +70 -0
- package/src/intl/pl.ts +63 -0
- package/src/intl/pt-BR.ts +70 -0
- package/src/intl/pt.ts +71 -0
- package/src/intl/ro.ts +70 -0
- package/src/intl/sk.ts +71 -0
- package/src/intl/sl.ts +70 -0
- package/src/intl/sv.ts +70 -0
- package/src/intl/sw.ts +62 -0
- package/src/intl/ta.ts +70 -0
- package/src/intl/te.ts +70 -0
- package/src/intl/th.ts +68 -0
- package/src/intl/tr.ts +70 -0
- package/src/intl/uk.ts +228 -155
- package/src/intl/ur.ts +70 -0
- package/src/intl/vi.ts +70 -0
- package/src/intl/zh-CN.ts +68 -0
- package/src/intl/zh-TW.ts +68 -0
- package/src/local-first/Db.ts +644 -339
- package/src/local-first/Evolu.test.ts +686 -21
- package/src/local-first/Evolu.ts +450 -228
- package/src/local-first/Owner.ts +13 -30
- package/src/local-first/Protocol.test.ts +618 -11
- package/src/local-first/Protocol.ts +197 -73
- package/src/local-first/Query.ts +8 -15
- package/src/local-first/Schema.test.ts +143 -0
- package/src/local-first/Schema.ts +374 -24
- package/src/local-first/Shared.test.ts +7731 -559
- package/src/local-first/Shared.ts +2036 -267
- package/src/local-first/Storage.ts +219 -33
- package/src/local-first/Timestamp.test.ts +344 -70
- package/src/local-first/Timestamp.ts +435 -119
- package/src/local-first/index.ts +0 -1
- package/dist/src/Binary.d.ts +0 -254
- package/dist/src/Binary.d.ts.map +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
|
@@ -3,34 +3,36 @@ import {
|
|
|
3
3
|
assertEqual,
|
|
4
4
|
assertErr,
|
|
5
5
|
assertFalse,
|
|
6
|
+
assertNotSame,
|
|
6
7
|
assertOk,
|
|
8
|
+
assertThrowsInstanceOf,
|
|
7
9
|
assertTrue,
|
|
8
10
|
} from "../Assert.ts";
|
|
9
11
|
import { increment } from "../Number.ts";
|
|
10
12
|
import { orderNumber } from "../Order.ts";
|
|
11
13
|
import type { Result } from "../Result.ts";
|
|
12
|
-
import { ok } from "../Result.ts";
|
|
13
14
|
import { testCreateDeps } from "../Task.ts";
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
15
|
+
import { maxMillis, Millis, minMillis } from "../Time.ts";
|
|
16
|
+
import { assertType } from "../Type.ts";
|
|
16
17
|
import type {
|
|
17
18
|
Timestamp,
|
|
18
19
|
TimestampBytes,
|
|
19
20
|
TimestampConfigDep,
|
|
20
|
-
TimestampCounterOverflowError,
|
|
21
21
|
TimestampDriftError,
|
|
22
|
-
TimestampTimeOutOfRangeError,
|
|
23
22
|
} from "./Timestamp.ts";
|
|
24
23
|
import {
|
|
25
24
|
Counter,
|
|
26
25
|
createInitialTimestamp,
|
|
27
26
|
createTimestamp,
|
|
28
27
|
defaultTimestampMaxDrift,
|
|
28
|
+
isTimestampBeyondMaxDrift,
|
|
29
29
|
maxCounter,
|
|
30
|
+
maxNodeId,
|
|
30
31
|
minCounter,
|
|
31
32
|
NodeId,
|
|
32
33
|
nodeIdBytesToNodeId,
|
|
33
34
|
nodeIdToNodeIdBytes,
|
|
35
|
+
orderTimestamp,
|
|
34
36
|
orderTimestampBytes,
|
|
35
37
|
receiveTimestamp,
|
|
36
38
|
sendTimestamp,
|
|
@@ -93,21 +95,90 @@ test("creates an initial Timestamp", () => {
|
|
|
93
95
|
});
|
|
94
96
|
});
|
|
95
97
|
|
|
98
|
+
describe("orderTimestamp", () => {
|
|
99
|
+
it("compares equal timestamps regardless of object identity", () => {
|
|
100
|
+
const timestamp = createTimestamp({
|
|
101
|
+
millis: maxMillis,
|
|
102
|
+
counter: maxCounter,
|
|
103
|
+
nodeId: maxNodeId,
|
|
104
|
+
});
|
|
105
|
+
const copy = { ...timestamp };
|
|
106
|
+
|
|
107
|
+
assertNotSame(timestamp, copy);
|
|
108
|
+
assertEqual(orderTimestamp(timestamp, timestamp), 0);
|
|
109
|
+
assertEqual(orderTimestamp(timestamp, copy), 0);
|
|
110
|
+
assertEqual(orderTimestamp(copy, timestamp), 0);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("orders millis, counter, then nodeId consistently with timestamp bytes", () => {
|
|
114
|
+
const timestamps = [
|
|
115
|
+
createTimestamp(),
|
|
116
|
+
createTimestamp({ nodeId: NodeId.orThrow("9999999999999999") }),
|
|
117
|
+
createTimestamp({ nodeId: NodeId.orThrow("aaaaaaaaaaaaaaaa") }),
|
|
118
|
+
createTimestamp({ nodeId: maxNodeId }),
|
|
119
|
+
createTimestamp({ counter: Counter.orThrow(255), nodeId: maxNodeId }),
|
|
120
|
+
createTimestamp({ counter: Counter.orThrow(256) }),
|
|
121
|
+
createTimestamp({ counter: maxCounter, nodeId: maxNodeId }),
|
|
122
|
+
createTimestamp({ millis: Millis.orThrow(1) }),
|
|
123
|
+
createTimestamp({
|
|
124
|
+
millis: Millis.orThrow(2 ** 32 - 1),
|
|
125
|
+
counter: maxCounter,
|
|
126
|
+
nodeId: maxNodeId,
|
|
127
|
+
}),
|
|
128
|
+
createTimestamp({ millis: Millis.orThrow(2 ** 32) }),
|
|
129
|
+
createTimestamp({
|
|
130
|
+
millis: maxMillis,
|
|
131
|
+
counter: maxCounter,
|
|
132
|
+
nodeId: maxNodeId,
|
|
133
|
+
}),
|
|
134
|
+
];
|
|
135
|
+
const bytes = timestamps.map(timestampToTimestampBytes);
|
|
136
|
+
|
|
137
|
+
for (const [i, a] of timestamps.entries()) {
|
|
138
|
+
for (const [j, b] of timestamps.entries()) {
|
|
139
|
+
const order = orderTimestamp(a, b);
|
|
140
|
+
assertEqual(order, orderNumber(i, j));
|
|
141
|
+
assertEqual(order, orderTimestampBytes(bytes[i], bytes[j]));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
96
147
|
const makeMillis = (millis: number): Millis => Millis.orThrow(millis);
|
|
97
148
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
149
|
+
describe("isTimestampBeyondMaxDrift", () => {
|
|
150
|
+
it("accepts past timestamps and the exact future limit", () => {
|
|
151
|
+
const isBeyondMaxDrift = isTimestampBeyondMaxDrift({
|
|
152
|
+
timestampConfig: { maxDrift: 10 },
|
|
153
|
+
});
|
|
154
|
+
const now = makeMillis(100);
|
|
155
|
+
|
|
156
|
+
assertFalse(isBeyondMaxDrift(makeMillis(0), now));
|
|
157
|
+
assertFalse(isBeyondMaxDrift(now, now));
|
|
158
|
+
assertFalse(isBeyondMaxDrift(makeMillis(109), now));
|
|
159
|
+
assertFalse(isBeyondMaxDrift(makeMillis(110), now));
|
|
160
|
+
assertTrue(isBeyondMaxDrift(makeMillis(111), now));
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("rejects any future timestamp when the limit is zero", () => {
|
|
164
|
+
const isBeyondMaxDrift = isTimestampBeyondMaxDrift({
|
|
165
|
+
timestampConfig: { maxDrift: 0 },
|
|
166
|
+
});
|
|
167
|
+
const now = makeMillis(100);
|
|
168
|
+
|
|
169
|
+
assertFalse(isBeyondMaxDrift(makeMillis(99), now));
|
|
170
|
+
assertFalse(isBeyondMaxDrift(now, now));
|
|
171
|
+
assertTrue(isBeyondMaxDrift(makeMillis(101), now));
|
|
172
|
+
});
|
|
173
|
+
});
|
|
102
174
|
|
|
103
|
-
const
|
|
104
|
-
time: testCreateTime({ startAt: (minMillis + 1) as Millis }),
|
|
175
|
+
const config: TimestampConfigDep = {
|
|
105
176
|
timestampConfig: { maxDrift: defaultTimestampMaxDrift },
|
|
106
177
|
};
|
|
107
178
|
|
|
108
179
|
describe("sendTimestamp", () => {
|
|
109
180
|
it("sends monotonically with a monotonic clock", () => {
|
|
110
|
-
assertOk(sendTimestamp(
|
|
181
|
+
assertOk(sendTimestamp(config)(createTimestamp(), makeMillis(1)), {
|
|
111
182
|
counter: 0,
|
|
112
183
|
millis: 1,
|
|
113
184
|
nodeId: "0000000000000000",
|
|
@@ -115,7 +186,7 @@ describe("sendTimestamp", () => {
|
|
|
115
186
|
});
|
|
116
187
|
|
|
117
188
|
it("sends monotonically with a stuttering clock", () => {
|
|
118
|
-
assertOk(sendTimestamp(
|
|
189
|
+
assertOk(sendTimestamp(config)(createTimestamp(), minMillis), {
|
|
119
190
|
counter: 1,
|
|
120
191
|
millis: 0,
|
|
121
192
|
nodeId: "0000000000000000",
|
|
@@ -124,8 +195,9 @@ describe("sendTimestamp", () => {
|
|
|
124
195
|
|
|
125
196
|
it("sends monotonically with a regressing clock", () => {
|
|
126
197
|
assertOk(
|
|
127
|
-
sendTimestamp(
|
|
198
|
+
sendTimestamp(config)(
|
|
128
199
|
createTimestamp({ millis: makeMillis(minMillis + 1) }),
|
|
200
|
+
minMillis,
|
|
129
201
|
),
|
|
130
202
|
{
|
|
131
203
|
counter: 1,
|
|
@@ -135,52 +207,64 @@ describe("sendTimestamp", () => {
|
|
|
135
207
|
);
|
|
136
208
|
});
|
|
137
209
|
|
|
138
|
-
it("
|
|
139
|
-
let timestamp
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
> = ok(createTimestamp());
|
|
145
|
-
|
|
146
|
-
// Note +1 in 65536.
|
|
147
|
-
for (let i = 0; i < 65536; i++) {
|
|
148
|
-
if (timestamp.ok) timestamp = sendTimestamp(deps0)(timestamp.value);
|
|
210
|
+
it("continues past a full counter with fixed wall time", () => {
|
|
211
|
+
let timestamp = createTimestamp();
|
|
212
|
+
for (let i = 0; i <= maxCounter; i++) {
|
|
213
|
+
const next = sendTimestamp(config)(timestamp, minMillis);
|
|
214
|
+
assertOk(next);
|
|
215
|
+
timestamp = next.value;
|
|
149
216
|
}
|
|
217
|
+
assertEqual(timestamp, createTimestamp({ millis: makeMillis(1) }));
|
|
218
|
+
assertOk(
|
|
219
|
+
sendTimestamp(config)(timestamp, minMillis),
|
|
220
|
+
createTimestamp({ millis: makeMillis(1), counter: Counter.orThrow(1) }),
|
|
221
|
+
);
|
|
222
|
+
});
|
|
150
223
|
|
|
151
|
-
|
|
224
|
+
it("continues past a full counter while the clock is ahead of advancing wall time", () => {
|
|
225
|
+
// Wall time advances by one millisecond per send, while the clock stays two
|
|
226
|
+
// minutes ahead, as after receiving a timestamp from a fast peer. Every
|
|
227
|
+
// send keeps the pinned millis, so the counter alone must carry ordering.
|
|
228
|
+
const pinnedMillis = makeMillis(2 * 60 * 1000);
|
|
229
|
+
let timestamp = createTimestamp({ millis: pinnedMillis });
|
|
230
|
+
for (let i = 0; i <= maxCounter; i++) {
|
|
231
|
+
const next = sendTimestamp(config)(timestamp, makeMillis(i));
|
|
232
|
+
assertOk(next);
|
|
233
|
+
assertEqual(
|
|
234
|
+
orderTimestampBytes(
|
|
235
|
+
timestampToTimestampBytes(timestamp),
|
|
236
|
+
timestampToTimestampBytes(next.value),
|
|
237
|
+
),
|
|
238
|
+
-1,
|
|
239
|
+
);
|
|
240
|
+
if (i < maxCounter) assertEqual(next.value.millis, pinnedMillis);
|
|
241
|
+
timestamp = next.value;
|
|
242
|
+
}
|
|
243
|
+
assertEqual(
|
|
244
|
+
timestamp,
|
|
245
|
+
createTimestamp({ millis: makeMillis(pinnedMillis + 1) }),
|
|
246
|
+
);
|
|
152
247
|
});
|
|
153
248
|
|
|
154
|
-
it("returns TimestampDriftError for
|
|
249
|
+
it("returns TimestampDriftError for an ahead local clock", () => {
|
|
155
250
|
assertErr(
|
|
156
|
-
sendTimestamp(
|
|
251
|
+
sendTimestamp(config)(
|
|
157
252
|
createTimestamp({
|
|
158
253
|
millis: makeMillis(minMillis + defaultTimestampMaxDrift + 1),
|
|
159
254
|
}),
|
|
255
|
+
minMillis,
|
|
160
256
|
),
|
|
161
257
|
{
|
|
162
|
-
next: 300001,
|
|
163
|
-
now: 0,
|
|
164
258
|
type: "TimestampDriftError",
|
|
259
|
+
now: minMillis,
|
|
260
|
+
timestamp: createTimestamp({
|
|
261
|
+
millis: makeMillis(300001),
|
|
262
|
+
counter: Counter.orThrow(1),
|
|
263
|
+
}),
|
|
264
|
+
cause: "local",
|
|
165
265
|
},
|
|
166
266
|
);
|
|
167
267
|
});
|
|
168
|
-
|
|
169
|
-
it("returns TimestampTimeOutOfRangeError for an invalid clock", () => {
|
|
170
|
-
const time = testCreateTime();
|
|
171
|
-
const invalidTime: Time = {
|
|
172
|
-
...time,
|
|
173
|
-
now: (() => -1) as Time["now"],
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
assertErr(
|
|
177
|
-
sendTimestamp({
|
|
178
|
-
time: invalidTime,
|
|
179
|
-
timestampConfig: { maxDrift: defaultTimestampMaxDrift },
|
|
180
|
-
})(createTimestamp()),
|
|
181
|
-
{ type: "TimestampTimeOutOfRangeError" },
|
|
182
|
-
);
|
|
183
|
-
});
|
|
184
268
|
});
|
|
185
269
|
|
|
186
270
|
describe("receiveTimestamp", () => {
|
|
@@ -200,7 +284,11 @@ describe("receiveTimestamp", () => {
|
|
|
200
284
|
|
|
201
285
|
it("uses a wall clock later than both timestamps", () => {
|
|
202
286
|
assertOk(
|
|
203
|
-
receiveTimestamp(
|
|
287
|
+
receiveTimestamp(config)(
|
|
288
|
+
makeNode1Timestamp(),
|
|
289
|
+
makeNode2Timestamp(),
|
|
290
|
+
makeMillis(1),
|
|
291
|
+
),
|
|
204
292
|
{
|
|
205
293
|
counter: 0,
|
|
206
294
|
millis: 1,
|
|
@@ -212,9 +300,10 @@ describe("receiveTimestamp", () => {
|
|
|
212
300
|
describe("wall clock is behind", () => {
|
|
213
301
|
it("increments the maximum counter when millis are equal", () => {
|
|
214
302
|
assertOk(
|
|
215
|
-
receiveTimestamp(
|
|
303
|
+
receiveTimestamp(config)(
|
|
216
304
|
makeNode1Timestamp(1, 0),
|
|
217
305
|
makeNode2Timestamp(1, 1),
|
|
306
|
+
makeMillis(1),
|
|
218
307
|
),
|
|
219
308
|
{
|
|
220
309
|
counter: 2,
|
|
@@ -224,9 +313,10 @@ describe("receiveTimestamp", () => {
|
|
|
224
313
|
);
|
|
225
314
|
|
|
226
315
|
assertOk(
|
|
227
|
-
receiveTimestamp(
|
|
316
|
+
receiveTimestamp(config)(
|
|
228
317
|
makeNode1Timestamp(1, 1),
|
|
229
318
|
makeNode2Timestamp(1, 0),
|
|
319
|
+
minMillis,
|
|
230
320
|
),
|
|
231
321
|
{
|
|
232
322
|
counter: 2,
|
|
@@ -238,7 +328,11 @@ describe("receiveTimestamp", () => {
|
|
|
238
328
|
|
|
239
329
|
it("increments the counter when local millis is later", () => {
|
|
240
330
|
assertOk(
|
|
241
|
-
receiveTimestamp(
|
|
331
|
+
receiveTimestamp(config)(
|
|
332
|
+
makeNode1Timestamp(2),
|
|
333
|
+
makeNode2Timestamp(1),
|
|
334
|
+
minMillis,
|
|
335
|
+
),
|
|
242
336
|
{
|
|
243
337
|
counter: 1,
|
|
244
338
|
millis: 2,
|
|
@@ -249,7 +343,11 @@ describe("receiveTimestamp", () => {
|
|
|
249
343
|
|
|
250
344
|
it("increments the counter when remote millis is later", () => {
|
|
251
345
|
assertOk(
|
|
252
|
-
receiveTimestamp(
|
|
346
|
+
receiveTimestamp(config)(
|
|
347
|
+
makeNode1Timestamp(1),
|
|
348
|
+
makeNode2Timestamp(2),
|
|
349
|
+
minMillis,
|
|
350
|
+
),
|
|
253
351
|
{
|
|
254
352
|
counter: 1,
|
|
255
353
|
millis: 2,
|
|
@@ -258,43 +356,219 @@ describe("receiveTimestamp", () => {
|
|
|
258
356
|
);
|
|
259
357
|
});
|
|
260
358
|
|
|
261
|
-
it("returns
|
|
262
|
-
const expected = {
|
|
263
|
-
next: 300001,
|
|
264
|
-
now: 0,
|
|
265
|
-
type: "TimestampDriftError",
|
|
266
|
-
} as const;
|
|
267
|
-
|
|
359
|
+
it("returns local drift when an accepted remote timestamp leaves the clock ahead", () => {
|
|
268
360
|
assertErr(
|
|
269
|
-
receiveTimestamp(
|
|
361
|
+
receiveTimestamp(config)(
|
|
270
362
|
createTimestamp({
|
|
271
363
|
millis: makeMillis(minMillis + defaultTimestampMaxDrift + 1),
|
|
272
364
|
}),
|
|
273
365
|
makeNode2Timestamp(),
|
|
366
|
+
minMillis,
|
|
367
|
+
),
|
|
368
|
+
{
|
|
369
|
+
type: "TimestampDriftError",
|
|
370
|
+
now: minMillis,
|
|
371
|
+
timestamp: createTimestamp({
|
|
372
|
+
millis: makeMillis(300001),
|
|
373
|
+
counter: Counter.orThrow(1),
|
|
374
|
+
}),
|
|
375
|
+
cause: "local",
|
|
376
|
+
},
|
|
377
|
+
);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it("rejects remote drift without a separate caller check", () => {
|
|
381
|
+
const remote = createTimestamp({
|
|
382
|
+
millis: makeMillis(minMillis + defaultTimestampMaxDrift + 1),
|
|
383
|
+
});
|
|
384
|
+
const local = makeNode2Timestamp();
|
|
385
|
+
const before = { ...local };
|
|
386
|
+
assertErr(receiveTimestamp(config)(local, remote, minMillis), {
|
|
387
|
+
type: "TimestampDriftError",
|
|
388
|
+
timestamp: remote,
|
|
389
|
+
cause: "remote",
|
|
390
|
+
now: minMillis,
|
|
391
|
+
});
|
|
392
|
+
assertEqual(local, before);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("rejects remote drift before arithmetic at the range ceiling", () => {
|
|
396
|
+
const remote = createTimestamp({
|
|
397
|
+
millis: maxMillis,
|
|
398
|
+
counter: maxCounter,
|
|
399
|
+
});
|
|
400
|
+
assertErr(
|
|
401
|
+
receiveTimestamp(config)(createTimestamp(), remote, minMillis),
|
|
402
|
+
{
|
|
403
|
+
type: "TimestampDriftError",
|
|
404
|
+
timestamp: remote,
|
|
405
|
+
cause: "remote",
|
|
406
|
+
now: minMillis,
|
|
407
|
+
},
|
|
408
|
+
);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it("uses the configured drift boundary, including a zero limit", () => {
|
|
412
|
+
for (const maxDrift of [0, 10]) {
|
|
413
|
+
const deps = { timestampConfig: { maxDrift } };
|
|
414
|
+
const now = makeMillis(100);
|
|
415
|
+
const local = makeNode1Timestamp();
|
|
416
|
+
const accepted = makeNode2Timestamp(100 + maxDrift);
|
|
417
|
+
assertOk(
|
|
418
|
+
receiveTimestamp(deps)(local, accepted, now),
|
|
419
|
+
makeNode1Timestamp(100 + maxDrift, 1),
|
|
420
|
+
);
|
|
421
|
+
const rejected = makeNode2Timestamp(101 + maxDrift);
|
|
422
|
+
assertErr(receiveTimestamp(deps)(local, rejected, now), {
|
|
423
|
+
type: "TimestampDriftError",
|
|
424
|
+
timestamp: rejected,
|
|
425
|
+
cause: "remote",
|
|
426
|
+
now: makeMillis(100),
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
for (const [
|
|
432
|
+
label,
|
|
433
|
+
localMillis,
|
|
434
|
+
remoteMillis,
|
|
435
|
+
localCounter,
|
|
436
|
+
remoteCounter,
|
|
437
|
+
] of [
|
|
438
|
+
["equal millis with the local counter exhausted", 1, 1, maxCounter, 0],
|
|
439
|
+
["equal millis with the remote counter exhausted", 1, 1, 0, maxCounter],
|
|
440
|
+
[
|
|
441
|
+
"equal millis with both counters exhausted",
|
|
442
|
+
1,
|
|
443
|
+
1,
|
|
444
|
+
maxCounter,
|
|
445
|
+
maxCounter,
|
|
446
|
+
],
|
|
447
|
+
["later local millis", 1, 0, maxCounter, 0],
|
|
448
|
+
["later remote millis", 0, 1, 0, maxCounter],
|
|
449
|
+
] as const) {
|
|
450
|
+
it(`rolls over for ${label}`, () => {
|
|
451
|
+
const local = makeNode1Timestamp(localMillis, localCounter);
|
|
452
|
+
const remote = makeNode2Timestamp(remoteMillis, remoteCounter);
|
|
453
|
+
const result = receiveTimestamp(config)(local, remote, minMillis);
|
|
454
|
+
assertOk(result, makeNode1Timestamp(2, 0));
|
|
455
|
+
const bytes = timestampToTimestampBytes(result.value);
|
|
456
|
+
assertEqual(
|
|
457
|
+
orderTimestampBytes(timestampToTimestampBytes(local), bytes),
|
|
458
|
+
-1,
|
|
459
|
+
);
|
|
460
|
+
assertEqual(
|
|
461
|
+
orderTimestampBytes(timestampToTimestampBytes(remote), bytes),
|
|
462
|
+
-1,
|
|
463
|
+
);
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
it("resets exhausted counters when wall time is newer", () => {
|
|
467
|
+
assertOk(
|
|
468
|
+
receiveTimestamp(config)(
|
|
469
|
+
makeNode1Timestamp(1, maxCounter),
|
|
470
|
+
makeNode2Timestamp(1, maxCounter),
|
|
471
|
+
makeMillis(2),
|
|
274
472
|
),
|
|
275
|
-
|
|
473
|
+
makeNode1Timestamp(2, 0),
|
|
276
474
|
);
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
});
|
|
277
478
|
|
|
479
|
+
describe("timestamp rollover boundaries", () => {
|
|
480
|
+
for (const operation of ["send", "receive"] as const) {
|
|
481
|
+
const nextTimestamp = (timestamp: Timestamp, now = minMillis) =>
|
|
482
|
+
operation === "send"
|
|
483
|
+
? sendTimestamp(config)(timestamp, now)
|
|
484
|
+
: receiveTimestamp(config)(timestamp, timestamp, now);
|
|
485
|
+
|
|
486
|
+
it(`${operation} uses the last counter before rolling over`, () => {
|
|
487
|
+
const lastCounter = createTimestamp({ counter: maxCounter });
|
|
488
|
+
assertOk(
|
|
489
|
+
nextTimestamp(
|
|
490
|
+
createTimestamp({ counter: Counter.orThrow(maxCounter - 1) }),
|
|
491
|
+
),
|
|
492
|
+
lastCounter,
|
|
493
|
+
);
|
|
494
|
+
const result = nextTimestamp(lastCounter);
|
|
495
|
+
assertOk(result, createTimestamp({ millis: makeMillis(1) }));
|
|
496
|
+
assertEqual(
|
|
497
|
+
timestampBytesToTimestamp(timestampToTimestampBytes(result.value)),
|
|
498
|
+
result.value,
|
|
499
|
+
);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it(`${operation} permits rollover at the drift limit and rejects the next millisecond`, () => {
|
|
503
|
+
assertOk(
|
|
504
|
+
nextTimestamp(
|
|
505
|
+
createTimestamp({
|
|
506
|
+
millis: makeMillis(defaultTimestampMaxDrift - 1),
|
|
507
|
+
counter: maxCounter,
|
|
508
|
+
}),
|
|
509
|
+
),
|
|
510
|
+
createTimestamp({ millis: makeMillis(defaultTimestampMaxDrift) }),
|
|
511
|
+
);
|
|
278
512
|
assertErr(
|
|
279
|
-
|
|
280
|
-
makeNode2Timestamp(),
|
|
513
|
+
nextTimestamp(
|
|
281
514
|
createTimestamp({
|
|
282
|
-
millis: makeMillis(
|
|
515
|
+
millis: makeMillis(defaultTimestampMaxDrift),
|
|
516
|
+
counter: maxCounter,
|
|
283
517
|
}),
|
|
284
518
|
),
|
|
285
|
-
|
|
519
|
+
{
|
|
520
|
+
type: "TimestampDriftError",
|
|
521
|
+
now: minMillis,
|
|
522
|
+
timestamp: createTimestamp({
|
|
523
|
+
millis: makeMillis(defaultTimestampMaxDrift + 1),
|
|
524
|
+
}),
|
|
525
|
+
cause: "local",
|
|
526
|
+
},
|
|
286
527
|
);
|
|
287
528
|
});
|
|
288
529
|
|
|
289
|
-
it(
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
530
|
+
it(`${operation} permits the maximum millis and throws on rollover beyond it`, () => {
|
|
531
|
+
const now = makeMillis(maxMillis - 1);
|
|
532
|
+
assertOk(
|
|
533
|
+
nextTimestamp(
|
|
534
|
+
createTimestamp({
|
|
535
|
+
millis: makeMillis(maxMillis - 1),
|
|
536
|
+
counter: maxCounter,
|
|
537
|
+
}),
|
|
538
|
+
now,
|
|
294
539
|
),
|
|
295
|
-
{
|
|
540
|
+
createTimestamp({ millis: maxMillis }),
|
|
296
541
|
);
|
|
542
|
+
assertThrowsInstanceOf(() => {
|
|
543
|
+
nextTimestamp(
|
|
544
|
+
createTimestamp({ millis: maxMillis, counter: maxCounter }),
|
|
545
|
+
now,
|
|
546
|
+
);
|
|
547
|
+
}, Error);
|
|
297
548
|
});
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
it("exposes the failures of each operation", () => {
|
|
552
|
+
assertType<
|
|
553
|
+
ReturnType<ReturnType<typeof sendTimestamp>>,
|
|
554
|
+
Result<Timestamp, TimestampDriftError>
|
|
555
|
+
>();
|
|
556
|
+
assertType<
|
|
557
|
+
ReturnType<ReturnType<typeof receiveTimestamp>>,
|
|
558
|
+
Result<Timestamp, TimestampDriftError>
|
|
559
|
+
>();
|
|
560
|
+
assertType<Parameters<typeof sendTimestamp>, [TimestampConfigDep]>();
|
|
561
|
+
assertType<
|
|
562
|
+
Parameters<ReturnType<typeof sendTimestamp>>,
|
|
563
|
+
[Timestamp, Millis]
|
|
564
|
+
>();
|
|
565
|
+
assertType<Parameters<typeof receiveTimestamp>, [TimestampConfigDep]>();
|
|
566
|
+
assertType<
|
|
567
|
+
Parameters<ReturnType<typeof receiveTimestamp>>,
|
|
568
|
+
[Timestamp, Timestamp, Millis]
|
|
569
|
+
>();
|
|
570
|
+
// @ts-expect-error TimestampCounterOverflowError is no longer exported; counters roll over.
|
|
571
|
+
type _RemovedError = import("../index.ts").TimestampCounterOverflowError;
|
|
298
572
|
});
|
|
299
573
|
});
|
|
300
574
|
|