@evolu/common 8.4.0 → 8.5.0

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 (74) hide show
  1. package/dist/src/Array.d.ts +24 -0
  2. package/dist/src/Array.d.ts.map +1 -1
  3. package/dist/src/Array.js +27 -1
  4. package/dist/src/Assert.d.ts.map +1 -1
  5. package/dist/src/Assert.js +1 -0
  6. package/dist/src/LeakDetector.d.ts.map +1 -1
  7. package/dist/src/LeakDetector.js +4 -1
  8. package/dist/src/Lookup.js +1 -0
  9. package/dist/src/Platform.js +1 -0
  10. package/dist/src/Relation.js +3 -3
  11. package/dist/src/Resource.d.ts.map +1 -1
  12. package/dist/src/Resource.js +9 -3
  13. package/dist/src/Result.d.ts.map +1 -1
  14. package/dist/src/Result.js +3 -5
  15. package/dist/src/Sqlite.d.ts.map +1 -1
  16. package/dist/src/Sqlite.js +9 -7
  17. package/dist/src/Store.d.ts.map +1 -1
  18. package/dist/src/Store.js +3 -1
  19. package/dist/src/String.d.ts.map +1 -1
  20. package/dist/src/String.js +1 -1
  21. package/dist/src/Task.d.ts +122 -60
  22. package/dist/src/Task.d.ts.map +1 -1
  23. package/dist/src/Task.js +40 -53
  24. package/dist/src/Test.d.ts.map +1 -1
  25. package/dist/src/Test.js +1 -0
  26. package/dist/src/Time.d.ts.map +1 -1
  27. package/dist/src/Time.js +9 -2
  28. package/dist/src/Type.d.ts +146 -9
  29. package/dist/src/Type.d.ts.map +1 -1
  30. package/dist/src/Type.js +10 -7
  31. package/dist/src/WebSocket.d.ts.map +1 -1
  32. package/dist/src/WebSocket.js +8 -0
  33. package/dist/src/Worker.js +1 -1
  34. package/dist/src/local-first/Db.js +3 -2
  35. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  36. package/dist/src/local-first/Evolu.js +1 -0
  37. package/dist/src/local-first/Owner.d.ts +8 -7
  38. package/dist/src/local-first/Owner.d.ts.map +1 -1
  39. package/dist/src/local-first/Owner.js +4 -4
  40. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  41. package/dist/src/local-first/Protocol.js +30 -24
  42. package/dist/src/local-first/Query.d.ts.map +1 -1
  43. package/dist/src/local-first/Query.js +2 -1
  44. package/dist/src/local-first/Relay.js +1 -1
  45. package/dist/src/local-first/Shared.d.ts.map +1 -1
  46. package/dist/src/local-first/Shared.js +4 -2
  47. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  48. package/dist/src/local-first/Timestamp.js +1 -1
  49. package/package.json +1 -1
  50. package/src/Array.ts +28 -1
  51. package/src/Assert.ts +1 -0
  52. package/src/LeakDetector.ts +4 -3
  53. package/src/Lookup.ts +1 -0
  54. package/src/Platform.ts +1 -0
  55. package/src/Relation.ts +3 -3
  56. package/src/Resource.ts +9 -3
  57. package/src/Result.ts +5 -5
  58. package/src/Sqlite.ts +9 -7
  59. package/src/Store.ts +3 -1
  60. package/src/String.ts +1 -1
  61. package/src/Task.ts +133 -82
  62. package/src/Test.ts +1 -0
  63. package/src/Time.ts +9 -2
  64. package/src/Type.ts +173 -29
  65. package/src/WebSocket.ts +8 -0
  66. package/src/Worker.ts +1 -1
  67. package/src/local-first/Db.ts +2 -1
  68. package/src/local-first/Evolu.ts +1 -0
  69. package/src/local-first/Owner.ts +8 -7
  70. package/src/local-first/Protocol.ts +40 -30
  71. package/src/local-first/Query.ts +6 -3
  72. package/src/local-first/Relay.ts +1 -1
  73. package/src/local-first/Shared.ts +4 -2
  74. package/src/local-first/Timestamp.ts +1 -1
@@ -178,7 +178,11 @@
178
178
  */
179
179
 
180
180
  import { Packr } from "msgpackr";
181
- import { isNonEmptyArray, type NonEmptyReadonlyArray } from "../Array.ts";
181
+ import {
182
+ createMutableArray,
183
+ isNonEmptyArray,
184
+ type NonEmptyReadonlyArray,
185
+ } from "../Array.ts";
182
186
  import { assert, assertNonNullable } from "../Assert.ts";
183
187
  import type { Brand } from "../Brand.ts";
184
188
  import {
@@ -692,11 +696,16 @@ export const createProtocolMessageBuffer = (
692
696
  * approach, contributions are welcome.
693
697
  */
694
698
  const safeMargins = {
695
- remainingRange: fingerprintSize + 10, // bytes: range type + possible increased count varint
696
- timestamp: 30, // bytes: max millis + max count + NodeId
697
- dbChangeLength: 8, // bytes: maximum encoded DbChange length varint
698
- splitRange: 800, // bytes: worst case is around 650 bytes
699
- timestampsRange: 50, // bytes: range type + its upperBound + possible increased count varint
699
+ // bytes: range type + possible increased count varint
700
+ remainingRange: fingerprintSize + 10,
701
+ // bytes: max millis + max count + NodeId
702
+ timestamp: 30,
703
+ // bytes: maximum encoded DbChange length varint
704
+ dbChangeLength: 8,
705
+ // bytes: worst case is around 650 bytes
706
+ splitRange: 800,
707
+ // bytes: range type + its upperBound + possible increased count varint
708
+ timestampsRange: 50,
700
709
  };
701
710
 
702
711
  const addMessageSafeMargin =
@@ -972,7 +981,7 @@ export const applyProtocolMessageAsClient =
972
981
  );
973
982
 
974
983
  if (messageType === MessageType.Response) {
975
- const errorCode = input.shift() as ProtocolErrorCode;
984
+ const errorCode = input.shift();
976
985
  if (errorCode !== ProtocolErrorCode.NoError) {
977
986
  switch (errorCode) {
978
987
  case ProtocolErrorCode.WriteKeyError:
@@ -1318,7 +1327,7 @@ const decodeMessages = (
1318
1327
  ): ReadonlyArray<EncryptedCrdtMessage> => {
1319
1328
  const timestamps = decodeTimestamps(buffer);
1320
1329
 
1321
- const messages = new Array<EncryptedCrdtMessage>(timestamps.length);
1330
+ const messages = createMutableArray<EncryptedCrdtMessage>(timestamps.length);
1322
1331
  for (let i = 0; i < timestamps.length; i++) {
1323
1332
  const timestamp = timestamps[i];
1324
1333
  const changeLength = decodeLength(buffer);
@@ -1441,21 +1450,19 @@ const sync =
1441
1450
 
1442
1451
  if (eqArrayNumber(range.fingerprint, ourFingerprint)) {
1443
1452
  skipRange(range);
1453
+ } else if (output.canSplitRange()) {
1454
+ coalesceSkipsBeforeAdd();
1455
+ splitRange(deps)(
1456
+ ownerIdBytes,
1457
+ lower,
1458
+ upper,
1459
+ currentUpperBound,
1460
+ output,
1461
+ );
1444
1462
  } else {
1445
- if (output.canSplitRange()) {
1446
- coalesceSkipsBeforeAdd();
1447
- splitRange(deps)(
1448
- ownerIdBytes,
1449
- lower,
1450
- upper,
1451
- currentUpperBound,
1452
- output,
1453
- );
1454
- } else {
1455
- return addFingerprintForRemainingRange(upper)
1456
- ? ok(true)
1457
- : err(ProtocolErrorCode.SyncError);
1458
- }
1463
+ return addFingerprintForRemainingRange(upper)
1464
+ ? ok(true)
1465
+ : err(ProtocolErrorCode.SyncError);
1459
1466
  }
1460
1467
  break;
1461
1468
  }
@@ -1635,7 +1642,7 @@ const decodeRanges = (buffer: Buffer): ReadonlyArray<Range> => {
1635
1642
  const timestampsCount = NonNegativeInt.orThrow(rangesCount - 1);
1636
1643
  const timestamps = decodeTimestamps(buffer, timestampsCount);
1637
1644
 
1638
- const rangeTypes = new Array<RangeType>(rangesCount);
1645
+ const rangeTypes = createMutableArray<RangeType>(rangesCount);
1639
1646
 
1640
1647
  for (let i = 0; i < rangesCount; i++) {
1641
1648
  const rangeType = decodeNonNegativeInt(buffer);
@@ -1650,7 +1657,7 @@ const decodeRanges = (buffer: Buffer): ReadonlyArray<Range> => {
1650
1657
  }
1651
1658
  }
1652
1659
 
1653
- const ranges = new Array<Range>(rangesCount);
1660
+ const ranges = createMutableArray<Range>(rangesCount);
1654
1661
 
1655
1662
  for (let i = 0; i < rangesCount; i++) {
1656
1663
  const upperBound =
@@ -1700,7 +1707,7 @@ const decodeTimestamps = (
1700
1707
 
1701
1708
  let previousMillis = 0 as Millis;
1702
1709
 
1703
- const millises = new Array<Millis>(length);
1710
+ const millises = createMutableArray<Millis>(length);
1704
1711
  for (let i = 0; i < length; i++) {
1705
1712
  const deltaMillis = decodeNonNegativeInt(buffer);
1706
1713
  const millis = Millis.fromUnknown(previousMillis + deltaMillis);
@@ -1717,7 +1724,7 @@ const decodeTimestamps = (
1717
1724
 
1718
1725
  const nodeIds = decodeRle(buffer, length, (): NodeId => decodeNodeId(buffer));
1719
1726
 
1720
- const timestamps = new Array<Timestamp>(length);
1727
+ const timestamps = createMutableArray<Timestamp>(length);
1721
1728
  for (let i = 0; i < length; i++) {
1722
1729
  timestamps[i] = {
1723
1730
  millis: millises[i],
@@ -1734,7 +1741,7 @@ export const decodeRle = <T>(
1734
1741
  length: NonNegativeInt,
1735
1742
  decodeValue: () => T,
1736
1743
  ): ReadonlyArray<T> => {
1737
- const values = new Array<T>(length);
1744
+ const values = createMutableArray<T>(length);
1738
1745
  let index = 0;
1739
1746
  while (index < length) {
1740
1747
  const value = decodeValue();
@@ -1851,7 +1858,7 @@ export const decodeFlags = (
1851
1858
  ): ReadonlyArray<boolean> => {
1852
1859
  const byte = buffer.shift();
1853
1860
  const length = globalThis.Math.min(count, 8);
1854
- const flags = new Array<boolean>(length);
1861
+ const flags = createMutableArray<boolean>(length);
1855
1862
  for (let i = 0; i < length; i++) {
1856
1863
  flags[i] = (byte & (1 << i)) !== 0;
1857
1864
  }
@@ -2088,7 +2095,8 @@ export const ProtocolValueType = {
2088
2095
  NonNegativeInt: /*#__PURE__*/ NonNegativeInt.orThrow(30),
2089
2096
 
2090
2097
  // String optimizations
2091
- EmptyString: /*#__PURE__*/ NonNegativeInt.orThrow(31), // 1 byte vs 2 bytes (50% reduction)
2098
+ // 1 byte vs 2 bytes (50% reduction)
2099
+ EmptyString: /*#__PURE__*/ NonNegativeInt.orThrow(31),
2092
2100
  Base64Url: /*#__PURE__*/ NonNegativeInt.orThrow(32),
2093
2101
  Id: /*#__PURE__*/ NonNegativeInt.orThrow(33),
2094
2102
  Json: /*#__PURE__*/ NonNegativeInt.orThrow(34),
@@ -2097,7 +2105,8 @@ export const ProtocolValueType = {
2097
2105
  // encoded with fixed length - 8 bytes
2098
2106
  // encode as NonNegativeInt - 6 bytes (additional 25% reduction)
2099
2107
  DateIsoWithNonNegativeTime: /*#__PURE__*/ NonNegativeInt.orThrow(35),
2100
- DateIsoWithNegativeTime: /*#__PURE__*/ NonNegativeInt.orThrow(36), // 9 bytes
2108
+ // 9 bytes
2109
+ DateIsoWithNegativeTime: /*#__PURE__*/ NonNegativeInt.orThrow(36),
2101
2110
 
2102
2111
  // TODO: Operations (from 40)
2103
2112
  // Increment, Decrement, Patch, whatever.
@@ -2109,6 +2118,7 @@ export const encodeSqliteValue = (buffer: Buffer, value: SqliteValue): void => {
2109
2118
  return;
2110
2119
  }
2111
2120
 
2121
+ // oxlint-disable-next-line typescript/switch-exhaustiveness-check -- The remaining SqliteValue is Uint8Array and is encoded after the switch.
2112
2122
  switch (typeof value) {
2113
2123
  case "string": {
2114
2124
  if (value === "") {
@@ -21,6 +21,7 @@ import {
21
21
  TableNode,
22
22
  ValueNode,
23
23
  } from "kysely";
24
+ import { createMutableArray } from "../Array.ts";
24
25
  import type { Brand } from "../Brand.ts";
25
26
  import { createRandomBytes } from "../Crypto.ts";
26
27
  import type { ReadonlyRecord } from "../Object.ts";
@@ -106,8 +107,10 @@ export type InferRow<T extends Query> =
106
107
  export interface Row {
107
108
  readonly [key: string]:
108
109
  | SqliteValue
109
- | Row // for evoluJsonObjectFrom
110
- | ReadonlyArray<Row>; // for evoluJsonArrayFrom
110
+ // for evoluJsonObjectFrom
111
+ | Row
112
+ // for evoluJsonArrayFrom
113
+ | ReadonlyArray<Row>;
111
114
  }
112
115
 
113
116
  /**
@@ -455,7 +458,7 @@ const getSqliteJsonObjectArgs = (
455
458
  export const parseSqliteJsonArray = <T>(
456
459
  arr: ReadonlyArray<T>,
457
460
  ): ReadonlyArray<T> => {
458
- const result = new Array<T>(arr.length);
461
+ const result = createMutableArray<T>(arr.length);
459
462
  for (let i = 0; i < arr.length; ++i) {
460
463
  result[i] = parse(arr[i]) as T;
461
464
  }
@@ -305,7 +305,7 @@ export const createRelaySqliteStorage =
305
305
  where "ownerId" = ${ownerId} and "timestamp" = ${timestamp};
306
306
  `);
307
307
 
308
- const row = result.rows[0];
308
+ const row = result.rows.at(0);
309
309
  assert(row, "Every timestamp must have a change");
310
310
  return row.change;
311
311
  },
@@ -324,7 +324,9 @@ export const initSharedWorker =
324
324
  const tenantLease = await run.ok(
325
325
  unabortable(tenantsByName.acquire(message)),
326
326
  );
327
- tenantLease.resource.addInstance(message, tenantLease.release);
327
+ tenantLease.resource.addInstance(message, () => {
328
+ tenantLease.release();
329
+ });
328
330
  return ok();
329
331
  });
330
332
  break;
@@ -479,7 +481,7 @@ const createEvoluTenant =
479
481
  new Map<EvoluInstanceId, EvoluInstance>(),
480
482
  async (instancesById) => {
481
483
  await using disposer = new AsyncDisposableStack();
482
- for (const instance of [...instancesById.values()]) {
484
+ for (const instance of instancesById.values()) {
483
485
  disposer.use(instance);
484
486
  }
485
487
  },
@@ -93,7 +93,7 @@ export const maxCounter = 65535 as Counter;
93
93
  * yet they will think they are synced. This is extremely rare and can be
94
94
  * resolved by resetting one device to generate a new NodeId.
95
95
  */
96
- export const NodeId = /*#__PURE__*/ regex("NodeId", /^[a-f0-9]{16}$/)(String);
96
+ export const NodeId = /*#__PURE__*/ regex("NodeId", /^[a-f0-9]{16}$/u)(String);
97
97
  export type NodeId = typeof NodeId.Output;
98
98
 
99
99
  export const minNodeId = "0000000000000000" as NodeId;