@evolu/common 7.4.1 → 8.0.0-next.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/README.md +4 -61
- package/dist/src/Array.d.ts +343 -102
- package/dist/src/Array.d.ts.map +1 -1
- package/dist/src/Array.js +181 -85
- package/dist/src/Assert.d.ts +57 -11
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +47 -11
- package/dist/src/BigInt.d.ts +6 -1
- package/dist/src/BigInt.d.ts.map +1 -1
- package/dist/src/BigInt.js +5 -0
- package/dist/src/Brand.d.ts +40 -12
- package/dist/src/Brand.d.ts.map +1 -1
- package/dist/src/Brand.js +5 -0
- package/dist/src/Buffer.d.ts +6 -1
- package/dist/src/Buffer.d.ts.map +1 -1
- package/dist/src/Buffer.js +7 -2
- package/dist/src/Cache.d.ts +10 -2
- package/dist/src/Cache.d.ts.map +1 -1
- package/dist/src/Cache.js +6 -0
- package/dist/src/Callbacks.d.ts +10 -7
- package/dist/src/Callbacks.d.ts.map +1 -1
- package/dist/src/Callbacks.js +23 -13
- package/dist/src/Console.d.ts +361 -69
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +217 -117
- package/dist/src/Crypto.d.ts +70 -21
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +72 -27
- package/dist/src/Eq.d.ts +22 -2
- package/dist/src/Eq.d.ts.map +1 -1
- package/dist/src/Eq.js +21 -1
- package/dist/src/Error.d.ts +23 -12
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +27 -11
- package/dist/src/Function.d.ts +128 -56
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +118 -14
- package/dist/src/Identicon.d.ts +5 -0
- package/dist/src/Identicon.d.ts.map +1 -1
- package/dist/src/Identicon.js +6 -1
- package/dist/src/Lookup.d.ts +160 -0
- package/dist/src/Lookup.d.ts.map +1 -0
- package/dist/src/Lookup.js +192 -0
- package/dist/src/Microtask.d.ts +21 -0
- package/dist/src/Microtask.d.ts.map +1 -0
- package/dist/src/Microtask.js +37 -0
- package/dist/src/Number.d.ts +18 -4
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +23 -1
- package/dist/src/Object.d.ts +110 -5
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +108 -5
- package/dist/src/Option.d.ts +58 -0
- package/dist/src/Option.d.ts.map +1 -0
- package/dist/src/Option.js +43 -0
- package/dist/src/Order.d.ts +5 -0
- package/dist/src/Order.d.ts.map +1 -1
- package/dist/src/Order.js +8 -3
- package/dist/src/Platform.d.ts +36 -2
- package/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +10 -6
- package/dist/src/Polyfills.d.ts +27 -0
- package/dist/src/Polyfills.d.ts.map +1 -0
- package/dist/src/Polyfills.js +299 -0
- package/dist/src/Random.d.ts +18 -7
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Random.js +9 -7
- package/dist/src/Redacted.d.ts +7 -4
- package/dist/src/Redacted.d.ts.map +1 -1
- package/dist/src/Redacted.js +5 -0
- package/dist/src/Ref.d.ts +31 -16
- package/dist/src/Ref.d.ts.map +1 -1
- package/dist/src/Ref.js +35 -7
- package/dist/src/RefCount.d.ts +62 -0
- package/dist/src/RefCount.d.ts.map +1 -0
- package/dist/src/RefCount.js +83 -0
- package/dist/src/Relation.d.ts +84 -29
- package/dist/src/Relation.d.ts.map +1 -1
- package/dist/src/Relation.js +83 -105
- package/dist/src/Resource.d.ts +263 -0
- package/dist/src/Resource.d.ts.map +1 -0
- package/dist/src/Resource.js +389 -0
- package/dist/src/Result.d.ts +390 -374
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +142 -70
- package/dist/src/Schedule.d.ts +953 -0
- package/dist/src/Schedule.d.ts.map +1 -0
- package/dist/src/Schedule.js +1199 -0
- package/dist/src/Set.d.ts +181 -0
- package/dist/src/Set.d.ts.map +1 -0
- package/dist/src/Set.js +137 -0
- package/dist/src/Skiplist.d.ts +6 -1
- package/dist/src/Skiplist.d.ts.map +1 -1
- package/dist/src/Skiplist.js +5 -0
- package/dist/src/Sqlite.d.ts +160 -60
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +290 -190
- package/dist/src/Store.d.ts +36 -28
- package/dist/src/Store.d.ts.map +1 -1
- package/dist/src/Store.js +55 -15
- package/dist/src/String.d.ts +5 -0
- package/dist/src/String.d.ts.map +1 -1
- package/dist/src/String.js +5 -0
- package/dist/src/Task.d.ts +2263 -423
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +1488 -348
- package/dist/src/Test.d.ts +122 -0
- package/dist/src/Test.d.ts.map +1 -0
- package/dist/src/Test.js +66 -0
- package/dist/src/Time.d.ts +201 -48
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +190 -86
- package/dist/src/Tracer.d.ts +48 -0
- package/dist/src/Tracer.d.ts.map +1 -0
- package/dist/src/Tracer.js +6 -0
- package/dist/src/Type.d.ts +582 -209
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +362 -240
- package/dist/src/Types.d.ts +160 -6
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/Types.js +22 -2
- package/dist/src/WebSocket.d.ts +113 -58
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +210 -118
- package/dist/src/Worker.d.ts +245 -60
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +220 -64
- package/dist/src/index.d.ts +25 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +19 -3
- package/dist/src/local-first/Db.d.ts +24 -213
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +622 -293
- package/dist/src/local-first/Error.d.ts +12 -0
- package/dist/src/local-first/Error.d.ts.map +1 -0
- package/dist/src/local-first/Error.js +6 -0
- package/dist/src/local-first/Evolu.d.ts +330 -285
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +472 -402
- package/dist/src/local-first/LocalAuth.d.ts +10 -10
- package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
- package/dist/src/local-first/LocalAuth.js +6 -1
- package/dist/src/local-first/Owner.d.ts +70 -27
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +27 -14
- package/dist/src/local-first/Protocol.d.ts +58 -50
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +253 -155
- package/dist/src/local-first/Query.d.ts +133 -43
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Query.js +139 -90
- package/dist/src/local-first/Relay.d.ts +18 -38
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Relay.js +59 -171
- package/dist/src/local-first/Schema.d.ts +139 -144
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +78 -163
- package/dist/src/local-first/Shared.d.ts +125 -0
- package/dist/src/local-first/Shared.d.ts.map +1 -0
- package/dist/src/local-first/Shared.js +569 -0
- package/dist/src/local-first/Storage.d.ts +50 -53
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +121 -173
- package/dist/src/local-first/Timestamp.d.ts +28 -35
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +24 -25
- package/dist/src/local-first/index.d.ts +2 -15
- package/dist/src/local-first/index.d.ts.map +1 -1
- package/dist/src/local-first/index.js +2 -15
- package/package.json +36 -26
- package/src/Array.ts +467 -106
- package/src/Assert.ts +87 -12
- package/src/BigInt.ts +7 -1
- package/src/Brand.ts +41 -12
- package/src/Buffer.ts +10 -4
- package/src/Cache.ts +11 -2
- package/src/Callbacks.ts +33 -23
- package/src/Console.ts +567 -178
- package/src/Crypto.ts +111 -62
- package/src/Eq.ts +30 -3
- package/src/Error.ts +50 -34
- package/src/Function.ts +140 -68
- package/src/Identicon.ts +7 -1
- package/src/Lookup.ts +415 -0
- package/src/Microtask.ts +58 -0
- package/src/Number.ts +40 -7
- package/src/Object.ts +155 -16
- package/src/Option.ts +74 -0
- package/src/Order.ts +11 -3
- package/src/Platform.ts +43 -7
- package/src/Polyfills.ts +465 -0
- package/src/Random.ts +25 -11
- package/src/Redacted.ts +8 -4
- package/src/Ref.ts +72 -21
- package/src/RefCount.ts +170 -0
- package/src/Relation.ts +188 -136
- package/src/Resource.ts +864 -0
- package/src/Result.ts +548 -381
- package/src/Schedule.ts +1471 -0
- package/src/Set.ts +247 -0
- package/src/Skiplist.ts +7 -1
- package/src/Sqlite.ts +409 -304
- package/src/Store.ts +94 -48
- package/src/String.ts +6 -0
- package/src/Task.ts +3685 -740
- package/src/Test.ts +162 -0
- package/src/Time.ts +348 -145
- package/src/Tracer.ts +54 -0
- package/src/Type.ts +851 -430
- package/src/Types.ts +202 -7
- package/src/WebSocket.ts +311 -178
- package/src/Worker.ts +527 -147
- package/src/index.ts +74 -3
- package/src/local-first/Db.ts +871 -597
- package/src/local-first/Error.ts +17 -0
- package/src/local-first/Evolu.ts +884 -824
- package/src/local-first/LocalAuth.ts +10 -10
- package/src/local-first/Owner.ts +94 -42
- package/src/local-first/Protocol.ts +440 -303
- package/src/local-first/Query.ts +243 -194
- package/src/local-first/Relay.ts +91 -252
- package/src/local-first/Schema.ts +284 -394
- package/src/local-first/Shared.ts +932 -0
- package/src/local-first/Storage.ts +206 -277
- package/src/local-first/Timestamp.ts +38 -45
- package/src/local-first/index.ts +2 -16
- package/LICENSE +0 -21
- package/dist/src/Instances.d.ts +0 -34
- package/dist/src/Instances.d.ts.map +0 -1
- package/dist/src/Instances.js +0 -44
- package/dist/src/Resources.d.ts +0 -118
- package/dist/src/Resources.d.ts.map +0 -1
- package/dist/src/Resources.js +0 -197
- package/dist/src/local-first/Platform.d.ts +0 -25
- package/dist/src/local-first/Platform.d.ts.map +0 -1
- package/dist/src/local-first/Platform.js +0 -1
- package/dist/src/local-first/Public.d.ts +0 -22
- package/dist/src/local-first/Public.d.ts.map +0 -1
- package/dist/src/local-first/Public.js +0 -15
- package/dist/src/local-first/PublicKysely.d.ts +0 -148
- package/dist/src/local-first/PublicKysely.d.ts.map +0 -1
- package/dist/src/local-first/PublicKysely.js +0 -184
- package/dist/src/local-first/Sync.d.ts +0 -112
- package/dist/src/local-first/Sync.d.ts.map +0 -1
- package/dist/src/local-first/Sync.js +0 -526
- package/src/Instances.ts +0 -92
- package/src/Resources.ts +0 -367
- package/src/local-first/Platform.ts +0 -27
- package/src/local-first/Public.ts +0 -42
- package/src/local-first/PublicKysely.ts +0 -239
- package/src/local-first/Sync.ts +0 -949
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* transfer, ownership, real-time broadcasting, request-response semantics, and
|
|
21
21
|
* error handling.
|
|
22
22
|
*
|
|
23
|
-
*
|
|
23
|
+
* ## Message structure
|
|
24
24
|
*
|
|
25
25
|
* | Field | Notes |
|
|
26
26
|
* | :----------------------------- | :------------------------ |
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
* | - {@link NonNegativeInt} | Number of ranges. |
|
|
44
44
|
* | - {@link Range} | |
|
|
45
45
|
*
|
|
46
|
-
*
|
|
46
|
+
* ## WriteKey validation
|
|
47
47
|
*
|
|
48
48
|
* The initiator sends a hasWriteKey flag and optionally a WriteKey. The
|
|
49
49
|
* WriteKey is required when sending messages as a secure token proving the
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
* validates the WriteKey immediately after parsing the initiator header, before
|
|
53
53
|
* processing any messages or ranges.
|
|
54
54
|
*
|
|
55
|
-
*
|
|
55
|
+
* ## Synchronization
|
|
56
56
|
*
|
|
57
57
|
* - **Messages**: Sends {@link EncryptedCrdtMessage}s in either direction.
|
|
58
58
|
* - **Ranges**: Determines messages to sync. Usage varies by transport—e.g., sent
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
* initiator. In relay-to-relay or P2P sync, both sides may require the
|
|
80
80
|
* {@link OwnerWriteKey} depending on who is the initiator.
|
|
81
81
|
*
|
|
82
|
-
*
|
|
82
|
+
* ## Protocol errors
|
|
83
83
|
*
|
|
84
84
|
* The protocol uses error codes in the header to signal issues:
|
|
85
85
|
*
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
* All protocol errors except `ProtocolInvalidDataError` include the `OwnerId`
|
|
95
95
|
* to allow clients to associate errors with the correct owner.
|
|
96
96
|
*
|
|
97
|
-
*
|
|
97
|
+
* ## Message size limit
|
|
98
98
|
*
|
|
99
99
|
* The protocol enforces a strict maximum size for all messages, defined by
|
|
100
100
|
* {@link ProtocolMessageMaxSize}. This ensures every {@link ProtocolMessage} is
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
* message limit to ensure efficient sync with
|
|
108
108
|
* {@link defaultProtocolMessageRangesMaxSize}.
|
|
109
109
|
*
|
|
110
|
-
*
|
|
110
|
+
* ## Why Binary?
|
|
111
111
|
*
|
|
112
112
|
* The protocol avoids JSON because:
|
|
113
113
|
*
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
* To avoid reinventing serialization where it’s unnecessary—like for JSON and
|
|
128
128
|
* certain numbers—the Evolu Protocol relies on MessagePack.
|
|
129
129
|
*
|
|
130
|
-
*
|
|
130
|
+
* ## Versioning
|
|
131
131
|
*
|
|
132
132
|
* Evolu Protocol uses explicit versioning to ensure compatibility between
|
|
133
133
|
* clients and relays (or peers). Each protocol message begins with a version
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
* Version negotiation is per-owner, allowing Evolu Protocol to evolve safely
|
|
149
149
|
* over time and provide clear feedback about version mismatches.
|
|
150
150
|
*
|
|
151
|
-
*
|
|
151
|
+
* ## Credible exit
|
|
152
152
|
*
|
|
153
153
|
* The protocol specification is intentionally non-configurable to ensure
|
|
154
154
|
* universal compatibility. This design allows applications (users) to switch
|
|
@@ -178,11 +178,12 @@
|
|
|
178
178
|
*/
|
|
179
179
|
|
|
180
180
|
import { Packr } from "msgpackr";
|
|
181
|
-
import {
|
|
181
|
+
import { isNonEmptyArray, type NonEmptyReadonlyArray } from "../Array.js";
|
|
182
182
|
import { assert } from "../Assert.js";
|
|
183
|
-
import { Brand } from "../Brand.js";
|
|
183
|
+
import type { Brand } from "../Brand.js";
|
|
184
|
+
import type { ConsoleDep } from "../Console.js";
|
|
184
185
|
import {
|
|
185
|
-
Buffer,
|
|
186
|
+
type Buffer,
|
|
186
187
|
bytesToHex,
|
|
187
188
|
bytesToUtf8,
|
|
188
189
|
createBuffer,
|
|
@@ -191,16 +192,22 @@ import {
|
|
|
191
192
|
} from "../Buffer.js";
|
|
192
193
|
import {
|
|
193
194
|
createPadmePadding,
|
|
195
|
+
decryptWithXChaCha20Poly1305,
|
|
196
|
+
type DecryptWithXChaCha20Poly1305Error,
|
|
194
197
|
EncryptionKey,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
+
encryptWithXChaCha20Poly1305,
|
|
199
|
+
Entropy24,
|
|
200
|
+
type RandomBytesDep,
|
|
201
|
+
XChaCha20Poly1305Ciphertext,
|
|
202
|
+
xChaCha20Poly1305NonceLength,
|
|
198
203
|
} from "../Crypto.js";
|
|
199
204
|
import { eqArrayNumber } from "../Eq.js";
|
|
200
205
|
import { computeBalancedBuckets } from "../Number.js";
|
|
201
206
|
import { createRecord, objectToEntries } from "../Object.js";
|
|
202
|
-
import { err, ok, Result } from "../Result.js";
|
|
207
|
+
import { err, ok, type Result } from "../Result.js";
|
|
203
208
|
import { SqliteValue } from "../Sqlite.js";
|
|
209
|
+
import type { Task } from "../Task.js";
|
|
210
|
+
import { Millis } from "../Time.js";
|
|
204
211
|
import {
|
|
205
212
|
Base64Url,
|
|
206
213
|
base64UrlToUint8Array,
|
|
@@ -216,13 +223,16 @@ import {
|
|
|
216
223
|
jsonToJsonValue,
|
|
217
224
|
NonNegativeInt,
|
|
218
225
|
Number,
|
|
226
|
+
onePositiveInt,
|
|
219
227
|
PositiveInt,
|
|
228
|
+
type Typed,
|
|
220
229
|
uint8ArrayToBase64Url,
|
|
230
|
+
zeroNonNegativeInt,
|
|
221
231
|
} from "../Type.js";
|
|
222
|
-
import { Predicate } from "../Types.js";
|
|
232
|
+
import type { Predicate } from "../Types.js";
|
|
223
233
|
import {
|
|
224
|
-
Owner,
|
|
225
|
-
OwnerError,
|
|
234
|
+
type Owner,
|
|
235
|
+
type OwnerError,
|
|
226
236
|
OwnerId,
|
|
227
237
|
OwnerIdBytes,
|
|
228
238
|
ownerIdToOwnerIdBytes,
|
|
@@ -230,26 +240,25 @@ import {
|
|
|
230
240
|
ownerWriteKeyLength,
|
|
231
241
|
} from "./Owner.js";
|
|
232
242
|
import {
|
|
233
|
-
BaseRange,
|
|
234
|
-
CrdtMessage,
|
|
243
|
+
type BaseRange,
|
|
244
|
+
type CrdtMessage,
|
|
235
245
|
DbChange,
|
|
236
|
-
EncryptedCrdtMessage,
|
|
237
|
-
EncryptedDbChange,
|
|
238
|
-
Fingerprint,
|
|
239
|
-
FingerprintRange,
|
|
246
|
+
type EncryptedCrdtMessage,
|
|
247
|
+
type EncryptedDbChange,
|
|
248
|
+
type Fingerprint,
|
|
249
|
+
type FingerprintRange,
|
|
240
250
|
fingerprintSize,
|
|
241
251
|
InfiniteUpperBound,
|
|
242
|
-
Range,
|
|
252
|
+
type Range,
|
|
243
253
|
RangeType,
|
|
244
|
-
RangeUpperBound,
|
|
245
|
-
SkipRange,
|
|
246
|
-
StorageDep,
|
|
247
|
-
TimestampsRange,
|
|
254
|
+
type RangeUpperBound,
|
|
255
|
+
type SkipRange,
|
|
256
|
+
type StorageDep,
|
|
257
|
+
type TimestampsRange,
|
|
248
258
|
} from "./Storage.js";
|
|
249
259
|
import {
|
|
250
260
|
Counter,
|
|
251
261
|
eqTimestamp,
|
|
252
|
-
Millis,
|
|
253
262
|
NodeId,
|
|
254
263
|
Timestamp,
|
|
255
264
|
TimestampBytes,
|
|
@@ -285,7 +294,7 @@ const maxProtocolMessageMaxSize = 100_000_000;
|
|
|
285
294
|
* Increasing this value on the client side would break compatibility with
|
|
286
295
|
* relays that enforce smaller limits.
|
|
287
296
|
*/
|
|
288
|
-
export const ProtocolMessageMaxSize = between(
|
|
297
|
+
export const ProtocolMessageMaxSize = /*#__PURE__*/ between(
|
|
289
298
|
minProtocolMessageMaxSize,
|
|
290
299
|
maxProtocolMessageMaxSize,
|
|
291
300
|
)(Int);
|
|
@@ -312,7 +321,10 @@ export const defaultProtocolMessageMaxSize =
|
|
|
312
321
|
* {@link defaultProtocolMessageMaxSize}, maintaining compatibility between all
|
|
313
322
|
* clients and relays.
|
|
314
323
|
*/
|
|
315
|
-
export const ProtocolMessageRangesMaxSize = between(
|
|
324
|
+
export const ProtocolMessageRangesMaxSize = /*#__PURE__*/ between(
|
|
325
|
+
3_000,
|
|
326
|
+
100_000,
|
|
327
|
+
)(Int);
|
|
316
328
|
export type ProtocolMessageRangesMaxSize =
|
|
317
329
|
typeof ProtocolMessageRangesMaxSize.Type;
|
|
318
330
|
|
|
@@ -329,7 +341,7 @@ export const defaultProtocolMessageRangesMaxSize =
|
|
|
329
341
|
export type ProtocolMessage = Uint8Array & Brand<"ProtocolMessage">;
|
|
330
342
|
|
|
331
343
|
/** Evolu Protocol version. */
|
|
332
|
-
export const protocolVersion =
|
|
344
|
+
export const protocolVersion = onePositiveInt;
|
|
333
345
|
|
|
334
346
|
export const MessageType = {
|
|
335
347
|
/** Request message from initiator (client) to non-initiator (relay). */
|
|
@@ -342,6 +354,33 @@ export const MessageType = {
|
|
|
342
354
|
|
|
343
355
|
export type MessageType = (typeof MessageType)[keyof typeof MessageType];
|
|
344
356
|
|
|
357
|
+
/** Parsed protocol header for supported protocol messages. */
|
|
358
|
+
export interface ProtocolHeader extends Typed<"ProtocolHeader"> {
|
|
359
|
+
readonly version: 1;
|
|
360
|
+
readonly ownerId: OwnerId;
|
|
361
|
+
readonly messageType: MessageType;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Parses the protocol header needed for routing.
|
|
366
|
+
*
|
|
367
|
+
* Every protocol message begins with `protocolVersion`, `ownerId`, and
|
|
368
|
+
* `messageType`.
|
|
369
|
+
*/
|
|
370
|
+
export const parseProtocolHeader = (
|
|
371
|
+
inputMessage: Uint8Array,
|
|
372
|
+
): Result<ProtocolHeader, ProtocolInvalidDataError> => {
|
|
373
|
+
try {
|
|
374
|
+
return ok(parseProtocolHeaderFromBuffer(createBuffer(inputMessage)));
|
|
375
|
+
} catch (error) {
|
|
376
|
+
return err<ProtocolInvalidDataError>({
|
|
377
|
+
type: "ProtocolInvalidDataError",
|
|
378
|
+
data: inputMessage,
|
|
379
|
+
error,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
|
|
345
384
|
export const SubscriptionFlags = {
|
|
346
385
|
/** No subscription changes for this owner. */
|
|
347
386
|
None: 0,
|
|
@@ -382,32 +421,29 @@ export type ProtocolError =
|
|
|
382
421
|
* Represents a version mismatch in the Evolu Protocol. Occurs when the
|
|
383
422
|
* initiator and non-initiator are using incompatible protocol versions.
|
|
384
423
|
*/
|
|
385
|
-
export interface ProtocolVersionError
|
|
386
|
-
|
|
424
|
+
export interface ProtocolVersionError
|
|
425
|
+
extends OwnerError, Typed<"ProtocolVersionError"> {
|
|
387
426
|
readonly version: NonNegativeInt;
|
|
388
427
|
/** Indicates which side is obsolete and should update. */
|
|
389
428
|
readonly isInitiator: boolean;
|
|
390
429
|
}
|
|
391
430
|
|
|
392
431
|
/** Error for invalid or corrupted protocol message data. */
|
|
393
|
-
export interface ProtocolInvalidDataError {
|
|
394
|
-
readonly type: "ProtocolInvalidDataError";
|
|
432
|
+
export interface ProtocolInvalidDataError extends Typed<"ProtocolInvalidDataError"> {
|
|
395
433
|
readonly data: globalThis.Uint8Array;
|
|
396
434
|
readonly error: unknown;
|
|
397
435
|
}
|
|
398
436
|
|
|
399
437
|
/** Error when a {@link OwnerWriteKey} is invalid, missing, or fails validation. */
|
|
400
|
-
export interface ProtocolWriteKeyError
|
|
401
|
-
|
|
402
|
-
}
|
|
438
|
+
export interface ProtocolWriteKeyError
|
|
439
|
+
extends OwnerError, Typed<"ProtocolWriteKeyError"> {}
|
|
403
440
|
|
|
404
441
|
/**
|
|
405
442
|
* Error indicating a serious relay-side write failure. Clients should log this
|
|
406
443
|
* error and show a generic sync error to the user.
|
|
407
444
|
*/
|
|
408
|
-
export interface ProtocolWriteError
|
|
409
|
-
|
|
410
|
-
}
|
|
445
|
+
export interface ProtocolWriteError
|
|
446
|
+
extends OwnerError, Typed<"ProtocolWriteError"> {}
|
|
411
447
|
|
|
412
448
|
/**
|
|
413
449
|
* Error when storage or billing quota is exceeded.
|
|
@@ -422,25 +458,22 @@ export interface ProtocolWriteError extends OwnerError {
|
|
|
422
458
|
* plan. Quota monitoring and management is the relay provider's
|
|
423
459
|
* responsibility.
|
|
424
460
|
*/
|
|
425
|
-
export interface ProtocolQuotaError
|
|
426
|
-
|
|
427
|
-
}
|
|
461
|
+
export interface ProtocolQuotaError
|
|
462
|
+
extends OwnerError, Typed<"ProtocolQuotaError"> {}
|
|
428
463
|
|
|
429
464
|
/**
|
|
430
465
|
* Error indicating a serious relay-side synchronization failure. Clients should
|
|
431
466
|
* log this error and show a generic sync error to the user.
|
|
432
467
|
*/
|
|
433
|
-
export interface ProtocolSyncError
|
|
434
|
-
|
|
435
|
-
}
|
|
468
|
+
export interface ProtocolSyncError
|
|
469
|
+
extends OwnerError, Typed<"ProtocolSyncError"> {}
|
|
436
470
|
|
|
437
471
|
/**
|
|
438
472
|
* Error when embedded timestamp doesn't match expected timestamp in
|
|
439
473
|
* EncryptedDbChange. Indicates potential tampering or corruption of CRDT
|
|
440
474
|
* messages.
|
|
441
475
|
*/
|
|
442
|
-
export interface ProtocolTimestampMismatchError {
|
|
443
|
-
readonly type: "ProtocolTimestampMismatchError";
|
|
476
|
+
export interface ProtocolTimestampMismatchError extends Typed<"ProtocolTimestampMismatchError"> {
|
|
444
477
|
readonly expected: Timestamp;
|
|
445
478
|
readonly timestamp: Timestamp;
|
|
446
479
|
}
|
|
@@ -453,7 +486,7 @@ export interface ProtocolTimestampMismatchError {
|
|
|
453
486
|
* unidirectional and stateless transports.
|
|
454
487
|
*/
|
|
455
488
|
export const createProtocolMessageFromCrdtMessages =
|
|
456
|
-
(deps: RandomBytesDep
|
|
489
|
+
(deps: RandomBytesDep) =>
|
|
457
490
|
(
|
|
458
491
|
owner: Owner,
|
|
459
492
|
messages: NonEmptyReadonlyArray<CrdtMessage>,
|
|
@@ -514,7 +547,7 @@ export const createProtocolMessageFromCrdtMessages =
|
|
|
514
547
|
|
|
515
548
|
/** Creates a {@link ProtocolMessage} for sync. */
|
|
516
549
|
export const createProtocolMessageForSync =
|
|
517
|
-
(deps: StorageDep) =>
|
|
550
|
+
(deps: StorageDep & ConsoleDep) =>
|
|
518
551
|
(
|
|
519
552
|
ownerId: OwnerId,
|
|
520
553
|
subscriptionFlag?: SubscriptionFlag,
|
|
@@ -526,12 +559,10 @@ export const createProtocolMessageForSync =
|
|
|
526
559
|
const ownerIdBytes = ownerIdToOwnerIdBytes(ownerId);
|
|
527
560
|
|
|
528
561
|
const size = deps.storage.getSize(ownerIdBytes);
|
|
529
|
-
// Errors are handled by the storage.
|
|
530
|
-
if (size == null) return null;
|
|
531
562
|
|
|
532
563
|
splitRange(deps)(
|
|
533
564
|
ownerIdBytes,
|
|
534
|
-
|
|
565
|
+
zeroNonNegativeInt,
|
|
535
566
|
size,
|
|
536
567
|
InfiniteUpperBound,
|
|
537
568
|
buffer,
|
|
@@ -687,9 +718,8 @@ export const createProtocolMessageBuffer = (
|
|
|
687
718
|
assert(isWithinSizeLimits(), "the message is too big");
|
|
688
719
|
},
|
|
689
720
|
|
|
690
|
-
canSplitRange: () =>
|
|
691
|
-
|
|
692
|
-
},
|
|
721
|
+
canSplitRange: () =>
|
|
722
|
+
getRangesSize() + safeMargins.splitRange <= rangesMaxSize,
|
|
693
723
|
|
|
694
724
|
canAddTimestampsRangeAndMessage: (timestamps, message) => {
|
|
695
725
|
const rangesNewSize =
|
|
@@ -790,7 +820,7 @@ export interface TimestampsBuffer {
|
|
|
790
820
|
}
|
|
791
821
|
|
|
792
822
|
export const createTimestampsBuffer = (): TimestampsBuffer => {
|
|
793
|
-
let count =
|
|
823
|
+
let count = zeroNonNegativeInt;
|
|
794
824
|
const countBuffer = createBuffer();
|
|
795
825
|
|
|
796
826
|
const syncCount = () => {
|
|
@@ -857,9 +887,9 @@ const createRunLengthEncoder = <T>(
|
|
|
857
887
|
encodeValue: (buffer: Buffer, value: T) => void,
|
|
858
888
|
): RunLengthEncoder<T> => {
|
|
859
889
|
const buffer = createBuffer();
|
|
860
|
-
let previousLength =
|
|
890
|
+
let previousLength = zeroNonNegativeInt;
|
|
861
891
|
let previousValue = null as T | null;
|
|
862
|
-
let runLength =
|
|
892
|
+
let runLength = zeroNonNegativeInt;
|
|
863
893
|
|
|
864
894
|
return {
|
|
865
895
|
add: (value) => {
|
|
@@ -882,7 +912,7 @@ const createRunLengthEncoder = <T>(
|
|
|
882
912
|
};
|
|
883
913
|
|
|
884
914
|
export interface ApplyProtocolMessageAsClientOptions {
|
|
885
|
-
|
|
915
|
+
writeKey?: OwnerWriteKey;
|
|
886
916
|
|
|
887
917
|
rangesMaxSize?: ProtocolMessageRangesMaxSize;
|
|
888
918
|
|
|
@@ -894,27 +924,35 @@ export interface ApplyProtocolMessageAsClientOptions {
|
|
|
894
924
|
* Result type for {@link applyProtocolMessageAsClient} that distinguishes
|
|
895
925
|
* between responses to client requests and broadcast messages.
|
|
896
926
|
*/
|
|
927
|
+
export interface ApplyProtocolMessageAsClientResponse extends Typed<"Response"> {
|
|
928
|
+
readonly message: ProtocolMessage;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
export interface ApplyProtocolMessageAsClientNoResponse extends Typed<"NoResponse"> {}
|
|
932
|
+
|
|
933
|
+
export interface ApplyProtocolMessageAsClientBroadcast extends Typed<"Broadcast"> {}
|
|
934
|
+
|
|
897
935
|
export type ApplyProtocolMessageAsClientResult =
|
|
898
|
-
|
|
|
899
|
-
|
|
|
900
|
-
|
|
|
936
|
+
| ApplyProtocolMessageAsClientResponse
|
|
937
|
+
| ApplyProtocolMessageAsClientNoResponse
|
|
938
|
+
| ApplyProtocolMessageAsClientBroadcast;
|
|
901
939
|
|
|
902
940
|
export const applyProtocolMessageAsClient =
|
|
903
|
-
(
|
|
904
|
-
async (
|
|
941
|
+
(
|
|
905
942
|
inputMessage: Uint8Array,
|
|
906
943
|
options: ApplyProtocolMessageAsClientOptions = {},
|
|
907
|
-
):
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
944
|
+
): Task<
|
|
945
|
+
ApplyProtocolMessageAsClientResult,
|
|
946
|
+
| ProtocolInvalidDataError
|
|
947
|
+
| ProtocolSyncError
|
|
948
|
+
| ProtocolVersionError
|
|
949
|
+
| ProtocolWriteError
|
|
950
|
+
| ProtocolWriteKeyError
|
|
951
|
+
| ProtocolQuotaError,
|
|
952
|
+
StorageDep
|
|
953
|
+
> =>
|
|
954
|
+
async (run) => {
|
|
955
|
+
const { storage } = run.deps;
|
|
918
956
|
try {
|
|
919
957
|
const input = createBuffer(inputMessage);
|
|
920
958
|
const [requestedVersion, ownerId] = decodeVersionAndOwner(input);
|
|
@@ -971,10 +1009,17 @@ export const applyProtocolMessageAsClient =
|
|
|
971
1009
|
const messages = decodeMessages(input);
|
|
972
1010
|
const ownerIdBytes = ownerIdToOwnerIdBytes(ownerId);
|
|
973
1011
|
|
|
974
|
-
if (
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1012
|
+
if (isNonEmptyArray(messages)) {
|
|
1013
|
+
try {
|
|
1014
|
+
const result = await run(
|
|
1015
|
+
storage.writeMessages(ownerIdBytes, messages),
|
|
1016
|
+
);
|
|
1017
|
+
// Quota errors are handled by Storage; protocol just stops syncing.
|
|
1018
|
+
if (!result.ok) return ok({ type: "NoResponse" });
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
run.deps.console.error(error);
|
|
1021
|
+
return ok({ type: "NoResponse" });
|
|
1022
|
+
}
|
|
978
1023
|
}
|
|
979
1024
|
|
|
980
1025
|
// Now: No writeKey, no sync.
|
|
@@ -982,19 +1027,19 @@ export const applyProtocolMessageAsClient =
|
|
|
982
1027
|
// Without local changes, writeKey will not be required.
|
|
983
1028
|
// With local changes, writeKey will be required and if not provided,
|
|
984
1029
|
// the sync will stop.
|
|
985
|
-
const writeKey = options.
|
|
1030
|
+
const writeKey = options.writeKey;
|
|
986
1031
|
if (writeKey == null) {
|
|
987
|
-
return ok({ type: "
|
|
1032
|
+
return ok({ type: "NoResponse" });
|
|
988
1033
|
}
|
|
989
1034
|
|
|
990
1035
|
if (messageType === MessageType.Broadcast) {
|
|
991
|
-
return ok({ type: "
|
|
1036
|
+
return ok({ type: "Broadcast" });
|
|
992
1037
|
}
|
|
993
1038
|
|
|
994
1039
|
const ranges = decodeRanges(input);
|
|
995
1040
|
|
|
996
|
-
if (!
|
|
997
|
-
return ok({ type: "
|
|
1041
|
+
if (!isNonEmptyArray(ranges)) {
|
|
1042
|
+
return ok({ type: "NoResponse" });
|
|
998
1043
|
}
|
|
999
1044
|
|
|
1000
1045
|
const output = createProtocolMessageBuffer(ownerId, {
|
|
@@ -1003,14 +1048,14 @@ export const applyProtocolMessageAsClient =
|
|
|
1003
1048
|
rangesMaxSize: options.rangesMaxSize,
|
|
1004
1049
|
});
|
|
1005
1050
|
|
|
1006
|
-
const result = sync(deps)(ranges, output, ownerIdBytes);
|
|
1051
|
+
const result = sync(run.deps)(ranges, output, ownerIdBytes);
|
|
1007
1052
|
|
|
1008
1053
|
// Client sync error (handled via Storage) or no changes.
|
|
1009
1054
|
if (!result.ok || !result.value) {
|
|
1010
|
-
return ok({ type: "
|
|
1055
|
+
return ok({ type: "NoResponse" });
|
|
1011
1056
|
}
|
|
1012
1057
|
|
|
1013
|
-
return ok({ type: "
|
|
1058
|
+
return ok({ type: "Response", message: output.unwrap() });
|
|
1014
1059
|
} catch (error) {
|
|
1015
1060
|
return err<ProtocolInvalidDataError>({
|
|
1016
1061
|
type: "ProtocolInvalidDataError",
|
|
@@ -1043,21 +1088,23 @@ export interface ApplyProtocolMessageAsRelayOptions {
|
|
|
1043
1088
|
* to sync. Clients may choose not to respond in certain cases (like when they
|
|
1044
1089
|
* receive broadcast messages or when they lack a write key for syncing).
|
|
1045
1090
|
*/
|
|
1046
|
-
export interface ApplyProtocolMessageAsRelayResult {
|
|
1047
|
-
readonly type: "response";
|
|
1091
|
+
export interface ApplyProtocolMessageAsRelayResult extends Typed<"Response"> {
|
|
1048
1092
|
readonly message: ProtocolMessage;
|
|
1049
1093
|
}
|
|
1050
1094
|
|
|
1051
1095
|
export const applyProtocolMessageAsRelay =
|
|
1052
|
-
(
|
|
1053
|
-
async (
|
|
1096
|
+
(
|
|
1054
1097
|
inputMessage: Uint8Array,
|
|
1055
1098
|
options: ApplyProtocolMessageAsRelayOptions = {},
|
|
1056
1099
|
/** For tests only. */
|
|
1057
|
-
version = protocolVersion,
|
|
1058
|
-
):
|
|
1059
|
-
|
|
1060
|
-
|
|
1100
|
+
version: NonNegativeInt = protocolVersion,
|
|
1101
|
+
): Task<
|
|
1102
|
+
ApplyProtocolMessageAsRelayResult,
|
|
1103
|
+
ProtocolInvalidDataError,
|
|
1104
|
+
StorageDep
|
|
1105
|
+
> =>
|
|
1106
|
+
async (run) => {
|
|
1107
|
+
const { storage } = run.deps;
|
|
1061
1108
|
try {
|
|
1062
1109
|
const input = createBuffer(inputMessage);
|
|
1063
1110
|
const [requestedVersion, ownerId] = decodeVersionAndOwner(input);
|
|
@@ -1069,7 +1116,7 @@ export const applyProtocolMessageAsRelay =
|
|
|
1069
1116
|
encodeNonNegativeInt(output, version);
|
|
1070
1117
|
output.extend(ownerIdBytes);
|
|
1071
1118
|
return ok({
|
|
1072
|
-
type: "
|
|
1119
|
+
type: "Response",
|
|
1073
1120
|
message: output.unwrap() as ProtocolMessage,
|
|
1074
1121
|
});
|
|
1075
1122
|
}
|
|
@@ -1098,10 +1145,10 @@ export const applyProtocolMessageAsRelay =
|
|
|
1098
1145
|
}
|
|
1099
1146
|
|
|
1100
1147
|
if (writeKey) {
|
|
1101
|
-
const isValid =
|
|
1148
|
+
const isValid = storage.validateWriteKey(ownerIdBytes, writeKey);
|
|
1102
1149
|
if (!isValid) {
|
|
1103
1150
|
return ok({
|
|
1104
|
-
type: "
|
|
1151
|
+
type: "Response",
|
|
1105
1152
|
message: createProtocolMessageBuffer(ownerId, {
|
|
1106
1153
|
messageType: MessageType.Response,
|
|
1107
1154
|
errorCode: ProtocolErrorCode.WriteKeyError,
|
|
@@ -1112,10 +1159,10 @@ export const applyProtocolMessageAsRelay =
|
|
|
1112
1159
|
|
|
1113
1160
|
const messages = decodeMessages(input);
|
|
1114
1161
|
|
|
1115
|
-
if (
|
|
1162
|
+
if (isNonEmptyArray(messages)) {
|
|
1116
1163
|
if (!writeKey) {
|
|
1117
1164
|
return ok({
|
|
1118
|
-
type: "
|
|
1165
|
+
type: "Response",
|
|
1119
1166
|
message: createProtocolMessageBuffer(ownerId, {
|
|
1120
1167
|
messageType: MessageType.Response,
|
|
1121
1168
|
errorCode: ProtocolErrorCode.WriteKeyError,
|
|
@@ -1123,18 +1170,25 @@ export const applyProtocolMessageAsRelay =
|
|
|
1123
1170
|
});
|
|
1124
1171
|
}
|
|
1125
1172
|
|
|
1126
|
-
|
|
1173
|
+
try {
|
|
1174
|
+
const result = await run(
|
|
1175
|
+
storage.writeMessages(ownerIdBytes, messages),
|
|
1176
|
+
);
|
|
1127
1177
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1178
|
+
if (!result.ok) {
|
|
1179
|
+
const message = createProtocolMessageBuffer(ownerId, {
|
|
1180
|
+
messageType: MessageType.Response,
|
|
1181
|
+
errorCode: ProtocolErrorCode.QuotaError,
|
|
1182
|
+
}).unwrap();
|
|
1183
|
+
return ok({ type: "Response", message });
|
|
1184
|
+
}
|
|
1185
|
+
} catch (error) {
|
|
1186
|
+
run.deps.console.error(error);
|
|
1133
1187
|
const message = createProtocolMessageBuffer(ownerId, {
|
|
1134
1188
|
messageType: MessageType.Response,
|
|
1135
|
-
errorCode,
|
|
1189
|
+
errorCode: ProtocolErrorCode.WriteError,
|
|
1136
1190
|
}).unwrap();
|
|
1137
|
-
return ok({ type: "
|
|
1191
|
+
return ok({ type: "Response", message });
|
|
1138
1192
|
}
|
|
1139
1193
|
|
|
1140
1194
|
/**
|
|
@@ -1179,11 +1233,11 @@ export const applyProtocolMessageAsRelay =
|
|
|
1179
1233
|
|
|
1180
1234
|
// Non-initiators always respond to provide sync completion feedback,
|
|
1181
1235
|
// even when there's nothing to sync.
|
|
1182
|
-
if (!
|
|
1183
|
-
return ok({ type: "
|
|
1236
|
+
if (!isNonEmptyArray(ranges)) {
|
|
1237
|
+
return ok({ type: "Response", message: output.unwrap() });
|
|
1184
1238
|
}
|
|
1185
1239
|
|
|
1186
|
-
const result = sync(deps)(ranges, output, ownerIdBytes);
|
|
1240
|
+
const result = sync(run.deps)(ranges, output, ownerIdBytes);
|
|
1187
1241
|
|
|
1188
1242
|
const message = result.ok
|
|
1189
1243
|
? output.unwrap()
|
|
@@ -1193,7 +1247,7 @@ export const applyProtocolMessageAsRelay =
|
|
|
1193
1247
|
}).unwrap();
|
|
1194
1248
|
|
|
1195
1249
|
// Non-initiators always respond to provide sync completion feedback,
|
|
1196
|
-
return ok({ type: "
|
|
1250
|
+
return ok({ type: "Response", message });
|
|
1197
1251
|
} catch (error) {
|
|
1198
1252
|
return err<ProtocolInvalidDataError>({
|
|
1199
1253
|
type: "ProtocolInvalidDataError",
|
|
@@ -1213,6 +1267,38 @@ const decodeVersionAndOwner = (input: Buffer): [NonNegativeInt, OwnerId] => {
|
|
|
1213
1267
|
return [version, ownerId];
|
|
1214
1268
|
};
|
|
1215
1269
|
|
|
1270
|
+
const parseProtocolHeaderFromBuffer = (input: Buffer): ProtocolHeader => {
|
|
1271
|
+
const [version, ownerId] = decodeVersionAndOwner(input);
|
|
1272
|
+
|
|
1273
|
+
if (version !== protocolVersion) {
|
|
1274
|
+
throw new ProtocolDecodeError(`Unsupported protocol version: ${version}`);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
const messageTypeValue = input.shift();
|
|
1278
|
+
let messageType: MessageType;
|
|
1279
|
+
|
|
1280
|
+
switch (messageTypeValue) {
|
|
1281
|
+
case MessageType.Request:
|
|
1282
|
+
messageType = MessageType.Request;
|
|
1283
|
+
break;
|
|
1284
|
+
case MessageType.Response:
|
|
1285
|
+
messageType = MessageType.Response;
|
|
1286
|
+
break;
|
|
1287
|
+
case MessageType.Broadcast:
|
|
1288
|
+
messageType = MessageType.Broadcast;
|
|
1289
|
+
break;
|
|
1290
|
+
default:
|
|
1291
|
+
throw new ProtocolDecodeError("Invalid MessageType");
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
return {
|
|
1295
|
+
type: "ProtocolHeader",
|
|
1296
|
+
version: 1,
|
|
1297
|
+
ownerId,
|
|
1298
|
+
messageType,
|
|
1299
|
+
};
|
|
1300
|
+
};
|
|
1301
|
+
|
|
1216
1302
|
/**
|
|
1217
1303
|
* Error thrown for internal protocol validation failures, such as invalid data
|
|
1218
1304
|
* or type errors.
|
|
@@ -1231,30 +1317,35 @@ const decodeMessages = (
|
|
|
1231
1317
|
): ReadonlyArray<EncryptedCrdtMessage> => {
|
|
1232
1318
|
const timestamps = decodeTimestamps(buffer);
|
|
1233
1319
|
|
|
1234
|
-
const messages
|
|
1235
|
-
for (
|
|
1320
|
+
const messages = new Array<EncryptedCrdtMessage>(timestamps.length);
|
|
1321
|
+
for (let i = 0; i < timestamps.length; i++) {
|
|
1322
|
+
const timestamp = timestamps[i];
|
|
1236
1323
|
const changeLength = decodeLength(buffer);
|
|
1237
1324
|
const change = buffer.shiftN(changeLength) as EncryptedDbChange;
|
|
1238
|
-
messages
|
|
1325
|
+
messages[i] = { timestamp, change };
|
|
1239
1326
|
}
|
|
1240
1327
|
|
|
1241
1328
|
return messages;
|
|
1242
1329
|
};
|
|
1243
1330
|
|
|
1244
1331
|
const sync =
|
|
1245
|
-
(deps: StorageDep) =>
|
|
1332
|
+
(deps: StorageDep & ConsoleDep) =>
|
|
1246
1333
|
(
|
|
1247
1334
|
ranges: NonEmptyReadonlyArray<Range>,
|
|
1248
1335
|
output: ProtocolMessageBuffer,
|
|
1249
1336
|
ownerIdBytes: OwnerIdBytes,
|
|
1250
1337
|
): Result<boolean, typeof ProtocolErrorCode.SyncError> => {
|
|
1251
1338
|
const outputInitialSize = output.getSize();
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1339
|
+
let storageSize: NonNegativeInt;
|
|
1340
|
+
try {
|
|
1341
|
+
storageSize = deps.storage.getSize(ownerIdBytes);
|
|
1342
|
+
} catch (error) {
|
|
1343
|
+
deps.console.error(error);
|
|
1344
|
+
return err(ProtocolErrorCode.SyncError);
|
|
1345
|
+
}
|
|
1255
1346
|
|
|
1256
1347
|
let prevUpperBound: RangeUpperBound | null = null;
|
|
1257
|
-
let prevIndex =
|
|
1348
|
+
let prevIndex = zeroNonNegativeInt;
|
|
1258
1349
|
|
|
1259
1350
|
let skip = false;
|
|
1260
1351
|
let nonSkipRangeAdded = false;
|
|
@@ -1291,12 +1382,17 @@ const sync =
|
|
|
1291
1382
|
const addFingerprintForRemainingRange = (
|
|
1292
1383
|
begin: NonNegativeInt,
|
|
1293
1384
|
): boolean => {
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1385
|
+
let fingerprint: Fingerprint;
|
|
1386
|
+
try {
|
|
1387
|
+
fingerprint = deps.storage.fingerprint(
|
|
1388
|
+
ownerIdBytes,
|
|
1389
|
+
begin,
|
|
1390
|
+
storageSize,
|
|
1391
|
+
);
|
|
1392
|
+
} catch (error) {
|
|
1393
|
+
deps.console.error(error);
|
|
1394
|
+
return false;
|
|
1395
|
+
}
|
|
1300
1396
|
// There is always a space for a ramaining range.
|
|
1301
1397
|
output.addRange({
|
|
1302
1398
|
type: RangeType.Fingerprint,
|
|
@@ -1310,13 +1406,18 @@ const sync =
|
|
|
1310
1406
|
const currentUpperBound = range.upperBound;
|
|
1311
1407
|
|
|
1312
1408
|
const lower = prevIndex;
|
|
1313
|
-
let upper
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1409
|
+
let upper: NonNegativeInt;
|
|
1410
|
+
try {
|
|
1411
|
+
upper = deps.storage.findLowerBound(
|
|
1412
|
+
ownerIdBytes,
|
|
1413
|
+
prevIndex,
|
|
1414
|
+
storageSize,
|
|
1415
|
+
currentUpperBound,
|
|
1416
|
+
);
|
|
1417
|
+
} catch (error) {
|
|
1418
|
+
deps.console.error(error);
|
|
1419
|
+
return err(ProtocolErrorCode.SyncError);
|
|
1420
|
+
}
|
|
1320
1421
|
|
|
1321
1422
|
switch (range.type) {
|
|
1322
1423
|
case RangeType.Skip: {
|
|
@@ -1325,12 +1426,17 @@ const sync =
|
|
|
1325
1426
|
}
|
|
1326
1427
|
|
|
1327
1428
|
case RangeType.Fingerprint: {
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1429
|
+
let ourFingerprint: Fingerprint;
|
|
1430
|
+
try {
|
|
1431
|
+
ourFingerprint = deps.storage.fingerprint(
|
|
1432
|
+
ownerIdBytes,
|
|
1433
|
+
lower,
|
|
1434
|
+
upper,
|
|
1435
|
+
);
|
|
1436
|
+
} catch (error) {
|
|
1437
|
+
deps.console.error(error);
|
|
1438
|
+
return err(ProtocolErrorCode.SyncError);
|
|
1439
|
+
}
|
|
1334
1440
|
|
|
1335
1441
|
if (eqArrayNumber(range.fingerprint, ourFingerprint)) {
|
|
1336
1442
|
skipRange(range);
|
|
@@ -1361,52 +1467,61 @@ const sync =
|
|
|
1361
1467
|
);
|
|
1362
1468
|
const ourTimestamps = createTimestampsBuffer();
|
|
1363
1469
|
|
|
1364
|
-
let cantReadDbChange = false as boolean;
|
|
1365
1470
|
let exceeded = false as boolean;
|
|
1471
|
+
let iterateFailed = false as boolean;
|
|
1472
|
+
|
|
1473
|
+
try {
|
|
1474
|
+
deps.storage.iterate(
|
|
1475
|
+
ownerIdBytes,
|
|
1476
|
+
lower,
|
|
1477
|
+
upper,
|
|
1478
|
+
(timestamp, index) => {
|
|
1479
|
+
const timestampString = timestamp.join();
|
|
1480
|
+
const timestampBinary = timestampBytesToTimestamp(timestamp);
|
|
1481
|
+
|
|
1482
|
+
let message: EncryptedCrdtMessage | null = null;
|
|
1483
|
+
|
|
1484
|
+
if (timestampsWeNeed.has(timestampString)) {
|
|
1485
|
+
timestampsWeNeed.delete(timestampString);
|
|
1486
|
+
} else {
|
|
1487
|
+
try {
|
|
1488
|
+
message = {
|
|
1489
|
+
timestamp: timestampBinary,
|
|
1490
|
+
change: deps.storage.readDbChange(
|
|
1491
|
+
ownerIdBytes,
|
|
1492
|
+
timestamp,
|
|
1493
|
+
),
|
|
1494
|
+
};
|
|
1495
|
+
} catch (error) {
|
|
1496
|
+
deps.console.error(error);
|
|
1497
|
+
iterateFailed = true;
|
|
1498
|
+
return false;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1366
1501
|
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
if (timestampsWeNeed.has(timestampString)) {
|
|
1378
|
-
timestampsWeNeed.delete(timestampString);
|
|
1379
|
-
} else {
|
|
1380
|
-
const dbChange = deps.storage.readDbChange(
|
|
1381
|
-
ownerIdBytes,
|
|
1382
|
-
timestamp,
|
|
1383
|
-
);
|
|
1384
|
-
if (dbChange == null) {
|
|
1385
|
-
cantReadDbChange = true;
|
|
1502
|
+
if (
|
|
1503
|
+
!output.canAddTimestampsRangeAndMessage(
|
|
1504
|
+
ourTimestamps,
|
|
1505
|
+
message,
|
|
1506
|
+
)
|
|
1507
|
+
) {
|
|
1508
|
+
exceeded = true;
|
|
1509
|
+
endBound = timestamp;
|
|
1510
|
+
upper = index;
|
|
1386
1511
|
return false;
|
|
1387
1512
|
}
|
|
1388
|
-
message = {
|
|
1389
|
-
timestamp: timestampBinary,
|
|
1390
|
-
change: dbChange,
|
|
1391
|
-
};
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
|
-
if (
|
|
1395
|
-
!output.canAddTimestampsRangeAndMessage(ourTimestamps, message)
|
|
1396
|
-
) {
|
|
1397
|
-
exceeded = true;
|
|
1398
|
-
endBound = timestamp;
|
|
1399
|
-
upper = index;
|
|
1400
|
-
return false;
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
ourTimestamps.add(timestampBinary);
|
|
1404
|
-
if (message) output.addMessage(message);
|
|
1405
|
-
return true;
|
|
1406
|
-
},
|
|
1407
|
-
);
|
|
1408
1513
|
|
|
1409
|
-
|
|
1514
|
+
ourTimestamps.add(timestampBinary);
|
|
1515
|
+
if (message) output.addMessage(message);
|
|
1516
|
+
return true;
|
|
1517
|
+
},
|
|
1518
|
+
);
|
|
1519
|
+
} catch (error) {
|
|
1520
|
+
deps.console.error(error);
|
|
1521
|
+
return err(ProtocolErrorCode.SyncError);
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
if (iterateFailed) {
|
|
1410
1525
|
return err(ProtocolErrorCode.SyncError);
|
|
1411
1526
|
}
|
|
1412
1527
|
|
|
@@ -1449,7 +1564,7 @@ const sync =
|
|
|
1449
1564
|
};
|
|
1450
1565
|
|
|
1451
1566
|
const splitRange =
|
|
1452
|
-
(deps: StorageDep) =>
|
|
1567
|
+
(deps: StorageDep & ConsoleDep) =>
|
|
1453
1568
|
(
|
|
1454
1569
|
ownerId: OwnerIdBytes,
|
|
1455
1570
|
lower: NonNegativeInt,
|
|
@@ -1469,7 +1584,7 @@ const splitRange =
|
|
|
1469
1584
|
|
|
1470
1585
|
deps.storage.iterate(
|
|
1471
1586
|
ownerId,
|
|
1472
|
-
|
|
1587
|
+
zeroNonNegativeInt,
|
|
1473
1588
|
itemCount,
|
|
1474
1589
|
(timestamp) => {
|
|
1475
1590
|
range.timestamps.add(timestampBytesToTimestamp(timestamp));
|
|
@@ -1490,13 +1605,17 @@ const splitRange =
|
|
|
1490
1605
|
...buckets.value.map((b) => NonNegativeInt.orThrow(b + lower)),
|
|
1491
1606
|
];
|
|
1492
1607
|
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1608
|
+
let fingerprintRanges: ReadonlyArray<FingerprintRange>;
|
|
1609
|
+
try {
|
|
1610
|
+
fingerprintRanges = deps.storage.fingerprintRanges(
|
|
1611
|
+
ownerId,
|
|
1612
|
+
fingerprintRangesBuckets,
|
|
1613
|
+
upperBound,
|
|
1614
|
+
);
|
|
1615
|
+
} catch (error) {
|
|
1616
|
+
deps.console.error(error);
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1500
1619
|
|
|
1501
1620
|
const rangesToUse =
|
|
1502
1621
|
lower > 0 ? fingerprintRanges.slice(1) : fingerprintRanges;
|
|
@@ -1514,7 +1633,8 @@ const decodeRanges = (buffer: Buffer): ReadonlyArray<Range> => {
|
|
|
1514
1633
|
|
|
1515
1634
|
const timestampsCount = NonNegativeInt.orThrow(rangesCount - 1);
|
|
1516
1635
|
const timestamps = decodeTimestamps(buffer, timestampsCount);
|
|
1517
|
-
|
|
1636
|
+
|
|
1637
|
+
const rangeTypes = new Array<RangeType>(rangesCount);
|
|
1518
1638
|
|
|
1519
1639
|
for (let i = 0; i < rangesCount; i++) {
|
|
1520
1640
|
const rangeType = decodeNonNegativeInt(buffer);
|
|
@@ -1522,14 +1642,14 @@ const decodeRanges = (buffer: Buffer): ReadonlyArray<Range> => {
|
|
|
1522
1642
|
case RangeType.Fingerprint:
|
|
1523
1643
|
case RangeType.Skip:
|
|
1524
1644
|
case RangeType.Timestamps:
|
|
1525
|
-
rangeTypes
|
|
1645
|
+
rangeTypes[i] = rangeType as RangeType;
|
|
1526
1646
|
break;
|
|
1527
1647
|
default:
|
|
1528
1648
|
throw new ProtocolDecodeError(`Invalid RangeType: ${rangeType}`);
|
|
1529
1649
|
}
|
|
1530
1650
|
}
|
|
1531
1651
|
|
|
1532
|
-
const ranges
|
|
1652
|
+
const ranges = new Array<Range>(rangesCount);
|
|
1533
1653
|
|
|
1534
1654
|
for (let i = 0; i < rangesCount; i++) {
|
|
1535
1655
|
const upperBound =
|
|
@@ -1541,16 +1661,16 @@ const decodeRanges = (buffer: Buffer): ReadonlyArray<Range> => {
|
|
|
1541
1661
|
|
|
1542
1662
|
switch (rangeType) {
|
|
1543
1663
|
case RangeType.Skip:
|
|
1544
|
-
ranges
|
|
1664
|
+
ranges[i] = { type: RangeType.Skip, upperBound };
|
|
1545
1665
|
break;
|
|
1546
1666
|
|
|
1547
1667
|
case RangeType.Fingerprint: {
|
|
1548
1668
|
const fingerprint = buffer.shiftN(fingerprintSize) as Fingerprint;
|
|
1549
|
-
ranges
|
|
1669
|
+
ranges[i] = {
|
|
1550
1670
|
type: RangeType.Fingerprint,
|
|
1551
1671
|
upperBound,
|
|
1552
1672
|
fingerprint,
|
|
1553
|
-
}
|
|
1673
|
+
};
|
|
1554
1674
|
break;
|
|
1555
1675
|
}
|
|
1556
1676
|
|
|
@@ -1558,11 +1678,11 @@ const decodeRanges = (buffer: Buffer): ReadonlyArray<Range> => {
|
|
|
1558
1678
|
const timestamps = decodeTimestamps(buffer).map(
|
|
1559
1679
|
timestampToTimestampBytes,
|
|
1560
1680
|
);
|
|
1561
|
-
ranges
|
|
1681
|
+
ranges[i] = {
|
|
1562
1682
|
type: RangeType.Timestamps,
|
|
1563
1683
|
upperBound,
|
|
1564
1684
|
timestamps,
|
|
1565
|
-
}
|
|
1685
|
+
};
|
|
1566
1686
|
break;
|
|
1567
1687
|
}
|
|
1568
1688
|
}
|
|
@@ -1571,7 +1691,6 @@ const decodeRanges = (buffer: Buffer): ReadonlyArray<Range> => {
|
|
|
1571
1691
|
return ranges;
|
|
1572
1692
|
};
|
|
1573
1693
|
|
|
1574
|
-
/** Decodes an array of sorted timestamps with delta-encoded millis. */
|
|
1575
1694
|
const decodeTimestamps = (
|
|
1576
1695
|
buffer: Buffer,
|
|
1577
1696
|
length?: NonNegativeInt,
|
|
@@ -1580,50 +1699,68 @@ const decodeTimestamps = (
|
|
|
1580
1699
|
|
|
1581
1700
|
let previousMillis = 0 as Millis;
|
|
1582
1701
|
|
|
1583
|
-
const millises
|
|
1702
|
+
const millises = new Array<Millis>(length);
|
|
1584
1703
|
for (let i = 0; i < length; i++) {
|
|
1585
1704
|
const deltaMillis = decodeNonNegativeInt(buffer);
|
|
1586
1705
|
const millis = Millis.from(previousMillis + deltaMillis);
|
|
1587
1706
|
if (!millis.ok) throw new ProtocolDecodeError(millis.error.type);
|
|
1588
|
-
millises
|
|
1707
|
+
millises[i] = millis.value;
|
|
1589
1708
|
previousMillis = millis.value;
|
|
1590
1709
|
}
|
|
1591
1710
|
|
|
1592
|
-
const counters:
|
|
1593
|
-
let counterIndex = 0;
|
|
1594
|
-
while (counterIndex < length) {
|
|
1711
|
+
const counters = decodeRle(buffer, length, (): Counter => {
|
|
1595
1712
|
const counter = Counter.from(decodeNonNegativeInt(buffer));
|
|
1596
1713
|
if (!counter.ok) throw new ProtocolDecodeError(counter.error.type);
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
counters.push(counter.value);
|
|
1600
|
-
counterIndex++;
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1714
|
+
return counter.value;
|
|
1715
|
+
});
|
|
1603
1716
|
|
|
1604
|
-
const nodeIds:
|
|
1605
|
-
let nodeIdIndex = 0;
|
|
1606
|
-
while (nodeIdIndex < length) {
|
|
1607
|
-
const nodeId = decodeNodeId(buffer);
|
|
1608
|
-
const runLength = decodeNonNegativeInt(buffer);
|
|
1609
|
-
for (let i = 0; i < runLength; i++) {
|
|
1610
|
-
nodeIds.push(nodeId);
|
|
1611
|
-
nodeIdIndex++;
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1717
|
+
const nodeIds = decodeRle(buffer, length, (): NodeId => decodeNodeId(buffer));
|
|
1614
1718
|
|
|
1615
|
-
const timestamps
|
|
1719
|
+
const timestamps = new Array<Timestamp>(length);
|
|
1616
1720
|
for (let i = 0; i < length; i++) {
|
|
1617
|
-
timestamps
|
|
1721
|
+
timestamps[i] = {
|
|
1618
1722
|
millis: millises[i],
|
|
1619
1723
|
counter: counters[i],
|
|
1620
1724
|
nodeId: nodeIds[i],
|
|
1621
|
-
}
|
|
1725
|
+
};
|
|
1622
1726
|
}
|
|
1623
1727
|
|
|
1624
1728
|
return timestamps;
|
|
1625
1729
|
};
|
|
1626
1730
|
|
|
1731
|
+
export const decodeRle = <T>(
|
|
1732
|
+
buffer: Buffer,
|
|
1733
|
+
length: NonNegativeInt,
|
|
1734
|
+
decodeValue: () => T,
|
|
1735
|
+
): ReadonlyArray<T> => {
|
|
1736
|
+
const values = new Array<T>(length);
|
|
1737
|
+
let index = 0;
|
|
1738
|
+
while (index < length) {
|
|
1739
|
+
const value = decodeValue();
|
|
1740
|
+
const runLength = decodeNonNegativeInt(buffer);
|
|
1741
|
+
|
|
1742
|
+
// Prevent infinite loop on malformed input.
|
|
1743
|
+
if (runLength === 0) {
|
|
1744
|
+
throw new ProtocolDecodeError(
|
|
1745
|
+
"Invalid RLE encoding: runLength must be positive",
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1748
|
+
const remaining = length - index;
|
|
1749
|
+
|
|
1750
|
+
// Prevent CPU/memory amplification via oversized runLength.
|
|
1751
|
+
if (runLength > remaining) {
|
|
1752
|
+
throw new ProtocolDecodeError(
|
|
1753
|
+
`Invalid RLE encoding: runLength ${runLength} exceeds remaining ${remaining}`,
|
|
1754
|
+
);
|
|
1755
|
+
}
|
|
1756
|
+
for (let i = 0; i < runLength; i++) {
|
|
1757
|
+
values[index] = value;
|
|
1758
|
+
index++;
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
return values;
|
|
1762
|
+
};
|
|
1763
|
+
|
|
1627
1764
|
const decodeId = (buffer: Buffer): Id => {
|
|
1628
1765
|
const bytes = buffer.shiftN(idBytesTypeValueLength);
|
|
1629
1766
|
return idBytesToId(bytes as IdBytes);
|
|
@@ -1699,9 +1836,10 @@ export const decodeFlags = (
|
|
|
1699
1836
|
count: PositiveInt,
|
|
1700
1837
|
): ReadonlyArray<boolean> => {
|
|
1701
1838
|
const byte = buffer.shift();
|
|
1702
|
-
const
|
|
1703
|
-
|
|
1704
|
-
|
|
1839
|
+
const length = globalThis.Math.min(count, 8);
|
|
1840
|
+
const flags = new Array<boolean>(length);
|
|
1841
|
+
for (let i = 0; i < length; i++) {
|
|
1842
|
+
flags[i] = (byte & (1 << i)) !== 0;
|
|
1705
1843
|
}
|
|
1706
1844
|
return flags;
|
|
1707
1845
|
};
|
|
@@ -1715,7 +1853,7 @@ export const decodeFlags = (
|
|
|
1715
1853
|
* data.
|
|
1716
1854
|
*/
|
|
1717
1855
|
export const encodeAndEncryptDbChange =
|
|
1718
|
-
(deps:
|
|
1856
|
+
(deps: RandomBytesDep) =>
|
|
1719
1857
|
(message: CrdtMessage, key: EncryptionKey): EncryptedDbChange => {
|
|
1720
1858
|
const buffer = createBuffer();
|
|
1721
1859
|
|
|
@@ -1727,6 +1865,7 @@ export const encodeAndEncryptDbChange =
|
|
|
1727
1865
|
|
|
1728
1866
|
encodeFlags(buffer, [
|
|
1729
1867
|
message.change.isInsert,
|
|
1868
|
+
// Encode nullable boolean as two flags: presence + value.
|
|
1730
1869
|
message.change.isDelete != null,
|
|
1731
1870
|
message.change.isDelete ?? false,
|
|
1732
1871
|
]);
|
|
@@ -1745,7 +1884,7 @@ export const encodeAndEncryptDbChange =
|
|
|
1745
1884
|
// Add PADMÉ padding (ignored during decoding)
|
|
1746
1885
|
buffer.extend(createPadmePadding(buffer.getLength()));
|
|
1747
1886
|
|
|
1748
|
-
const
|
|
1887
|
+
const [ciphertext, nonce] = encryptWithXChaCha20Poly1305(deps)(
|
|
1749
1888
|
buffer.unwrap(),
|
|
1750
1889
|
key,
|
|
1751
1890
|
);
|
|
@@ -1763,78 +1902,76 @@ export const encodeAndEncryptDbChange =
|
|
|
1763
1902
|
* owner's encryption key. Verifies that the embedded timestamp matches the
|
|
1764
1903
|
* expected timestamp to ensure message integrity.
|
|
1765
1904
|
*/
|
|
1766
|
-
export const decryptAndDecodeDbChange =
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
try {
|
|
1778
|
-
const buffer = createBuffer(message.change);
|
|
1905
|
+
export const decryptAndDecodeDbChange = (
|
|
1906
|
+
message: EncryptedCrdtMessage,
|
|
1907
|
+
key: EncryptionKey,
|
|
1908
|
+
): Result<
|
|
1909
|
+
DbChange,
|
|
1910
|
+
| DecryptWithXChaCha20Poly1305Error
|
|
1911
|
+
| ProtocolInvalidDataError
|
|
1912
|
+
| ProtocolTimestampMismatchError
|
|
1913
|
+
> => {
|
|
1914
|
+
try {
|
|
1915
|
+
const buffer = createBuffer(message.change);
|
|
1779
1916
|
|
|
1780
|
-
|
|
1781
|
-
|
|
1917
|
+
const nonce = buffer.shiftN(xChaCha20Poly1305NonceLength as NonNegativeInt);
|
|
1918
|
+
const ciphertext = buffer.shiftN(decodeLength(buffer));
|
|
1782
1919
|
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1920
|
+
const plaintextBytes = decryptWithXChaCha20Poly1305(
|
|
1921
|
+
XChaCha20Poly1305Ciphertext.orThrow(ciphertext),
|
|
1922
|
+
Entropy24.orThrow(nonce),
|
|
1923
|
+
key,
|
|
1924
|
+
);
|
|
1925
|
+
if (!plaintextBytes.ok) return plaintextBytes;
|
|
1789
1926
|
|
|
1790
|
-
|
|
1791
|
-
|
|
1927
|
+
buffer.reset();
|
|
1928
|
+
buffer.extend(plaintextBytes.value);
|
|
1792
1929
|
|
|
1793
|
-
|
|
1794
|
-
|
|
1930
|
+
// Decode version (for future compatibility, not need yet)
|
|
1931
|
+
decodeNonNegativeInt(buffer);
|
|
1795
1932
|
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1933
|
+
const timestamp = timestampBytesToTimestamp(
|
|
1934
|
+
TimestampBytes.orThrow(buffer.shiftN(timestampBytesLength)),
|
|
1935
|
+
);
|
|
1799
1936
|
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1937
|
+
if (!eqTimestamp(timestamp, message.timestamp)) {
|
|
1938
|
+
return err<ProtocolTimestampMismatchError>({
|
|
1939
|
+
type: "ProtocolTimestampMismatchError",
|
|
1940
|
+
expected: message.timestamp,
|
|
1941
|
+
timestamp,
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1807
1944
|
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1945
|
+
const flags = decodeFlags(buffer, PositiveInt.orThrow(3));
|
|
1946
|
+
const table = decodeString(buffer);
|
|
1947
|
+
const id = decodeId(buffer);
|
|
1811
1948
|
|
|
1812
|
-
|
|
1813
|
-
|
|
1949
|
+
const length = decodeLength(buffer);
|
|
1950
|
+
const values = createRecord<string, SqliteValue>();
|
|
1814
1951
|
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1952
|
+
for (let i = 0; i < length; i++) {
|
|
1953
|
+
const column = decodeString(buffer);
|
|
1954
|
+
const value = decodeSqliteValue(buffer);
|
|
1955
|
+
values[column] = value;
|
|
1956
|
+
}
|
|
1820
1957
|
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1958
|
+
const dbChange = DbChange.orThrow({
|
|
1959
|
+
table,
|
|
1960
|
+
id,
|
|
1961
|
+
values,
|
|
1962
|
+
isInsert: flags[0],
|
|
1963
|
+
isDelete: flags[1] ? flags[2] : null,
|
|
1964
|
+
});
|
|
1828
1965
|
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1966
|
+
return ok(dbChange);
|
|
1967
|
+
} catch (error) {
|
|
1968
|
+
return err<ProtocolInvalidDataError>({
|
|
1969
|
+
type: "ProtocolInvalidDataError",
|
|
1970
|
+
data: message.change,
|
|
1971
|
+
error,
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
};
|
|
1838
1975
|
|
|
1839
1976
|
/**
|
|
1840
1977
|
* Encodes a non-negative integer into a variable-length integer format. It's
|
|
@@ -1926,26 +2063,26 @@ export const ProtocolValueType = {
|
|
|
1926
2063
|
// 0-19 small ints
|
|
1927
2064
|
|
|
1928
2065
|
// SQLite types
|
|
1929
|
-
String: NonNegativeInt.orThrow(20),
|
|
1930
|
-
Number: NonNegativeInt.orThrow(21),
|
|
1931
|
-
Null: NonNegativeInt.orThrow(22),
|
|
1932
|
-
Bytes: NonNegativeInt.orThrow(23),
|
|
2066
|
+
String: /*#__PURE__*/ NonNegativeInt.orThrow(20),
|
|
2067
|
+
Number: /*#__PURE__*/ NonNegativeInt.orThrow(21),
|
|
2068
|
+
Null: /*#__PURE__*/ NonNegativeInt.orThrow(22),
|
|
2069
|
+
Bytes: /*#__PURE__*/ NonNegativeInt.orThrow(23),
|
|
1933
2070
|
// We can add more types for other DBs or anything else later.
|
|
1934
2071
|
|
|
1935
2072
|
// Optimized types
|
|
1936
|
-
NonNegativeInt: NonNegativeInt.orThrow(30),
|
|
2073
|
+
NonNegativeInt: /*#__PURE__*/ NonNegativeInt.orThrow(30),
|
|
1937
2074
|
|
|
1938
2075
|
// String optimizations
|
|
1939
|
-
EmptyString: NonNegativeInt.orThrow(31), // 1 byte vs 2 bytes (50% reduction)
|
|
1940
|
-
Base64Url: NonNegativeInt.orThrow(32),
|
|
1941
|
-
Id: NonNegativeInt.orThrow(33),
|
|
1942
|
-
Json: NonNegativeInt.orThrow(34),
|
|
2076
|
+
EmptyString: /*#__PURE__*/ NonNegativeInt.orThrow(31), // 1 byte vs 2 bytes (50% reduction)
|
|
2077
|
+
Base64Url: /*#__PURE__*/ NonNegativeInt.orThrow(32),
|
|
2078
|
+
Id: /*#__PURE__*/ NonNegativeInt.orThrow(33),
|
|
2079
|
+
Json: /*#__PURE__*/ NonNegativeInt.orThrow(34),
|
|
1943
2080
|
|
|
1944
2081
|
// new Date().toISOString() - 24 bytes
|
|
1945
2082
|
// encoded with fixed length - 8 bytes
|
|
1946
2083
|
// encode as NonNegativeInt - 6 bytes (additional 25% reduction)
|
|
1947
|
-
DateIsoWithNonNegativeTime: NonNegativeInt.orThrow(35),
|
|
1948
|
-
DateIsoWithNegativeTime: NonNegativeInt.orThrow(36), // 9 bytes
|
|
2084
|
+
DateIsoWithNonNegativeTime: /*#__PURE__*/ NonNegativeInt.orThrow(35),
|
|
2085
|
+
DateIsoWithNegativeTime: /*#__PURE__*/ NonNegativeInt.orThrow(36), // 9 bytes
|
|
1949
2086
|
|
|
1950
2087
|
// TODO: Operations (from 40)
|
|
1951
2088
|
// Increment, Decrement, Patch, whatever.
|