@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.
Files changed (55) hide show
  1. package/README.md +2 -2
  2. package/dist/src/Array.d.ts +3 -3
  3. package/dist/src/Array.js +3 -3
  4. package/dist/src/Crypto.d.ts +6 -8
  5. package/dist/src/Crypto.d.ts.map +1 -1
  6. package/dist/src/Crypto.js +9 -9
  7. package/dist/src/Evolu/Db.d.ts.map +1 -1
  8. package/dist/src/Evolu/Db.js +4 -4
  9. package/dist/src/Evolu/Evolu.d.ts +9 -4
  10. package/dist/src/Evolu/Evolu.d.ts.map +1 -1
  11. package/dist/src/Evolu/Evolu.js +14 -14
  12. package/dist/src/Evolu/Protocol.d.ts +27 -2
  13. package/dist/src/Evolu/Protocol.d.ts.map +1 -1
  14. package/dist/src/Evolu/Protocol.js +71 -31
  15. package/dist/src/Evolu/PublicKysely.d.ts.map +1 -1
  16. package/dist/src/Evolu/PublicKysely.js +0 -1
  17. package/dist/src/Evolu/Query.d.ts +1 -1
  18. package/dist/src/Evolu/Query.d.ts.map +1 -1
  19. package/dist/src/Evolu/Query.js +1 -1
  20. package/dist/src/Evolu/Schema.d.ts +28 -22
  21. package/dist/src/Evolu/Schema.d.ts.map +1 -1
  22. package/dist/src/Evolu/Schema.js +37 -32
  23. package/dist/src/Evolu/Storage.d.ts +11 -2
  24. package/dist/src/Evolu/Storage.d.ts.map +1 -1
  25. package/dist/src/Evolu/Storage.js +21 -3
  26. package/dist/src/Evolu/Sync.d.ts +4 -3
  27. package/dist/src/Evolu/Sync.d.ts.map +1 -1
  28. package/dist/src/Evolu/Sync.js +68 -40
  29. package/dist/src/Evolu/Timestamp.d.ts +10 -14
  30. package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
  31. package/dist/src/Evolu/Timestamp.js +3 -16
  32. package/dist/src/Object.d.ts +10 -4
  33. package/dist/src/Object.d.ts.map +1 -1
  34. package/dist/src/Object.js +9 -3
  35. package/dist/src/Sqlite.d.ts +29 -3
  36. package/dist/src/Sqlite.d.ts.map +1 -1
  37. package/dist/src/Sqlite.js +29 -3
  38. package/dist/src/Type.d.ts +94 -47
  39. package/dist/src/Type.d.ts.map +1 -1
  40. package/dist/src/Type.js +110 -65
  41. package/package.json +2 -2
  42. package/src/Array.ts +3 -3
  43. package/src/Crypto.ts +11 -9
  44. package/src/Evolu/Db.ts +7 -7
  45. package/src/Evolu/Evolu.ts +31 -24
  46. package/src/Evolu/Protocol.ts +84 -37
  47. package/src/Evolu/PublicKysely.ts +1 -2
  48. package/src/Evolu/Query.ts +2 -2
  49. package/src/Evolu/Schema.ts +54 -50
  50. package/src/Evolu/Storage.ts +39 -2
  51. package/src/Evolu/Sync.ts +97 -43
  52. package/src/Evolu/Timestamp.ts +5 -25
  53. package/src/Object.ts +13 -5
  54. package/src/Sqlite.ts +33 -3
  55. 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 calls are hard to follow
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
- * // Pragmatic: name each step (or use p1, p2 if lazy)
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. See the PURBs paper for details:
176
- * https://bford.info/pub/sec/purb.pdf
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 padmePaddedLength = (length: NonNegativeInt): NonNegativeInt => {
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
- * Returns the PADMÉ padding length for a given input length. Uses
189
- * {@link padmePaddedLength}.
190
- */
191
- export const padmePaddingLength = (length: NonNegativeInt): NonNegativeInt => {
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
- TimestampString,
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: TimestampString;
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
- timestampStringToTimestamp(config.clock),
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" text not null,
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
- ${timestampToTimestampString(initialClock)},
530
+ ${timestampToTimestampBytes(initialClock)},
531
531
  ${initialAppOwner.id},
532
532
  ${initialAppOwner.encryptionKey},
533
533
  ${initialAppOwner.writeKey},
@@ -4,7 +4,7 @@ import {
4
4
  isNonEmptyArray,
5
5
  isNonEmptyReadonlyArray,
6
6
  } from "../Array.js";
7
- import { assert, assertNonEmptyReadonlyArray } from "../Assert.js";
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 { isSqlMutation, SafeSql, SqliteError, SqliteQuery } from "../Sqlite.js";
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 add and update {@link DefaultColumns}.
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 add and update {@link DefaultColumns}.
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 add and update {@link DefaultColumns}.
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
- /** Export SQLite database file as Uint8Array. */
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 = { ...result.value };
717
- delete values.id;
718
-
719
- if (kind === "insert" || kind === "upsert") {
720
- // Only set createdAt if not provided by user
721
- if (!("createdAt" in values)) {
722
- values.createdAt = new Date(deps.time.now()).toISOString();
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
- const mutationChange = { ...dbChange, ownerId: options?.ownerId };
733
- mutateMicrotaskQueue.push([mutationChange, options?.onComplete]);
737
+ mutateMicrotaskQueue.push([
738
+ { ...dbChange, ownerId: options?.ownerId },
739
+ options?.onComplete,
740
+ ]);
734
741
  }
735
742
 
736
743
  if (mutateMicrotaskQueue.length === 1) {
@@ -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
- * - ProtocolQuotaError should return storedBytes and actual quota.
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 plan.
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 embedded: Timestamp;
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 (after version) for tamper verification
1685
- const timestampBytes = timestampToTimestampBytes(message.timestamp);
1686
- buffer.extend(timestampBytes);
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
- encodeString(buffer, change.table);
1734
+ encodeFlags(buffer, [
1735
+ message.change.isInsert,
1736
+ message.change.isDelete != null,
1737
+ message.change.isDelete ?? false,
1738
+ ]);
1689
1739
 
1690
- buffer.extend(idToIdBytes(change.id));
1740
+ encodeString(buffer, message.change.table);
1741
+ buffer.extend(idToIdBytes(message.change.id));
1691
1742
 
1692
- const entries = objectToEntries(change.values).map(
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
- const paddingLength = padmePaddingLength(buffer.getLength());
1706
- // Add zero bytes as PADMÉ padding - these will be ignored during decoding.
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 ciphertextLength = decodeLength(buffer);
1744
- const ciphertext = buffer.shiftN(ciphertextLength);
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, no validation needed for now)
1799
+ // Decode version (for future compatibility, not need yet)
1757
1800
  decodeNonNegativeInt(buffer);
1758
1801
 
1759
- // Decode and verify the embedded timestamp
1760
- const embeddedTimestampBytes = buffer.shiftN(timestampBytesLength);
1761
- const embeddedTimestamp = timestampBytesToTimestamp(
1762
- embeddedTimestampBytes as TimestampBytes,
1802
+ const timestamp = timestampBytesToTimestamp(
1803
+ buffer.shiftN(timestampBytesLength) as TimestampBytes,
1763
1804
  );
1764
1805
 
1765
- // Verify timestamp integrity
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
- embedded: embeddedTimestamp,
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 = { table, id, values };
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
- args.push(kyselyJsonIdentifier, kyselyJsonIdentifier);
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(
@@ -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).sort(([a], [b]) => a.localeCompare(b))
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>;