@evolu/common 6.0.1-preview.5 → 6.0.1-preview.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/Assert.d.ts +6 -3
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +6 -3
- package/dist/src/Evolu/Db.js +1 -0
- package/dist/src/Evolu/Protocol.d.ts +67 -25
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +146 -37
- package/dist/src/Evolu/Relay.d.ts.map +1 -1
- package/dist/src/Evolu/Relay.js +38 -1
- package/dist/src/Evolu/Storage.d.ts +1 -0
- package/dist/src/Evolu/Storage.d.ts.map +1 -1
- package/dist/src/Evolu/Storage.js +10 -0
- package/package.json +1 -1
- package/src/Assert.ts +6 -3
- package/src/Evolu/Db.ts +1 -0
- package/src/Evolu/Protocol.ts +184 -57
- package/src/Evolu/Relay.ts +40 -1
- package/src/Evolu/Storage.ts +12 -0
package/dist/src/Assert.d.ts
CHANGED
|
@@ -16,9 +16,12 @@
|
|
|
16
16
|
* assert(true, "true is not true"); // no-op
|
|
17
17
|
* assert(false, "true is not true"); // throws Error
|
|
18
18
|
*
|
|
19
|
-
* const
|
|
20
|
-
* //
|
|
21
|
-
* assert(
|
|
19
|
+
* const length = buffer.getLength();
|
|
20
|
+
* // We know length is logically non-negative, but TypeScript doesn't
|
|
21
|
+
* assert(
|
|
22
|
+
* NonNegativeInt.is(length),
|
|
23
|
+
* "buffer length should be non-negative",
|
|
24
|
+
* );
|
|
22
25
|
* ```
|
|
23
26
|
*/
|
|
24
27
|
export declare const assert: (condition: unknown, message: string) => asserts condition;
|
package/dist/src/Assert.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Assert.d.ts","sourceRoot":"","sources":["../../src/Assert.ts"],"names":[],"mappings":"AAgBA
|
|
1
|
+
{"version":3,"file":"Assert.d.ts","sourceRoot":"","sources":["../../src/Assert.ts"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,MAAM,EAAE,CACnB,SAAS,EAAE,OAAO,EAClB,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC,SAIZ,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,mBAAmB,EAAE,CAAC,CAAC,EAClC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,EACb,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAKlC,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,2BAA2B,EAAE,CAAC,CAAC,EAC1C,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,EACrB,OAAO,CAAC,EAAE,MAAM,KACb,OAAO,CAAC,GAAG,IAAI,SAAS,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAK3C,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,KAAK,CAO3E"}
|
package/dist/src/Assert.js
CHANGED
|
@@ -16,9 +16,12 @@
|
|
|
16
16
|
* assert(true, "true is not true"); // no-op
|
|
17
17
|
* assert(false, "true is not true"); // throws Error
|
|
18
18
|
*
|
|
19
|
-
* const
|
|
20
|
-
* //
|
|
21
|
-
* assert(
|
|
19
|
+
* const length = buffer.getLength();
|
|
20
|
+
* // We know length is logically non-negative, but TypeScript doesn't
|
|
21
|
+
* assert(
|
|
22
|
+
* NonNegativeInt.is(length),
|
|
23
|
+
* "buffer length should be non-negative",
|
|
24
|
+
* );
|
|
22
25
|
* ```
|
|
23
26
|
*/
|
|
24
27
|
export const assert = (condition, message) => {
|
package/dist/src/Evolu/Db.js
CHANGED
|
@@ -720,6 +720,7 @@ const createClientStorage = (deps) => (options) => {
|
|
|
720
720
|
const storage = {
|
|
721
721
|
...sqliteStorageBase.value,
|
|
722
722
|
validateWriteKey: constFalse,
|
|
723
|
+
setWriteKey: constFalse,
|
|
723
724
|
writeMessages: (_ownerId, messages) => {
|
|
724
725
|
// TODO: Get owner by _ownerId when we support more.
|
|
725
726
|
const owner = deps.ownerRowRef.get();
|
|
@@ -15,21 +15,39 @@
|
|
|
15
15
|
*
|
|
16
16
|
* ### Message Structure
|
|
17
17
|
*
|
|
18
|
-
* | Field | Notes
|
|
19
|
-
* | :----------------------------- |
|
|
20
|
-
* | **Header** |
|
|
21
|
-
* | - {@link protocolVersion} |
|
|
22
|
-
* | - {@link OwnerId} |
|
|
23
|
-
* |
|
|
24
|
-
* |
|
|
25
|
-
* | - {@link
|
|
26
|
-
* | - {@link
|
|
27
|
-
* | -
|
|
28
|
-
* |
|
|
29
|
-
* |
|
|
30
|
-
* | - {@link
|
|
31
|
-
*
|
|
32
|
-
*
|
|
18
|
+
* | Field | Notes |
|
|
19
|
+
* | :----------------------------- | :------------------------ |
|
|
20
|
+
* | **Header** | |
|
|
21
|
+
* | - {@link protocolVersion} | |
|
|
22
|
+
* | - {@link OwnerId} | {@link Owner} |
|
|
23
|
+
* | **Initiator** | |
|
|
24
|
+
* | - {@link WriteKeyMode} | |
|
|
25
|
+
* | - {@link WriteKey} | If WriteKeyMode >= 1 |
|
|
26
|
+
* | - {@link WriteKey} | If WriteKeyMode = 2 (new) |
|
|
27
|
+
* | **Non-initiator** | |
|
|
28
|
+
* | - {@link ProtocolErrorCode} | |
|
|
29
|
+
* | **Messages** | |
|
|
30
|
+
* | - {@link NonNegativeInt} | A number of messages. |
|
|
31
|
+
* | - {@link EncryptedCrdtMessage} | |
|
|
32
|
+
* | **Ranges** | |
|
|
33
|
+
* | - {@link NonNegativeInt} | Number of ranges. |
|
|
34
|
+
* | - {@link Range} | |
|
|
35
|
+
*
|
|
36
|
+
* ### WriteKey Validation
|
|
37
|
+
*
|
|
38
|
+
* The initiator sends WriteKeyMode and optionally one or two WriteKeys. One key
|
|
39
|
+
* for write operations and two for key rotation (current and new). Note that
|
|
40
|
+
* it's ok to not send any key if initiator is going to be synced with readonly
|
|
41
|
+
* owner. The non-initiator validates them immediately after parsing the
|
|
42
|
+
* initiator header, before processing any messages or ranges.
|
|
43
|
+
*
|
|
44
|
+
* ### WriteKey Rotation
|
|
45
|
+
*
|
|
46
|
+
* When initiator's {@link WriteKeyMode} is `Rotation`, two WriteKeys are
|
|
47
|
+
* present:
|
|
48
|
+
*
|
|
49
|
+
* 1. Current WriteKey (for validation)
|
|
50
|
+
* 2. New WriteKey (to be stored)
|
|
33
51
|
*
|
|
34
52
|
* ### Synchronization
|
|
35
53
|
*
|
|
@@ -147,6 +165,11 @@ export declare const ProtocolErrorCode: {
|
|
|
147
165
|
readonly SyncError: 3;
|
|
148
166
|
};
|
|
149
167
|
type ProtocolErrorCode = (typeof ProtocolErrorCode)[keyof typeof ProtocolErrorCode];
|
|
168
|
+
export declare const WriteKeyMode: {
|
|
169
|
+
readonly None: 0;
|
|
170
|
+
readonly Single: 1;
|
|
171
|
+
readonly Rotation: 2;
|
|
172
|
+
};
|
|
150
173
|
/**
|
|
151
174
|
* Evolu Protocol Storage
|
|
152
175
|
*
|
|
@@ -168,17 +191,16 @@ export interface Storage {
|
|
|
168
191
|
readonly fingerprintRanges: (ownerId: BinaryOwnerId, buckets: ReadonlyArray<NonNegativeInt>, upperBound?: RangeUpperBound) => ReadonlyArray<FingerprintRange> | null;
|
|
169
192
|
readonly findLowerBound: (ownerId: BinaryOwnerId, begin: NonNegativeInt, end: NonNegativeInt, upperBound: RangeUpperBound) => NonNegativeInt | null;
|
|
170
193
|
readonly iterate: (ownerId: BinaryOwnerId, begin: NonNegativeInt, end: NonNegativeInt, callback: (timestamp: BinaryTimestamp, index: NonNegativeInt) => boolean) => void;
|
|
171
|
-
/**
|
|
172
|
-
* Authorizes the initiator's {@link WriteKey} for the given
|
|
173
|
-
* {@link BinaryOwnerId}.
|
|
174
|
-
*
|
|
175
|
-
* For a client that does not expect foreign writes, return `false`.
|
|
176
|
-
*/
|
|
194
|
+
/** Validates the {@link WriteKey} for the given {@link Owner}. */
|
|
177
195
|
readonly validateWriteKey: (ownerId: BinaryOwnerId, writeKey: WriteKey) => boolean;
|
|
196
|
+
/** Sets the {@link WriteKey} for the given {@link Owner}. */
|
|
197
|
+
readonly setWriteKey: (ownerId: BinaryOwnerId, writeKey: WriteKey) => boolean;
|
|
178
198
|
/** Write encrypted {@link CrdtMessage}s to storage. */
|
|
179
199
|
readonly writeMessages: (ownerId: BinaryOwnerId, messages: NonEmptyReadonlyArray<EncryptedCrdtMessage>) => boolean;
|
|
180
200
|
/** Read encrypted {@link DbChange}s from storage. */
|
|
181
201
|
readonly readDbChange: (ownerId: BinaryOwnerId, timestamp: BinaryTimestamp) => EncryptedDbChange | null;
|
|
202
|
+
/** Delete all data for the given {@link Owner}. */
|
|
203
|
+
readonly deleteOwner: (ownerId: BinaryOwnerId) => boolean;
|
|
182
204
|
}
|
|
183
205
|
export interface StorageDep {
|
|
184
206
|
readonly storage: Storage;
|
|
@@ -306,6 +328,8 @@ export interface ProtocolSyncError extends ProtocolErrorBase {
|
|
|
306
328
|
export declare const createProtocolMessageFromCrdtMessages: (deps: SymmetricCryptoDep & CreateRandomBytesDep) => (owner: OwnerWithWriteAccess, messages: NonEmptyReadonlyArray<CrdtMessage>, maxSize?: PositiveInt) => ProtocolMessage;
|
|
307
329
|
/** Creates a {@link ProtocolMessage} for sync. */
|
|
308
330
|
export declare const createProtocolMessageForSync: (deps: StorageDep) => (ownerId: OwnerId) => ProtocolMessage | null;
|
|
331
|
+
/** Creates a ProtocolMessage for {@link WriteKey} rotation. */
|
|
332
|
+
export declare const createProtocolMessageForWriteKeyRotation: (ownerId: OwnerId, currentWriteKey: WriteKey, newWriteKey: WriteKey) => ProtocolMessage;
|
|
309
333
|
/**
|
|
310
334
|
* Mutable builder for constructing {@link ProtocolMessage} respecting size
|
|
311
335
|
* limits.
|
|
@@ -319,13 +343,18 @@ export interface ProtocolMessageBuffer {
|
|
|
319
343
|
readonly unwrap: () => ProtocolMessage;
|
|
320
344
|
readonly getSize: () => PositiveInt;
|
|
321
345
|
}
|
|
322
|
-
export declare const createProtocolMessageBuffer: (ownerId: OwnerId, options
|
|
323
|
-
readonly errorCode?: ProtocolErrorCode;
|
|
324
|
-
readonly writeKey?: WriteKey;
|
|
346
|
+
export declare const createProtocolMessageBuffer: (ownerId: OwnerId, options: {
|
|
325
347
|
readonly totalMaxSize?: PositiveInt | undefined;
|
|
326
348
|
readonly rangesMaxSize?: PositiveInt | undefined;
|
|
327
349
|
readonly version?: NonNegativeInt;
|
|
328
|
-
}
|
|
350
|
+
} & ({
|
|
351
|
+
readonly type: "initiator";
|
|
352
|
+
/** Single key or [current, new] for rotation. */
|
|
353
|
+
readonly writeKey?: WriteKey | readonly [WriteKey, WriteKey];
|
|
354
|
+
} | {
|
|
355
|
+
readonly type: "non-initiator";
|
|
356
|
+
readonly errorCode: ProtocolErrorCode;
|
|
357
|
+
})) => ProtocolMessageBuffer;
|
|
329
358
|
export interface TimestampsBuffer {
|
|
330
359
|
readonly add: (timestamp: Timestamp) => void;
|
|
331
360
|
readonly addInfinite: () => void;
|
|
@@ -425,5 +454,18 @@ export declare const ProtocolValueType: {
|
|
|
425
454
|
};
|
|
426
455
|
export declare const encodeSqliteValue: (buffer: Buffer, value: SqliteValue) => void;
|
|
427
456
|
export declare const decodeSqliteValue: (buffer: Buffer) => SqliteValue;
|
|
457
|
+
/**
|
|
458
|
+
* Decodes a ProtocolMessage into a readable JSON object for debugging.
|
|
459
|
+
*
|
|
460
|
+
* Note: This is a stub for future implementation. It should use:
|
|
461
|
+
*
|
|
462
|
+
* - DecodeVersionAndOwner
|
|
463
|
+
* - DecodeError or decodeWriteKeys (depending on context)
|
|
464
|
+
* - DecodeMessages
|
|
465
|
+
* - DecodeRanges
|
|
466
|
+
*
|
|
467
|
+
* If you want to help, please contribute to this function.
|
|
468
|
+
*/
|
|
469
|
+
export declare const decodeProtocolMessageToJson: (_protocolMessage: ProtocolMessage, _isInitiator: boolean) => unknown;
|
|
428
470
|
export {};
|
|
429
471
|
//# sourceMappingURL=Protocol.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Protocol.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Protocol.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"Protocol.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,21 +15,39 @@
|
|
|
15
15
|
*
|
|
16
16
|
* ### Message Structure
|
|
17
17
|
*
|
|
18
|
-
* | Field | Notes
|
|
19
|
-
* | :----------------------------- |
|
|
20
|
-
* | **Header** |
|
|
21
|
-
* | - {@link protocolVersion} |
|
|
22
|
-
* | - {@link OwnerId} |
|
|
23
|
-
* |
|
|
24
|
-
* |
|
|
25
|
-
* | - {@link
|
|
26
|
-
* | - {@link
|
|
27
|
-
* | -
|
|
28
|
-
* |
|
|
29
|
-
* |
|
|
30
|
-
* | - {@link
|
|
18
|
+
* | Field | Notes |
|
|
19
|
+
* | :----------------------------- | :------------------------ |
|
|
20
|
+
* | **Header** | |
|
|
21
|
+
* | - {@link protocolVersion} | |
|
|
22
|
+
* | - {@link OwnerId} | {@link Owner} |
|
|
23
|
+
* | **Initiator** | |
|
|
24
|
+
* | - {@link WriteKeyMode} | |
|
|
25
|
+
* | - {@link WriteKey} | If WriteKeyMode >= 1 |
|
|
26
|
+
* | - {@link WriteKey} | If WriteKeyMode = 2 (new) |
|
|
27
|
+
* | **Non-initiator** | |
|
|
28
|
+
* | - {@link ProtocolErrorCode} | |
|
|
29
|
+
* | **Messages** | |
|
|
30
|
+
* | - {@link NonNegativeInt} | A number of messages. |
|
|
31
|
+
* | - {@link EncryptedCrdtMessage} | |
|
|
32
|
+
* | **Ranges** | |
|
|
33
|
+
* | - {@link NonNegativeInt} | Number of ranges. |
|
|
34
|
+
* | - {@link Range} | |
|
|
31
35
|
*
|
|
32
|
-
*
|
|
36
|
+
* ### WriteKey Validation
|
|
37
|
+
*
|
|
38
|
+
* The initiator sends WriteKeyMode and optionally one or two WriteKeys. One key
|
|
39
|
+
* for write operations and two for key rotation (current and new). Note that
|
|
40
|
+
* it's ok to not send any key if initiator is going to be synced with readonly
|
|
41
|
+
* owner. The non-initiator validates them immediately after parsing the
|
|
42
|
+
* initiator header, before processing any messages or ranges.
|
|
43
|
+
*
|
|
44
|
+
* ### WriteKey Rotation
|
|
45
|
+
*
|
|
46
|
+
* When initiator's {@link WriteKeyMode} is `Rotation`, two WriteKeys are
|
|
47
|
+
* present:
|
|
48
|
+
*
|
|
49
|
+
* 1. Current WriteKey (for validation)
|
|
50
|
+
* 2. New WriteKey (to be stored)
|
|
33
51
|
*
|
|
34
52
|
* ### Synchronization
|
|
35
53
|
*
|
|
@@ -124,7 +142,7 @@ import { sha256 } from "@noble/hashes/sha2";
|
|
|
124
142
|
import { pack, unpack, unpackMultiple } from "msgpackr";
|
|
125
143
|
import { isNonEmptyReadonlyArray } from "../Array.js";
|
|
126
144
|
import { assert } from "../Assert.js";
|
|
127
|
-
import { BufferError, bytesToHex, bytesToUtf8,
|
|
145
|
+
import { BufferError, bytesToHex, bytesToUtf8, createBuffer, hexToBytes, utf8ToBytes, } from "../Buffer.js";
|
|
128
146
|
import { padmePaddingLength, } from "../Crypto.js";
|
|
129
147
|
import { eqArrayNumber } from "../Eq.js";
|
|
130
148
|
import { computeBalancedBuckets } from "../Number.js";
|
|
@@ -149,6 +167,11 @@ export const ProtocolErrorCode = {
|
|
|
149
167
|
/** A code for {@link ProtocolSyncError}. */
|
|
150
168
|
SyncError: 3,
|
|
151
169
|
};
|
|
170
|
+
export const WriteKeyMode = {
|
|
171
|
+
None: 0,
|
|
172
|
+
Single: 1,
|
|
173
|
+
Rotation: 2,
|
|
174
|
+
};
|
|
152
175
|
/**
|
|
153
176
|
* Base64Url string with maximum length of 256 characters. Encoding strings as
|
|
154
177
|
* Base64UrlString saves up to 25% in size compared to regular strings.
|
|
@@ -181,6 +204,7 @@ export const zeroFingerprint = new Uint8Array(fingerprintSize);
|
|
|
181
204
|
*/
|
|
182
205
|
export const createProtocolMessageFromCrdtMessages = (deps) => (owner, messages, maxSize) => {
|
|
183
206
|
const buffer = createProtocolMessageBuffer(owner.id, {
|
|
207
|
+
type: "initiator",
|
|
184
208
|
totalMaxSize: maxSize ?? maxProtocolMessageSize,
|
|
185
209
|
writeKey: owner.writeKey,
|
|
186
210
|
});
|
|
@@ -224,7 +248,7 @@ export const createProtocolMessageFromCrdtMessages = (deps) => (owner, messages,
|
|
|
224
248
|
};
|
|
225
249
|
/** Creates a {@link ProtocolMessage} for sync. */
|
|
226
250
|
export const createProtocolMessageForSync = (deps) => (ownerId) => {
|
|
227
|
-
const buffer = createProtocolMessageBuffer(ownerId);
|
|
251
|
+
const buffer = createProtocolMessageBuffer(ownerId, { type: "initiator" });
|
|
228
252
|
const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
|
|
229
253
|
const size = deps.storage.getSize(binaryOwnerId);
|
|
230
254
|
// Errors are handled by the storage.
|
|
@@ -233,8 +257,16 @@ export const createProtocolMessageForSync = (deps) => (ownerId) => {
|
|
|
233
257
|
splitRange(deps)(binaryOwnerId, 0, size, InfiniteUpperBound, buffer);
|
|
234
258
|
return buffer.unwrap();
|
|
235
259
|
};
|
|
236
|
-
|
|
237
|
-
|
|
260
|
+
/** Creates a ProtocolMessage for {@link WriteKey} rotation. */
|
|
261
|
+
export const createProtocolMessageForWriteKeyRotation = (ownerId, currentWriteKey, newWriteKey) => {
|
|
262
|
+
const buffer = createProtocolMessageBuffer(ownerId, {
|
|
263
|
+
type: "initiator",
|
|
264
|
+
writeKey: [currentWriteKey, newWriteKey],
|
|
265
|
+
});
|
|
266
|
+
return buffer.unwrap();
|
|
267
|
+
};
|
|
268
|
+
export const createProtocolMessageBuffer = (ownerId, options) => {
|
|
269
|
+
const { totalMaxSize = maxProtocolMessageSize, rangesMaxSize = maxProtocolMessageRangesSize, version = protocolVersion, } = options;
|
|
238
270
|
const buffers = {
|
|
239
271
|
header: createBuffer(),
|
|
240
272
|
messages: {
|
|
@@ -249,17 +281,29 @@ export const createProtocolMessageBuffer = (ownerId, options = {}) => {
|
|
|
249
281
|
};
|
|
250
282
|
encodeNonNegativeInt(buffers.header, version);
|
|
251
283
|
buffers.header.extend(ownerIdToBinaryOwnerId(ownerId));
|
|
252
|
-
if (
|
|
253
|
-
|
|
284
|
+
if (options.type === "initiator") {
|
|
285
|
+
if (!options.writeKey) {
|
|
286
|
+
buffers.header.extend([WriteKeyMode.None]);
|
|
287
|
+
}
|
|
288
|
+
else if (!Array.isArray(options.writeKey)) {
|
|
289
|
+
buffers.header.extend([WriteKeyMode.Single]);
|
|
290
|
+
buffers.header.extend(options.writeKey);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
buffers.header.extend([WriteKeyMode.Rotation]);
|
|
294
|
+
buffers.header.extend(options.writeKey[0]); // current
|
|
295
|
+
buffers.header.extend(options.writeKey[1]); // new
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
buffers.header.extend([options.errorCode]);
|
|
300
|
+
}
|
|
254
301
|
let isLastRangeInfinite = false;
|
|
255
302
|
const isWithinSizeLimits = () => getSize() <= totalMaxSize;
|
|
256
303
|
const getSize = () => (getHeaderAndMessagesSize() + getRangesSize());
|
|
257
304
|
const getHeaderAndMessagesSize = () => buffers.header.getLength() +
|
|
258
305
|
buffers.messages.timestamps.getLength() +
|
|
259
|
-
buffers.messages.dbChanges.getLength()
|
|
260
|
-
(buffers.messages.timestamps.getCount() > 0 && writeKey
|
|
261
|
-
? writeKeyLength
|
|
262
|
-
: 0);
|
|
306
|
+
buffers.messages.dbChanges.getLength();
|
|
263
307
|
const getRangesSize = () => buffers.ranges.timestamps.getCount() > 0
|
|
264
308
|
? buffers.ranges.timestamps.getLength() +
|
|
265
309
|
buffers.ranges.types.getLength() +
|
|
@@ -345,8 +389,6 @@ export const createProtocolMessageBuffer = (ownerId, options = {}) => {
|
|
|
345
389
|
}
|
|
346
390
|
buffers.messages.timestamps.append(buffers.header);
|
|
347
391
|
buffers.header.extend(buffers.messages.dbChanges.unwrap());
|
|
348
|
-
if (buffers.messages.timestamps.getCount() > 0 && writeKey)
|
|
349
|
-
buffers.header.extend(writeKey);
|
|
350
392
|
if (buffers.ranges.timestamps.getCount() > 0) {
|
|
351
393
|
buffers.ranges.timestamps.append(buffers.header);
|
|
352
394
|
buffers.header.extend(buffers.ranges.types.unwrap());
|
|
@@ -434,7 +476,6 @@ export const applyProtocolMessageAsClient = (deps) => (inputMessage, { getWriteK
|
|
|
434
476
|
ownerId,
|
|
435
477
|
});
|
|
436
478
|
}
|
|
437
|
-
const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
|
|
438
479
|
const errorCode = input.shift();
|
|
439
480
|
if (errorCode !== ProtocolErrorCode.NoError) {
|
|
440
481
|
switch (errorCode) {
|
|
@@ -458,6 +499,7 @@ export const applyProtocolMessageAsClient = (deps) => (inputMessage, { getWriteK
|
|
|
458
499
|
}
|
|
459
500
|
}
|
|
460
501
|
const messages = decodeMessages(input);
|
|
502
|
+
const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
|
|
461
503
|
if (isNonEmptyReadonlyArray(messages) &&
|
|
462
504
|
!deps.storage.writeMessages(binaryOwnerId, messages)) {
|
|
463
505
|
return ok(null);
|
|
@@ -468,6 +510,7 @@ export const applyProtocolMessageAsClient = (deps) => (inputMessage, { getWriteK
|
|
|
468
510
|
if (writeKey == null)
|
|
469
511
|
return ok(null);
|
|
470
512
|
const output = createProtocolMessageBuffer(ownerId, {
|
|
513
|
+
type: "initiator",
|
|
471
514
|
writeKey,
|
|
472
515
|
totalMaxSize,
|
|
473
516
|
rangesMaxSize,
|
|
@@ -477,8 +520,7 @@ export const applyProtocolMessageAsClient = (deps) => (inputMessage, { getWriteK
|
|
|
477
520
|
export const applyProtocolMessageAsRelay = (deps) => (inputMessage, { subscribe, broadcast, totalMaxSize, rangesMaxSize, } = {},
|
|
478
521
|
/** For testing purposes only; should not be used in production. */
|
|
479
522
|
version = protocolVersion) => tryDecodeProtocolData(inputMessage, (input) => {
|
|
480
|
-
const requestedVersion =
|
|
481
|
-
const ownerId = decodeOwnerId(input);
|
|
523
|
+
const [requestedVersion, ownerId] = decodeVersionAndOwner(input);
|
|
482
524
|
const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
|
|
483
525
|
if (requestedVersion !== version) {
|
|
484
526
|
// Non-initiator responds with its version and ownerId.
|
|
@@ -488,26 +530,68 @@ version = protocolVersion) => tryDecodeProtocolData(inputMessage, (input) => {
|
|
|
488
530
|
return ok(output.unwrap());
|
|
489
531
|
}
|
|
490
532
|
subscribe?.(ownerId);
|
|
533
|
+
const writeKeyMode = input.shift();
|
|
534
|
+
let writeKey;
|
|
535
|
+
let newWriteKey;
|
|
536
|
+
if (writeKeyMode !== WriteKeyMode.None) {
|
|
537
|
+
writeKey = input.shiftN(writeKeyLength);
|
|
538
|
+
switch (writeKeyMode) {
|
|
539
|
+
case WriteKeyMode.Single:
|
|
540
|
+
break;
|
|
541
|
+
case WriteKeyMode.Rotation:
|
|
542
|
+
newWriteKey = input.shiftN(writeKeyLength);
|
|
543
|
+
break;
|
|
544
|
+
default:
|
|
545
|
+
throw new ProtocolDecodeError(`Invalid WriteKeyMode: ${writeKeyMode}`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (writeKey) {
|
|
549
|
+
const isValid = deps.storage.validateWriteKey(binaryOwnerId, writeKey);
|
|
550
|
+
if (!isValid) {
|
|
551
|
+
return ok(createProtocolMessageBuffer(ownerId, {
|
|
552
|
+
type: "non-initiator",
|
|
553
|
+
errorCode: ProtocolErrorCode.WriteKeyError,
|
|
554
|
+
}).unwrap());
|
|
555
|
+
}
|
|
556
|
+
if (newWriteKey) {
|
|
557
|
+
const rotationSuccess = deps.storage.setWriteKey(binaryOwnerId, newWriteKey);
|
|
558
|
+
if (!rotationSuccess) {
|
|
559
|
+
return ok(createProtocolMessageBuffer(ownerId, {
|
|
560
|
+
type: "non-initiator",
|
|
561
|
+
errorCode: ProtocolErrorCode.WriteError,
|
|
562
|
+
}).unwrap());
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
491
566
|
const messages = decodeMessages(input);
|
|
492
567
|
if (isNonEmptyReadonlyArray(messages)) {
|
|
493
|
-
|
|
494
|
-
const writeKey = input.shiftN(writeKeyLength);
|
|
495
|
-
const writeKeyIsValid = deps.storage.validateWriteKey(binaryOwnerId, writeKey);
|
|
496
|
-
if (!writeKeyIsValid)
|
|
568
|
+
if (!writeKey)
|
|
497
569
|
return ok(createProtocolMessageBuffer(ownerId, {
|
|
570
|
+
type: "non-initiator",
|
|
498
571
|
errorCode: ProtocolErrorCode.WriteKeyError,
|
|
499
572
|
}).unwrap());
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const
|
|
503
|
-
|
|
573
|
+
// Only broadcast if there's no ranges.
|
|
574
|
+
if (broadcast && input.getLength() === 0) {
|
|
575
|
+
const broadcastBuffer = createProtocolMessageBuffer(ownerId, {
|
|
576
|
+
type: "non-initiator",
|
|
577
|
+
errorCode: ProtocolErrorCode.NoError,
|
|
578
|
+
totalMaxSize,
|
|
579
|
+
rangesMaxSize,
|
|
580
|
+
version,
|
|
581
|
+
});
|
|
582
|
+
for (const message of messages) {
|
|
583
|
+
broadcastBuffer.addMessage(message);
|
|
584
|
+
}
|
|
585
|
+
broadcast(ownerId, broadcastBuffer.unwrap());
|
|
504
586
|
}
|
|
505
587
|
if (!deps.storage.writeMessages(binaryOwnerId, messages))
|
|
506
588
|
return ok(createProtocolMessageBuffer(ownerId, {
|
|
589
|
+
type: "non-initiator",
|
|
507
590
|
errorCode: ProtocolErrorCode.WriteError,
|
|
508
591
|
}).unwrap());
|
|
509
592
|
}
|
|
510
593
|
const output = createProtocolMessageBuffer(ownerId, {
|
|
594
|
+
type: "non-initiator",
|
|
511
595
|
errorCode: ProtocolErrorCode.NoError,
|
|
512
596
|
totalMaxSize,
|
|
513
597
|
rangesMaxSize,
|
|
@@ -534,6 +618,10 @@ const tryDecodeProtocolData = (data, callback) => {
|
|
|
534
618
|
}
|
|
535
619
|
};
|
|
536
620
|
const decodeVersionAndOwner = (input) => {
|
|
621
|
+
// This structure must never change across protocol versions. The version
|
|
622
|
+
// and owner ID must always be the first two fields in every protocol message
|
|
623
|
+
// to enable version negotiation and owner identification before any other
|
|
624
|
+
// processing occurs.
|
|
537
625
|
const version = decodeNonNegativeInt(input);
|
|
538
626
|
const ownerId = decodeOwnerId(input);
|
|
539
627
|
return [version, ownerId];
|
|
@@ -573,6 +661,7 @@ const sync = (deps) => (role, input, output, ownerId) => {
|
|
|
573
661
|
return ok(null);
|
|
574
662
|
}
|
|
575
663
|
const message = createProtocolMessageBuffer(binaryOwnerId, {
|
|
664
|
+
type: "non-initiator",
|
|
576
665
|
errorCode: ProtocolErrorCode.SyncError,
|
|
577
666
|
});
|
|
578
667
|
return ok(message.unwrap());
|
|
@@ -1192,3 +1281,23 @@ export const decodeSqliteValue = (buffer) => {
|
|
|
1192
1281
|
throw new ProtocolDecodeError("invalid ProtocolValueType");
|
|
1193
1282
|
}
|
|
1194
1283
|
};
|
|
1284
|
+
/**
|
|
1285
|
+
* Decodes a ProtocolMessage into a readable JSON object for debugging.
|
|
1286
|
+
*
|
|
1287
|
+
* Note: This is a stub for future implementation. It should use:
|
|
1288
|
+
*
|
|
1289
|
+
* - DecodeVersionAndOwner
|
|
1290
|
+
* - DecodeError or decodeWriteKeys (depending on context)
|
|
1291
|
+
* - DecodeMessages
|
|
1292
|
+
* - DecodeRanges
|
|
1293
|
+
*
|
|
1294
|
+
* If you want to help, please contribute to this function.
|
|
1295
|
+
*/
|
|
1296
|
+
export const decodeProtocolMessageToJson = (_protocolMessage, _isInitiator) => {
|
|
1297
|
+
// TODO: Implement using
|
|
1298
|
+
// - decodeVersionAndOwner
|
|
1299
|
+
// -- decodeError or decodeWriteKeys (should be refactored out),
|
|
1300
|
+
// -- decodeMessages, and decodeRanges.
|
|
1301
|
+
// This is a stub for PRs and community contributions.
|
|
1302
|
+
throw new Error("decodeProtocolMessageToJson is not implemented yet.");
|
|
1303
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Relay.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Relay.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,
|
|
1
|
+
{"version":3,"file":"Relay.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Relay.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAW,MAAM,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAO,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,OAAO,EAAqB,OAAO,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAEL,8BAA8B,EAC9B,iBAAiB,EAClB,MAAM,cAAc,CAAC;AAGtB,MAAM,WAAW,KAAM,SAAQ,UAAU;CAAG;AAE5C,MAAM,WAAW,WAAY,SAAQ,aAAa;IAChD,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;CAC5B;AAED,MAAM,MAAM,sBAAsB,GAAG,iBAAiB,GAAG,kBAAkB,CAAC;AAE5E,eAAO,MAAM,kBAAkB,GAC5B,MAAM,sBAAsB,MAC5B,SAAS,8BAA8B,KAAG,MAAM,CAAC,OAAO,EAAE,WAAW,CA6JrE,CAAC"}
|
package/dist/src/Evolu/Relay.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isNonEmptyReadonlyArray } from "../Array.js";
|
|
2
|
-
import { ok } from "../Result.js";
|
|
2
|
+
import { err, ok } from "../Result.js";
|
|
3
3
|
import { sql } from "../Sqlite.js";
|
|
4
4
|
import { createSqliteStorageBase, } from "./Storage.js";
|
|
5
5
|
import { timestampToBinaryTimestamp } from "./Timestamp.js";
|
|
@@ -65,6 +65,19 @@ export const createRelayStorage = (deps) => (options) => {
|
|
|
65
65
|
}
|
|
66
66
|
return deps.timingSafeEqual(rows[0].writeKey, writeKey);
|
|
67
67
|
},
|
|
68
|
+
setWriteKey: (ownerId, writeKey) => {
|
|
69
|
+
const upsertWriteKey = deps.sqlite.exec(sql `
|
|
70
|
+
insert into evolu_writeKey (ownerId, writeKey)
|
|
71
|
+
values (${ownerId}, ${writeKey})
|
|
72
|
+
on conflict (ownerId) do update
|
|
73
|
+
set writeKey = excluded.writeKey;
|
|
74
|
+
`);
|
|
75
|
+
if (!upsertWriteKey.ok) {
|
|
76
|
+
options.onStorageError(upsertWriteKey.error);
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
},
|
|
68
81
|
writeMessages: (ownerId, messages) => {
|
|
69
82
|
const result = deps.sqlite.transaction(() => {
|
|
70
83
|
for (const message of messages) {
|
|
@@ -104,5 +117,29 @@ export const createRelayStorage = (deps) => (options) => {
|
|
|
104
117
|
}
|
|
105
118
|
return result.value.rows[0]?.change;
|
|
106
119
|
},
|
|
120
|
+
deleteOwner: (ownerId) => {
|
|
121
|
+
const result = deps.sqlite.transaction(() => {
|
|
122
|
+
const del1 = deps.sqlite.exec(sql `
|
|
123
|
+
delete from evolu_writeKey where ownerId = ${ownerId};
|
|
124
|
+
`);
|
|
125
|
+
if (!del1.ok)
|
|
126
|
+
return del1;
|
|
127
|
+
const del2 = deps.sqlite.exec(sql `
|
|
128
|
+
delete from evolu_message where ownerId = ${ownerId};
|
|
129
|
+
`);
|
|
130
|
+
if (!del2.ok)
|
|
131
|
+
return del2;
|
|
132
|
+
const del3 = sqliteStorageBase.value.deleteOwner(ownerId);
|
|
133
|
+
if (!del3)
|
|
134
|
+
return err(null);
|
|
135
|
+
return ok();
|
|
136
|
+
});
|
|
137
|
+
if (!result.ok) {
|
|
138
|
+
if (result.error)
|
|
139
|
+
options.onStorageError(result.error);
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
},
|
|
107
144
|
});
|
|
108
145
|
};
|
|
@@ -36,6 +36,7 @@ export interface SqliteStorageBase {
|
|
|
36
36
|
readonly fingerprintRanges: Storage["fingerprintRanges"];
|
|
37
37
|
readonly findLowerBound: Storage["findLowerBound"];
|
|
38
38
|
readonly iterate: Storage["iterate"];
|
|
39
|
+
readonly deleteOwner: Storage["deleteOwner"];
|
|
39
40
|
}
|
|
40
41
|
export interface SqliteStorageBaseDep {
|
|
41
42
|
readonly storage: SqliteStorageBase;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Storage.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAM,MAAM,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAO,SAAS,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAe,cAAc,EAAe,MAAM,YAAY,CAAC;AAGtE,OAAO,EACL,aAAa,EAQb,OAAO,EAER,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAwB,MAAM,gBAAgB,CAAC;AAEvE,kEAAkE;AAClE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,eAAe,EAAE,CACxB,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,eAAe,KACvB,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAE/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IACrC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IAC7C,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACzD,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACnD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"Storage.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAM,MAAM,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAO,SAAS,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAe,cAAc,EAAe,MAAM,YAAY,CAAC;AAGtE,OAAO,EACL,aAAa,EAQb,OAAO,EAER,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAwB,MAAM,gBAAgB,CAAC;AAEvE,kEAAkE;AAClE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,eAAe,EAAE,CACxB,OAAO,EAAE,aAAa,EACtB,SAAS,EAAE,eAAe,KACvB,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAE/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IACrC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IAC7C,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACzD,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACnD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IACrC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAC;CACrC;AAED,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,SAAS,CAAC;AAEtD,MAAM,WAAW,8BAA8B;IAC7C,cAAc,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;CAC9C;AAED,eAAO,MAAM,uBAAuB,GACjC,MAAM,iBAAiB,MAEtB,SAAS,8BAA8B,KACtC,MAAM,CAAC,iBAAiB,EAAE,WAAW,CAoJvC,CAAC;AA09BJ,eAAO,MAAM,mBAAmB,GAC7B,MAAM,SAAS,MAEd,SAAS,aAAa,EACtB,OAAO,cAAc,KACpB,MAAM,CAAC,eAAe,EAAE,WAAW,CA6ErC,CAAC"}
|
|
@@ -141,6 +141,16 @@ export const createSqliteStorageBase = (deps) => (options) => {
|
|
|
141
141
|
return;
|
|
142
142
|
}
|
|
143
143
|
},
|
|
144
|
+
deleteOwner: (ownerId) => {
|
|
145
|
+
const result = deps.sqlite.exec(sql `
|
|
146
|
+
delete from evolu_timestamp where ownerId = ${ownerId};
|
|
147
|
+
`);
|
|
148
|
+
if (!result.ok) {
|
|
149
|
+
options.onStorageError(result.error);
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
},
|
|
144
154
|
});
|
|
145
155
|
};
|
|
146
156
|
const assertBeginEnd = (begin, end) => {
|
package/package.json
CHANGED
package/src/Assert.ts
CHANGED
|
@@ -32,9 +32,12 @@ import type { Type } from "./Type.js";
|
|
|
32
32
|
* assert(true, "true is not true"); // no-op
|
|
33
33
|
* assert(false, "true is not true"); // throws Error
|
|
34
34
|
*
|
|
35
|
-
* const
|
|
36
|
-
* //
|
|
37
|
-
* assert(
|
|
35
|
+
* const length = buffer.getLength();
|
|
36
|
+
* // We know length is logically non-negative, but TypeScript doesn't
|
|
37
|
+
* assert(
|
|
38
|
+
* NonNegativeInt.is(length),
|
|
39
|
+
* "buffer length should be non-negative",
|
|
40
|
+
* );
|
|
38
41
|
* ```
|
|
39
42
|
*/
|
|
40
43
|
export const assert: (
|
package/src/Evolu/Db.ts
CHANGED
package/src/Evolu/Protocol.ts
CHANGED
|
@@ -15,21 +15,39 @@
|
|
|
15
15
|
*
|
|
16
16
|
* ### Message Structure
|
|
17
17
|
*
|
|
18
|
-
* | Field | Notes
|
|
19
|
-
* | :----------------------------- |
|
|
20
|
-
* | **Header** |
|
|
21
|
-
* | - {@link protocolVersion} |
|
|
22
|
-
* | - {@link OwnerId} |
|
|
23
|
-
* |
|
|
24
|
-
* |
|
|
25
|
-
* | - {@link
|
|
26
|
-
* | - {@link
|
|
27
|
-
* | -
|
|
28
|
-
* |
|
|
29
|
-
* |
|
|
30
|
-
* | - {@link
|
|
18
|
+
* | Field | Notes |
|
|
19
|
+
* | :----------------------------- | :------------------------ |
|
|
20
|
+
* | **Header** | |
|
|
21
|
+
* | - {@link protocolVersion} | |
|
|
22
|
+
* | - {@link OwnerId} | {@link Owner} |
|
|
23
|
+
* | **Initiator** | |
|
|
24
|
+
* | - {@link WriteKeyMode} | |
|
|
25
|
+
* | - {@link WriteKey} | If WriteKeyMode >= 1 |
|
|
26
|
+
* | - {@link WriteKey} | If WriteKeyMode = 2 (new) |
|
|
27
|
+
* | **Non-initiator** | |
|
|
28
|
+
* | - {@link ProtocolErrorCode} | |
|
|
29
|
+
* | **Messages** | |
|
|
30
|
+
* | - {@link NonNegativeInt} | A number of messages. |
|
|
31
|
+
* | - {@link EncryptedCrdtMessage} | |
|
|
32
|
+
* | **Ranges** | |
|
|
33
|
+
* | - {@link NonNegativeInt} | Number of ranges. |
|
|
34
|
+
* | - {@link Range} | |
|
|
31
35
|
*
|
|
32
|
-
*
|
|
36
|
+
* ### WriteKey Validation
|
|
37
|
+
*
|
|
38
|
+
* The initiator sends WriteKeyMode and optionally one or two WriteKeys. One key
|
|
39
|
+
* for write operations and two for key rotation (current and new). Note that
|
|
40
|
+
* it's ok to not send any key if initiator is going to be synced with readonly
|
|
41
|
+
* owner. The non-initiator validates them immediately after parsing the
|
|
42
|
+
* initiator header, before processing any messages or ranges.
|
|
43
|
+
*
|
|
44
|
+
* ### WriteKey Rotation
|
|
45
|
+
*
|
|
46
|
+
* When initiator's {@link WriteKeyMode} is `Rotation`, two WriteKeys are
|
|
47
|
+
* present:
|
|
48
|
+
*
|
|
49
|
+
* 1. Current WriteKey (for validation)
|
|
50
|
+
* 2. New WriteKey (to be stored)
|
|
33
51
|
*
|
|
34
52
|
* ### Synchronization
|
|
35
53
|
*
|
|
@@ -130,7 +148,6 @@ import {
|
|
|
130
148
|
BufferError,
|
|
131
149
|
bytesToHex,
|
|
132
150
|
bytesToUtf8,
|
|
133
|
-
concatBytes,
|
|
134
151
|
createBuffer,
|
|
135
152
|
hexToBytes,
|
|
136
153
|
utf8ToBytes,
|
|
@@ -163,7 +180,6 @@ import {
|
|
|
163
180
|
} from "../Type.js";
|
|
164
181
|
import { Brand, Predicate } from "../Types.js";
|
|
165
182
|
import {
|
|
166
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
167
183
|
Owner,
|
|
168
184
|
OwnerId,
|
|
169
185
|
OwnerWithWriteAccess,
|
|
@@ -205,6 +221,14 @@ export const ProtocolErrorCode = {
|
|
|
205
221
|
type ProtocolErrorCode =
|
|
206
222
|
(typeof ProtocolErrorCode)[keyof typeof ProtocolErrorCode];
|
|
207
223
|
|
|
224
|
+
export const WriteKeyMode = {
|
|
225
|
+
None: 0,
|
|
226
|
+
Single: 1,
|
|
227
|
+
Rotation: 2,
|
|
228
|
+
} as const;
|
|
229
|
+
|
|
230
|
+
type WriteKeyMode = (typeof WriteKeyMode)[keyof typeof WriteKeyMode];
|
|
231
|
+
|
|
208
232
|
/**
|
|
209
233
|
* Evolu Protocol Storage
|
|
210
234
|
*
|
|
@@ -249,17 +273,15 @@ export interface Storage {
|
|
|
249
273
|
callback: (timestamp: BinaryTimestamp, index: NonNegativeInt) => boolean,
|
|
250
274
|
) => void;
|
|
251
275
|
|
|
252
|
-
/**
|
|
253
|
-
* Authorizes the initiator's {@link WriteKey} for the given
|
|
254
|
-
* {@link BinaryOwnerId}.
|
|
255
|
-
*
|
|
256
|
-
* For a client that does not expect foreign writes, return `false`.
|
|
257
|
-
*/
|
|
276
|
+
/** Validates the {@link WriteKey} for the given {@link Owner}. */
|
|
258
277
|
readonly validateWriteKey: (
|
|
259
278
|
ownerId: BinaryOwnerId,
|
|
260
279
|
writeKey: WriteKey,
|
|
261
280
|
) => boolean;
|
|
262
281
|
|
|
282
|
+
/** Sets the {@link WriteKey} for the given {@link Owner}. */
|
|
283
|
+
readonly setWriteKey: (ownerId: BinaryOwnerId, writeKey: WriteKey) => boolean;
|
|
284
|
+
|
|
263
285
|
/** Write encrypted {@link CrdtMessage}s to storage. */
|
|
264
286
|
readonly writeMessages: (
|
|
265
287
|
ownerId: BinaryOwnerId,
|
|
@@ -271,6 +293,9 @@ export interface Storage {
|
|
|
271
293
|
ownerId: BinaryOwnerId,
|
|
272
294
|
timestamp: BinaryTimestamp,
|
|
273
295
|
) => EncryptedDbChange | null;
|
|
296
|
+
|
|
297
|
+
/** Delete all data for the given {@link Owner}. */
|
|
298
|
+
readonly deleteOwner: (ownerId: BinaryOwnerId) => boolean;
|
|
274
299
|
}
|
|
275
300
|
|
|
276
301
|
export interface StorageDep {
|
|
@@ -435,6 +460,7 @@ export const createProtocolMessageFromCrdtMessages =
|
|
|
435
460
|
maxSize?: PositiveInt,
|
|
436
461
|
): ProtocolMessage => {
|
|
437
462
|
const buffer = createProtocolMessageBuffer(owner.id, {
|
|
463
|
+
type: "initiator",
|
|
438
464
|
totalMaxSize: maxSize ?? maxProtocolMessageSize,
|
|
439
465
|
writeKey: owner.writeKey,
|
|
440
466
|
});
|
|
@@ -490,7 +516,7 @@ export const createProtocolMessageFromCrdtMessages =
|
|
|
490
516
|
export const createProtocolMessageForSync =
|
|
491
517
|
(deps: StorageDep) =>
|
|
492
518
|
(ownerId: OwnerId): ProtocolMessage | null => {
|
|
493
|
-
const buffer = createProtocolMessageBuffer(ownerId);
|
|
519
|
+
const buffer = createProtocolMessageBuffer(ownerId, { type: "initiator" });
|
|
494
520
|
const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
|
|
495
521
|
|
|
496
522
|
const size = deps.storage.getSize(binaryOwnerId);
|
|
@@ -508,6 +534,19 @@ export const createProtocolMessageForSync =
|
|
|
508
534
|
return buffer.unwrap();
|
|
509
535
|
};
|
|
510
536
|
|
|
537
|
+
/** Creates a ProtocolMessage for {@link WriteKey} rotation. */
|
|
538
|
+
export const createProtocolMessageForWriteKeyRotation = (
|
|
539
|
+
ownerId: OwnerId,
|
|
540
|
+
currentWriteKey: WriteKey,
|
|
541
|
+
newWriteKey: WriteKey,
|
|
542
|
+
): ProtocolMessage => {
|
|
543
|
+
const buffer = createProtocolMessageBuffer(ownerId, {
|
|
544
|
+
type: "initiator",
|
|
545
|
+
writeKey: [currentWriteKey, newWriteKey],
|
|
546
|
+
});
|
|
547
|
+
return buffer.unwrap();
|
|
548
|
+
};
|
|
549
|
+
|
|
511
550
|
/**
|
|
512
551
|
* Mutable builder for constructing {@link ProtocolMessage} respecting size
|
|
513
552
|
* limits.
|
|
@@ -535,16 +574,22 @@ export interface ProtocolMessageBuffer {
|
|
|
535
574
|
export const createProtocolMessageBuffer = (
|
|
536
575
|
ownerId: OwnerId,
|
|
537
576
|
options: {
|
|
538
|
-
readonly errorCode?: ProtocolErrorCode;
|
|
539
|
-
readonly writeKey?: WriteKey;
|
|
540
577
|
readonly totalMaxSize?: PositiveInt | undefined;
|
|
541
578
|
readonly rangesMaxSize?: PositiveInt | undefined;
|
|
542
579
|
readonly version?: NonNegativeInt;
|
|
543
|
-
}
|
|
580
|
+
} & (
|
|
581
|
+
| {
|
|
582
|
+
readonly type: "initiator";
|
|
583
|
+
/** Single key or [current, new] for rotation. */
|
|
584
|
+
readonly writeKey?: WriteKey | readonly [WriteKey, WriteKey];
|
|
585
|
+
}
|
|
586
|
+
| {
|
|
587
|
+
readonly type: "non-initiator";
|
|
588
|
+
readonly errorCode: ProtocolErrorCode;
|
|
589
|
+
}
|
|
590
|
+
),
|
|
544
591
|
): ProtocolMessageBuffer => {
|
|
545
592
|
const {
|
|
546
|
-
errorCode,
|
|
547
|
-
writeKey,
|
|
548
593
|
totalMaxSize = maxProtocolMessageSize,
|
|
549
594
|
rangesMaxSize = maxProtocolMessageRangesSize,
|
|
550
595
|
version = protocolVersion,
|
|
@@ -565,7 +610,21 @@ export const createProtocolMessageBuffer = (
|
|
|
565
610
|
|
|
566
611
|
encodeNonNegativeInt(buffers.header, version);
|
|
567
612
|
buffers.header.extend(ownerIdToBinaryOwnerId(ownerId));
|
|
568
|
-
|
|
613
|
+
|
|
614
|
+
if (options.type === "initiator") {
|
|
615
|
+
if (!options.writeKey) {
|
|
616
|
+
buffers.header.extend([WriteKeyMode.None]);
|
|
617
|
+
} else if (!Array.isArray(options.writeKey)) {
|
|
618
|
+
buffers.header.extend([WriteKeyMode.Single]);
|
|
619
|
+
buffers.header.extend(options.writeKey as WriteKey);
|
|
620
|
+
} else {
|
|
621
|
+
buffers.header.extend([WriteKeyMode.Rotation]);
|
|
622
|
+
buffers.header.extend(options.writeKey[0] as WriteKey); // current
|
|
623
|
+
buffers.header.extend(options.writeKey[1] as WriteKey); // new
|
|
624
|
+
}
|
|
625
|
+
} else {
|
|
626
|
+
buffers.header.extend([options.errorCode]);
|
|
627
|
+
}
|
|
569
628
|
|
|
570
629
|
let isLastRangeInfinite = false;
|
|
571
630
|
|
|
@@ -577,10 +636,7 @@ export const createProtocolMessageBuffer = (
|
|
|
577
636
|
const getHeaderAndMessagesSize = () =>
|
|
578
637
|
buffers.header.getLength() +
|
|
579
638
|
buffers.messages.timestamps.getLength() +
|
|
580
|
-
buffers.messages.dbChanges.getLength()
|
|
581
|
-
(buffers.messages.timestamps.getCount() > 0 && writeKey
|
|
582
|
-
? writeKeyLength
|
|
583
|
-
: 0);
|
|
639
|
+
buffers.messages.dbChanges.getLength();
|
|
584
640
|
|
|
585
641
|
const getRangesSize = () =>
|
|
586
642
|
buffers.ranges.timestamps.getCount() > 0
|
|
@@ -695,8 +751,6 @@ export const createProtocolMessageBuffer = (
|
|
|
695
751
|
|
|
696
752
|
buffers.messages.timestamps.append(buffers.header);
|
|
697
753
|
buffers.header.extend(buffers.messages.dbChanges.unwrap());
|
|
698
|
-
if (buffers.messages.timestamps.getCount() > 0 && writeKey)
|
|
699
|
-
buffers.header.extend(writeKey);
|
|
700
754
|
|
|
701
755
|
if (buffers.ranges.timestamps.getCount() > 0) {
|
|
702
756
|
buffers.ranges.timestamps.append(buffers.header);
|
|
@@ -846,8 +900,6 @@ export const applyProtocolMessageAsClient =
|
|
|
846
900
|
});
|
|
847
901
|
}
|
|
848
902
|
|
|
849
|
-
const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
|
|
850
|
-
|
|
851
903
|
const errorCode = input.shift() as ProtocolErrorCode;
|
|
852
904
|
if (errorCode !== ProtocolErrorCode.NoError) {
|
|
853
905
|
switch (errorCode) {
|
|
@@ -874,6 +926,7 @@ export const applyProtocolMessageAsClient =
|
|
|
874
926
|
}
|
|
875
927
|
|
|
876
928
|
const messages = decodeMessages(input);
|
|
929
|
+
const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
|
|
877
930
|
|
|
878
931
|
if (
|
|
879
932
|
isNonEmptyReadonlyArray(messages) &&
|
|
@@ -887,6 +940,7 @@ export const applyProtocolMessageAsClient =
|
|
|
887
940
|
if (writeKey == null) return ok(null);
|
|
888
941
|
|
|
889
942
|
const output = createProtocolMessageBuffer(ownerId, {
|
|
943
|
+
type: "initiator",
|
|
890
944
|
writeKey,
|
|
891
945
|
totalMaxSize,
|
|
892
946
|
rangesMaxSize,
|
|
@@ -921,8 +975,7 @@ export const applyProtocolMessageAsRelay =
|
|
|
921
975
|
version = protocolVersion,
|
|
922
976
|
): Result<ProtocolMessage | null, ProtocolInvalidDataError> =>
|
|
923
977
|
tryDecodeProtocolData(inputMessage, (input) => {
|
|
924
|
-
const requestedVersion =
|
|
925
|
-
const ownerId = decodeOwnerId(input);
|
|
978
|
+
const [requestedVersion, ownerId] = decodeVersionAndOwner(input);
|
|
926
979
|
const binaryOwnerId = ownerIdToBinaryOwnerId(ownerId);
|
|
927
980
|
|
|
928
981
|
if (requestedVersion !== version) {
|
|
@@ -935,44 +988,89 @@ export const applyProtocolMessageAsRelay =
|
|
|
935
988
|
|
|
936
989
|
subscribe?.(ownerId);
|
|
937
990
|
|
|
938
|
-
const
|
|
991
|
+
const writeKeyMode = input.shift() as WriteKeyMode;
|
|
992
|
+
let writeKey: WriteKey | undefined;
|
|
993
|
+
let newWriteKey: WriteKey | undefined;
|
|
994
|
+
|
|
995
|
+
if (writeKeyMode !== WriteKeyMode.None) {
|
|
996
|
+
writeKey = input.shiftN(writeKeyLength) as WriteKey;
|
|
997
|
+
switch (writeKeyMode) {
|
|
998
|
+
case WriteKeyMode.Single:
|
|
999
|
+
break;
|
|
1000
|
+
case WriteKeyMode.Rotation:
|
|
1001
|
+
newWriteKey = input.shiftN(writeKeyLength) as WriteKey;
|
|
1002
|
+
break;
|
|
1003
|
+
default:
|
|
1004
|
+
throw new ProtocolDecodeError(
|
|
1005
|
+
`Invalid WriteKeyMode: ${writeKeyMode}`,
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
939
1009
|
|
|
940
|
-
if (
|
|
941
|
-
const
|
|
942
|
-
|
|
1010
|
+
if (writeKey) {
|
|
1011
|
+
const isValid = deps.storage.validateWriteKey(binaryOwnerId, writeKey);
|
|
1012
|
+
if (!isValid) {
|
|
1013
|
+
return ok(
|
|
1014
|
+
createProtocolMessageBuffer(ownerId, {
|
|
1015
|
+
type: "non-initiator",
|
|
1016
|
+
errorCode: ProtocolErrorCode.WriteKeyError,
|
|
1017
|
+
}).unwrap(),
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
943
1020
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1021
|
+
if (newWriteKey) {
|
|
1022
|
+
const rotationSuccess = deps.storage.setWriteKey(
|
|
1023
|
+
binaryOwnerId,
|
|
1024
|
+
newWriteKey,
|
|
1025
|
+
);
|
|
1026
|
+
if (!rotationSuccess) {
|
|
1027
|
+
return ok(
|
|
1028
|
+
createProtocolMessageBuffer(ownerId, {
|
|
1029
|
+
type: "non-initiator",
|
|
1030
|
+
errorCode: ProtocolErrorCode.WriteError,
|
|
1031
|
+
}).unwrap(),
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
948
1036
|
|
|
949
|
-
|
|
1037
|
+
const messages = decodeMessages(input);
|
|
1038
|
+
|
|
1039
|
+
if (isNonEmptyReadonlyArray(messages)) {
|
|
1040
|
+
if (!writeKey)
|
|
950
1041
|
return ok(
|
|
951
1042
|
createProtocolMessageBuffer(ownerId, {
|
|
1043
|
+
type: "non-initiator",
|
|
952
1044
|
errorCode: ProtocolErrorCode.WriteKeyError,
|
|
953
1045
|
}).unwrap(),
|
|
954
1046
|
);
|
|
955
1047
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
const
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1048
|
+
// Only broadcast if there's no ranges.
|
|
1049
|
+
if (broadcast && input.getLength() === 0) {
|
|
1050
|
+
const broadcastBuffer = createProtocolMessageBuffer(ownerId, {
|
|
1051
|
+
type: "non-initiator",
|
|
1052
|
+
errorCode: ProtocolErrorCode.NoError,
|
|
1053
|
+
totalMaxSize,
|
|
1054
|
+
rangesMaxSize,
|
|
1055
|
+
version,
|
|
1056
|
+
});
|
|
1057
|
+
for (const message of messages) {
|
|
1058
|
+
broadcastBuffer.addMessage(message);
|
|
1059
|
+
}
|
|
1060
|
+
broadcast(ownerId, broadcastBuffer.unwrap());
|
|
965
1061
|
}
|
|
966
1062
|
|
|
967
1063
|
if (!deps.storage.writeMessages(binaryOwnerId, messages))
|
|
968
1064
|
return ok(
|
|
969
1065
|
createProtocolMessageBuffer(ownerId, {
|
|
1066
|
+
type: "non-initiator",
|
|
970
1067
|
errorCode: ProtocolErrorCode.WriteError,
|
|
971
1068
|
}).unwrap(),
|
|
972
1069
|
);
|
|
973
1070
|
}
|
|
974
1071
|
|
|
975
1072
|
const output = createProtocolMessageBuffer(ownerId, {
|
|
1073
|
+
type: "non-initiator",
|
|
976
1074
|
errorCode: ProtocolErrorCode.NoError,
|
|
977
1075
|
totalMaxSize,
|
|
978
1076
|
rangesMaxSize,
|
|
@@ -1005,6 +1103,10 @@ const tryDecodeProtocolData = <T, E>(
|
|
|
1005
1103
|
};
|
|
1006
1104
|
|
|
1007
1105
|
const decodeVersionAndOwner = (input: Buffer): [NonNegativeInt, OwnerId] => {
|
|
1106
|
+
// This structure must never change across protocol versions. The version
|
|
1107
|
+
// and owner ID must always be the first two fields in every protocol message
|
|
1108
|
+
// to enable version negotiation and owner identification before any other
|
|
1109
|
+
// processing occurs.
|
|
1008
1110
|
const version = decodeNonNegativeInt(input);
|
|
1009
1111
|
const ownerId = decodeOwnerId(input);
|
|
1010
1112
|
return [version, ownerId];
|
|
@@ -1062,6 +1164,7 @@ const sync =
|
|
|
1062
1164
|
return ok(null);
|
|
1063
1165
|
}
|
|
1064
1166
|
const message = createProtocolMessageBuffer(binaryOwnerId, {
|
|
1167
|
+
type: "non-initiator",
|
|
1065
1168
|
errorCode: ProtocolErrorCode.SyncError,
|
|
1066
1169
|
});
|
|
1067
1170
|
return ok(message.unwrap());
|
|
@@ -1898,3 +2001,27 @@ export const decodeSqliteValue = (buffer: Buffer): SqliteValue => {
|
|
|
1898
2001
|
throw new ProtocolDecodeError("invalid ProtocolValueType");
|
|
1899
2002
|
}
|
|
1900
2003
|
};
|
|
2004
|
+
|
|
2005
|
+
/**
|
|
2006
|
+
* Decodes a ProtocolMessage into a readable JSON object for debugging.
|
|
2007
|
+
*
|
|
2008
|
+
* Note: This is a stub for future implementation. It should use:
|
|
2009
|
+
*
|
|
2010
|
+
* - DecodeVersionAndOwner
|
|
2011
|
+
* - DecodeError or decodeWriteKeys (depending on context)
|
|
2012
|
+
* - DecodeMessages
|
|
2013
|
+
* - DecodeRanges
|
|
2014
|
+
*
|
|
2015
|
+
* If you want to help, please contribute to this function.
|
|
2016
|
+
*/
|
|
2017
|
+
export const decodeProtocolMessageToJson = (
|
|
2018
|
+
_protocolMessage: ProtocolMessage,
|
|
2019
|
+
_isInitiator: boolean,
|
|
2020
|
+
): unknown => {
|
|
2021
|
+
// TODO: Implement using
|
|
2022
|
+
// - decodeVersionAndOwner
|
|
2023
|
+
// -- decodeError or decodeWriteKeys (should be refactored out),
|
|
2024
|
+
// -- decodeMessages, and decodeRanges.
|
|
2025
|
+
// This is a stub for PRs and community contributions.
|
|
2026
|
+
throw new Error("decodeProtocolMessageToJson is not implemented yet.");
|
|
2027
|
+
};
|
package/src/Evolu/Relay.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isNonEmptyReadonlyArray } from "../Array.js";
|
|
2
2
|
import { ConsoleConfig } from "../Console.js";
|
|
3
3
|
import { TimingSafeEqualDep } from "../Crypto.js";
|
|
4
|
-
import { ok, Result } from "../Result.js";
|
|
4
|
+
import { err, ok, Result } from "../Result.js";
|
|
5
5
|
import { sql, SqliteError } from "../Sqlite.js";
|
|
6
6
|
import { SimpleName } from "../Type.js";
|
|
7
7
|
import { OwnerId, WriteKey } from "./Owner.js";
|
|
@@ -92,6 +92,21 @@ export const createRelayStorage =
|
|
|
92
92
|
return deps.timingSafeEqual(rows[0].writeKey, writeKey);
|
|
93
93
|
},
|
|
94
94
|
|
|
95
|
+
setWriteKey: (ownerId, writeKey) => {
|
|
96
|
+
const upsertWriteKey = deps.sqlite.exec(sql`
|
|
97
|
+
insert into evolu_writeKey (ownerId, writeKey)
|
|
98
|
+
values (${ownerId}, ${writeKey})
|
|
99
|
+
on conflict (ownerId) do update
|
|
100
|
+
set writeKey = excluded.writeKey;
|
|
101
|
+
`);
|
|
102
|
+
if (!upsertWriteKey.ok) {
|
|
103
|
+
options.onStorageError(upsertWriteKey.error);
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return true;
|
|
108
|
+
},
|
|
109
|
+
|
|
95
110
|
writeMessages: (ownerId, messages) => {
|
|
96
111
|
const result = deps.sqlite.transaction(() => {
|
|
97
112
|
for (const message of messages) {
|
|
@@ -140,5 +155,29 @@ export const createRelayStorage =
|
|
|
140
155
|
|
|
141
156
|
return result.value.rows[0]?.change;
|
|
142
157
|
},
|
|
158
|
+
|
|
159
|
+
deleteOwner: (ownerId) => {
|
|
160
|
+
const result = deps.sqlite.transaction(() => {
|
|
161
|
+
const del1 = deps.sqlite.exec(sql`
|
|
162
|
+
delete from evolu_writeKey where ownerId = ${ownerId};
|
|
163
|
+
`);
|
|
164
|
+
if (!del1.ok) return del1;
|
|
165
|
+
|
|
166
|
+
const del2 = deps.sqlite.exec(sql`
|
|
167
|
+
delete from evolu_message where ownerId = ${ownerId};
|
|
168
|
+
`);
|
|
169
|
+
if (!del2.ok) return del2;
|
|
170
|
+
|
|
171
|
+
const del3 = sqliteStorageBase.value.deleteOwner(ownerId);
|
|
172
|
+
if (!del3) return err(null);
|
|
173
|
+
|
|
174
|
+
return ok();
|
|
175
|
+
});
|
|
176
|
+
if (!result.ok) {
|
|
177
|
+
if (result.error) options.onStorageError(result.error);
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
return true;
|
|
181
|
+
},
|
|
143
182
|
});
|
|
144
183
|
};
|
package/src/Evolu/Storage.ts
CHANGED
|
@@ -57,6 +57,7 @@ export interface SqliteStorageBase {
|
|
|
57
57
|
readonly fingerprintRanges: Storage["fingerprintRanges"];
|
|
58
58
|
readonly findLowerBound: Storage["findLowerBound"];
|
|
59
59
|
readonly iterate: Storage["iterate"];
|
|
60
|
+
readonly deleteOwner: Storage["deleteOwner"];
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
export interface SqliteStorageBaseDep {
|
|
@@ -209,6 +210,17 @@ export const createSqliteStorageBase =
|
|
|
209
210
|
if (!callback(result.value.rows[i].t, index)) return;
|
|
210
211
|
}
|
|
211
212
|
},
|
|
213
|
+
|
|
214
|
+
deleteOwner: (ownerId) => {
|
|
215
|
+
const result = deps.sqlite.exec(sql`
|
|
216
|
+
delete from evolu_timestamp where ownerId = ${ownerId};
|
|
217
|
+
`);
|
|
218
|
+
if (!result.ok) {
|
|
219
|
+
options.onStorageError(result.error);
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
return true;
|
|
223
|
+
},
|
|
212
224
|
});
|
|
213
225
|
};
|
|
214
226
|
|