@evolu/common 7.4.1 → 8.0.0-next.1
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 +162 -0
- package/dist/src/Lookup.d.ts.map +1 -0
- package/dist/src/Lookup.js +200 -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 +29 -0
- package/dist/src/Polyfills.d.ts.map +1 -0
- package/dist/src/Polyfills.js +329 -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 +2274 -423
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +1499 -348
- package/dist/src/Test.d.ts +150 -0
- package/dist/src/Test.d.ts.map +1 -0
- package/dist/src/Test.js +97 -0
- package/dist/src/Time.d.ts +202 -48
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +199 -88
- 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 +587 -209
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +370 -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 +130 -58
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +274 -113
- 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 +621 -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 +34 -49
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Relay.js +59 -177
- 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 +54 -58
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +127 -179
- 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 +37 -24
- 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 +429 -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 +520 -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 +3696 -740
- package/src/Test.ts +202 -0
- package/src/Time.ts +357 -147
- package/src/Tracer.ts +54 -0
- package/src/Type.ts +866 -430
- package/src/Types.ts +202 -7
- package/src/WebSocket.ts +404 -167
- package/src/Worker.ts +527 -147
- package/src/index.ts +74 -3
- package/src/local-first/Db.ts +867 -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 +441 -307
- package/src/local-first/Query.ts +243 -194
- package/src/local-first/Relay.ts +119 -273
- package/src/local-first/Schema.ts +284 -394
- package/src/local-first/Shared.ts +932 -0
- package/src/local-first/Storage.ts +211 -283
- package/src/local-first/Timestamp.ts +38 -45
- package/src/local-first/index.ts +2 -16
- 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,29 +178,36 @@
|
|
|
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
184
|
import {
|
|
185
|
-
Buffer,
|
|
185
|
+
type Buffer,
|
|
186
186
|
bytesToHex,
|
|
187
187
|
bytesToUtf8,
|
|
188
188
|
createBuffer,
|
|
189
189
|
hexToBytes,
|
|
190
190
|
utf8ToBytes,
|
|
191
191
|
} from "../Buffer.js";
|
|
192
|
+
import type { ConsoleDep } from "../Console.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,11 +547,8 @@ export const createProtocolMessageFromCrdtMessages =
|
|
|
514
547
|
|
|
515
548
|
/** Creates a {@link ProtocolMessage} for sync. */
|
|
516
549
|
export const createProtocolMessageForSync =
|
|
517
|
-
(deps: StorageDep) =>
|
|
518
|
-
(
|
|
519
|
-
ownerId: OwnerId,
|
|
520
|
-
subscriptionFlag?: SubscriptionFlag,
|
|
521
|
-
): ProtocolMessage | null => {
|
|
550
|
+
(deps: StorageDep & ConsoleDep) =>
|
|
551
|
+
(ownerId: OwnerId, subscriptionFlag?: SubscriptionFlag): ProtocolMessage => {
|
|
522
552
|
const buffer = createProtocolMessageBuffer(ownerId, {
|
|
523
553
|
messageType: MessageType.Request,
|
|
524
554
|
subscriptionFlag: subscriptionFlag ?? SubscriptionFlags.None,
|
|
@@ -526,12 +556,10 @@ export const createProtocolMessageForSync =
|
|
|
526
556
|
const ownerIdBytes = ownerIdToOwnerIdBytes(ownerId);
|
|
527
557
|
|
|
528
558
|
const size = deps.storage.getSize(ownerIdBytes);
|
|
529
|
-
// Errors are handled by the storage.
|
|
530
|
-
if (size == null) return null;
|
|
531
559
|
|
|
532
560
|
splitRange(deps)(
|
|
533
561
|
ownerIdBytes,
|
|
534
|
-
|
|
562
|
+
zeroNonNegativeInt,
|
|
535
563
|
size,
|
|
536
564
|
InfiniteUpperBound,
|
|
537
565
|
buffer,
|
|
@@ -687,9 +715,8 @@ export const createProtocolMessageBuffer = (
|
|
|
687
715
|
assert(isWithinSizeLimits(), "the message is too big");
|
|
688
716
|
},
|
|
689
717
|
|
|
690
|
-
canSplitRange: () =>
|
|
691
|
-
|
|
692
|
-
},
|
|
718
|
+
canSplitRange: () =>
|
|
719
|
+
getRangesSize() + safeMargins.splitRange <= rangesMaxSize,
|
|
693
720
|
|
|
694
721
|
canAddTimestampsRangeAndMessage: (timestamps, message) => {
|
|
695
722
|
const rangesNewSize =
|
|
@@ -790,7 +817,7 @@ export interface TimestampsBuffer {
|
|
|
790
817
|
}
|
|
791
818
|
|
|
792
819
|
export const createTimestampsBuffer = (): TimestampsBuffer => {
|
|
793
|
-
let count =
|
|
820
|
+
let count = zeroNonNegativeInt;
|
|
794
821
|
const countBuffer = createBuffer();
|
|
795
822
|
|
|
796
823
|
const syncCount = () => {
|
|
@@ -857,9 +884,9 @@ const createRunLengthEncoder = <T>(
|
|
|
857
884
|
encodeValue: (buffer: Buffer, value: T) => void,
|
|
858
885
|
): RunLengthEncoder<T> => {
|
|
859
886
|
const buffer = createBuffer();
|
|
860
|
-
let previousLength =
|
|
887
|
+
let previousLength = zeroNonNegativeInt;
|
|
861
888
|
let previousValue = null as T | null;
|
|
862
|
-
let runLength =
|
|
889
|
+
let runLength = zeroNonNegativeInt;
|
|
863
890
|
|
|
864
891
|
return {
|
|
865
892
|
add: (value) => {
|
|
@@ -882,7 +909,7 @@ const createRunLengthEncoder = <T>(
|
|
|
882
909
|
};
|
|
883
910
|
|
|
884
911
|
export interface ApplyProtocolMessageAsClientOptions {
|
|
885
|
-
|
|
912
|
+
writeKey?: OwnerWriteKey;
|
|
886
913
|
|
|
887
914
|
rangesMaxSize?: ProtocolMessageRangesMaxSize;
|
|
888
915
|
|
|
@@ -894,27 +921,35 @@ export interface ApplyProtocolMessageAsClientOptions {
|
|
|
894
921
|
* Result type for {@link applyProtocolMessageAsClient} that distinguishes
|
|
895
922
|
* between responses to client requests and broadcast messages.
|
|
896
923
|
*/
|
|
924
|
+
export interface ApplyProtocolMessageAsClientResponse extends Typed<"Response"> {
|
|
925
|
+
readonly message: ProtocolMessage;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
export interface ApplyProtocolMessageAsClientNoResponse extends Typed<"NoResponse"> {}
|
|
929
|
+
|
|
930
|
+
export interface ApplyProtocolMessageAsClientBroadcast extends Typed<"Broadcast"> {}
|
|
931
|
+
|
|
897
932
|
export type ApplyProtocolMessageAsClientResult =
|
|
898
|
-
|
|
|
899
|
-
|
|
|
900
|
-
|
|
|
933
|
+
| ApplyProtocolMessageAsClientResponse
|
|
934
|
+
| ApplyProtocolMessageAsClientNoResponse
|
|
935
|
+
| ApplyProtocolMessageAsClientBroadcast;
|
|
901
936
|
|
|
902
937
|
export const applyProtocolMessageAsClient =
|
|
903
|
-
(
|
|
904
|
-
async (
|
|
938
|
+
(
|
|
905
939
|
inputMessage: Uint8Array,
|
|
906
940
|
options: ApplyProtocolMessageAsClientOptions = {},
|
|
907
|
-
):
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
941
|
+
): Task<
|
|
942
|
+
ApplyProtocolMessageAsClientResult,
|
|
943
|
+
| ProtocolInvalidDataError
|
|
944
|
+
| ProtocolSyncError
|
|
945
|
+
| ProtocolVersionError
|
|
946
|
+
| ProtocolWriteError
|
|
947
|
+
| ProtocolWriteKeyError
|
|
948
|
+
| ProtocolQuotaError,
|
|
949
|
+
StorageDep
|
|
950
|
+
> =>
|
|
951
|
+
async (run) => {
|
|
952
|
+
const { storage } = run.deps;
|
|
918
953
|
try {
|
|
919
954
|
const input = createBuffer(inputMessage);
|
|
920
955
|
const [requestedVersion, ownerId] = decodeVersionAndOwner(input);
|
|
@@ -971,10 +1006,17 @@ export const applyProtocolMessageAsClient =
|
|
|
971
1006
|
const messages = decodeMessages(input);
|
|
972
1007
|
const ownerIdBytes = ownerIdToOwnerIdBytes(ownerId);
|
|
973
1008
|
|
|
974
|
-
if (
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1009
|
+
if (isNonEmptyArray(messages)) {
|
|
1010
|
+
try {
|
|
1011
|
+
const result = await run(
|
|
1012
|
+
storage.writeMessages(ownerIdBytes, messages),
|
|
1013
|
+
);
|
|
1014
|
+
// Quota errors are handled by Storage; protocol just stops syncing.
|
|
1015
|
+
if (!result.ok) return ok({ type: "NoResponse" });
|
|
1016
|
+
} catch (error) {
|
|
1017
|
+
run.deps.console.error(error);
|
|
1018
|
+
return ok({ type: "NoResponse" });
|
|
1019
|
+
}
|
|
978
1020
|
}
|
|
979
1021
|
|
|
980
1022
|
// Now: No writeKey, no sync.
|
|
@@ -982,19 +1024,19 @@ export const applyProtocolMessageAsClient =
|
|
|
982
1024
|
// Without local changes, writeKey will not be required.
|
|
983
1025
|
// With local changes, writeKey will be required and if not provided,
|
|
984
1026
|
// the sync will stop.
|
|
985
|
-
const writeKey = options.
|
|
1027
|
+
const writeKey = options.writeKey;
|
|
986
1028
|
if (writeKey == null) {
|
|
987
|
-
return ok({ type: "
|
|
1029
|
+
return ok({ type: "NoResponse" });
|
|
988
1030
|
}
|
|
989
1031
|
|
|
990
1032
|
if (messageType === MessageType.Broadcast) {
|
|
991
|
-
return ok({ type: "
|
|
1033
|
+
return ok({ type: "Broadcast" });
|
|
992
1034
|
}
|
|
993
1035
|
|
|
994
1036
|
const ranges = decodeRanges(input);
|
|
995
1037
|
|
|
996
|
-
if (!
|
|
997
|
-
return ok({ type: "
|
|
1038
|
+
if (!isNonEmptyArray(ranges)) {
|
|
1039
|
+
return ok({ type: "NoResponse" });
|
|
998
1040
|
}
|
|
999
1041
|
|
|
1000
1042
|
const output = createProtocolMessageBuffer(ownerId, {
|
|
@@ -1003,14 +1045,14 @@ export const applyProtocolMessageAsClient =
|
|
|
1003
1045
|
rangesMaxSize: options.rangesMaxSize,
|
|
1004
1046
|
});
|
|
1005
1047
|
|
|
1006
|
-
const result = sync(deps)(ranges, output, ownerIdBytes);
|
|
1048
|
+
const result = sync(run.deps)(ranges, output, ownerIdBytes);
|
|
1007
1049
|
|
|
1008
1050
|
// Client sync error (handled via Storage) or no changes.
|
|
1009
1051
|
if (!result.ok || !result.value) {
|
|
1010
|
-
return ok({ type: "
|
|
1052
|
+
return ok({ type: "NoResponse" });
|
|
1011
1053
|
}
|
|
1012
1054
|
|
|
1013
|
-
return ok({ type: "
|
|
1055
|
+
return ok({ type: "Response", message: output.unwrap() });
|
|
1014
1056
|
} catch (error) {
|
|
1015
1057
|
return err<ProtocolInvalidDataError>({
|
|
1016
1058
|
type: "ProtocolInvalidDataError",
|
|
@@ -1043,21 +1085,23 @@ export interface ApplyProtocolMessageAsRelayOptions {
|
|
|
1043
1085
|
* to sync. Clients may choose not to respond in certain cases (like when they
|
|
1044
1086
|
* receive broadcast messages or when they lack a write key for syncing).
|
|
1045
1087
|
*/
|
|
1046
|
-
export interface ApplyProtocolMessageAsRelayResult {
|
|
1047
|
-
readonly type: "response";
|
|
1088
|
+
export interface ApplyProtocolMessageAsRelayResult extends Typed<"Response"> {
|
|
1048
1089
|
readonly message: ProtocolMessage;
|
|
1049
1090
|
}
|
|
1050
1091
|
|
|
1051
1092
|
export const applyProtocolMessageAsRelay =
|
|
1052
|
-
(
|
|
1053
|
-
async (
|
|
1093
|
+
(
|
|
1054
1094
|
inputMessage: Uint8Array,
|
|
1055
1095
|
options: ApplyProtocolMessageAsRelayOptions = {},
|
|
1056
1096
|
/** For tests only. */
|
|
1057
|
-
version = protocolVersion,
|
|
1058
|
-
):
|
|
1059
|
-
|
|
1060
|
-
|
|
1097
|
+
version: NonNegativeInt = protocolVersion,
|
|
1098
|
+
): Task<
|
|
1099
|
+
ApplyProtocolMessageAsRelayResult,
|
|
1100
|
+
ProtocolInvalidDataError,
|
|
1101
|
+
StorageDep
|
|
1102
|
+
> =>
|
|
1103
|
+
async (run) => {
|
|
1104
|
+
const { storage } = run.deps;
|
|
1061
1105
|
try {
|
|
1062
1106
|
const input = createBuffer(inputMessage);
|
|
1063
1107
|
const [requestedVersion, ownerId] = decodeVersionAndOwner(input);
|
|
@@ -1069,7 +1113,7 @@ export const applyProtocolMessageAsRelay =
|
|
|
1069
1113
|
encodeNonNegativeInt(output, version);
|
|
1070
1114
|
output.extend(ownerIdBytes);
|
|
1071
1115
|
return ok({
|
|
1072
|
-
type: "
|
|
1116
|
+
type: "Response",
|
|
1073
1117
|
message: output.unwrap() as ProtocolMessage,
|
|
1074
1118
|
});
|
|
1075
1119
|
}
|
|
@@ -1098,10 +1142,10 @@ export const applyProtocolMessageAsRelay =
|
|
|
1098
1142
|
}
|
|
1099
1143
|
|
|
1100
1144
|
if (writeKey) {
|
|
1101
|
-
const isValid =
|
|
1145
|
+
const isValid = storage.validateWriteKey(ownerIdBytes, writeKey);
|
|
1102
1146
|
if (!isValid) {
|
|
1103
1147
|
return ok({
|
|
1104
|
-
type: "
|
|
1148
|
+
type: "Response",
|
|
1105
1149
|
message: createProtocolMessageBuffer(ownerId, {
|
|
1106
1150
|
messageType: MessageType.Response,
|
|
1107
1151
|
errorCode: ProtocolErrorCode.WriteKeyError,
|
|
@@ -1112,10 +1156,10 @@ export const applyProtocolMessageAsRelay =
|
|
|
1112
1156
|
|
|
1113
1157
|
const messages = decodeMessages(input);
|
|
1114
1158
|
|
|
1115
|
-
if (
|
|
1159
|
+
if (isNonEmptyArray(messages)) {
|
|
1116
1160
|
if (!writeKey) {
|
|
1117
1161
|
return ok({
|
|
1118
|
-
type: "
|
|
1162
|
+
type: "Response",
|
|
1119
1163
|
message: createProtocolMessageBuffer(ownerId, {
|
|
1120
1164
|
messageType: MessageType.Response,
|
|
1121
1165
|
errorCode: ProtocolErrorCode.WriteKeyError,
|
|
@@ -1123,18 +1167,25 @@ export const applyProtocolMessageAsRelay =
|
|
|
1123
1167
|
});
|
|
1124
1168
|
}
|
|
1125
1169
|
|
|
1126
|
-
|
|
1170
|
+
try {
|
|
1171
|
+
const result = await run(
|
|
1172
|
+
storage.writeMessages(ownerIdBytes, messages),
|
|
1173
|
+
);
|
|
1127
1174
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1175
|
+
if (!result.ok) {
|
|
1176
|
+
const message = createProtocolMessageBuffer(ownerId, {
|
|
1177
|
+
messageType: MessageType.Response,
|
|
1178
|
+
errorCode: ProtocolErrorCode.QuotaError,
|
|
1179
|
+
}).unwrap();
|
|
1180
|
+
return ok({ type: "Response", message });
|
|
1181
|
+
}
|
|
1182
|
+
} catch (error) {
|
|
1183
|
+
run.deps.console.error(error);
|
|
1133
1184
|
const message = createProtocolMessageBuffer(ownerId, {
|
|
1134
1185
|
messageType: MessageType.Response,
|
|
1135
|
-
errorCode,
|
|
1186
|
+
errorCode: ProtocolErrorCode.WriteError,
|
|
1136
1187
|
}).unwrap();
|
|
1137
|
-
return ok({ type: "
|
|
1188
|
+
return ok({ type: "Response", message });
|
|
1138
1189
|
}
|
|
1139
1190
|
|
|
1140
1191
|
/**
|
|
@@ -1179,11 +1230,11 @@ export const applyProtocolMessageAsRelay =
|
|
|
1179
1230
|
|
|
1180
1231
|
// Non-initiators always respond to provide sync completion feedback,
|
|
1181
1232
|
// even when there's nothing to sync.
|
|
1182
|
-
if (!
|
|
1183
|
-
return ok({ type: "
|
|
1233
|
+
if (!isNonEmptyArray(ranges)) {
|
|
1234
|
+
return ok({ type: "Response", message: output.unwrap() });
|
|
1184
1235
|
}
|
|
1185
1236
|
|
|
1186
|
-
const result = sync(deps)(ranges, output, ownerIdBytes);
|
|
1237
|
+
const result = sync(run.deps)(ranges, output, ownerIdBytes);
|
|
1187
1238
|
|
|
1188
1239
|
const message = result.ok
|
|
1189
1240
|
? output.unwrap()
|
|
@@ -1193,7 +1244,7 @@ export const applyProtocolMessageAsRelay =
|
|
|
1193
1244
|
}).unwrap();
|
|
1194
1245
|
|
|
1195
1246
|
// Non-initiators always respond to provide sync completion feedback,
|
|
1196
|
-
return ok({ type: "
|
|
1247
|
+
return ok({ type: "Response", message });
|
|
1197
1248
|
} catch (error) {
|
|
1198
1249
|
return err<ProtocolInvalidDataError>({
|
|
1199
1250
|
type: "ProtocolInvalidDataError",
|
|
@@ -1213,6 +1264,38 @@ const decodeVersionAndOwner = (input: Buffer): [NonNegativeInt, OwnerId] => {
|
|
|
1213
1264
|
return [version, ownerId];
|
|
1214
1265
|
};
|
|
1215
1266
|
|
|
1267
|
+
const parseProtocolHeaderFromBuffer = (input: Buffer): ProtocolHeader => {
|
|
1268
|
+
const [version, ownerId] = decodeVersionAndOwner(input);
|
|
1269
|
+
|
|
1270
|
+
if (version !== protocolVersion) {
|
|
1271
|
+
throw new ProtocolDecodeError(`Unsupported protocol version: ${version}`);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
const messageTypeValue = input.shift();
|
|
1275
|
+
let messageType: MessageType;
|
|
1276
|
+
|
|
1277
|
+
switch (messageTypeValue) {
|
|
1278
|
+
case MessageType.Request:
|
|
1279
|
+
messageType = MessageType.Request;
|
|
1280
|
+
break;
|
|
1281
|
+
case MessageType.Response:
|
|
1282
|
+
messageType = MessageType.Response;
|
|
1283
|
+
break;
|
|
1284
|
+
case MessageType.Broadcast:
|
|
1285
|
+
messageType = MessageType.Broadcast;
|
|
1286
|
+
break;
|
|
1287
|
+
default:
|
|
1288
|
+
throw new ProtocolDecodeError("Invalid MessageType");
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
return {
|
|
1292
|
+
type: "ProtocolHeader",
|
|
1293
|
+
version: 1,
|
|
1294
|
+
ownerId,
|
|
1295
|
+
messageType,
|
|
1296
|
+
};
|
|
1297
|
+
};
|
|
1298
|
+
|
|
1216
1299
|
/**
|
|
1217
1300
|
* Error thrown for internal protocol validation failures, such as invalid data
|
|
1218
1301
|
* or type errors.
|
|
@@ -1231,30 +1314,35 @@ const decodeMessages = (
|
|
|
1231
1314
|
): ReadonlyArray<EncryptedCrdtMessage> => {
|
|
1232
1315
|
const timestamps = decodeTimestamps(buffer);
|
|
1233
1316
|
|
|
1234
|
-
const messages
|
|
1235
|
-
for (
|
|
1317
|
+
const messages = new Array<EncryptedCrdtMessage>(timestamps.length);
|
|
1318
|
+
for (let i = 0; i < timestamps.length; i++) {
|
|
1319
|
+
const timestamp = timestamps[i];
|
|
1236
1320
|
const changeLength = decodeLength(buffer);
|
|
1237
1321
|
const change = buffer.shiftN(changeLength) as EncryptedDbChange;
|
|
1238
|
-
messages
|
|
1322
|
+
messages[i] = { timestamp, change };
|
|
1239
1323
|
}
|
|
1240
1324
|
|
|
1241
1325
|
return messages;
|
|
1242
1326
|
};
|
|
1243
1327
|
|
|
1244
1328
|
const sync =
|
|
1245
|
-
(deps: StorageDep) =>
|
|
1329
|
+
(deps: StorageDep & ConsoleDep) =>
|
|
1246
1330
|
(
|
|
1247
1331
|
ranges: NonEmptyReadonlyArray<Range>,
|
|
1248
1332
|
output: ProtocolMessageBuffer,
|
|
1249
1333
|
ownerIdBytes: OwnerIdBytes,
|
|
1250
1334
|
): Result<boolean, typeof ProtocolErrorCode.SyncError> => {
|
|
1251
1335
|
const outputInitialSize = output.getSize();
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1336
|
+
let storageSize: NonNegativeInt;
|
|
1337
|
+
try {
|
|
1338
|
+
storageSize = deps.storage.getSize(ownerIdBytes);
|
|
1339
|
+
} catch (error) {
|
|
1340
|
+
deps.console.error(error);
|
|
1341
|
+
return err(ProtocolErrorCode.SyncError);
|
|
1342
|
+
}
|
|
1255
1343
|
|
|
1256
1344
|
let prevUpperBound: RangeUpperBound | null = null;
|
|
1257
|
-
let prevIndex =
|
|
1345
|
+
let prevIndex = zeroNonNegativeInt;
|
|
1258
1346
|
|
|
1259
1347
|
let skip = false;
|
|
1260
1348
|
let nonSkipRangeAdded = false;
|
|
@@ -1291,12 +1379,17 @@ const sync =
|
|
|
1291
1379
|
const addFingerprintForRemainingRange = (
|
|
1292
1380
|
begin: NonNegativeInt,
|
|
1293
1381
|
): boolean => {
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1382
|
+
let fingerprint: Fingerprint;
|
|
1383
|
+
try {
|
|
1384
|
+
fingerprint = deps.storage.fingerprint(
|
|
1385
|
+
ownerIdBytes,
|
|
1386
|
+
begin,
|
|
1387
|
+
storageSize,
|
|
1388
|
+
);
|
|
1389
|
+
} catch (error) {
|
|
1390
|
+
deps.console.error(error);
|
|
1391
|
+
return false;
|
|
1392
|
+
}
|
|
1300
1393
|
// There is always a space for a ramaining range.
|
|
1301
1394
|
output.addRange({
|
|
1302
1395
|
type: RangeType.Fingerprint,
|
|
@@ -1310,13 +1403,18 @@ const sync =
|
|
|
1310
1403
|
const currentUpperBound = range.upperBound;
|
|
1311
1404
|
|
|
1312
1405
|
const lower = prevIndex;
|
|
1313
|
-
let upper
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1406
|
+
let upper: NonNegativeInt;
|
|
1407
|
+
try {
|
|
1408
|
+
upper = deps.storage.findLowerBound(
|
|
1409
|
+
ownerIdBytes,
|
|
1410
|
+
prevIndex,
|
|
1411
|
+
storageSize,
|
|
1412
|
+
currentUpperBound,
|
|
1413
|
+
);
|
|
1414
|
+
} catch (error) {
|
|
1415
|
+
deps.console.error(error);
|
|
1416
|
+
return err(ProtocolErrorCode.SyncError);
|
|
1417
|
+
}
|
|
1320
1418
|
|
|
1321
1419
|
switch (range.type) {
|
|
1322
1420
|
case RangeType.Skip: {
|
|
@@ -1325,12 +1423,17 @@ const sync =
|
|
|
1325
1423
|
}
|
|
1326
1424
|
|
|
1327
1425
|
case RangeType.Fingerprint: {
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1426
|
+
let ourFingerprint: Fingerprint;
|
|
1427
|
+
try {
|
|
1428
|
+
ourFingerprint = deps.storage.fingerprint(
|
|
1429
|
+
ownerIdBytes,
|
|
1430
|
+
lower,
|
|
1431
|
+
upper,
|
|
1432
|
+
);
|
|
1433
|
+
} catch (error) {
|
|
1434
|
+
deps.console.error(error);
|
|
1435
|
+
return err(ProtocolErrorCode.SyncError);
|
|
1436
|
+
}
|
|
1334
1437
|
|
|
1335
1438
|
if (eqArrayNumber(range.fingerprint, ourFingerprint)) {
|
|
1336
1439
|
skipRange(range);
|
|
@@ -1361,52 +1464,61 @@ const sync =
|
|
|
1361
1464
|
);
|
|
1362
1465
|
const ourTimestamps = createTimestampsBuffer();
|
|
1363
1466
|
|
|
1364
|
-
let cantReadDbChange = false as boolean;
|
|
1365
1467
|
let exceeded = false as boolean;
|
|
1468
|
+
let iterateFailed = false as boolean;
|
|
1469
|
+
|
|
1470
|
+
try {
|
|
1471
|
+
deps.storage.iterate(
|
|
1472
|
+
ownerIdBytes,
|
|
1473
|
+
lower,
|
|
1474
|
+
upper,
|
|
1475
|
+
(timestamp, index) => {
|
|
1476
|
+
const timestampString = timestamp.join();
|
|
1477
|
+
const timestampBinary = timestampBytesToTimestamp(timestamp);
|
|
1478
|
+
|
|
1479
|
+
let message: EncryptedCrdtMessage | null = null;
|
|
1480
|
+
|
|
1481
|
+
if (timestampsWeNeed.has(timestampString)) {
|
|
1482
|
+
timestampsWeNeed.delete(timestampString);
|
|
1483
|
+
} else {
|
|
1484
|
+
try {
|
|
1485
|
+
message = {
|
|
1486
|
+
timestamp: timestampBinary,
|
|
1487
|
+
change: deps.storage.readDbChange(
|
|
1488
|
+
ownerIdBytes,
|
|
1489
|
+
timestamp,
|
|
1490
|
+
),
|
|
1491
|
+
};
|
|
1492
|
+
} catch (error) {
|
|
1493
|
+
deps.console.error(error);
|
|
1494
|
+
iterateFailed = true;
|
|
1495
|
+
return false;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1366
1498
|
|
|
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;
|
|
1499
|
+
if (
|
|
1500
|
+
!output.canAddTimestampsRangeAndMessage(
|
|
1501
|
+
ourTimestamps,
|
|
1502
|
+
message,
|
|
1503
|
+
)
|
|
1504
|
+
) {
|
|
1505
|
+
exceeded = true;
|
|
1506
|
+
endBound = timestamp;
|
|
1507
|
+
upper = index;
|
|
1386
1508
|
return false;
|
|
1387
1509
|
}
|
|
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
1510
|
|
|
1409
|
-
|
|
1511
|
+
ourTimestamps.add(timestampBinary);
|
|
1512
|
+
if (message) output.addMessage(message);
|
|
1513
|
+
return true;
|
|
1514
|
+
},
|
|
1515
|
+
);
|
|
1516
|
+
} catch (error) {
|
|
1517
|
+
deps.console.error(error);
|
|
1518
|
+
return err(ProtocolErrorCode.SyncError);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
if (iterateFailed) {
|
|
1410
1522
|
return err(ProtocolErrorCode.SyncError);
|
|
1411
1523
|
}
|
|
1412
1524
|
|
|
@@ -1449,7 +1561,7 @@ const sync =
|
|
|
1449
1561
|
};
|
|
1450
1562
|
|
|
1451
1563
|
const splitRange =
|
|
1452
|
-
(deps: StorageDep) =>
|
|
1564
|
+
(deps: StorageDep & ConsoleDep) =>
|
|
1453
1565
|
(
|
|
1454
1566
|
ownerId: OwnerIdBytes,
|
|
1455
1567
|
lower: NonNegativeInt,
|
|
@@ -1469,7 +1581,7 @@ const splitRange =
|
|
|
1469
1581
|
|
|
1470
1582
|
deps.storage.iterate(
|
|
1471
1583
|
ownerId,
|
|
1472
|
-
|
|
1584
|
+
zeroNonNegativeInt,
|
|
1473
1585
|
itemCount,
|
|
1474
1586
|
(timestamp) => {
|
|
1475
1587
|
range.timestamps.add(timestampBytesToTimestamp(timestamp));
|
|
@@ -1490,13 +1602,17 @@ const splitRange =
|
|
|
1490
1602
|
...buckets.value.map((b) => NonNegativeInt.orThrow(b + lower)),
|
|
1491
1603
|
];
|
|
1492
1604
|
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1605
|
+
let fingerprintRanges: ReadonlyArray<FingerprintRange>;
|
|
1606
|
+
try {
|
|
1607
|
+
fingerprintRanges = deps.storage.fingerprintRanges(
|
|
1608
|
+
ownerId,
|
|
1609
|
+
fingerprintRangesBuckets,
|
|
1610
|
+
upperBound,
|
|
1611
|
+
);
|
|
1612
|
+
} catch (error) {
|
|
1613
|
+
deps.console.error(error);
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1500
1616
|
|
|
1501
1617
|
const rangesToUse =
|
|
1502
1618
|
lower > 0 ? fingerprintRanges.slice(1) : fingerprintRanges;
|
|
@@ -1514,7 +1630,8 @@ const decodeRanges = (buffer: Buffer): ReadonlyArray<Range> => {
|
|
|
1514
1630
|
|
|
1515
1631
|
const timestampsCount = NonNegativeInt.orThrow(rangesCount - 1);
|
|
1516
1632
|
const timestamps = decodeTimestamps(buffer, timestampsCount);
|
|
1517
|
-
|
|
1633
|
+
|
|
1634
|
+
const rangeTypes = new Array<RangeType>(rangesCount);
|
|
1518
1635
|
|
|
1519
1636
|
for (let i = 0; i < rangesCount; i++) {
|
|
1520
1637
|
const rangeType = decodeNonNegativeInt(buffer);
|
|
@@ -1522,14 +1639,14 @@ const decodeRanges = (buffer: Buffer): ReadonlyArray<Range> => {
|
|
|
1522
1639
|
case RangeType.Fingerprint:
|
|
1523
1640
|
case RangeType.Skip:
|
|
1524
1641
|
case RangeType.Timestamps:
|
|
1525
|
-
rangeTypes
|
|
1642
|
+
rangeTypes[i] = rangeType as RangeType;
|
|
1526
1643
|
break;
|
|
1527
1644
|
default:
|
|
1528
1645
|
throw new ProtocolDecodeError(`Invalid RangeType: ${rangeType}`);
|
|
1529
1646
|
}
|
|
1530
1647
|
}
|
|
1531
1648
|
|
|
1532
|
-
const ranges
|
|
1649
|
+
const ranges = new Array<Range>(rangesCount);
|
|
1533
1650
|
|
|
1534
1651
|
for (let i = 0; i < rangesCount; i++) {
|
|
1535
1652
|
const upperBound =
|
|
@@ -1541,16 +1658,16 @@ const decodeRanges = (buffer: Buffer): ReadonlyArray<Range> => {
|
|
|
1541
1658
|
|
|
1542
1659
|
switch (rangeType) {
|
|
1543
1660
|
case RangeType.Skip:
|
|
1544
|
-
ranges
|
|
1661
|
+
ranges[i] = { type: RangeType.Skip, upperBound };
|
|
1545
1662
|
break;
|
|
1546
1663
|
|
|
1547
1664
|
case RangeType.Fingerprint: {
|
|
1548
1665
|
const fingerprint = buffer.shiftN(fingerprintSize) as Fingerprint;
|
|
1549
|
-
ranges
|
|
1666
|
+
ranges[i] = {
|
|
1550
1667
|
type: RangeType.Fingerprint,
|
|
1551
1668
|
upperBound,
|
|
1552
1669
|
fingerprint,
|
|
1553
|
-
}
|
|
1670
|
+
};
|
|
1554
1671
|
break;
|
|
1555
1672
|
}
|
|
1556
1673
|
|
|
@@ -1558,11 +1675,11 @@ const decodeRanges = (buffer: Buffer): ReadonlyArray<Range> => {
|
|
|
1558
1675
|
const timestamps = decodeTimestamps(buffer).map(
|
|
1559
1676
|
timestampToTimestampBytes,
|
|
1560
1677
|
);
|
|
1561
|
-
ranges
|
|
1678
|
+
ranges[i] = {
|
|
1562
1679
|
type: RangeType.Timestamps,
|
|
1563
1680
|
upperBound,
|
|
1564
1681
|
timestamps,
|
|
1565
|
-
}
|
|
1682
|
+
};
|
|
1566
1683
|
break;
|
|
1567
1684
|
}
|
|
1568
1685
|
}
|
|
@@ -1571,7 +1688,6 @@ const decodeRanges = (buffer: Buffer): ReadonlyArray<Range> => {
|
|
|
1571
1688
|
return ranges;
|
|
1572
1689
|
};
|
|
1573
1690
|
|
|
1574
|
-
/** Decodes an array of sorted timestamps with delta-encoded millis. */
|
|
1575
1691
|
const decodeTimestamps = (
|
|
1576
1692
|
buffer: Buffer,
|
|
1577
1693
|
length?: NonNegativeInt,
|
|
@@ -1580,50 +1696,68 @@ const decodeTimestamps = (
|
|
|
1580
1696
|
|
|
1581
1697
|
let previousMillis = 0 as Millis;
|
|
1582
1698
|
|
|
1583
|
-
const millises
|
|
1699
|
+
const millises = new Array<Millis>(length);
|
|
1584
1700
|
for (let i = 0; i < length; i++) {
|
|
1585
1701
|
const deltaMillis = decodeNonNegativeInt(buffer);
|
|
1586
1702
|
const millis = Millis.from(previousMillis + deltaMillis);
|
|
1587
1703
|
if (!millis.ok) throw new ProtocolDecodeError(millis.error.type);
|
|
1588
|
-
millises
|
|
1704
|
+
millises[i] = millis.value;
|
|
1589
1705
|
previousMillis = millis.value;
|
|
1590
1706
|
}
|
|
1591
1707
|
|
|
1592
|
-
const counters:
|
|
1593
|
-
let counterIndex = 0;
|
|
1594
|
-
while (counterIndex < length) {
|
|
1708
|
+
const counters = decodeRle(buffer, length, (): Counter => {
|
|
1595
1709
|
const counter = Counter.from(decodeNonNegativeInt(buffer));
|
|
1596
1710
|
if (!counter.ok) throw new ProtocolDecodeError(counter.error.type);
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
counters.push(counter.value);
|
|
1600
|
-
counterIndex++;
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1711
|
+
return counter.value;
|
|
1712
|
+
});
|
|
1603
1713
|
|
|
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
|
-
}
|
|
1714
|
+
const nodeIds = decodeRle(buffer, length, (): NodeId => decodeNodeId(buffer));
|
|
1614
1715
|
|
|
1615
|
-
const timestamps
|
|
1716
|
+
const timestamps = new Array<Timestamp>(length);
|
|
1616
1717
|
for (let i = 0; i < length; i++) {
|
|
1617
|
-
timestamps
|
|
1718
|
+
timestamps[i] = {
|
|
1618
1719
|
millis: millises[i],
|
|
1619
1720
|
counter: counters[i],
|
|
1620
1721
|
nodeId: nodeIds[i],
|
|
1621
|
-
}
|
|
1722
|
+
};
|
|
1622
1723
|
}
|
|
1623
1724
|
|
|
1624
1725
|
return timestamps;
|
|
1625
1726
|
};
|
|
1626
1727
|
|
|
1728
|
+
export const decodeRle = <T>(
|
|
1729
|
+
buffer: Buffer,
|
|
1730
|
+
length: NonNegativeInt,
|
|
1731
|
+
decodeValue: () => T,
|
|
1732
|
+
): ReadonlyArray<T> => {
|
|
1733
|
+
const values = new Array<T>(length);
|
|
1734
|
+
let index = 0;
|
|
1735
|
+
while (index < length) {
|
|
1736
|
+
const value = decodeValue();
|
|
1737
|
+
const runLength = decodeNonNegativeInt(buffer);
|
|
1738
|
+
|
|
1739
|
+
// Prevent infinite loop on malformed input.
|
|
1740
|
+
if (runLength === 0) {
|
|
1741
|
+
throw new ProtocolDecodeError(
|
|
1742
|
+
"Invalid RLE encoding: runLength must be positive",
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1745
|
+
const remaining = length - index;
|
|
1746
|
+
|
|
1747
|
+
// Prevent CPU/memory amplification via oversized runLength.
|
|
1748
|
+
if (runLength > remaining) {
|
|
1749
|
+
throw new ProtocolDecodeError(
|
|
1750
|
+
`Invalid RLE encoding: runLength ${runLength} exceeds remaining ${remaining}`,
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
for (let i = 0; i < runLength; i++) {
|
|
1754
|
+
values[index] = value;
|
|
1755
|
+
index++;
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
return values;
|
|
1759
|
+
};
|
|
1760
|
+
|
|
1627
1761
|
const decodeId = (buffer: Buffer): Id => {
|
|
1628
1762
|
const bytes = buffer.shiftN(idBytesTypeValueLength);
|
|
1629
1763
|
return idBytesToId(bytes as IdBytes);
|
|
@@ -1699,9 +1833,10 @@ export const decodeFlags = (
|
|
|
1699
1833
|
count: PositiveInt,
|
|
1700
1834
|
): ReadonlyArray<boolean> => {
|
|
1701
1835
|
const byte = buffer.shift();
|
|
1702
|
-
const
|
|
1703
|
-
|
|
1704
|
-
|
|
1836
|
+
const length = globalThis.Math.min(count, 8);
|
|
1837
|
+
const flags = new Array<boolean>(length);
|
|
1838
|
+
for (let i = 0; i < length; i++) {
|
|
1839
|
+
flags[i] = (byte & (1 << i)) !== 0;
|
|
1705
1840
|
}
|
|
1706
1841
|
return flags;
|
|
1707
1842
|
};
|
|
@@ -1715,7 +1850,7 @@ export const decodeFlags = (
|
|
|
1715
1850
|
* data.
|
|
1716
1851
|
*/
|
|
1717
1852
|
export const encodeAndEncryptDbChange =
|
|
1718
|
-
(deps:
|
|
1853
|
+
(deps: RandomBytesDep) =>
|
|
1719
1854
|
(message: CrdtMessage, key: EncryptionKey): EncryptedDbChange => {
|
|
1720
1855
|
const buffer = createBuffer();
|
|
1721
1856
|
|
|
@@ -1727,6 +1862,7 @@ export const encodeAndEncryptDbChange =
|
|
|
1727
1862
|
|
|
1728
1863
|
encodeFlags(buffer, [
|
|
1729
1864
|
message.change.isInsert,
|
|
1865
|
+
// Encode nullable boolean as two flags: presence + value.
|
|
1730
1866
|
message.change.isDelete != null,
|
|
1731
1867
|
message.change.isDelete ?? false,
|
|
1732
1868
|
]);
|
|
@@ -1745,7 +1881,7 @@ export const encodeAndEncryptDbChange =
|
|
|
1745
1881
|
// Add PADMÉ padding (ignored during decoding)
|
|
1746
1882
|
buffer.extend(createPadmePadding(buffer.getLength()));
|
|
1747
1883
|
|
|
1748
|
-
const
|
|
1884
|
+
const [ciphertext, nonce] = encryptWithXChaCha20Poly1305(deps)(
|
|
1749
1885
|
buffer.unwrap(),
|
|
1750
1886
|
key,
|
|
1751
1887
|
);
|
|
@@ -1763,78 +1899,76 @@ export const encodeAndEncryptDbChange =
|
|
|
1763
1899
|
* owner's encryption key. Verifies that the embedded timestamp matches the
|
|
1764
1900
|
* expected timestamp to ensure message integrity.
|
|
1765
1901
|
*/
|
|
1766
|
-
export const decryptAndDecodeDbChange =
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
try {
|
|
1778
|
-
const buffer = createBuffer(message.change);
|
|
1902
|
+
export const decryptAndDecodeDbChange = (
|
|
1903
|
+
message: EncryptedCrdtMessage,
|
|
1904
|
+
key: EncryptionKey,
|
|
1905
|
+
): Result<
|
|
1906
|
+
DbChange,
|
|
1907
|
+
| DecryptWithXChaCha20Poly1305Error
|
|
1908
|
+
| ProtocolInvalidDataError
|
|
1909
|
+
| ProtocolTimestampMismatchError
|
|
1910
|
+
> => {
|
|
1911
|
+
try {
|
|
1912
|
+
const buffer = createBuffer(message.change);
|
|
1779
1913
|
|
|
1780
|
-
|
|
1781
|
-
|
|
1914
|
+
const nonce = buffer.shiftN(xChaCha20Poly1305NonceLength as NonNegativeInt);
|
|
1915
|
+
const ciphertext = buffer.shiftN(decodeLength(buffer));
|
|
1782
1916
|
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1917
|
+
const plaintextBytes = decryptWithXChaCha20Poly1305(
|
|
1918
|
+
XChaCha20Poly1305Ciphertext.orThrow(ciphertext),
|
|
1919
|
+
Entropy24.orThrow(nonce),
|
|
1920
|
+
key,
|
|
1921
|
+
);
|
|
1922
|
+
if (!plaintextBytes.ok) return plaintextBytes;
|
|
1789
1923
|
|
|
1790
|
-
|
|
1791
|
-
|
|
1924
|
+
buffer.reset();
|
|
1925
|
+
buffer.extend(plaintextBytes.value);
|
|
1792
1926
|
|
|
1793
|
-
|
|
1794
|
-
|
|
1927
|
+
// Decode version (for future compatibility, not need yet)
|
|
1928
|
+
decodeNonNegativeInt(buffer);
|
|
1795
1929
|
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1930
|
+
const timestamp = timestampBytesToTimestamp(
|
|
1931
|
+
TimestampBytes.orThrow(buffer.shiftN(timestampBytesLength)),
|
|
1932
|
+
);
|
|
1799
1933
|
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1934
|
+
if (!eqTimestamp(timestamp, message.timestamp)) {
|
|
1935
|
+
return err<ProtocolTimestampMismatchError>({
|
|
1936
|
+
type: "ProtocolTimestampMismatchError",
|
|
1937
|
+
expected: message.timestamp,
|
|
1938
|
+
timestamp,
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1807
1941
|
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1942
|
+
const flags = decodeFlags(buffer, PositiveInt.orThrow(3));
|
|
1943
|
+
const table = decodeString(buffer);
|
|
1944
|
+
const id = decodeId(buffer);
|
|
1811
1945
|
|
|
1812
|
-
|
|
1813
|
-
|
|
1946
|
+
const length = decodeLength(buffer);
|
|
1947
|
+
const values = createRecord<string, SqliteValue>();
|
|
1814
1948
|
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1949
|
+
for (let i = 0; i < length; i++) {
|
|
1950
|
+
const column = decodeString(buffer);
|
|
1951
|
+
const value = decodeSqliteValue(buffer);
|
|
1952
|
+
values[column] = value;
|
|
1953
|
+
}
|
|
1820
1954
|
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1955
|
+
const dbChange = DbChange.orThrow({
|
|
1956
|
+
table,
|
|
1957
|
+
id,
|
|
1958
|
+
values,
|
|
1959
|
+
isInsert: flags[0],
|
|
1960
|
+
isDelete: flags[1] ? flags[2] : null,
|
|
1961
|
+
});
|
|
1828
1962
|
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1963
|
+
return ok(dbChange);
|
|
1964
|
+
} catch (error) {
|
|
1965
|
+
return err<ProtocolInvalidDataError>({
|
|
1966
|
+
type: "ProtocolInvalidDataError",
|
|
1967
|
+
data: message.change,
|
|
1968
|
+
error,
|
|
1969
|
+
});
|
|
1970
|
+
}
|
|
1971
|
+
};
|
|
1838
1972
|
|
|
1839
1973
|
/**
|
|
1840
1974
|
* Encodes a non-negative integer into a variable-length integer format. It's
|
|
@@ -1926,26 +2060,26 @@ export const ProtocolValueType = {
|
|
|
1926
2060
|
// 0-19 small ints
|
|
1927
2061
|
|
|
1928
2062
|
// SQLite types
|
|
1929
|
-
String: NonNegativeInt.orThrow(20),
|
|
1930
|
-
Number: NonNegativeInt.orThrow(21),
|
|
1931
|
-
Null: NonNegativeInt.orThrow(22),
|
|
1932
|
-
Bytes: NonNegativeInt.orThrow(23),
|
|
2063
|
+
String: /*#__PURE__*/ NonNegativeInt.orThrow(20),
|
|
2064
|
+
Number: /*#__PURE__*/ NonNegativeInt.orThrow(21),
|
|
2065
|
+
Null: /*#__PURE__*/ NonNegativeInt.orThrow(22),
|
|
2066
|
+
Bytes: /*#__PURE__*/ NonNegativeInt.orThrow(23),
|
|
1933
2067
|
// We can add more types for other DBs or anything else later.
|
|
1934
2068
|
|
|
1935
2069
|
// Optimized types
|
|
1936
|
-
NonNegativeInt: NonNegativeInt.orThrow(30),
|
|
2070
|
+
NonNegativeInt: /*#__PURE__*/ NonNegativeInt.orThrow(30),
|
|
1937
2071
|
|
|
1938
2072
|
// 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),
|
|
2073
|
+
EmptyString: /*#__PURE__*/ NonNegativeInt.orThrow(31), // 1 byte vs 2 bytes (50% reduction)
|
|
2074
|
+
Base64Url: /*#__PURE__*/ NonNegativeInt.orThrow(32),
|
|
2075
|
+
Id: /*#__PURE__*/ NonNegativeInt.orThrow(33),
|
|
2076
|
+
Json: /*#__PURE__*/ NonNegativeInt.orThrow(34),
|
|
1943
2077
|
|
|
1944
2078
|
// new Date().toISOString() - 24 bytes
|
|
1945
2079
|
// encoded with fixed length - 8 bytes
|
|
1946
2080
|
// encode as NonNegativeInt - 6 bytes (additional 25% reduction)
|
|
1947
|
-
DateIsoWithNonNegativeTime: NonNegativeInt.orThrow(35),
|
|
1948
|
-
DateIsoWithNegativeTime: NonNegativeInt.orThrow(36), // 9 bytes
|
|
2081
|
+
DateIsoWithNonNegativeTime: /*#__PURE__*/ NonNegativeInt.orThrow(35),
|
|
2082
|
+
DateIsoWithNegativeTime: /*#__PURE__*/ NonNegativeInt.orThrow(36), // 9 bytes
|
|
1949
2083
|
|
|
1950
2084
|
// TODO: Operations (from 40)
|
|
1951
2085
|
// Increment, Decrement, Patch, whatever.
|