@delali/sirannon-db 0.2.0 → 0.2.1

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 (75) hide show
  1. package/dist/backup-scheduler/index.d.ts +15 -1
  2. package/dist/backup-scheduler/index.mjs +2 -2
  3. package/dist/baseline-D93hcIEE.d.ts +17 -0
  4. package/dist/{change-tracker-DKRVUC3l.d.ts → change-tracker-DDmXB754.d.ts} +56 -8
  5. package/dist/{chunk-5NOIGN5Y.mjs → chunk-2QLXDHAP.mjs} +1 -1
  6. package/dist/{chunk-LNY2VVHE.mjs → chunk-7C36BCSN.mjs} +1 -1
  7. package/dist/{chunk-NVQS53NT.mjs → chunk-7FQRQH5Z.mjs} +53 -64
  8. package/dist/{chunk-D7LAYTKN.mjs → chunk-7R4ER4FB.mjs} +1 -1
  9. package/dist/{chunk-FHWTZFI4.mjs → chunk-BQFQ65OL.mjs} +1 -1
  10. package/dist/{chunk-O7SLN3GI.mjs → chunk-BTTFW4Z4.mjs} +1 -1
  11. package/dist/{chunk-H6PIVVDN.mjs → chunk-CCZK6LCB.mjs} +38 -25
  12. package/dist/{chunk-JZGINXTN.mjs → chunk-HCCGEIZ2.mjs} +2 -2
  13. package/dist/{chunk-67M7KAH6.mjs → chunk-IWGIYDMZ.mjs} +1 -1
  14. package/dist/{chunk-LFZ37BSX.mjs → chunk-OUSWVNWT.mjs} +1 -1
  15. package/dist/{chunk-HR5CWTLC.mjs → chunk-P2VJYRVY.mjs} +60 -7
  16. package/dist/{chunk-UC3SCMIN.mjs → chunk-PBRXXISQ.mjs} +3 -0
  17. package/dist/{chunk-JU64Y7HM.mjs → chunk-SBL6GN43.mjs} +1 -1
  18. package/dist/{chunk-EBJXPQQO.mjs → chunk-UPKKSUPA.mjs} +2 -2
  19. package/dist/{chunk-TJF5GZSV.mjs → chunk-VOSJBZ6Q.mjs} +1 -1
  20. package/dist/{chunk-PIKHN33N.mjs → chunk-VOYGMAU7.mjs} +9 -1
  21. package/dist/{chunk-H237TXZW.mjs → chunk-WJ67DTD6.mjs} +48 -6
  22. package/dist/{chunk-OQVZBEBY.mjs → chunk-XF2HH5E6.mjs} +4 -61
  23. package/dist/client/index.d.ts +211 -12
  24. package/dist/client/index.mjs +155 -67
  25. package/dist/client/topology.d.ts +55 -7
  26. package/dist/client/topology.mjs +20 -1
  27. package/dist/{client-base-CLWmH5Ln.d.ts → client-base-CmZO0v3m.d.ts} +133 -24
  28. package/dist/codegen/cli.mjs +3 -3
  29. package/dist/codegen/index.d.ts +92 -2
  30. package/dist/codegen/index.mjs +3 -3
  31. package/dist/core/index.d.ts +177 -15
  32. package/dist/core/index.mjs +2481 -2226
  33. package/dist/core/writer-worker.mjs +3 -3
  34. package/dist/database-B5Qv1-cU.d.ts +380 -0
  35. package/dist/driver/better-sqlite3.d.ts +18 -1
  36. package/dist/driver/better-sqlite3.mjs +5 -5
  37. package/dist/driver/bun.d.ts +28 -0
  38. package/dist/driver/expo.d.ts +17 -0
  39. package/dist/driver/node.d.ts +18 -1
  40. package/dist/driver/node.mjs +5 -5
  41. package/dist/driver/wa-sqlite.d.ts +18 -1
  42. package/dist/{errors-Bw5MdNCu.d.ts → errors-Dei4GdBb.d.ts} +80 -7
  43. package/dist/file-migrations/index.d.ts +54 -2
  44. package/dist/file-migrations/index.mjs +3 -3
  45. package/dist/{operation-registry-9DcvxcE5.d.ts → operation-registry-hlbhqu7q.d.ts} +50 -1
  46. package/dist/{primary-wins-DPAm2AKG.d.ts → primary-wins-B0np8JS3.d.ts} +25 -1
  47. package/dist/protocol-rqANt-9Q.d.ts +152 -0
  48. package/dist/query-types-DL3LtPvY.d.ts +95 -0
  49. package/dist/react/index.d.ts +58 -3
  50. package/dist/replication/coordinator/etcd.d.ts +63 -3
  51. package/dist/replication/coordinator/etcd.mjs +82 -46
  52. package/dist/replication/index.d.ts +329 -95
  53. package/dist/replication/index.mjs +256 -141
  54. package/dist/server/index.d.ts +230 -12
  55. package/dist/server/index.mjs +63 -50
  56. package/dist/{server-options-1JHu8pid.d.ts → server-options-Dab_Jvd_.d.ts} +96 -12
  57. package/dist/sirannon-CMhiJa5Y.d.ts +111 -0
  58. package/dist/transport/grpc.d.ts +93 -9
  59. package/dist/transport/grpc.mjs +63 -20
  60. package/dist/transport/memory.d.ts +50 -20
  61. package/dist/transport/memory.mjs +25 -0
  62. package/dist/{types-CL6piSnD.d.ts → types-BCejqzNA.d.ts} +20 -0
  63. package/dist/types-CMBcFPhb.d.ts +336 -0
  64. package/dist/types-CjhxcjhA.d.ts +123 -0
  65. package/dist/types-DyrCiWuc.d.ts +499 -0
  66. package/dist/types-rVZKnKN-.d.ts +591 -0
  67. package/package.json +7 -1
  68. package/dist/baseline-Br77Fnhb.d.ts +0 -6
  69. package/dist/database-BY0L5Q2n.d.ts +0 -172
  70. package/dist/protocol-6KrSq2Hy.d.ts +0 -66
  71. package/dist/sirannon-DaQSyhbJ.d.ts +0 -36
  72. package/dist/types-B7gmEsZW.d.ts +0 -221
  73. package/dist/types-BsVabqSI.d.ts +0 -139
  74. package/dist/types-C_D8IhpO.d.ts +0 -60
  75. package/dist/types-zhnRXrsb.d.ts +0 -384
@@ -1,16 +1,16 @@
1
1
  import { compatibilityAllowsPromotion } from '../chunk-ER7ODTDA.mjs';
2
- import { WriteConcernError, AuthorityError, SyncError, TopologyError, CoordinatorError, StalePrimaryError, NodeDrainingError, NodeNotInSyncError, ReadConcernError, ProtocolVersionMismatchError } from '../chunk-O7SLN3GI.mjs';
3
- export { AuthorityError, ConflictError, CoordinatorError, FailoverError, NoSafePrimaryError, NodeDrainingError, NodeNotInSyncError, ProtocolVersionMismatchError, ReadConcernError, StalePrimaryError, SyncError, TopologyError, TransportError, UnsafeRecoveryRequiredError, WriteConcernError } from '../chunk-O7SLN3GI.mjs';
4
- import { LWWResolver, HLC, BatchReader, BatchApplier, StampOps, isWellFormedHlc, Transaction, loadPersistedHlc } from '../chunk-HR5CWTLC.mjs';
5
- export { HLC, LWWResolver } from '../chunk-HR5CWTLC.mjs';
6
- import '../chunk-JZGINXTN.mjs';
7
- import { DumpOps, dumpSchema, tablesInFkOrder } from '../chunk-OQVZBEBY.mjs';
8
- import { canonicaliseForChecksum, PkResolver, setForeignKeysEnabled, selectMaxAppliedSourceSeqByNode, selectMaxChangeHlc, selectMaxColumnVersionHlc, selectMaxAppliedSourceSeq, upsertPeerAckedSeq, selectPeerAckedSeq, selectMaxChangeSeq, selectMinPeerAckedSeq, upsertSyncTableStatus, upsertSyncMeta, selectSyncMetaRow, selectCompletedSyncTableNames, ensureChangesTable, ensureReplicationStateTables, ensureMetaTable, IDENTIFIER_RE, deleteSyncTableStates, extractDroppedTable, insertDdlChange, prepareInsertAppliedChange } from '../chunk-NVQS53NT.mjs';
9
- import { ReplicationError, BatchValidationError } from '../chunk-5NOIGN5Y.mjs';
10
- export { BatchValidationError, ReplicationError } from '../chunk-5NOIGN5Y.mjs';
11
- import { CHANGES_TABLE, INTERNAL_TABLE_PREFIX } from '../chunk-D7LAYTKN.mjs';
2
+ import { WriteConcernError, AuthorityError, SyncError, TopologyError, CoordinatorError, StalePrimaryError, NodeDrainingError, NodeNotInSyncError, ReadConcernError, ProtocolVersionMismatchError } from '../chunk-BTTFW4Z4.mjs';
3
+ export { AuthorityError, ConflictError, CoordinatorError, FailoverError, NoSafePrimaryError, NodeDrainingError, NodeNotInSyncError, ProtocolVersionMismatchError, ReadConcernError, StalePrimaryError, SyncError, TopologyError, TransportError, UnsafeRecoveryRequiredError, WriteConcernError } from '../chunk-BTTFW4Z4.mjs';
4
+ import { LWWResolver, HLC, BatchReader, BatchApplier, StampOps, isWellFormedHlc, Transaction, loadPersistedHlc } from '../chunk-P2VJYRVY.mjs';
5
+ export { HLC, LWWResolver } from '../chunk-P2VJYRVY.mjs';
6
+ import '../chunk-HCCGEIZ2.mjs';
7
+ import { DumpOps, dumpSchema, tablesInFkOrder } from '../chunk-XF2HH5E6.mjs';
8
+ import { canonicaliseForChecksum, PkResolver, setForeignKeysEnabled, selectMaxChangeHlc, selectMaxColumnVersionHlc, selectMaxAppliedSourceSeq, upsertPeerAckedSeq, selectPeerAckedSeq, selectMaxChangeSeq, selectMinPeerAckedSeq, upsertSyncTableStatus, upsertSyncMeta, selectSyncMetaRow, selectCompletedSyncTableNames, ensureChangesTable, ensureReplicationStateTables, ensureMetaTable, IDENTIFIER_RE, deleteSyncTableStates, extractDroppedTable, insertDdlChange, prepareInsertAppliedChange, selectMaxAppliedSourceSeqByNode } from '../chunk-7FQRQH5Z.mjs';
9
+ import { ReplicationError, BatchValidationError } from '../chunk-2QLXDHAP.mjs';
10
+ export { BatchValidationError, ReplicationError } from '../chunk-2QLXDHAP.mjs';
11
+ import { CHANGES_TABLE, INTERNAL_TABLE_PREFIX } from '../chunk-7R4ER4FB.mjs';
12
12
  import '../chunk-GVCNMPOS.mjs';
13
- import '../chunk-UC3SCMIN.mjs';
13
+ import '../chunk-PBRXXISQ.mjs';
14
14
  import { EventEmitter } from 'events';
15
15
  import { randomBytes, randomUUID, createHash } from 'crypto';
16
16
 
@@ -21,6 +21,12 @@ var FieldMergeResolver = class {
21
21
  constructor(getColumnVersions) {
22
22
  this.getColumnVersions = getColumnVersions;
23
23
  }
24
+ /**
25
+ * Merges columns only one side changed, and settles overlapping columns by their per-column stamps.
26
+ *
27
+ * @param ctx - The local and incoming versions of one row.
28
+ * @returns Which version to write, or the merged row.
29
+ */
24
30
  async resolve(ctx) {
25
31
  const columnVersions = await this.getColumnVersions(ctx.table, ctx.rowId);
26
32
  if (columnVersions.size === 0) {
@@ -95,6 +101,12 @@ var PrimaryWinsResolver = class {
95
101
  constructor(primaryNodeId) {
96
102
  this.primaryNodeId = primaryNodeId;
97
103
  }
104
+ /**
105
+ * Takes the version authored by the configured primary node, and falls back to last-writer-wins otherwise.
106
+ *
107
+ * @param ctx - The local and incoming versions of one row.
108
+ * @returns Which version to write.
109
+ */
98
110
  resolve(ctx) {
99
111
  if (ctx.remoteChange.nodeId === this.primaryNodeId) {
100
112
  return { action: "accept_remote" };
@@ -246,7 +258,7 @@ var ReplicationLog = class {
246
258
  changesTable
247
259
  );
248
260
  this.stampOps = new StampOps(localNodeId, hlc, changesTable);
249
- this.dump = new DumpOps(conn, localNodeId, hlc, this.pkResolver);
261
+ this.dump = new DumpOps(conn, this.pkResolver);
250
262
  }
251
263
  pkResolver;
252
264
  state;
@@ -294,9 +306,6 @@ var ReplicationLog = class {
294
306
  unregisterActiveSyncSeq(seq) {
295
307
  this.state.unregisterActiveSyncSeq(seq);
296
308
  }
297
- dumpTable(table, batchSize) {
298
- return this.dump.dumpTable(table, batchSize);
299
- }
300
309
  dumpTableOnConnection(conn, table, batchSize) {
301
310
  return this.dump.dumpTableOnConnection(conn, table, batchSize);
302
311
  }
@@ -657,6 +666,14 @@ async function refreshCoordinatorState(engine) {
657
666
  );
658
667
  }
659
668
  }
669
+ function resolverFor(engine, table) {
670
+ const specific = table ? engine.config.conflictResolvers?.[table] : void 0;
671
+ return specific ?? engine.defaultResolver;
672
+ }
673
+ function resolveWriteConcern(engine, wc) {
674
+ if (wc) return wc;
675
+ return engine.isCoordinatorMode() ? { level: "majority" } : void 0;
676
+ }
660
677
  function getCoordinatorMessageFields(engine) {
661
678
  const config = engine.config.coordinator;
662
679
  if (!config) return {};
@@ -1409,6 +1426,19 @@ async function forwardStatements(engine, statements, _options) {
1409
1426
  });
1410
1427
  }
1411
1428
 
1429
+ // src/replication/engine/internal-types.ts
1430
+ function initialSyncState() {
1431
+ return {
1432
+ phase: "ready",
1433
+ sourcePeerId: null,
1434
+ snapshotSeq: null,
1435
+ completedTables: [],
1436
+ totalTables: 0,
1437
+ startedAt: null,
1438
+ error: null
1439
+ };
1440
+ }
1441
+
1412
1442
  // src/replication/engine/replication-transaction.ts
1413
1443
  var ReplicationTransaction = class extends Transaction {
1414
1444
  constructor(txConn, hooks) {
@@ -1435,6 +1465,18 @@ var ReplicationTransaction = class extends Transaction {
1435
1465
  }
1436
1466
  };
1437
1467
 
1468
+ // src/replication/engine/trigger-refresh.ts
1469
+ async function refreshTriggersAfterDdl(engine) {
1470
+ const tracker = engine.tracker;
1471
+ if (!tracker) return;
1472
+ for (const table of Array.from(tracker.watchedTables)) {
1473
+ try {
1474
+ await tracker.watch(engine.writerConn, table);
1475
+ } catch {
1476
+ }
1477
+ }
1478
+ }
1479
+
1438
1480
  // src/replication/engine/local-executor.ts
1439
1481
  var LocalExecutor = class {
1440
1482
  constructor(engine) {
@@ -1488,11 +1530,11 @@ var LocalExecutor = class {
1488
1530
  if (droppedTable !== null && engine.tracker) {
1489
1531
  await engine.tracker.pruneDroppedTables(engine.writerConn, [droppedTable]);
1490
1532
  }
1491
- await engine.refreshTriggersAfterDdl();
1533
+ await refreshTriggersAfterDdl(engine);
1492
1534
  }
1493
- const writeConcern = engine.resolveWriteConcern(options?.writeConcern);
1535
+ const writeConcern = resolveWriteConcern(engine, options?.writeConcern);
1494
1536
  if (writeConcern) {
1495
- await engine.waitForWriteConcern(newSeq, writeConcern);
1537
+ await waitForWriteConcern(engine, newSeq, writeConcern);
1496
1538
  }
1497
1539
  return result;
1498
1540
  }
@@ -1556,7 +1598,7 @@ var LocalExecutor = class {
1556
1598
  if (droppedTables.length > 0 && engine.tracker) {
1557
1599
  await engine.tracker.pruneDroppedTables(engine.writerConn, droppedTables);
1558
1600
  }
1559
- await engine.refreshTriggersAfterDdl();
1601
+ await refreshTriggersAfterDdl(engine);
1560
1602
  }
1561
1603
  return { results, requestId };
1562
1604
  }
@@ -1609,11 +1651,11 @@ var LocalExecutor = class {
1609
1651
  if (hooks.droppedTables.length > 0 && engine.tracker) {
1610
1652
  await engine.tracker.pruneDroppedTables(engine.writerConn, hooks.droppedTables);
1611
1653
  }
1612
- await engine.refreshTriggersAfterDdl();
1654
+ await refreshTriggersAfterDdl(engine);
1613
1655
  }
1614
- const writeConcern = engine.resolveWriteConcern(options?.writeConcern);
1656
+ const writeConcern = resolveWriteConcern(engine, options?.writeConcern);
1615
1657
  if (writeConcern) {
1616
- await engine.waitForWriteConcern(newSeq, writeConcern);
1658
+ await waitForWriteConcern(engine, newSeq, writeConcern);
1617
1659
  }
1618
1660
  return userResult;
1619
1661
  }
@@ -1725,7 +1767,7 @@ var SenderLoop = class {
1725
1767
  const fromSeq = peerState?.lastSentSeq ?? engine.lastSentSeq;
1726
1768
  const rawBatch = await engine.log.readBatch(fromSeq, engine.batchSize);
1727
1769
  if (!rawBatch) continue;
1728
- const batch = engine.decorateBatch(rawBatch);
1770
+ const batch = engine.decorate(rawBatch);
1729
1771
  const previousSeq = peerState?.lastSentSeq ?? 0n;
1730
1772
  if (peerState) {
1731
1773
  peerState.pendingBatches += 1;
@@ -1796,6 +1838,9 @@ async function delayAckIfConfigured(engine) {
1796
1838
  }
1797
1839
 
1798
1840
  // src/replication/engine/transport-wiring.ts
1841
+ function clockDriftMs(remoteHlc) {
1842
+ return Math.abs(Date.now() - HLC.decode(remoteHlc).wallMs);
1843
+ }
1799
1844
  function replicatesFrom(engine, peer) {
1800
1845
  if (engine.isCoordinatorMode()) {
1801
1846
  return engine.coordinatorState?.currentPrimary?.nodeId === peer.id;
@@ -1811,11 +1856,11 @@ async function reportAppliedPosition(engine, peer, appliedSeq) {
1811
1856
  if (!servesItsData((await engine.log.getSyncState()).phase)) return;
1812
1857
  await engine.config.transport.sendAck(
1813
1858
  peer.id,
1814
- engine.decorateAck({ batchId: "", ackedSeq: appliedSeq, nodeId: engine.nodeId })
1859
+ engine.decorate({ batchId: "", ackedSeq: appliedSeq, nodeId: engine.nodeId })
1815
1860
  );
1816
1861
  }
1817
1862
  function persistAckProgress(engine, nodeId, ackedSeq) {
1818
- engine.log.setLastAppliedSeq(nodeId, ackedSeq).then(() => engine.handleCoordinatorAckProgress(nodeId, ackedSeq)).catch((err) => {
1863
+ engine.log.setLastAppliedSeq(nodeId, ackedSeq).then(() => handleCoordinatorAckProgress(engine, nodeId, ackedSeq)).catch((err) => {
1819
1864
  const wrappedErr = err instanceof Error ? err : new Error(String(err));
1820
1865
  engine.emitError({ error: wrappedErr, operation: "ack-state-persist", peerId: nodeId, recoverable: true });
1821
1866
  });
@@ -1840,23 +1885,23 @@ function wireTransportHandlers(engine) {
1840
1885
  throw new ReplicationError(`Rejected batch from unauthorized peer: ${fromPeerId}`);
1841
1886
  }
1842
1887
  }
1843
- await engine.assertInboundCoordinatorMessage(batch, fromPeerId, "batch");
1888
+ await assertInboundCoordinatorMessage(engine, batch, fromPeerId, "batch");
1844
1889
  if (batch.changes.length > engine.maxBatchChanges) {
1845
1890
  throw new BatchValidationError(
1846
1891
  `Batch too large: ${batch.changes.length} changes exceeds max ${engine.maxBatchChanges}`
1847
1892
  );
1848
1893
  }
1849
- const drift = engine.checkClockDrift(batch.hlcRange.max);
1894
+ const drift = clockDriftMs(batch.hlcRange.max);
1850
1895
  if (drift > engine.maxClockDriftMs) {
1851
1896
  throw new BatchValidationError(`Clock drift too high: ${drift}ms exceeds max ${engine.maxClockDriftMs}ms`);
1852
1897
  }
1853
- const applyResult = await engine.log.applyBatch(batch, (table) => engine.getResolver(table));
1898
+ const applyResult = await engine.log.applyBatch(batch, (table) => resolverFor(engine, table));
1854
1899
  const batchContainedDdl = batch.changes.some((c) => c.operation === "ddl");
1855
1900
  if (batchContainedDdl) {
1856
1901
  if (applyResult.droppedTables.length > 0 && engine.tracker) {
1857
1902
  await engine.tracker.pruneDroppedTables(engine.writerConn, applyResult.droppedTables);
1858
1903
  }
1859
- await engine.refreshTriggersAfterDdl();
1904
+ await refreshTriggersAfterDdl(engine);
1860
1905
  }
1861
1906
  await engine.log.setLastAppliedSeq(fromPeerId, batch.toSeq);
1862
1907
  const previousApplied = engine.appliedSeqByPeer.get(fromPeerId) ?? 0n;
@@ -1869,7 +1914,7 @@ function wireTransportHandlers(engine) {
1869
1914
  await delayAckIfConfigured(engine);
1870
1915
  await engine.config.transport.sendAck(
1871
1916
  fromPeerId,
1872
- engine.decorateAck({
1917
+ engine.decorate({
1873
1918
  batchId: batch.batchId,
1874
1919
  ackedSeq: batch.toSeq,
1875
1920
  nodeId: engine.nodeId
@@ -1900,7 +1945,7 @@ function wireTransportHandlers(engine) {
1900
1945
  persistAckProgress(engine, ack.nodeId, ack.ackedSeq);
1901
1946
  return;
1902
1947
  }
1903
- engine.assertInboundCoordinatorMessage(ack, fromPeerId, "ack").then(() => {
1948
+ assertInboundCoordinatorMessage(engine, ack, fromPeerId, "ack").then(() => {
1904
1949
  engine.peerTracker.onAckReceived(ack.nodeId, ack.ackedSeq);
1905
1950
  persistAckProgress(engine, ack.nodeId, ack.ackedSeq);
1906
1951
  }).catch((err) => {
@@ -1913,14 +1958,14 @@ function wireTransportHandlers(engine) {
1913
1958
  if (engine.syncState.phase !== "ready" && engine.syncState.phase !== "catching-up") {
1914
1959
  throw new ReplicationError("Node is not ready to handle forwarded requests");
1915
1960
  }
1916
- await engine.assertInboundCoordinatorMessage(request, fromPeerId, "forward");
1961
+ await assertInboundCoordinatorMessage(engine, request, fromPeerId, "forward");
1917
1962
  const knownPeers = engine.config.transport.peers();
1918
1963
  if (!knownPeers.has(fromPeerId)) {
1919
1964
  throw new ReplicationError(`Rejected forward from unknown peer: ${fromPeerId}`);
1920
1965
  }
1921
1966
  try {
1922
1967
  const result = await engine.localExecutor.executeForwardedLocally(request.statements);
1923
- return engine.decorateForwardResult(result);
1968
+ return engine.decorate(result);
1924
1969
  } catch (err) {
1925
1970
  const wrappedErr = err instanceof Error ? err : new Error(String(err));
1926
1971
  engine.emitError({ error: wrappedErr, operation: "forward-execution", peerId: fromPeerId, recoverable: true });
@@ -1934,7 +1979,7 @@ function wireTransportHandlers(engine) {
1934
1979
  if (!engine.running) return;
1935
1980
  engine.peerTracker.onAckReceived(peer.id, ackedSeq);
1936
1981
  await engine.log.setLastAppliedSeq(peer.id, ackedSeq);
1937
- await engine.handleCoordinatorAckProgress(peer.id, ackedSeq);
1982
+ await handleCoordinatorAckProgress(engine, peer.id, ackedSeq);
1938
1983
  await reportAppliedPosition(engine, peer, ackedSeq);
1939
1984
  }).catch((err) => {
1940
1985
  const wrappedErr = err instanceof Error ? err : new Error(String(err));
@@ -1967,17 +2012,17 @@ function wireTransportHandlers(engine) {
1967
2012
  });
1968
2013
  engine.config.transport.onSyncRequested(async (request, fromPeerId) => {
1969
2014
  if (!engine.running) return;
1970
- await engine.assertInboundCoordinatorMessage(request, fromPeerId, "sync-request");
2015
+ await assertInboundCoordinatorMessage(engine, request, fromPeerId, "sync-request");
1971
2016
  await engine.syncServer.handleSyncRequest(request, fromPeerId);
1972
2017
  });
1973
2018
  engine.config.transport.onSyncBatchReceived(async (batch, fromPeerId) => {
1974
2019
  if (!engine.running) return;
1975
- await engine.assertInboundCoordinatorMessage(batch, fromPeerId, "sync-data");
2020
+ await assertInboundCoordinatorMessage(engine, batch, fromPeerId, "sync-data");
1976
2021
  await engine.syncJoiner.handleSyncBatchReceived(batch, fromPeerId);
1977
2022
  });
1978
2023
  engine.config.transport.onSyncCompleteReceived(async (complete, fromPeerId) => {
1979
2024
  if (!engine.running) return;
1980
- await engine.assertInboundCoordinatorMessage(complete, fromPeerId, "sync-data");
2025
+ await assertInboundCoordinatorMessage(engine, complete, fromPeerId, "sync-data");
1981
2026
  await engine.syncJoiner.handleSyncCompleteReceived(complete, fromPeerId);
1982
2027
  });
1983
2028
  engine.config.transport.onSyncAckReceived((ack, fromPeerId) => {
@@ -2002,7 +2047,7 @@ function wireTransportHandlers(engine) {
2002
2047
  engine.syncServer.handleSyncAckReceived(ack);
2003
2048
  return;
2004
2049
  }
2005
- engine.assertInboundCoordinatorMessage(ack, fromPeerId, "ack").then(() => {
2050
+ assertInboundCoordinatorMessage(engine, ack, fromPeerId, "ack").then(() => {
2006
2051
  engine.syncServer.handleSyncAckReceived(ack);
2007
2052
  }).catch((err) => {
2008
2053
  const wrappedErr = err instanceof Error ? err : new Error(String(err));
@@ -2012,6 +2057,11 @@ function wireTransportHandlers(engine) {
2012
2057
  }
2013
2058
 
2014
2059
  // src/replication/engine/startup.ts
2060
+ async function loadAppliedSeqs(engine) {
2061
+ for (const [nodeId, seq] of await selectMaxAppliedSourceSeqByNode(engine.writerConn)) {
2062
+ engine.appliedSeqByPeer.set(nodeId, seq);
2063
+ }
2064
+ }
2015
2065
  async function recoverHlcFromDurableState(engine) {
2016
2066
  const persisted = await loadPersistedHlc(engine.writerConn);
2017
2067
  if (persisted !== null) {
@@ -2028,9 +2078,9 @@ async function startEngine(engine) {
2028
2078
  await recoverHlcFromDurableState(engine);
2029
2079
  engine.lastSentSeq = await engine.log.getLocalSeq();
2030
2080
  engine.lastLocalSeq = engine.lastSentSeq;
2031
- await engine.loadAppliedSeqs();
2032
- await engine.startCoordinatorMode();
2033
- await engine.prepareCoordinatorRejoinIfNeeded();
2081
+ await loadAppliedSeqs(engine);
2082
+ await startCoordinatorMode(engine);
2083
+ await prepareCoordinatorRejoinIfNeeded(engine);
2034
2084
  wireTransportHandlers(engine);
2035
2085
  const transportConfig = {
2036
2086
  ...engine.config.transportConfig,
@@ -2040,10 +2090,10 @@ async function startEngine(engine) {
2040
2090
  protocolVersion: engine.config.coordinator?.compatibility?.protocolVersion ?? engine.config.transportConfig?.protocolVersion
2041
2091
  };
2042
2092
  await engine.config.transport.connect(engine.nodeId, transportConfig);
2043
- const isPrimary = engine.isCoordinatorMode() ? engine.hasCoordinatorWriteAuthority() : engine.config.topology.role === "primary";
2093
+ const isPrimary = engine.isCoordinatorMode() ? engine.coordinatorAuthority : engine.config.topology.role === "primary";
2044
2094
  const syncCompleted = await engine.log.isSyncCompleted();
2045
- const requiresCoordinatorRejoinSync2 = engine.requiresCoordinatorRejoinSync();
2046
- if (engine.initialSync && !isPrimary && (!syncCompleted || requiresCoordinatorRejoinSync2)) {
2095
+ const rejoinSyncRequired = requiresCoordinatorRejoinSync(engine, engine.coordinatorState);
2096
+ if (engine.initialSync && !isPrimary && (!syncCompleted || rejoinSyncRequired)) {
2047
2097
  const savedState = await engine.log.getSyncState();
2048
2098
  if (savedState.phase === "syncing") {
2049
2099
  if (!engine.tracker) {
@@ -2080,7 +2130,7 @@ async function startEngine(engine) {
2080
2130
  startedAt: null,
2081
2131
  error: null
2082
2132
  };
2083
- engine.startSenderLoop();
2133
+ engine.senderLoop.start();
2084
2134
  engine.syncJoiner.startCatchUpCheck();
2085
2135
  return;
2086
2136
  }
@@ -2099,7 +2149,7 @@ async function startEngine(engine) {
2099
2149
  } else {
2100
2150
  engine.syncState.phase = "ready";
2101
2151
  }
2102
- engine.startSenderLoop();
2152
+ engine.senderLoop.start();
2103
2153
  }
2104
2154
 
2105
2155
  // src/replication/engine/sync-recovery.ts
@@ -2270,7 +2320,7 @@ var SyncJoiner = class {
2270
2320
  await engine.log.setSyncMeta("syncing", void 0, sourcePeerId, requestId);
2271
2321
  await engine.config.transport.requestSync(
2272
2322
  sourcePeerId,
2273
- engine.decorateSyncRequest({
2323
+ engine.decorate({
2274
2324
  requestId,
2275
2325
  joinerNodeId: engine.nodeId,
2276
2326
  completedTables,
@@ -2463,7 +2513,7 @@ var SyncJoiner = class {
2463
2513
  engine.syncState.phase = "catching-up";
2464
2514
  engine.syncState.snapshotSeq = complete.snapshotSeq;
2465
2515
  await engine.log.setSyncMeta("catching-up", complete.snapshotSeq);
2466
- engine.startSenderLoop();
2516
+ engine.senderLoop.start();
2467
2517
  this.startCatchUpCheck();
2468
2518
  } catch {
2469
2519
  try {
@@ -2539,7 +2589,7 @@ var SyncJoiner = class {
2539
2589
  }
2540
2590
  async sendSyncAck(peerId, ack) {
2541
2591
  await delayAckIfConfigured(this.engine);
2542
- await this.engine.config.transport.sendSyncAck(peerId, this.engine.decorateSyncAck(ack));
2592
+ await this.engine.config.transport.sendSyncAck(peerId, this.engine.decorate(ack));
2543
2593
  }
2544
2594
  async finishCatchUpAsReady() {
2545
2595
  const engine = this.engine;
@@ -2559,7 +2609,7 @@ var SyncServer = class {
2559
2609
  async rejectSyncRequest(fromPeerId, request, error) {
2560
2610
  await this.engine.config.transport.sendSyncAck(
2561
2611
  fromPeerId,
2562
- this.engine.decorateSyncAck({
2612
+ this.engine.decorate({
2563
2613
  requestId: request.requestId,
2564
2614
  joinerNodeId: request.joinerNodeId,
2565
2615
  table: "__schema__",
@@ -2729,7 +2779,7 @@ var SyncServer = class {
2729
2779
  async sendSyncBatchAndWaitForAck(peerId, batch) {
2730
2780
  const ackPromise = this.waitForSyncAck(batch.requestId, batch.table, batch.batchIndex);
2731
2781
  try {
2732
- await this.engine.config.transport.sendSyncBatch(peerId, this.engine.decorateSyncBatch(batch));
2782
+ await this.engine.config.transport.sendSyncBatch(peerId, this.engine.decorate(batch));
2733
2783
  } catch (err) {
2734
2784
  const key = `${batch.requestId}:${batch.table}:${batch.batchIndex}`;
2735
2785
  const waiter = this.syncAckWaiters.get(key);
@@ -2837,7 +2887,7 @@ var SyncServer = class {
2837
2887
  }
2838
2888
  await engine.config.transport.sendSyncComplete(
2839
2889
  session.joinerNodeId,
2840
- engine.decorateSyncComplete({
2890
+ engine.decorate({
2841
2891
  requestId: session.requestId,
2842
2892
  snapshotSeq: session.snapshotSeq,
2843
2893
  manifests
@@ -2873,62 +2923,105 @@ var SyncServer = class {
2873
2923
 
2874
2924
  // src/replication/engine/engine.ts
2875
2925
  var ReplicationEngine = class extends EventEmitter {
2926
+ /** @internal */
2876
2927
  database;
2928
+ /** @internal */
2877
2929
  writerConn;
2930
+ /** @internal */
2878
2931
  config;
2932
+ /**
2933
+ * Identifier of this node, which every change it authors carries.
2934
+ */
2879
2935
  nodeId;
2936
+ /** @internal */
2880
2937
  hlc;
2938
+ /** @internal */
2881
2939
  log;
2940
+ /** @internal */
2882
2941
  peerTracker = new PeerTracker();
2942
+ /** @internal */
2883
2943
  defaultResolver;
2944
+ /** @internal */
2884
2945
  tracker;
2946
+ /** @internal */
2885
2947
  snapshotConnectionFactory;
2948
+ /** @internal */
2886
2949
  batchSize;
2950
+ /** @internal */
2887
2951
  batchIntervalMs;
2952
+ /** @internal */
2888
2953
  maxClockDriftMs;
2954
+ /** @internal */
2889
2955
  maxPendingBatches;
2956
+ /** @internal */
2890
2957
  maxBatchChanges;
2958
+ /** @internal */
2891
2959
  ackTimeoutMs;
2960
+ /** @internal */
2892
2961
  initialSync;
2962
+ /** @internal */
2893
2963
  syncBatchSize;
2964
+ /** @internal */
2894
2965
  maxConcurrentSyncs;
2966
+ /** @internal */
2895
2967
  maxSyncDurationMs;
2968
+ /** @internal */
2896
2969
  maxSyncLagBeforeReady;
2970
+ /** @internal */
2897
2971
  syncAckTimeoutMs;
2972
+ /** @internal */
2898
2973
  catchUpDeadlineMs;
2974
+ /** @internal */
2899
2975
  resumeFromSeq;
2976
+ /** @internal */
2900
2977
  running = false;
2978
+ /** @internal */
2901
2979
  coordinatorState = null;
2980
+ /** @internal */
2902
2981
  coordinatorAuthority = false;
2982
+ /** @internal */
2903
2983
  controllerState = "disabled";
2984
+ /** @internal */
2904
2985
  nodeSessionLeaseId = null;
2986
+ /** @internal */
2905
2987
  controllerLeaseId = null;
2988
+ /** @internal */
2906
2989
  coordinatorWatchDisposer = null;
2990
+ /** @internal */
2907
2991
  coordinatorLeaseTimer = null;
2992
+ /** @internal */
2908
2993
  controllerTimer = null;
2994
+ /** @internal */
2909
2995
  coordinatorRejoinSyncStarting = false;
2996
+ /** @internal */
2910
2997
  coordinatorSessionRestoring = false;
2998
+ /** @internal */
2911
2999
  coordinatorLastContactMs = 0;
3000
+ /** @internal */
2912
3001
  inSyncReconcileTimer = null;
3002
+ /** @internal */
2913
3003
  inSyncReconciling = false;
3004
+ /** @internal */
2914
3005
  lastSentSeq = 0n;
3006
+ /** @internal */
2915
3007
  lastLocalSeq = 0n;
3008
+ /** @internal */
2916
3009
  highestSourceSeqSeen = 0n;
3010
+ /** @internal */
2917
3011
  appliedSeqByPeer = /* @__PURE__ */ new Map();
3012
+ /** @internal */
2918
3013
  expectedBatchIndex = /* @__PURE__ */ new Map();
3014
+ /** @internal */
2919
3015
  syncTableDigests = /* @__PURE__ */ new Map();
2920
- syncState = {
2921
- phase: "ready",
2922
- sourcePeerId: null,
2923
- snapshotSeq: null,
2924
- completedTables: [],
2925
- totalTables: 0,
2926
- startedAt: null,
2927
- error: null
2928
- };
3016
+ /** @internal */
3017
+ syncState = initialSyncState();
3018
+ /** @internal */
2929
3019
  localExecutor;
3020
+ /** @internal */
2930
3021
  syncServer;
3022
+ /** @internal */
2931
3023
  syncJoiner;
3024
+ /** @internal */
2932
3025
  senderLoop;
2933
3026
  constructor(database, writerConn, config) {
2934
3027
  super();
@@ -2964,9 +3057,15 @@ var ReplicationEngine = class extends EventEmitter {
2964
3057
  this.senderLoop = new SenderLoop(this);
2965
3058
  installTestHooks(this);
2966
3059
  }
3060
+ /**
3061
+ * Connects the transport, pulls a full copy when this node needs one, and starts replicating.
3062
+ */
2967
3063
  start() {
2968
3064
  return startEngine(this);
2969
3065
  }
3066
+ /**
3067
+ * Stops replicating, abandons any sync in flight, and disconnects the transport.
3068
+ */
2970
3069
  async stop() {
2971
3070
  if (!this.running) return;
2972
3071
  this.running = false;
@@ -2988,6 +3087,11 @@ var ReplicationEngine = class extends EventEmitter {
2988
3087
  await stopCoordinatorMode(this);
2989
3088
  await this.config.transport.disconnect();
2990
3089
  }
3090
+ /**
3091
+ * Reports where this node stands.
3092
+ *
3093
+ * @returns The node's role, its peers, its progress, its health, and its group state.
3094
+ */
2991
3095
  status() {
2992
3096
  return {
2993
3097
  nodeId: this.nodeId,
@@ -3000,30 +3104,77 @@ var ReplicationEngine = class extends EventEmitter {
3000
3104
  coordinator: getCoordinatorRuntimeStatus(this)
3001
3105
  };
3002
3106
  }
3107
+ /**
3108
+ * Returns the highest change-log position this node has recorded locally.
3109
+ *
3110
+ * @returns That position, which a caller waits for a replica to reach.
3111
+ */
3003
3112
  getCurrentSeq() {
3004
3113
  return this.lastLocalSeq;
3005
3114
  }
3115
+ /**
3116
+ * Returns how far this node has applied one peer's changes.
3117
+ *
3118
+ * @param peerId - Identifier of the peer.
3119
+ * @returns The highest position from that peer this node has applied.
3120
+ */
3006
3121
  getAppliedSeq(peerId) {
3007
3122
  return this.appliedSeqByPeer.get(peerId) ?? 0n;
3008
3123
  }
3124
+ /**
3125
+ * Runs a read, refusing it when the node cannot meet the read concern.
3126
+ *
3127
+ * @param sql - The statement to run.
3128
+ * @param params - Values bound to the statement, named or positional.
3129
+ * @param options - Read concern for this statement.
3130
+ * @returns The rows the statement produced.
3131
+ */
3009
3132
  query(sql, params, options) {
3010
3133
  return query(this, sql, params, options);
3011
3134
  }
3135
+ /**
3136
+ * Runs one write, forwarding it to the primary when this node accepts no writes and forwarding is on.
3137
+ *
3138
+ * @param sql - The statement to run.
3139
+ * @param params - Values bound to the statement, named or positional.
3140
+ * @param options - Write concern for this statement.
3141
+ * @returns How many rows changed, and the last inserted row id.
3142
+ */
3012
3143
  execute(sql, params, options) {
3013
3144
  return execute(this, sql, params, options);
3014
3145
  }
3146
+ /**
3147
+ * Runs one statement over many parameter sets in a single transaction.
3148
+ *
3149
+ * @param sql - The statement to run for each parameter set.
3150
+ * @param paramsBatch - One parameter set per run.
3151
+ * @param options - Write concern for the transaction.
3152
+ * @returns One result per parameter set, in order.
3153
+ */
3015
3154
  executeBatch(sql, paramsBatch, options) {
3016
3155
  return executeBatch(this, sql, paramsBatch, options);
3017
3156
  }
3157
+ /**
3158
+ * Runs a function inside one transaction on this node.
3159
+ *
3160
+ * @param fn - Receives the transaction and runs statements on it.
3161
+ * @param options - Write concern for the transaction.
3162
+ * @returns Whatever the function returned.
3163
+ */
3018
3164
  transaction(fn, options) {
3019
3165
  return transaction(this, fn, options);
3020
3166
  }
3167
+ /**
3168
+ * Sends a write to the primary and waits for its result.
3169
+ *
3170
+ * @param statements - The statements to run, in order, each with its own parameters.
3171
+ * @param options - Write concern the primary applies.
3172
+ * @returns What the primary reported for each statement.
3173
+ */
3021
3174
  forwardStatements(statements, options) {
3022
3175
  return forwardStatements(this, statements);
3023
3176
  }
3024
- startSenderLoop() {
3025
- this.senderLoop.start();
3026
- }
3177
+ /** @internal */
3027
3178
  emitError(event) {
3028
3179
  if (this.listenerCount("replication-error") > 0) {
3029
3180
  try {
@@ -3032,110 +3183,74 @@ var ReplicationEngine = class extends EventEmitter {
3032
3183
  }
3033
3184
  }
3034
3185
  }
3035
- getResolver(table) {
3036
- if (table && this.config.conflictResolvers) {
3037
- const specific = this.config.conflictResolvers[table];
3038
- if (specific) return specific;
3039
- }
3040
- return this.defaultResolver;
3041
- }
3042
- checkClockDrift(remoteHlc) {
3043
- const decoded = HLC.decode(remoteHlc);
3044
- return Math.abs(Date.now() - decoded.wallMs);
3045
- }
3046
- async refreshTriggersAfterDdl() {
3047
- if (!this.tracker) return;
3048
- const tables = Array.from(this.tracker.watchedTables);
3049
- for (const table of tables) {
3050
- try {
3051
- await this.tracker.watch(this.writerConn, table);
3052
- } catch {
3053
- }
3054
- }
3055
- }
3056
- waitForWriteConcern(seq, wc) {
3057
- return waitForWriteConcern(this, seq, wc);
3058
- }
3059
- async loadAppliedSeqs() {
3060
- for (const [nodeId, seq] of await selectMaxAppliedSourceSeqByNode(this.writerConn)) {
3061
- this.appliedSeqByPeer.set(nodeId, seq);
3062
- }
3063
- }
3186
+ /** @internal */
3064
3187
  isCoordinatorMode() {
3065
3188
  return this.config.coordinator !== void 0;
3066
3189
  }
3067
- startCoordinatorMode() {
3068
- return startCoordinatorMode(this);
3069
- }
3070
- prepareCoordinatorRejoinIfNeeded() {
3071
- return prepareCoordinatorRejoinIfNeeded(this);
3072
- }
3073
- hasCoordinatorWriteAuthority() {
3074
- return this.coordinatorAuthority;
3075
- }
3076
- requiresCoordinatorRejoinSync(state = this.coordinatorState) {
3077
- return requiresCoordinatorRejoinSync(this, state);
3078
- }
3079
- markCoordinatorSyncReady() {
3080
- return markCoordinatorSyncReady(this);
3081
- }
3082
- handleCoordinatorAckProgress(nodeId, ackedSeq) {
3083
- return handleCoordinatorAckProgress(this, nodeId, ackedSeq);
3084
- }
3190
+ /** @internal */
3085
3191
  verifyPrimaryAuthority() {
3086
3192
  return verifyPrimaryAuthority(this);
3087
3193
  }
3088
- assertInboundCoordinatorMessage(message, fromPeerId, direction) {
3089
- return assertInboundCoordinatorMessage(this, message, fromPeerId, direction);
3090
- }
3091
- decorateBatch(batch) {
3092
- return { ...batch, ...getCoordinatorMessageFields(this) };
3093
- }
3094
- decorateAck(ack) {
3095
- return { ...ack, ...getCoordinatorMessageFields(this) };
3096
- }
3097
- decorateForwardResult(result) {
3098
- return { ...result, ...getCoordinatorMessageFields(this) };
3099
- }
3100
- decorateSyncRequest(request) {
3101
- return { ...request, ...getCoordinatorMessageFields(this) };
3102
- }
3103
- decorateSyncBatch(batch) {
3104
- return { ...batch, ...getCoordinatorMessageFields(this) };
3105
- }
3106
- decorateSyncComplete(complete) {
3107
- return { ...complete, ...getCoordinatorMessageFields(this) };
3108
- }
3109
- decorateSyncAck(ack) {
3110
- return { ...ack, ...getCoordinatorMessageFields(this) };
3111
- }
3112
- resolveWriteConcern(wc) {
3113
- if (wc) return wc;
3114
- if (this.isCoordinatorMode()) {
3115
- return { level: "majority" };
3116
- }
3117
- return void 0;
3194
+ /** @internal */
3195
+ markCoordinatorSyncReady() {
3196
+ return markCoordinatorSyncReady(this);
3118
3197
  }
3198
+ /** @internal */
3119
3199
  getCurrentPrimaryPeerId() {
3120
3200
  return getForwardingPrimaryPeerId(this);
3121
3201
  }
3202
+ /**
3203
+ * Stamps an outgoing replication message with this node's group and primary term.
3204
+ *
3205
+ * @internal
3206
+ */
3207
+ decorate(message) {
3208
+ return { ...message, ...getCoordinatorMessageFields(this) };
3209
+ }
3122
3210
  };
3123
3211
 
3124
3212
  // src/replication/topology/primary-replica.ts
3125
3213
  var PrimaryReplicaTopology = class {
3214
+ /**
3215
+ * Whether this node accepts writes or serves reads.
3216
+ */
3126
3217
  role;
3127
3218
  constructor(role) {
3128
3219
  this.role = role;
3129
3220
  }
3221
+ /**
3222
+ * Reports whether this node accepts writes, which only the primary does.
3223
+ *
3224
+ * @returns True on the primary.
3225
+ */
3130
3226
  canWrite() {
3131
3227
  return this.role === "primary";
3132
3228
  }
3229
+ /**
3230
+ * Reports whether this node sends its changes to a given peer.
3231
+ *
3232
+ * @param _peerId - Identifier of the peer, which this topology ignores.
3233
+ * @param peerRole - Role of the peer.
3234
+ * @returns True when this node is the primary and the peer is a replica.
3235
+ */
3133
3236
  shouldReplicateTo(_peerId, peerRole) {
3134
3237
  return this.role === "primary" && peerRole === "replica";
3135
3238
  }
3239
+ /**
3240
+ * Reports whether this node applies changes arriving from a given peer.
3241
+ *
3242
+ * @param _peerId - Identifier of the peer, which this topology ignores.
3243
+ * @param peerRole - Role of the peer.
3244
+ * @returns True when this node is a replica and the peer is the primary.
3245
+ */
3136
3246
  shouldAcceptFrom(_peerId, peerRole) {
3137
3247
  return this.role === "replica" && peerRole === "primary";
3138
3248
  }
3249
+ /**
3250
+ * Reports whether incoming changes need a resolver, which a single writer removes the need for.
3251
+ *
3252
+ * @returns False.
3253
+ */
3139
3254
  requiresConflictResolution() {
3140
3255
  return false;
3141
3256
  }