@evolu/common 6.0.1-preview.13 → 6.0.1-preview.15
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/Evolu/Db.d.ts.map +1 -1
- package/dist/src/Evolu/Db.js +10 -7
- package/dist/src/Evolu/Protocol.d.ts +24 -5
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +46 -8
- package/dist/src/Evolu/Timestamp.d.ts +6 -0
- package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
- package/dist/src/Evolu/Timestamp.js +7 -0
- package/package.json +3 -2
- package/src/Evolu/Db.ts +10 -7
- package/src/Evolu/Protocol.ts +106 -38
- package/src/Evolu/Timestamp.ts +8 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Db.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Db.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EAEpB,2BAA2B,EAE5B,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAa,GAAG,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAM,MAAM,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAEL,qBAAqB,EAErB,OAAO,EAEP,SAAS,EACT,WAAW,EAEX,SAAS,EAEV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAS,EAAE,EAAE,QAAQ,EAAU,UAAU,EAAU,MAAM,YAAY,CAAC;AAC7E,OAAO,EAEL,MAAM,EAEP,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAe,YAAY,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EACL,QAAQ,EAGR,QAAQ,EAET,MAAM,YAAY,CAAC;AACpB,OAAO,EAKL,aAAa,EACb,WAAW,EAGX,QAAQ,EAKR,aAAa,EAEb,OAAO,EAER,MAAM,eAAe,CAAC;AACvB,OAAO,EAIL,KAAK,EAEN,MAAM,YAAY,CAAC;AACpB,OAAO,EAGL,iBAAiB,EAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAuB,MAAM,WAAW,CAAC;AAC/D,OAAO,
|
|
1
|
+
{"version":3,"file":"Db.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Db.ts"],"names":[],"mappings":"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"}
|
package/dist/src/Evolu/Db.js
CHANGED
|
@@ -14,7 +14,7 @@ import { createAppOwner, createOwnerRow, } from "./Owner.js";
|
|
|
14
14
|
import { applyProtocolMessageAsClient, Base64Url256, binaryIdToId, createProtocolMessageForSync, createProtocolMessageFromCrdtMessages, decryptAndDecodeDbChange, encodeAndEncryptDbChange, idToBinaryId, ownerIdToBinaryOwnerId, protocolVersion, } from "./Protocol.js";
|
|
15
15
|
import { createQueryRowsCache, deserializeQuery, emptyRows, } from "./Query.js";
|
|
16
16
|
import { createSqliteStorageBase, } from "./Storage.js";
|
|
17
|
-
import { receiveTimestamp, sendTimestamp, timestampStringToTimestamp, timestampToBinaryTimestamp, timestampToTimestampString, } from "./Timestamp.js";
|
|
17
|
+
import { binaryTimestampToTimestamp, receiveTimestamp, sendTimestamp, timestampStringToTimestamp, timestampToBinaryTimestamp, timestampToTimestampString, } from "./Timestamp.js";
|
|
18
18
|
export const DbTable = object({
|
|
19
19
|
name: Base64Url256,
|
|
20
20
|
columns: array(Base64Url256),
|
|
@@ -676,7 +676,7 @@ const createClientStorage = (deps) => (options) => {
|
|
|
676
676
|
const owner = deps.ownerRowRef.get();
|
|
677
677
|
const decodedAndDecryptedMessages = [];
|
|
678
678
|
for (const message of messages) {
|
|
679
|
-
const dbChange = decryptAndDecodeDbChange(deps)(message
|
|
679
|
+
const dbChange = decryptAndDecodeDbChange(deps)(message, owner.encryptionKey);
|
|
680
680
|
if (!dbChange.ok) {
|
|
681
681
|
deps.postMessage({
|
|
682
682
|
type: "onError",
|
|
@@ -728,13 +728,16 @@ const createClientStorage = (deps) => (options) => {
|
|
|
728
728
|
assert(eqArrayNumber(r.id, id), "All rows must have the same Id");
|
|
729
729
|
values[r.column] = r.value;
|
|
730
730
|
}
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
731
|
+
const message = {
|
|
732
|
+
timestamp: binaryTimestampToTimestamp(timestamp),
|
|
733
|
+
change: {
|
|
734
|
+
table: rows[0].table,
|
|
735
|
+
id: binaryIdToId(rows[0].id),
|
|
736
|
+
values,
|
|
737
|
+
},
|
|
735
738
|
};
|
|
736
739
|
const { encryptionKey } = deps.ownerRowRef.get();
|
|
737
|
-
return encodeAndEncryptDbChange(deps)(
|
|
740
|
+
return encodeAndEncryptDbChange(deps)(message, encryptionKey);
|
|
738
741
|
},
|
|
739
742
|
};
|
|
740
743
|
return ok(storage);
|
|
@@ -61,6 +61,10 @@
|
|
|
61
61
|
* if further sync is needed or possible, continuing until both sides are
|
|
62
62
|
* synchronized.
|
|
63
63
|
*
|
|
64
|
+
* The **non-initiator always responds** to provide sync completion feedback,
|
|
65
|
+
* even with empty messages containing only the header and no error. This allows
|
|
66
|
+
* the initiator to reliably detect when synchronization is complete.
|
|
67
|
+
*
|
|
64
68
|
* Both **Messages** and **Ranges** are optional, allowing each side to send,
|
|
65
69
|
* sync, or only subscribe data as needed.
|
|
66
70
|
*
|
|
@@ -279,7 +283,7 @@ export interface TimestampsRangeWithTimestampsBuffer extends BaseRange {
|
|
|
279
283
|
readonly timestamps: TimestampsBuffer;
|
|
280
284
|
}
|
|
281
285
|
export type Range = SkipRange | FingerprintRange | TimestampsRange;
|
|
282
|
-
export type ProtocolError = ProtocolUnsupportedVersionError | ProtocolInvalidDataError | ProtocolWriteKeyError | ProtocolWriteError | ProtocolSyncError;
|
|
286
|
+
export type ProtocolError = ProtocolUnsupportedVersionError | ProtocolInvalidDataError | ProtocolWriteKeyError | ProtocolWriteError | ProtocolSyncError | ProtocolTimestampMismatchError;
|
|
283
287
|
/** Base interface for all protocol errors. */
|
|
284
288
|
export interface ProtocolErrorBase {
|
|
285
289
|
readonly ownerId: OwnerId;
|
|
@@ -318,6 +322,16 @@ export interface ProtocolWriteError extends ProtocolErrorBase {
|
|
|
318
322
|
export interface ProtocolSyncError extends ProtocolErrorBase {
|
|
319
323
|
readonly type: "ProtocolSyncError";
|
|
320
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* Error when embedded timestamp doesn't match expected timestamp in
|
|
327
|
+
* EncryptedDbChange. Indicates potential tampering or corruption of CRDT
|
|
328
|
+
* messages.
|
|
329
|
+
*/
|
|
330
|
+
export interface ProtocolTimestampMismatchError {
|
|
331
|
+
readonly type: "ProtocolTimestampMismatchError";
|
|
332
|
+
readonly expected: Timestamp;
|
|
333
|
+
readonly embedded: Timestamp;
|
|
334
|
+
}
|
|
321
335
|
/**
|
|
322
336
|
* Creates a {@link ProtocolMessage} from CRDT messages.
|
|
323
337
|
*
|
|
@@ -412,13 +426,18 @@ export declare const binaryTimestampToFingerprint: (timestamp: BinaryTimestamp)
|
|
|
412
426
|
/**
|
|
413
427
|
* Encodes and encrypts a {@link DbChange} using the provided owner's encryption
|
|
414
428
|
* key. Returns an encrypted binary representation as {@link EncryptedDbChange}.
|
|
429
|
+
*
|
|
430
|
+
* The format includes the protocol version for backward compatibility and the
|
|
431
|
+
* timestamp for tamper-proof verification that the timestamp matches the change
|
|
432
|
+
* data.
|
|
415
433
|
*/
|
|
416
|
-
export declare const encodeAndEncryptDbChange: (deps: SymmetricCryptoDep) => (
|
|
434
|
+
export declare const encodeAndEncryptDbChange: (deps: SymmetricCryptoDep) => (message: CrdtMessage, key: EncryptionKey) => EncryptedDbChange;
|
|
417
435
|
/**
|
|
418
|
-
* Decrypts and decodes an {@link
|
|
419
|
-
* encryption key.
|
|
436
|
+
* Decrypts and decodes an {@link EncryptedCrdtMessage} using the provided
|
|
437
|
+
* owner's encryption key. Verifies that the embedded timestamp matches the
|
|
438
|
+
* expected timestamp to ensure message integrity.
|
|
420
439
|
*/
|
|
421
|
-
export declare const decryptAndDecodeDbChange: (deps: SymmetricCryptoDep) => (
|
|
440
|
+
export declare const decryptAndDecodeDbChange: (deps: SymmetricCryptoDep) => (message: EncryptedCrdtMessage, key: EncryptionKey) => Result<DbChange, SymmetricCryptoDecryptError | ProtocolInvalidDataError | ProtocolTimestampMismatchError>;
|
|
422
441
|
/**
|
|
423
442
|
* Encodes a non-negative integer into a variable-length integer format. It's
|
|
424
443
|
* more efficient than encoding via {@link encodeNumber}.
|
|
@@ -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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"}
|
|
@@ -61,6 +61,10 @@
|
|
|
61
61
|
* if further sync is needed or possible, continuing until both sides are
|
|
62
62
|
* synchronized.
|
|
63
63
|
*
|
|
64
|
+
* The **non-initiator always responds** to provide sync completion feedback,
|
|
65
|
+
* even with empty messages containing only the header and no error. This allows
|
|
66
|
+
* the initiator to reliably detect when synchronization is complete.
|
|
67
|
+
*
|
|
64
68
|
* Both **Messages** and **Ranges** are optional, allowing each side to send,
|
|
65
69
|
* sync, or only subscribe data as needed.
|
|
66
70
|
*
|
|
@@ -151,7 +155,7 @@ import { err, ok } from "../Result.js";
|
|
|
151
155
|
import { SqliteValue } from "../Sqlite.js";
|
|
152
156
|
import { Base64Url, base64UrlAlphabet, DateIsoString, Id, idTypeValueLength, JsonValueFromString, maxLength, NonNegativeInt, Number, object, record, } from "../Type.js";
|
|
153
157
|
import { writeKeyLength, } from "./Owner.js";
|
|
154
|
-
import { binaryTimestampToTimestamp, Counter, Millis, timestampToBinaryTimestamp, } from "./Timestamp.js";
|
|
158
|
+
import { binaryTimestampLength, binaryTimestampToTimestamp, Counter, eqTimestamp, Millis, timestampToBinaryTimestamp, } from "./Timestamp.js";
|
|
155
159
|
/** Maximum size of the entire protocol message in bytes. */
|
|
156
160
|
export const maxProtocolMessageSize = 1_000_000;
|
|
157
161
|
/** Maximum size of the ranges in bytes. */
|
|
@@ -210,7 +214,7 @@ export const createProtocolMessageFromCrdtMessages = (deps) => (owner, messages,
|
|
|
210
214
|
});
|
|
211
215
|
let notAllMessagesSent = false;
|
|
212
216
|
for (const message of messages) {
|
|
213
|
-
const change = encodeAndEncryptDbChange(deps)(message
|
|
217
|
+
const change = encodeAndEncryptDbChange(deps)(message, owner.encryptionKey);
|
|
214
218
|
const encryptedCrdtMessage = { timestamp: message.timestamp, change };
|
|
215
219
|
if (buffer.canAddMessage(encryptedCrdtMessage)) {
|
|
216
220
|
buffer.addMessage(encryptedCrdtMessage);
|
|
@@ -648,6 +652,11 @@ const decodeMessages = (buffer) => {
|
|
|
648
652
|
const sync = (deps) => (role, input, output, ownerId) => {
|
|
649
653
|
const ranges = decodeRanges(input);
|
|
650
654
|
if (!isNonEmptyReadonlyArray(ranges)) {
|
|
655
|
+
// Non-initiators always respond to provide sync completion feedback,
|
|
656
|
+
// even when there's nothing to sync.
|
|
657
|
+
if (role === "non-initiator") {
|
|
658
|
+
return ok(output.unwrap());
|
|
659
|
+
}
|
|
651
660
|
// Nothing to sync.
|
|
652
661
|
return ok(null);
|
|
653
662
|
}
|
|
@@ -808,6 +817,11 @@ const sync = (deps) => (role, input, output, ownerId) => {
|
|
|
808
817
|
}
|
|
809
818
|
// If all ranges were skipped, there are no changes and sync is complete.
|
|
810
819
|
const hasChange = output.getSize() > outputInitialSize;
|
|
820
|
+
// Non-initiators always respond to provide sync completion feedback,
|
|
821
|
+
// even with empty messages. This allows clients to detect sync completion.
|
|
822
|
+
if (role === "non-initiator" && !hasChange) {
|
|
823
|
+
return ok(output.unwrap());
|
|
824
|
+
}
|
|
811
825
|
return ok(hasChange ? output.unwrap() : null);
|
|
812
826
|
};
|
|
813
827
|
const splitRange = (deps) => (ownerId, lower, upper, upperBound, buffer) => {
|
|
@@ -901,7 +915,7 @@ const decodeTimestamps = (buffer, length) => {
|
|
|
901
915
|
const deltaMillis = decodeNonNegativeInt(buffer);
|
|
902
916
|
const millis = Millis.from(previousMillis + deltaMillis);
|
|
903
917
|
if (!millis.ok)
|
|
904
|
-
throw new
|
|
918
|
+
throw new ProtocolDecodeError(millis.error.type);
|
|
905
919
|
millises.push(millis.value);
|
|
906
920
|
previousMillis = millis.value;
|
|
907
921
|
}
|
|
@@ -910,7 +924,7 @@ const decodeTimestamps = (buffer, length) => {
|
|
|
910
924
|
while (counterIndex < length) {
|
|
911
925
|
const counter = Counter.from(decodeNonNegativeInt(buffer));
|
|
912
926
|
if (!counter.ok)
|
|
913
|
-
throw new
|
|
927
|
+
throw new ProtocolDecodeError(counter.error.type);
|
|
914
928
|
const runLength = decodeNonNegativeInt(buffer);
|
|
915
929
|
for (let i = 0; i < runLength; i++) {
|
|
916
930
|
counters.push(counter.value);
|
|
@@ -1025,9 +1039,19 @@ export const binaryTimestampToFingerprint = (timestamp) => {
|
|
|
1025
1039
|
/**
|
|
1026
1040
|
* Encodes and encrypts a {@link DbChange} using the provided owner's encryption
|
|
1027
1041
|
* key. Returns an encrypted binary representation as {@link EncryptedDbChange}.
|
|
1042
|
+
*
|
|
1043
|
+
* The format includes the protocol version for backward compatibility and the
|
|
1044
|
+
* timestamp for tamper-proof verification that the timestamp matches the change
|
|
1045
|
+
* data.
|
|
1028
1046
|
*/
|
|
1029
|
-
export const encodeAndEncryptDbChange = (deps) => (
|
|
1047
|
+
export const encodeAndEncryptDbChange = (deps) => (message, key) => {
|
|
1048
|
+
const change = message.change;
|
|
1030
1049
|
const buffer = createBuffer();
|
|
1050
|
+
// Encode protocol version first for backward compatibility
|
|
1051
|
+
encodeNonNegativeInt(buffer, protocolVersion);
|
|
1052
|
+
// Encode the timestamp (after version) for tamper verification
|
|
1053
|
+
const binaryTimestamp = timestampToBinaryTimestamp(message.timestamp);
|
|
1054
|
+
buffer.extend(binaryTimestamp);
|
|
1031
1055
|
encodeBase64Url256(buffer, change.table);
|
|
1032
1056
|
buffer.extend(idToBinaryId(change.id));
|
|
1033
1057
|
const entries = objectToEntries(change.values).map(([column, value]) => {
|
|
@@ -1049,10 +1073,11 @@ export const encodeAndEncryptDbChange = (deps) => (change, key) => {
|
|
|
1049
1073
|
return buffer.unwrap();
|
|
1050
1074
|
};
|
|
1051
1075
|
/**
|
|
1052
|
-
* Decrypts and decodes an {@link
|
|
1053
|
-
* encryption key.
|
|
1076
|
+
* Decrypts and decodes an {@link EncryptedCrdtMessage} using the provided
|
|
1077
|
+
* owner's encryption key. Verifies that the embedded timestamp matches the
|
|
1078
|
+
* expected timestamp to ensure message integrity.
|
|
1054
1079
|
*/
|
|
1055
|
-
export const decryptAndDecodeDbChange = (deps) => (
|
|
1080
|
+
export const decryptAndDecodeDbChange = (deps) => (message, key) => tryDecodeProtocolData(message.change, (buffer) => {
|
|
1056
1081
|
const nonce = buffer.shiftN(deps.symmetricCrypto.nonceLength);
|
|
1057
1082
|
const ciphertextLength = decodeLength(buffer);
|
|
1058
1083
|
const ciphertext = buffer.shiftN(ciphertextLength);
|
|
@@ -1061,6 +1086,19 @@ export const decryptAndDecodeDbChange = (deps) => (change, key) => tryDecodeProt
|
|
|
1061
1086
|
return plaintextBytes;
|
|
1062
1087
|
buffer.reset();
|
|
1063
1088
|
buffer.extend(plaintextBytes.value);
|
|
1089
|
+
// Decode version (for future compatibility, no validation needed for now)
|
|
1090
|
+
decodeNonNegativeInt(buffer);
|
|
1091
|
+
// Decode and verify the embedded timestamp
|
|
1092
|
+
const embeddedBinaryTimestamp = buffer.shiftN(binaryTimestampLength);
|
|
1093
|
+
const embeddedTimestamp = binaryTimestampToTimestamp(embeddedBinaryTimestamp);
|
|
1094
|
+
// Verify timestamp integrity
|
|
1095
|
+
if (!eqTimestamp(embeddedTimestamp, message.timestamp)) {
|
|
1096
|
+
return err({
|
|
1097
|
+
type: "ProtocolTimestampMismatchError",
|
|
1098
|
+
expected: message.timestamp,
|
|
1099
|
+
embedded: embeddedTimestamp,
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1064
1102
|
const table = decodeBase64Url256WithLength(buffer);
|
|
1065
1103
|
const id = decodeId(buffer);
|
|
1066
1104
|
const length = decodeLength(buffer);
|
|
@@ -89,6 +89,12 @@ export declare const Timestamp: import("../Type.js").ObjectType<{
|
|
|
89
89
|
nodeId: import("../Type.js").BrandType<import("../Type.js").Type<"String", string, string, import("../Type.js").StringError, string, import("../Type.js").StringError>, "NodeId", import("../Type.js").RegexError<"NodeId">, import("../Type.js").StringError>;
|
|
90
90
|
}>;
|
|
91
91
|
export type Timestamp = typeof Timestamp.Type;
|
|
92
|
+
/** Equality function for comparing {@link Timestamp}. */
|
|
93
|
+
export declare const eqTimestamp: import("../Eq.js").Eq<{
|
|
94
|
+
readonly millis: number & Brand<"Int"> & Brand<"NonNegative"> & Brand<`LessThanOrEqualTo${number}`> & Brand<"Millis">;
|
|
95
|
+
readonly counter: number & Brand<"Int"> & Brand<"NonNegative"> & Brand<"LessThanOrEqualTo65535"> & Brand<"Counter">;
|
|
96
|
+
readonly nodeId: string & Brand<"NodeId">;
|
|
97
|
+
}>;
|
|
92
98
|
export declare const createTimestamp: ({ millis, counter, nodeId, }?: Partial<Timestamp>) => Timestamp;
|
|
93
99
|
export declare const createInitialTimestamp: (deps: NanoIdLibDep) => Timestamp;
|
|
94
100
|
/** TimestampString is a sortable string version of {@link Timestamp}. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Timestamp.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Timestamp.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Timestamp.d.ts","sourceRoot":"","sources":["../../../src/Evolu/Timestamp.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { assert } from "../Assert.js";
|
|
2
|
+
import { createEqObject, eqNumber, eqString } from "../Eq.js";
|
|
2
3
|
import { increment } from "../Number.js";
|
|
3
4
|
import { orderUint8Array } from "../Order.js";
|
|
4
5
|
import { err, getOrThrow, ok } from "../Result.js";
|
|
@@ -58,6 +59,12 @@ export const Timestamp = object({
|
|
|
58
59
|
counter: Counter,
|
|
59
60
|
nodeId: NodeId,
|
|
60
61
|
});
|
|
62
|
+
/** Equality function for comparing {@link Timestamp}. */
|
|
63
|
+
export const eqTimestamp = createEqObject({
|
|
64
|
+
millis: eqNumber,
|
|
65
|
+
counter: eqNumber,
|
|
66
|
+
nodeId: eqString,
|
|
67
|
+
});
|
|
61
68
|
export const createTimestamp = ({ millis = minMillis, counter = minCounter, nodeId = minNodeId, } = {}) => ({ millis, counter, nodeId });
|
|
62
69
|
const hexAlphabet = "0123456789abcdef";
|
|
63
70
|
export const createInitialTimestamp = (deps) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evolu/common",
|
|
3
|
-
"version": "6.0.1-preview.
|
|
3
|
+
"version": "6.0.1-preview.15",
|
|
4
4
|
"description": "TypeScript library and local-first framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"evolu",
|
|
@@ -71,7 +71,8 @@
|
|
|
71
71
|
"scripts": {
|
|
72
72
|
"dev": "tsc --watch",
|
|
73
73
|
"build": "shx rm -rf dist ./README.md && tsc && shx cp ../../README.md ./",
|
|
74
|
-
"test": "vitest --disableConsoleIntercept",
|
|
74
|
+
"test": "vitest run --disableConsoleIntercept",
|
|
75
|
+
"test:watch": "vitest --disableConsoleIntercept",
|
|
75
76
|
"clean": "shx rm -rf .turbo node_modules dist",
|
|
76
77
|
"format": "prettier --write \"src/*.{ts,tsx,md}\"",
|
|
77
78
|
"bench": "vitest bench"
|
package/src/Evolu/Db.ts
CHANGED
|
@@ -82,6 +82,7 @@ import {
|
|
|
82
82
|
} from "./Storage.js";
|
|
83
83
|
import { CreateSyncDep, SyncConfig, SyncDep } from "./Sync.js";
|
|
84
84
|
import {
|
|
85
|
+
binaryTimestampToTimestamp,
|
|
85
86
|
receiveTimestamp,
|
|
86
87
|
sendTimestamp,
|
|
87
88
|
Timestamp,
|
|
@@ -1114,7 +1115,7 @@ const createClientStorage =
|
|
|
1114
1115
|
|
|
1115
1116
|
for (const message of messages) {
|
|
1116
1117
|
const dbChange = decryptAndDecodeDbChange(deps)(
|
|
1117
|
-
message
|
|
1118
|
+
message,
|
|
1118
1119
|
owner.encryptionKey,
|
|
1119
1120
|
);
|
|
1120
1121
|
|
|
@@ -1189,15 +1190,17 @@ const createClientStorage =
|
|
|
1189
1190
|
values[r.column] = r.value;
|
|
1190
1191
|
}
|
|
1191
1192
|
|
|
1192
|
-
const
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1193
|
+
const message: CrdtMessage = {
|
|
1194
|
+
timestamp: binaryTimestampToTimestamp(timestamp),
|
|
1195
|
+
change: {
|
|
1196
|
+
table: rows[0].table,
|
|
1197
|
+
id: binaryIdToId(rows[0].id),
|
|
1198
|
+
values,
|
|
1199
|
+
},
|
|
1196
1200
|
};
|
|
1197
|
-
|
|
1198
1201
|
const { encryptionKey } = deps.ownerRowRef.get();
|
|
1199
1202
|
|
|
1200
|
-
return encodeAndEncryptDbChange(deps)(
|
|
1203
|
+
return encodeAndEncryptDbChange(deps)(message, encryptionKey);
|
|
1201
1204
|
},
|
|
1202
1205
|
};
|
|
1203
1206
|
|
package/src/Evolu/Protocol.ts
CHANGED
|
@@ -61,6 +61,10 @@
|
|
|
61
61
|
* if further sync is needed or possible, continuing until both sides are
|
|
62
62
|
* synchronized.
|
|
63
63
|
*
|
|
64
|
+
* The **non-initiator always responds** to provide sync completion feedback,
|
|
65
|
+
* even with empty messages containing only the header and no error. This allows
|
|
66
|
+
* the initiator to reliably detect when synchronization is complete.
|
|
67
|
+
*
|
|
64
68
|
* Both **Messages** and **Ranges** are optional, allowing each side to send,
|
|
65
69
|
* sync, or only subscribe data as needed.
|
|
66
70
|
*
|
|
@@ -188,8 +192,10 @@ import {
|
|
|
188
192
|
} from "./Owner.js";
|
|
189
193
|
import {
|
|
190
194
|
BinaryTimestamp,
|
|
195
|
+
binaryTimestampLength,
|
|
191
196
|
binaryTimestampToTimestamp,
|
|
192
197
|
Counter,
|
|
198
|
+
eqTimestamp,
|
|
193
199
|
Millis,
|
|
194
200
|
NodeId,
|
|
195
201
|
Timestamp,
|
|
@@ -399,7 +405,8 @@ export type ProtocolError =
|
|
|
399
405
|
| ProtocolInvalidDataError
|
|
400
406
|
| ProtocolWriteKeyError
|
|
401
407
|
| ProtocolWriteError
|
|
402
|
-
| ProtocolSyncError
|
|
408
|
+
| ProtocolSyncError
|
|
409
|
+
| ProtocolTimestampMismatchError;
|
|
403
410
|
|
|
404
411
|
/** Base interface for all protocol errors. */
|
|
405
412
|
export interface ProtocolErrorBase {
|
|
@@ -445,6 +452,17 @@ export interface ProtocolSyncError extends ProtocolErrorBase {
|
|
|
445
452
|
readonly type: "ProtocolSyncError";
|
|
446
453
|
}
|
|
447
454
|
|
|
455
|
+
/**
|
|
456
|
+
* Error when embedded timestamp doesn't match expected timestamp in
|
|
457
|
+
* EncryptedDbChange. Indicates potential tampering or corruption of CRDT
|
|
458
|
+
* messages.
|
|
459
|
+
*/
|
|
460
|
+
export interface ProtocolTimestampMismatchError {
|
|
461
|
+
readonly type: "ProtocolTimestampMismatchError";
|
|
462
|
+
readonly expected: Timestamp;
|
|
463
|
+
readonly embedded: Timestamp;
|
|
464
|
+
}
|
|
465
|
+
|
|
448
466
|
/**
|
|
449
467
|
* Creates a {@link ProtocolMessage} from CRDT messages.
|
|
450
468
|
*
|
|
@@ -469,7 +487,7 @@ export const createProtocolMessageFromCrdtMessages =
|
|
|
469
487
|
|
|
470
488
|
for (const message of messages) {
|
|
471
489
|
const change = encodeAndEncryptDbChange(deps)(
|
|
472
|
-
message
|
|
490
|
+
message,
|
|
473
491
|
owner.encryptionKey,
|
|
474
492
|
);
|
|
475
493
|
const encryptedCrdtMessage = { timestamp: message.timestamp, change };
|
|
@@ -1148,6 +1166,11 @@ const sync =
|
|
|
1148
1166
|
const ranges = decodeRanges(input);
|
|
1149
1167
|
|
|
1150
1168
|
if (!isNonEmptyReadonlyArray(ranges)) {
|
|
1169
|
+
// Non-initiators always respond to provide sync completion feedback,
|
|
1170
|
+
// even when there's nothing to sync.
|
|
1171
|
+
if (role === "non-initiator") {
|
|
1172
|
+
return ok(output.unwrap());
|
|
1173
|
+
}
|
|
1151
1174
|
// Nothing to sync.
|
|
1152
1175
|
return ok(null);
|
|
1153
1176
|
}
|
|
@@ -1348,6 +1371,13 @@ const sync =
|
|
|
1348
1371
|
|
|
1349
1372
|
// If all ranges were skipped, there are no changes and sync is complete.
|
|
1350
1373
|
const hasChange = output.getSize() > outputInitialSize;
|
|
1374
|
+
|
|
1375
|
+
// Non-initiators always respond to provide sync completion feedback,
|
|
1376
|
+
// even with empty messages. This allows clients to detect sync completion.
|
|
1377
|
+
if (role === "non-initiator" && !hasChange) {
|
|
1378
|
+
return ok(output.unwrap());
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1351
1381
|
return ok(hasChange ? output.unwrap() : null);
|
|
1352
1382
|
};
|
|
1353
1383
|
|
|
@@ -1484,7 +1514,7 @@ const decodeTimestamps = (
|
|
|
1484
1514
|
for (let i = 0; i < length; i++) {
|
|
1485
1515
|
const deltaMillis = decodeNonNegativeInt(buffer);
|
|
1486
1516
|
const millis = Millis.from(previousMillis + deltaMillis);
|
|
1487
|
-
if (!millis.ok) throw new
|
|
1517
|
+
if (!millis.ok) throw new ProtocolDecodeError(millis.error.type);
|
|
1488
1518
|
millises.push(millis.value);
|
|
1489
1519
|
previousMillis = millis.value;
|
|
1490
1520
|
}
|
|
@@ -1493,7 +1523,7 @@ const decodeTimestamps = (
|
|
|
1493
1523
|
let counterIndex = 0;
|
|
1494
1524
|
while (counterIndex < length) {
|
|
1495
1525
|
const counter = Counter.from(decodeNonNegativeInt(buffer));
|
|
1496
|
-
if (!counter.ok) throw new
|
|
1526
|
+
if (!counter.ok) throw new ProtocolDecodeError(counter.error.type);
|
|
1497
1527
|
const runLength = decodeNonNegativeInt(buffer);
|
|
1498
1528
|
for (let i = 0; i < runLength; i++) {
|
|
1499
1529
|
counters.push(counter.value);
|
|
@@ -1659,12 +1689,24 @@ export const binaryTimestampToFingerprint = (
|
|
|
1659
1689
|
/**
|
|
1660
1690
|
* Encodes and encrypts a {@link DbChange} using the provided owner's encryption
|
|
1661
1691
|
* key. Returns an encrypted binary representation as {@link EncryptedDbChange}.
|
|
1692
|
+
*
|
|
1693
|
+
* The format includes the protocol version for backward compatibility and the
|
|
1694
|
+
* timestamp for tamper-proof verification that the timestamp matches the change
|
|
1695
|
+
* data.
|
|
1662
1696
|
*/
|
|
1663
1697
|
export const encodeAndEncryptDbChange =
|
|
1664
1698
|
(deps: SymmetricCryptoDep) =>
|
|
1665
|
-
(
|
|
1699
|
+
(message: CrdtMessage, key: EncryptionKey): EncryptedDbChange => {
|
|
1700
|
+
const change = message.change;
|
|
1666
1701
|
const buffer = createBuffer();
|
|
1667
1702
|
|
|
1703
|
+
// Encode protocol version first for backward compatibility
|
|
1704
|
+
encodeNonNegativeInt(buffer, protocolVersion);
|
|
1705
|
+
|
|
1706
|
+
// Encode the timestamp (after version) for tamper verification
|
|
1707
|
+
const binaryTimestamp = timestampToBinaryTimestamp(message.timestamp);
|
|
1708
|
+
buffer.extend(binaryTimestamp);
|
|
1709
|
+
|
|
1668
1710
|
encodeBase64Url256(buffer, change.table);
|
|
1669
1711
|
|
|
1670
1712
|
buffer.extend(idToBinaryId(change.id));
|
|
@@ -1700,50 +1742,76 @@ export const encodeAndEncryptDbChange =
|
|
|
1700
1742
|
};
|
|
1701
1743
|
|
|
1702
1744
|
/**
|
|
1703
|
-
* Decrypts and decodes an {@link
|
|
1704
|
-
* encryption key.
|
|
1745
|
+
* Decrypts and decodes an {@link EncryptedCrdtMessage} using the provided
|
|
1746
|
+
* owner's encryption key. Verifies that the embedded timestamp matches the
|
|
1747
|
+
* expected timestamp to ensure message integrity.
|
|
1705
1748
|
*/
|
|
1706
1749
|
export const decryptAndDecodeDbChange =
|
|
1707
1750
|
(deps: SymmetricCryptoDep) =>
|
|
1708
1751
|
(
|
|
1709
|
-
|
|
1752
|
+
message: EncryptedCrdtMessage,
|
|
1710
1753
|
key: EncryptionKey,
|
|
1711
|
-
): Result<
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1754
|
+
): Result<
|
|
1755
|
+
DbChange,
|
|
1756
|
+
| SymmetricCryptoDecryptError
|
|
1757
|
+
| ProtocolInvalidDataError
|
|
1758
|
+
| ProtocolTimestampMismatchError
|
|
1759
|
+
> =>
|
|
1760
|
+
tryDecodeProtocolData<
|
|
1761
|
+
DbChange,
|
|
1762
|
+
SymmetricCryptoDecryptError | ProtocolTimestampMismatchError
|
|
1763
|
+
>(message.change, (buffer) => {
|
|
1764
|
+
const nonce = buffer.shiftN(deps.symmetricCrypto.nonceLength);
|
|
1765
|
+
|
|
1766
|
+
const ciphertextLength = decodeLength(buffer);
|
|
1767
|
+
const ciphertext = buffer.shiftN(ciphertextLength);
|
|
1768
|
+
|
|
1769
|
+
const plaintextBytes = deps.symmetricCrypto.decrypt(
|
|
1770
|
+
ciphertext,
|
|
1771
|
+
key,
|
|
1772
|
+
nonce,
|
|
1773
|
+
);
|
|
1774
|
+
if (!plaintextBytes.ok) return plaintextBytes;
|
|
1726
1775
|
|
|
1727
|
-
|
|
1728
|
-
|
|
1776
|
+
buffer.reset();
|
|
1777
|
+
buffer.extend(plaintextBytes.value);
|
|
1729
1778
|
|
|
1730
|
-
|
|
1731
|
-
|
|
1779
|
+
// Decode version (for future compatibility, no validation needed for now)
|
|
1780
|
+
decodeNonNegativeInt(buffer);
|
|
1732
1781
|
|
|
1733
|
-
|
|
1734
|
-
|
|
1782
|
+
// Decode and verify the embedded timestamp
|
|
1783
|
+
const embeddedBinaryTimestamp = buffer.shiftN(
|
|
1784
|
+
binaryTimestampLength,
|
|
1785
|
+
) as BinaryTimestamp;
|
|
1786
|
+
const embeddedTimestamp = binaryTimestampToTimestamp(
|
|
1787
|
+
embeddedBinaryTimestamp,
|
|
1788
|
+
);
|
|
1735
1789
|
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1790
|
+
// Verify timestamp integrity
|
|
1791
|
+
if (!eqTimestamp(embeddedTimestamp, message.timestamp)) {
|
|
1792
|
+
return err<ProtocolTimestampMismatchError>({
|
|
1793
|
+
type: "ProtocolTimestampMismatchError",
|
|
1794
|
+
expected: message.timestamp,
|
|
1795
|
+
embedded: embeddedTimestamp,
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1741
1798
|
|
|
1742
|
-
|
|
1799
|
+
const table = decodeBase64Url256WithLength(buffer);
|
|
1800
|
+
const id = decodeId(buffer);
|
|
1743
1801
|
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1802
|
+
const length = decodeLength(buffer);
|
|
1803
|
+
const values = Object.create(null) as Record<string, SqliteValue>;
|
|
1804
|
+
|
|
1805
|
+
for (let i = 0; i < length; i++) {
|
|
1806
|
+
const column = decodeBase64Url256WithLength(buffer);
|
|
1807
|
+
const value = decodeSqliteValue(buffer);
|
|
1808
|
+
values[column] = value;
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
const dbChange = { table, id, values };
|
|
1812
|
+
|
|
1813
|
+
return ok(dbChange);
|
|
1814
|
+
});
|
|
1747
1815
|
|
|
1748
1816
|
/**
|
|
1749
1817
|
* Encodes a non-negative integer into a variable-length integer format. It's
|
package/src/Evolu/Timestamp.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { assert } from "../Assert.js";
|
|
2
|
+
import { createEqObject, eqNumber, eqString } from "../Eq.js";
|
|
2
3
|
import { NanoIdLibDep } from "../NanoId.js";
|
|
3
4
|
import { increment } from "../Number.js";
|
|
4
5
|
import { Order, orderUint8Array } from "../Order.js";
|
|
@@ -124,6 +125,13 @@ export const Timestamp = object({
|
|
|
124
125
|
});
|
|
125
126
|
export type Timestamp = typeof Timestamp.Type;
|
|
126
127
|
|
|
128
|
+
/** Equality function for comparing {@link Timestamp}. */
|
|
129
|
+
export const eqTimestamp = createEqObject<Timestamp>({
|
|
130
|
+
millis: eqNumber,
|
|
131
|
+
counter: eqNumber,
|
|
132
|
+
nodeId: eqString,
|
|
133
|
+
});
|
|
134
|
+
|
|
127
135
|
export const createTimestamp = ({
|
|
128
136
|
millis = minMillis,
|
|
129
137
|
counter = minCounter,
|