@evolu/common 6.0.1-preview.33 → 6.0.1-preview.35
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 +2 -2
- package/dist/src/Array.d.ts +3 -3
- package/dist/src/Array.js +3 -3
- package/dist/src/Crypto.d.ts +6 -8
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +9 -9
- package/dist/src/Evolu/Db.d.ts.map +1 -1
- package/dist/src/Evolu/Db.js +4 -4
- package/dist/src/Evolu/Evolu.d.ts +9 -4
- package/dist/src/Evolu/Evolu.d.ts.map +1 -1
- package/dist/src/Evolu/Evolu.js +14 -14
- package/dist/src/Evolu/Protocol.d.ts +27 -2
- package/dist/src/Evolu/Protocol.d.ts.map +1 -1
- package/dist/src/Evolu/Protocol.js +71 -31
- package/dist/src/Evolu/PublicKysely.d.ts.map +1 -1
- package/dist/src/Evolu/PublicKysely.js +0 -1
- package/dist/src/Evolu/Query.d.ts +1 -1
- package/dist/src/Evolu/Query.d.ts.map +1 -1
- package/dist/src/Evolu/Query.js +1 -1
- package/dist/src/Evolu/Schema.d.ts +28 -22
- package/dist/src/Evolu/Schema.d.ts.map +1 -1
- package/dist/src/Evolu/Schema.js +37 -32
- package/dist/src/Evolu/Storage.d.ts +11 -2
- package/dist/src/Evolu/Storage.d.ts.map +1 -1
- package/dist/src/Evolu/Storage.js +21 -3
- package/dist/src/Evolu/Sync.d.ts +4 -3
- package/dist/src/Evolu/Sync.d.ts.map +1 -1
- package/dist/src/Evolu/Sync.js +68 -40
- package/dist/src/Evolu/Timestamp.d.ts +10 -14
- package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
- package/dist/src/Evolu/Timestamp.js +3 -16
- package/dist/src/Object.d.ts +10 -4
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +9 -3
- package/dist/src/Sqlite.d.ts +29 -3
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +29 -3
- package/dist/src/Type.d.ts +94 -47
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +110 -65
- package/package.json +2 -2
- package/src/Array.ts +3 -3
- package/src/Crypto.ts +11 -9
- package/src/Evolu/Db.ts +7 -7
- package/src/Evolu/Evolu.ts +31 -24
- package/src/Evolu/Protocol.ts +84 -37
- package/src/Evolu/PublicKysely.ts +1 -2
- package/src/Evolu/Query.ts +2 -2
- package/src/Evolu/Schema.ts +54 -50
- package/src/Evolu/Storage.ts +39 -2
- package/src/Evolu/Sync.ts +97 -43
- package/src/Evolu/Timestamp.ts +5 -25
- package/src/Object.ts +13 -5
- package/src/Sqlite.ts +33 -3
- package/src/Type.ts +124 -80
package/src/Array.ts
CHANGED
|
@@ -6,19 +6,19 @@
|
|
|
6
6
|
* Prepared for TC39 Hack pipes:
|
|
7
7
|
*
|
|
8
8
|
* ```ts
|
|
9
|
-
* // Problem: nested
|
|
9
|
+
* // Problem: nested functions can be hard to follow
|
|
10
10
|
* const result = firstInArray(
|
|
11
11
|
* mapArray(dedupeArray(appendToArray(value, 2)), (x) => x * 2),
|
|
12
12
|
* );
|
|
13
13
|
*
|
|
14
|
-
* // Ideal: TC39 Hack pipes (when available)
|
|
14
|
+
* // Ideal solution: TC39 Hack pipes (when available)
|
|
15
15
|
* // const result = value
|
|
16
16
|
* // |> appendToArray(%, 2)
|
|
17
17
|
* // |> dedupeArray(%)
|
|
18
18
|
* // |> mapArray(%, (x) => x * 2)
|
|
19
19
|
* // |> firstInArray(%);
|
|
20
20
|
*
|
|
21
|
-
* //
|
|
21
|
+
* // Current solution: name each step (or use p1, p2 if lazy)
|
|
22
22
|
* const p1 = appendToArray(value, 2);
|
|
23
23
|
* const p2 = dedupeArray(p1);
|
|
24
24
|
* const p3 = mapArray(p2, (x) => x * 2);
|
package/src/Crypto.ts
CHANGED
|
@@ -172,10 +172,13 @@ export const createSymmetricCrypto = (
|
|
|
172
172
|
* Returns the PADMÉ padded length for a given input length.
|
|
173
173
|
*
|
|
174
174
|
* PADMÉ limits information leakage about the length of the plain-text for a
|
|
175
|
-
* wide range of encrypted data sizes.
|
|
176
|
-
*
|
|
175
|
+
* wide range of encrypted data sizes.
|
|
176
|
+
*
|
|
177
|
+
* See the PURBs paper for details: https://bford.info/pub/sec/purb.pdf
|
|
177
178
|
*/
|
|
178
|
-
export const
|
|
179
|
+
export const createPadmePaddedLength = (
|
|
180
|
+
length: NonNegativeInt,
|
|
181
|
+
): NonNegativeInt => {
|
|
179
182
|
if (length <= 0) return NonNegativeInt.orThrow(0);
|
|
180
183
|
const e = 31 - Math.clz32(length >>> 0);
|
|
181
184
|
const s = 32 - Math.clz32(e >>> 0);
|
|
@@ -184,12 +187,11 @@ export const padmePaddedLength = (length: NonNegativeInt): NonNegativeInt => {
|
|
|
184
187
|
return NonNegativeInt.orThrow((length + mask) & ~mask);
|
|
185
188
|
};
|
|
186
189
|
|
|
187
|
-
/**
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return NonNegativeInt.orThrow(padmePaddedLength(length) - length);
|
|
190
|
+
/** Creates a PADMÉ padding array of zeros for the given input length. */
|
|
191
|
+
export const createPadmePadding = (length: NonNegativeInt): Uint8Array => {
|
|
192
|
+
const paddedLength = createPadmePaddedLength(length);
|
|
193
|
+
const paddingLength = NonNegativeInt.orThrow(paddedLength - length);
|
|
194
|
+
return new globalThis.Uint8Array(paddingLength);
|
|
193
195
|
};
|
|
194
196
|
|
|
195
197
|
/**
|
package/src/Evolu/Db.ts
CHANGED
|
@@ -63,11 +63,11 @@ import {
|
|
|
63
63
|
} from "./Sync.js";
|
|
64
64
|
import {
|
|
65
65
|
Timestamp,
|
|
66
|
+
TimestampBytes,
|
|
67
|
+
timestampBytesToTimestamp,
|
|
66
68
|
TimestampConfig,
|
|
67
69
|
TimestampError,
|
|
68
|
-
|
|
69
|
-
timestampStringToTimestamp,
|
|
70
|
-
timestampToTimestampString,
|
|
70
|
+
timestampToTimestampBytes,
|
|
71
71
|
} from "./Timestamp.js";
|
|
72
72
|
|
|
73
73
|
export interface DbConfig extends ConsoleConfig, TimestampConfig {
|
|
@@ -387,7 +387,7 @@ const createDbWorkerDeps =
|
|
|
387
387
|
// }
|
|
388
388
|
|
|
389
389
|
const configResult = sqlite.exec<{
|
|
390
|
-
clock:
|
|
390
|
+
clock: TimestampBytes;
|
|
391
391
|
appOwnerId: OwnerId;
|
|
392
392
|
appOwnerEncryptionKey: OwnerEncryptionKey;
|
|
393
393
|
appOwnerWriteKey: OwnerWriteKey;
|
|
@@ -415,7 +415,7 @@ const createDbWorkerDeps =
|
|
|
415
415
|
};
|
|
416
416
|
|
|
417
417
|
clock = createClock({ ...platformDeps, sqlite })(
|
|
418
|
-
|
|
418
|
+
timestampBytesToTimestamp(config.clock),
|
|
419
419
|
);
|
|
420
420
|
} else {
|
|
421
421
|
appOwner =
|
|
@@ -507,7 +507,7 @@ const initializeDb =
|
|
|
507
507
|
|
|
508
508
|
sql`
|
|
509
509
|
create table evolu_config (
|
|
510
|
-
"clock"
|
|
510
|
+
"clock" blob not null,
|
|
511
511
|
"appOwnerId" text not null,
|
|
512
512
|
"appOwnerEncryptionKey" blob not null,
|
|
513
513
|
"appOwnerWriteKey" blob not null,
|
|
@@ -527,7 +527,7 @@ const initializeDb =
|
|
|
527
527
|
)
|
|
528
528
|
values
|
|
529
529
|
(
|
|
530
|
-
${
|
|
530
|
+
${timestampToTimestampBytes(initialClock)},
|
|
531
531
|
${initialAppOwner.id},
|
|
532
532
|
${initialAppOwner.encryptionKey},
|
|
533
533
|
${initialAppOwner.writeKey},
|
package/src/Evolu/Evolu.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
isNonEmptyArray,
|
|
5
5
|
isNonEmptyReadonlyArray,
|
|
6
6
|
} from "../Array.js";
|
|
7
|
-
import {
|
|
7
|
+
import { assertNonEmptyReadonlyArray } from "../Assert.js";
|
|
8
8
|
import { createCallbacks } from "../Callbacks.js";
|
|
9
9
|
import { ConsoleDep } from "../Console.js";
|
|
10
10
|
import { RandomBytesDep, SymmetricCryptoDecryptError } from "../Crypto.js";
|
|
@@ -13,7 +13,13 @@ import { TransferableError } from "../Error.js";
|
|
|
13
13
|
import { exhaustiveCheck } from "../Function.js";
|
|
14
14
|
import { createInstances, Instances } from "../Instances.js";
|
|
15
15
|
import { err, ok, Result } from "../Result.js";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
isSqlMutation,
|
|
18
|
+
SafeSql,
|
|
19
|
+
SqliteBoolean,
|
|
20
|
+
SqliteError,
|
|
21
|
+
SqliteQuery,
|
|
22
|
+
} from "../Sqlite.js";
|
|
17
23
|
import { createStore, StoreSubscribe } from "../Store.js";
|
|
18
24
|
import { TimeDep } from "../Time.js";
|
|
19
25
|
import {
|
|
@@ -48,7 +54,6 @@ import {
|
|
|
48
54
|
} from "./Query.js";
|
|
49
55
|
import {
|
|
50
56
|
CreateQuery,
|
|
51
|
-
DefaultColumns,
|
|
52
57
|
EvoluSchema,
|
|
53
58
|
evoluSchemaToDbSchema,
|
|
54
59
|
IndexesConfig,
|
|
@@ -59,6 +64,7 @@ import {
|
|
|
59
64
|
MutationKind,
|
|
60
65
|
MutationMapping,
|
|
61
66
|
MutationOptions,
|
|
67
|
+
SystemColumns,
|
|
62
68
|
updateable,
|
|
63
69
|
upsertable,
|
|
64
70
|
ValidateSchema,
|
|
@@ -239,7 +245,7 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> extends Disposable {
|
|
|
239
245
|
*
|
|
240
246
|
* Evolu does not use SQL for mutations to ensure data can be safely and
|
|
241
247
|
* predictably merged without conflicts. Explicit mutations also allow Evolu
|
|
242
|
-
* to automatically
|
|
248
|
+
* to automatically update {@link SystemColumns}.
|
|
243
249
|
*
|
|
244
250
|
* ### Example
|
|
245
251
|
*
|
|
@@ -284,7 +290,7 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> extends Disposable {
|
|
|
284
290
|
*
|
|
285
291
|
* Evolu does not use SQL for mutations to ensure data can be safely and
|
|
286
292
|
* predictably merged without conflicts. Explicit mutations also allow Evolu
|
|
287
|
-
* to automatically
|
|
293
|
+
* to automatically update {@link SystemColumns}.
|
|
288
294
|
*
|
|
289
295
|
* ### Example
|
|
290
296
|
*
|
|
@@ -337,7 +343,7 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> extends Disposable {
|
|
|
337
343
|
*
|
|
338
344
|
* Evolu does not use SQL for mutations to ensure data can be safely and
|
|
339
345
|
* predictably merged without conflicts. Explicit mutations also allow Evolu
|
|
340
|
-
* to automatically
|
|
346
|
+
* to automatically update {@link SystemColumns}.
|
|
341
347
|
*
|
|
342
348
|
* ### Example
|
|
343
349
|
*
|
|
@@ -408,7 +414,12 @@ export interface Evolu<S extends EvoluSchema = EvoluSchema> extends Disposable {
|
|
|
408
414
|
*/
|
|
409
415
|
readonly reloadApp: () => void;
|
|
410
416
|
|
|
411
|
-
/**
|
|
417
|
+
/**
|
|
418
|
+
* Export SQLite database file as Uint8Array.
|
|
419
|
+
*
|
|
420
|
+
* In the future, it will be possible to import a database and export/import
|
|
421
|
+
* history for 1:1 migrations across owners.
|
|
422
|
+
*/
|
|
412
423
|
readonly exportDatabase: () => Promise<Uint8Array<ArrayBuffer>>;
|
|
413
424
|
|
|
414
425
|
/**
|
|
@@ -713,24 +724,20 @@ const createEvoluInstance =
|
|
|
713
724
|
// Mark the transaction as invalid by pushing null
|
|
714
725
|
mutateMicrotaskQueue.push([null, undefined]);
|
|
715
726
|
} else {
|
|
716
|
-
const values =
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
const dbChange = { table, id, values };
|
|
727
|
-
assert(
|
|
728
|
-
DbChange.is(dbChange),
|
|
729
|
-
`Failed to create DbChange for table "${dbChange.table}"`,
|
|
730
|
-
);
|
|
727
|
+
const { id: _, isDeleted, ...values } = result.value;
|
|
728
|
+
|
|
729
|
+
const dbChange = DbChange.orThrow({
|
|
730
|
+
table,
|
|
731
|
+
id,
|
|
732
|
+
values,
|
|
733
|
+
isInsert: kind === "insert" || kind === "upsert",
|
|
734
|
+
isDelete: SqliteBoolean.is(isDeleted) ? Boolean(isDeleted) : null,
|
|
735
|
+
});
|
|
731
736
|
|
|
732
|
-
|
|
733
|
-
|
|
737
|
+
mutateMicrotaskQueue.push([
|
|
738
|
+
{ ...dbChange, ownerId: options?.ownerId },
|
|
739
|
+
options?.onComplete,
|
|
740
|
+
]);
|
|
734
741
|
}
|
|
735
742
|
|
|
736
743
|
if (mutateMicrotaskQueue.length === 1) {
|
package/src/Evolu/Protocol.ts
CHANGED
|
@@ -170,7 +170,11 @@
|
|
|
170
170
|
* initiator/non-initiator terminology instead, and consolidate into a single
|
|
171
171
|
* `applyProtocolMessage` function with conditional arguments to reduce code
|
|
172
172
|
* duplication.
|
|
173
|
-
* -
|
|
173
|
+
* - Replace try-catch with Result + new Error (to preserve stacktraces). Measure
|
|
174
|
+
* Result overhead, it should be super small.
|
|
175
|
+
* - Allow clients to broadcast messages that are not persisted by relays. This
|
|
176
|
+
* would enable real-time ephemeral data (like cursor positions, typing
|
|
177
|
+
* indicators) to be forwarded by relays without storage overhead.
|
|
174
178
|
*/
|
|
175
179
|
|
|
176
180
|
import { Packr } from "msgpackr";
|
|
@@ -186,8 +190,8 @@ import {
|
|
|
186
190
|
utf8ToBytes,
|
|
187
191
|
} from "../Buffer.js";
|
|
188
192
|
import {
|
|
193
|
+
createPadmePadding,
|
|
189
194
|
EncryptionKey,
|
|
190
|
-
padmePaddingLength,
|
|
191
195
|
RandomBytesDep,
|
|
192
196
|
SymmetricCryptoDecryptError,
|
|
193
197
|
SymmetricCryptoDep,
|
|
@@ -414,7 +418,9 @@ export interface ProtocolWriteError extends BaseOwnerError {
|
|
|
414
418
|
* excess local data is affected. Other devices that haven't exceeded quota can
|
|
415
419
|
* still sync normally.
|
|
416
420
|
*
|
|
417
|
-
* Clients should prompt the user to upgrade their
|
|
421
|
+
* Clients should prompt the user to contact the relay provider or upgrade their
|
|
422
|
+
* plan. Quota monitoring and management is the relay provider's
|
|
423
|
+
* responsibility.
|
|
418
424
|
*/
|
|
419
425
|
export interface ProtocolQuotaError extends BaseOwnerError {
|
|
420
426
|
readonly type: "ProtocolQuotaError";
|
|
@@ -436,7 +442,7 @@ export interface ProtocolSyncError extends BaseOwnerError {
|
|
|
436
442
|
export interface ProtocolTimestampMismatchError {
|
|
437
443
|
readonly type: "ProtocolTimestampMismatchError";
|
|
438
444
|
readonly expected: Timestamp;
|
|
439
|
-
readonly
|
|
445
|
+
readonly timestamp: Timestamp;
|
|
440
446
|
}
|
|
441
447
|
|
|
442
448
|
/**
|
|
@@ -909,8 +915,6 @@ export const applyProtocolMessageAsClient =
|
|
|
909
915
|
| ProtocolQuotaError
|
|
910
916
|
>
|
|
911
917
|
> => {
|
|
912
|
-
// try-catch instead of Result for performance and stacktraces
|
|
913
|
-
// DEV: Measure it again, I think we should use Result with new Error.
|
|
914
918
|
try {
|
|
915
919
|
const input = createBuffer(inputMessage);
|
|
916
920
|
const [requestedVersion, ownerId] = decodeVersionAndOwner(input);
|
|
@@ -1057,8 +1061,6 @@ export const applyProtocolMessageAsRelay =
|
|
|
1057
1061
|
): Promise<
|
|
1058
1062
|
Result<ApplyProtocolMessageAsRelayResult, ProtocolInvalidDataError>
|
|
1059
1063
|
> => {
|
|
1060
|
-
// try-catch instead of Result for performance and stacktraces
|
|
1061
|
-
// DEV: Measure it again, I think we should use Result with new Error.
|
|
1062
1064
|
try {
|
|
1063
1065
|
const input = createBuffer(inputMessage);
|
|
1064
1066
|
const [requestedVersion, ownerId] = decodeVersionAndOwner(input);
|
|
@@ -1664,6 +1666,52 @@ export const decodeNumber = (buffer: Buffer): number => {
|
|
|
1664
1666
|
return numberResult.value;
|
|
1665
1667
|
};
|
|
1666
1668
|
|
|
1669
|
+
/**
|
|
1670
|
+
* Encodes an array of boolean flags into a single byte.
|
|
1671
|
+
*
|
|
1672
|
+
* Each element in the array corresponds to a bit (0-7). Array can have 0-8
|
|
1673
|
+
* elements.
|
|
1674
|
+
*
|
|
1675
|
+
* ### Example
|
|
1676
|
+
*
|
|
1677
|
+
* ```ts
|
|
1678
|
+
* encodeFlags(buffer, [true, false, true]); // Encodes bits 0, 1, 2
|
|
1679
|
+
* ```
|
|
1680
|
+
*/
|
|
1681
|
+
export const encodeFlags = (
|
|
1682
|
+
buffer: Buffer,
|
|
1683
|
+
flags: ReadonlyArray<boolean>,
|
|
1684
|
+
): void => {
|
|
1685
|
+
let byte = 0;
|
|
1686
|
+
for (let i = 0; i < flags.length && i < 8; i++) {
|
|
1687
|
+
if (flags[i]) {
|
|
1688
|
+
byte |= 1 << i;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
buffer.extend([byte]);
|
|
1692
|
+
};
|
|
1693
|
+
|
|
1694
|
+
/**
|
|
1695
|
+
* Decodes a byte into an array of boolean flags.
|
|
1696
|
+
*
|
|
1697
|
+
* ### Example
|
|
1698
|
+
*
|
|
1699
|
+
* ```ts
|
|
1700
|
+
* const flags = decodeFlags(buffer, 3); // Decode 3 flags
|
|
1701
|
+
* ```
|
|
1702
|
+
*/
|
|
1703
|
+
export const decodeFlags = (
|
|
1704
|
+
buffer: Buffer,
|
|
1705
|
+
count: PositiveInt,
|
|
1706
|
+
): ReadonlyArray<boolean> => {
|
|
1707
|
+
const byte = buffer.shift();
|
|
1708
|
+
const flags: Array<boolean> = [];
|
|
1709
|
+
for (let i = 0; i < count && i < 8; i++) {
|
|
1710
|
+
flags.push((byte & (1 << i)) !== 0);
|
|
1711
|
+
}
|
|
1712
|
+
return flags;
|
|
1713
|
+
};
|
|
1714
|
+
|
|
1667
1715
|
/**
|
|
1668
1716
|
* Encodes and encrypts a {@link DbChange} using the provided owner's encryption
|
|
1669
1717
|
* key. Returns an encrypted binary representation as {@link EncryptedDbChange}.
|
|
@@ -1675,36 +1723,33 @@ export const decodeNumber = (buffer: Buffer): number => {
|
|
|
1675
1723
|
export const encodeAndEncryptDbChange =
|
|
1676
1724
|
(deps: SymmetricCryptoDep) =>
|
|
1677
1725
|
(message: CrdtMessage, key: EncryptionKey): EncryptedDbChange => {
|
|
1678
|
-
const change = message.change;
|
|
1679
1726
|
const buffer = createBuffer();
|
|
1680
1727
|
|
|
1681
|
-
// Encode protocol version first for backward compatibility
|
|
1682
1728
|
encodeNonNegativeInt(buffer, protocolVersion);
|
|
1683
1729
|
|
|
1684
|
-
// Encode the timestamp (
|
|
1685
|
-
|
|
1686
|
-
buffer.extend(
|
|
1730
|
+
// Encode the timestamp to prevent tampering (e.g., a malicious relay
|
|
1731
|
+
// assigning this EncryptedDbChange to a different EncryptedCrdtMessage)
|
|
1732
|
+
buffer.extend(timestampToTimestampBytes(message.timestamp));
|
|
1687
1733
|
|
|
1688
|
-
|
|
1734
|
+
encodeFlags(buffer, [
|
|
1735
|
+
message.change.isInsert,
|
|
1736
|
+
message.change.isDelete != null,
|
|
1737
|
+
message.change.isDelete ?? false,
|
|
1738
|
+
]);
|
|
1689
1739
|
|
|
1690
|
-
buffer.
|
|
1740
|
+
encodeString(buffer, message.change.table);
|
|
1741
|
+
buffer.extend(idToIdBytes(message.change.id));
|
|
1691
1742
|
|
|
1692
|
-
const entries = objectToEntries(change.values)
|
|
1693
|
-
([column, value]): [string, SqliteValue] => {
|
|
1694
|
-
return [column, value];
|
|
1695
|
-
},
|
|
1696
|
-
);
|
|
1743
|
+
const entries = objectToEntries(message.change.values);
|
|
1697
1744
|
|
|
1698
1745
|
encodeLength(buffer, entries);
|
|
1699
|
-
|
|
1700
1746
|
for (const [column, value] of entries) {
|
|
1701
1747
|
encodeString(buffer, column);
|
|
1702
1748
|
encodeSqliteValue(buffer, value);
|
|
1703
1749
|
}
|
|
1704
1750
|
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
buffer.extend(new Uint8Array(paddingLength));
|
|
1751
|
+
// Add PADMÉ padding (ignored during decoding)
|
|
1752
|
+
buffer.extend(createPadmePadding(buffer.getLength()));
|
|
1708
1753
|
|
|
1709
1754
|
const { nonce, ciphertext } = deps.symmetricCrypto.encrypt(
|
|
1710
1755
|
buffer.unwrap(),
|
|
@@ -1735,13 +1780,11 @@ export const decryptAndDecodeDbChange =
|
|
|
1735
1780
|
| ProtocolInvalidDataError
|
|
1736
1781
|
| ProtocolTimestampMismatchError
|
|
1737
1782
|
> => {
|
|
1738
|
-
// try-catch instead of Result for performance and stacktraces
|
|
1739
1783
|
try {
|
|
1740
1784
|
const buffer = createBuffer(message.change);
|
|
1741
|
-
const nonce = buffer.shiftN(deps.symmetricCrypto.nonceLength);
|
|
1742
1785
|
|
|
1743
|
-
const
|
|
1744
|
-
const ciphertext = buffer.shiftN(
|
|
1786
|
+
const nonce = buffer.shiftN(deps.symmetricCrypto.nonceLength);
|
|
1787
|
+
const ciphertext = buffer.shiftN(decodeLength(buffer));
|
|
1745
1788
|
|
|
1746
1789
|
const plaintextBytes = deps.symmetricCrypto.decrypt(
|
|
1747
1790
|
ciphertext,
|
|
@@ -1753,24 +1796,22 @@ export const decryptAndDecodeDbChange =
|
|
|
1753
1796
|
buffer.reset();
|
|
1754
1797
|
buffer.extend(plaintextBytes.value);
|
|
1755
1798
|
|
|
1756
|
-
// Decode version (for future compatibility,
|
|
1799
|
+
// Decode version (for future compatibility, not need yet)
|
|
1757
1800
|
decodeNonNegativeInt(buffer);
|
|
1758
1801
|
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
const embeddedTimestamp = timestampBytesToTimestamp(
|
|
1762
|
-
embeddedTimestampBytes as TimestampBytes,
|
|
1802
|
+
const timestamp = timestampBytesToTimestamp(
|
|
1803
|
+
buffer.shiftN(timestampBytesLength) as TimestampBytes,
|
|
1763
1804
|
);
|
|
1764
1805
|
|
|
1765
|
-
|
|
1766
|
-
if (!eqTimestamp(embeddedTimestamp, message.timestamp)) {
|
|
1806
|
+
if (!eqTimestamp(timestamp, message.timestamp)) {
|
|
1767
1807
|
return err<ProtocolTimestampMismatchError>({
|
|
1768
1808
|
type: "ProtocolTimestampMismatchError",
|
|
1769
1809
|
expected: message.timestamp,
|
|
1770
|
-
|
|
1810
|
+
timestamp,
|
|
1771
1811
|
});
|
|
1772
1812
|
}
|
|
1773
1813
|
|
|
1814
|
+
const flags = decodeFlags(buffer, PositiveInt.orThrow(3));
|
|
1774
1815
|
const table = decodeString(buffer);
|
|
1775
1816
|
const id = decodeId(buffer);
|
|
1776
1817
|
|
|
@@ -1783,7 +1824,13 @@ export const decryptAndDecodeDbChange =
|
|
|
1783
1824
|
values[column] = value;
|
|
1784
1825
|
}
|
|
1785
1826
|
|
|
1786
|
-
const dbChange = {
|
|
1827
|
+
const dbChange = DbChange.orThrow({
|
|
1828
|
+
table,
|
|
1829
|
+
id,
|
|
1830
|
+
values,
|
|
1831
|
+
isInsert: flags[0],
|
|
1832
|
+
isDelete: flags[1] ? flags[2] : null,
|
|
1833
|
+
});
|
|
1787
1834
|
|
|
1788
1835
|
return ok(dbChange);
|
|
1789
1836
|
} catch (error) {
|
|
@@ -209,8 +209,7 @@ export function getJsonObjectArgs(
|
|
|
209
209
|
table: string,
|
|
210
210
|
): Array<Expression<unknown> | string> {
|
|
211
211
|
const args: Array<Expression<unknown> | string> = [];
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
|
|
214
213
|
for (const { selection: s } of node.selections ?? []) {
|
|
215
214
|
if (ReferenceNode.is(s) && ColumnNode.is(s.column)) {
|
|
216
215
|
args.push(
|
package/src/Evolu/Query.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Brand } from "../Brand.js";
|
|
1
2
|
import { bytesToHex, hexToBytes } from "../Buffer.js";
|
|
2
3
|
import { objectToEntries } from "../Object.js";
|
|
3
4
|
import {
|
|
@@ -9,7 +10,6 @@ import {
|
|
|
9
10
|
} from "../Sqlite.js";
|
|
10
11
|
import { Store, StoreSubscribe } from "../Store.js";
|
|
11
12
|
import { Simplify } from "../Types.js";
|
|
12
|
-
import { Brand } from "../Brand.js";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* A type-safe SQL query.
|
|
@@ -52,7 +52,7 @@ export const serializeQuery = <R extends Row>(query: SqliteQuery): Query<R> => {
|
|
|
52
52
|
);
|
|
53
53
|
|
|
54
54
|
const options = query.options
|
|
55
|
-
? objectToEntries(query.options).
|
|
55
|
+
? objectToEntries(query.options).toSorted(([a], [b]) => a.localeCompare(b))
|
|
56
56
|
: [];
|
|
57
57
|
|
|
58
58
|
return JSON.stringify([query.sql, params, options]) as Query<R>;
|