@delali/sirannon-db 0.3.4-next.62 → 0.3.4

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 (108) hide show
  1. package/README.md +79 -56
  2. package/dist/backup/index.d.ts +44 -45
  3. package/dist/backup/index.mjs +7 -7
  4. package/dist/backup-scheduler/index.d.ts +42 -41
  5. package/dist/backup-scheduler/index.mjs +4 -4
  6. package/dist/baseline-Co3iIsN_.d.ts +19 -0
  7. package/dist/change-tracker-DqCdeEyx.d.ts +128 -0
  8. package/dist/{chunk-VOSJBZ6Q.mjs → chunk-2ES3DT4A.mjs} +71 -33
  9. package/dist/{chunk-6CMCO7S4.mjs → chunk-2O3PP4CF.mjs} +1 -1
  10. package/dist/{chunk-E7Q7HE46.mjs → chunk-3BZLZ47R.mjs} +26 -26
  11. package/dist/chunk-4KASJKKK.mjs +2501 -0
  12. package/dist/{chunk-MYNI2WFS.mjs → chunk-52XW3Z76.mjs} +40 -8
  13. package/dist/chunk-55YRLSM2.mjs +57 -0
  14. package/dist/{chunk-KDAHYFW7.mjs → chunk-6AOJCNRU.mjs} +5 -5
  15. package/dist/{chunk-KXTYR2OQ.mjs → chunk-A3PHCO6W.mjs} +39 -111
  16. package/dist/chunk-ADNWEHGZ.mjs +299 -0
  17. package/dist/{chunk-2QLXDHAP.mjs → chunk-BLMRNSUG.mjs} +11 -2
  18. package/dist/chunk-BVMZJOVM.mjs +5 -0
  19. package/dist/{chunk-IWGIYDMZ.mjs → chunk-BZWDPF4G.mjs} +1 -1
  20. package/dist/{chunk-YVF2ZJTE.mjs → chunk-CZL4D3BM.mjs} +1 -1
  21. package/dist/{chunk-7SC2TZAS.mjs → chunk-DX4WGJL7.mjs} +2 -2
  22. package/dist/{chunk-45MKELYA.mjs → chunk-ESTZZPT5.mjs} +132 -13
  23. package/dist/{chunk-QP5L5DFJ.mjs → chunk-FJPR2CJX.mjs} +3 -3
  24. package/dist/chunk-HR7K3GZW.mjs +36 -0
  25. package/dist/{chunk-LXCGPZPA.mjs → chunk-IEKIYO4V.mjs} +1 -1
  26. package/dist/{chunk-TUD5CJ76.mjs → chunk-KB4OLJAD.mjs} +26 -19
  27. package/dist/{chunk-PBRXXISQ.mjs → chunk-KINDFBQP.mjs} +3 -2
  28. package/dist/{chunk-E6IMX7RS.mjs → chunk-KJLNYOOL.mjs} +2 -2
  29. package/dist/{chunk-BTTFW4Z4.mjs → chunk-KU32OT4H.mjs} +2 -10
  30. package/dist/{chunk-JQWGHSEH.mjs → chunk-NVSQ2T5Q.mjs} +71 -70
  31. package/dist/{chunk-W4EUTJNT.mjs → chunk-NZGSDALX.mjs} +94 -96
  32. package/dist/{chunk-U445A3C4.mjs → chunk-QGVRRKB2.mjs} +1 -1
  33. package/dist/{chunk-GYCEJNU4.mjs → chunk-R7GBXNTO.mjs} +1 -1
  34. package/dist/{chunk-7C36BCSN.mjs → chunk-VDN6FHU4.mjs} +1 -1
  35. package/dist/{chunk-6IYS4WLH.mjs → chunk-VO3ZWA2G.mjs} +142 -115
  36. package/dist/chunk-WOMADPM4.mjs +12 -0
  37. package/dist/{chunk-OUSWVNWT.mjs → chunk-YCMB6GR4.mjs} +1 -1
  38. package/dist/{chunk-YNTDOFTN.mjs → chunk-YW7IKQ7T.mjs} +1 -1
  39. package/dist/{chunk-M2BEAH5I.mjs → chunk-ZN4HI5FK.mjs} +126 -72
  40. package/dist/{chunk-SBL6GN43.mjs → chunk-ZYWI45EN.mjs} +1 -1
  41. package/dist/client/index.d.ts +135 -137
  42. package/dist/client/index.mjs +177 -82
  43. package/dist/client/topology.d.ts +31 -31
  44. package/dist/client/topology.mjs +42 -23
  45. package/dist/client-base-BxTocU1x.d.ts +398 -0
  46. package/dist/client-streams-YKANXMWO.mjs +112 -0
  47. package/dist/codegen/cli.mjs +4 -4
  48. package/dist/codegen/index.d.ts +36 -29
  49. package/dist/codegen/index.mjs +4 -4
  50. package/dist/core/index.d.ts +130 -115
  51. package/dist/core/index.mjs +387 -297
  52. package/dist/core/writer-worker.mjs +3 -3
  53. package/dist/database-CGDsXW1K.d.ts +959 -0
  54. package/dist/database-backup-DcLhxoa1.d.ts +143 -0
  55. package/dist/driver/better-sqlite3.d.ts +11 -10
  56. package/dist/driver/better-sqlite3.mjs +11 -11
  57. package/dist/driver/bun.d.ts +9 -8
  58. package/dist/driver/expo.d.ts +6 -7
  59. package/dist/driver/node.d.ts +11 -10
  60. package/dist/driver/node.mjs +13 -11
  61. package/dist/driver/wa-sqlite.d.ts +6 -6
  62. package/dist/errors-CJUkQ-KL.d.ts +199 -0
  63. package/dist/file-migrations/index.d.ts +20 -18
  64. package/dist/file-migrations/index.mjs +3 -3
  65. package/dist/forward-rpc-VRWFZXHN.mjs +44 -0
  66. package/dist/operation-registry-CGhNT-Sn.d.ts +96 -0
  67. package/dist/primary-wins-CHNOYuN7.d.ts +81 -0
  68. package/dist/protocol-DFpNNsIT.d.ts +153 -0
  69. package/dist/query-types-5B02ixH0.d.ts +129 -0
  70. package/dist/react/index.d.ts +22 -22
  71. package/dist/replication/coordinator/etcd.d.ts +37 -31
  72. package/dist/replication/coordinator/etcd.mjs +296 -131
  73. package/dist/replication/index.d.ts +201 -161
  74. package/dist/replication/index.mjs +630 -529
  75. package/dist/restore-options-CH8mE-Tb.d.ts +84 -0
  76. package/dist/server/index.d.ts +201 -191
  77. package/dist/server/index.mjs +736 -619
  78. package/dist/server-options-aYtCkefX.d.ts +317 -0
  79. package/dist/server-streams-24XL4T43.mjs +254 -0
  80. package/dist/sirannon-J3EgPVD7.d.ts +179 -0
  81. package/dist/transport/grpc.d.ts +37 -24
  82. package/dist/transport/grpc.mjs +83 -3199
  83. package/dist/transport/memory.d.ts +25 -26
  84. package/dist/transport/memory.mjs +18 -9
  85. package/dist/types-BrAiDGOb.d.ts +505 -0
  86. package/dist/types-CEJG0eFS.d.ts +88 -0
  87. package/dist/types-Dc8oWlxj.d.ts +1580 -0
  88. package/dist/types-DqODKeOq.d.ts +123 -0
  89. package/dist/types-guEKSzMr.d.ts +367 -0
  90. package/package.json +7 -7
  91. package/dist/baseline-D93hcIEE.d.ts +0 -17
  92. package/dist/change-tracker-CjRFuZdB.d.ts +0 -144
  93. package/dist/client-base-Cmd9EVme.d.ts +0 -397
  94. package/dist/database-BLQvqjsw.d.ts +0 -917
  95. package/dist/database-backup-B7oszQ2n.d.ts +0 -141
  96. package/dist/errors-Dei4GdBb.d.ts +0 -196
  97. package/dist/operation-registry-oiPuSYI6.d.ts +0 -94
  98. package/dist/primary-wins-B0np8JS3.d.ts +0 -85
  99. package/dist/protocol-HI-nY8DD.d.ts +0 -152
  100. package/dist/query-types-p1liOP-b.d.ts +0 -127
  101. package/dist/restore-options-CksYsH1l.d.ts +0 -83
  102. package/dist/server-options-Bj5iJnjx.d.ts +0 -292
  103. package/dist/sirannon-BPxC5tC2.d.ts +0 -178
  104. package/dist/types-B0EhgDbr.d.ts +0 -340
  105. package/dist/types-CbjfRwrK.d.ts +0 -499
  106. package/dist/types-CjhxcjhA.d.ts +0 -123
  107. package/dist/types-DPSmjU54.d.ts +0 -1518
  108. package/dist/types-DTO4CYhW.d.ts +0 -88
@@ -1,5 +1,25 @@
1
- import { assertSqlAllowed, encodeWireRowsInPlace } from './chunk-6CMCO7S4.mjs';
2
- import { SirannonError, QueryError } from './chunk-PBRXXISQ.mjs';
1
+ import { assertSqlAllowed, encodeWireRowsInPlace } from './chunk-2O3PP4CF.mjs';
2
+ import { SirannonError, TransactionError, QueryError } from './chunk-KINDFBQP.mjs';
3
+
4
+ // src/core/writer-transaction.ts
5
+ function commitFailure(err) {
6
+ if (err instanceof SirannonError) return err;
7
+ const reason = err instanceof Error ? err.message : String(err);
8
+ return new TransactionError(`SQLite could not commit the transaction: ${reason}`, err);
9
+ }
10
+ async function runWriterTransaction(conn, body) {
11
+ let bodyFinished = false;
12
+ try {
13
+ return await conn.transaction(async (txConn) => {
14
+ const value = await body(txConn);
15
+ bodyFinished = true;
16
+ return value;
17
+ });
18
+ } catch (err) {
19
+ if (!bodyFinished) throw err;
20
+ throw commitFailure(err);
21
+ }
22
+ }
3
23
 
4
24
  // src/core/query-executor.ts
5
25
  var STATEMENT_CACHE_CAPACITY = 128;
@@ -97,8 +117,7 @@ async function executeGroup(conn, units) {
97
117
  }
98
118
  const failure = await execControl(conn, "COMMIT");
99
119
  if (!failure) return outcomes;
100
- await rollbackQuietly(conn);
101
- return outcomes.map(() => ({ ok: false, error: failure }));
120
+ return settleRefusedCommit(conn, units, outcomes, failure);
102
121
  }
103
122
  async function runUnit(conn, unit) {
104
123
  const values = new Array(unit.statements.length);
@@ -109,7 +128,7 @@ async function runUnit(conn, unit) {
109
128
  return values;
110
129
  }
111
130
  function controlError(err, sql) {
112
- return asQueryError(err, sql);
131
+ return sql === "COMMIT" ? commitFailure(err) : asQueryError(err, sql);
113
132
  }
114
133
  async function execControl(conn, sql) {
115
134
  try {
@@ -135,8 +154,21 @@ async function executeGroupIsolated(conn, units) {
135
154
  }
136
155
  const failure = await execControl(conn, "COMMIT");
137
156
  if (!failure) return outcomes;
138
- await rollbackQuietly(conn);
139
- return outcomes.map((outcome) => outcome.ok ? { ok: false, error: failure } : outcome);
157
+ return settleRefusedCommit(conn, units, outcomes, failure);
158
+ }
159
+ async function settleRefusedCommit(conn, units, outcomes, failure) {
160
+ const transactionStillOpen = await execControl(conn, "ROLLBACK") === null;
161
+ const rerunEachAlone = transactionStillOpen && succeededUnitCount(outcomes) > 1;
162
+ for (let i = 0; i < units.length; i++) {
163
+ if (!outcomes[i].ok) continue;
164
+ outcomes[i] = rerunEachAlone ? await runUnitAlone(conn, units[i]) : { ok: false, error: failure };
165
+ }
166
+ return outcomes;
167
+ }
168
+ function succeededUnitCount(outcomes) {
169
+ let count = 0;
170
+ for (const outcome of outcomes) if (outcome.ok) count++;
171
+ return count;
140
172
  }
141
173
  async function runIsolatedUnit(conn, unit, savepoint) {
142
174
  const notOpened = await execControl(conn, `SAVEPOINT ${savepoint}`);
@@ -221,4 +253,4 @@ async function executeBatchSummary(conn, sql, paramsBatch) {
221
253
  return { rowsLoaded: paramsBatch.length, changes };
222
254
  }
223
255
 
224
- export { bindParams, execute, executeBatch, executeBatchSummary, executeGroup, query, queryForWire, queryOne };
256
+ export { bindParams, execute, executeBatch, executeBatchSummary, executeGroup, query, queryForWire, queryOne, runWriterTransaction };
@@ -0,0 +1,57 @@
1
+ // src/transport/grpc/client-reconnect.ts
2
+ var RECONNECT_MIN_DELAY_MS = 250;
3
+ var RECONNECT_MAX_DELAY_MS = 5e3;
4
+ var stateByTransport = /* @__PURE__ */ new WeakMap();
5
+ function stateFor(transport) {
6
+ const existing = stateByTransport.get(transport);
7
+ if (existing) return existing;
8
+ const created = { timers: /* @__PURE__ */ new Map(), delays: /* @__PURE__ */ new Map(), dialled: /* @__PURE__ */ new Map() };
9
+ stateByTransport.set(transport, created);
10
+ return created;
11
+ }
12
+ function claimEndpoint(transport, endpoint) {
13
+ const state = stateFor(transport);
14
+ if (state.dialled.has(endpoint)) return false;
15
+ state.dialled.set(endpoint, () => void 0);
16
+ return true;
17
+ }
18
+ function registerEndpointAbort(transport, endpoint, abort) {
19
+ const state = stateFor(transport);
20
+ if (!state.dialled.has(endpoint)) return;
21
+ state.dialled.set(endpoint, abort);
22
+ }
23
+ function releaseEndpoint(transport, endpoint) {
24
+ stateFor(transport).dialled.delete(endpoint);
25
+ }
26
+ function noteEndpointReachable(transport, endpoint) {
27
+ stateFor(transport).delays.delete(endpoint);
28
+ }
29
+ function scheduleEndpointRedial(transport, endpoint, redial) {
30
+ if (!transport.connected) return;
31
+ const state = stateFor(transport);
32
+ if (state.timers.has(endpoint)) return;
33
+ const delayMs = state.delays.get(endpoint) ?? RECONNECT_MIN_DELAY_MS;
34
+ state.delays.set(endpoint, Math.min(delayMs * 2, RECONNECT_MAX_DELAY_MS));
35
+ const timer = setTimeout(() => {
36
+ state.timers.delete(endpoint);
37
+ if (!transport.connected) return;
38
+ redial();
39
+ }, delayMs);
40
+ state.timers.set(endpoint, timer);
41
+ }
42
+ function stopEndpointDialling(transport) {
43
+ const state = stateByTransport.get(transport);
44
+ if (!state) return;
45
+ for (const timer of state.timers.values()) {
46
+ clearTimeout(timer);
47
+ }
48
+ state.timers.clear();
49
+ state.delays.clear();
50
+ const aborts = [...state.dialled.values()];
51
+ state.dialled.clear();
52
+ for (const abort of aborts) {
53
+ abort();
54
+ }
55
+ }
56
+
57
+ export { claimEndpoint, noteEndpointReachable, registerEndpointAbort, releaseEndpoint, scheduleEndpointRedial, stopEndpointDialling };
@@ -1,9 +1,9 @@
1
- import { checkpointLog } from './chunk-U445A3C4.mjs';
2
- import { readLogFileHeader, sameLog, cursorChecksum, scanLogFrames, copyLogRange } from './chunk-E6IMX7RS.mjs';
3
- import { logFrameOffset } from './chunk-GYCEJNU4.mjs';
4
- import { isBackupChainPosition } from './chunk-YVF2ZJTE.mjs';
1
+ import { checkpointLog } from './chunk-QGVRRKB2.mjs';
2
+ import { readLogFileHeader, sameLog, cursorChecksum, scanLogFrames, copyLogRange } from './chunk-KJLNYOOL.mjs';
3
+ import { logFrameOffset } from './chunk-R7GBXNTO.mjs';
4
+ import { isBackupChainPosition } from './chunk-CZL4D3BM.mjs';
5
5
  import { randomHex } from './chunk-QBBLSQHQ.mjs';
6
- import { SirannonError } from './chunk-PBRXXISQ.mjs';
6
+ import { SirannonError } from './chunk-KINDFBQP.mjs';
7
7
  import { readFile, mkdir, writeFile, rename, rm, readdir } from 'fs/promises';
8
8
  import { dirname, join } from 'path';
9
9
 
@@ -1,7 +1,7 @@
1
- import { query, execute, executeBatch } from './chunk-MYNI2WFS.mjs';
2
- import { ensureMetaTable, selectMetaValue, IDENTIFIER_RE, selectAppliedSourceSeqsInRange, prepareInsertAppliedChange, validateDdlSafety, extractDroppedTable, selectMaxChangeSeq, updateChangeStampsAfterSeqSql, selectOutboundChangesSql, selectNodeChangesAfterSeqSql, prepareDeleteRowColumnVersionsUpToHlc, canonicaliseForChecksum, prepareUpsertNewerColumnVersion, upsertMetaValue, selectMaxColumnVersionHlcForRow, selectMaxRowChangeHlc, validateIdentifier, prepareDeleteRowColumnVersions, prepareUpsertColumnVersion } from './chunk-45MKELYA.mjs';
3
- import { ReplicationError, BatchValidationError } from './chunk-2QLXDHAP.mjs';
4
- import { CHANGES_TABLE, isReservedIdentifier, decodeTaggedValues } from './chunk-6CMCO7S4.mjs';
1
+ import { query, execute, executeBatch, runWriterTransaction } from './chunk-52XW3Z76.mjs';
2
+ import { HLC, ensureMetaTable, selectMetaValue, IDENTIFIER_RE, selectAppliedSourceSeqsInRange, prepareInsertAppliedChange, validateDdlSafety, extractDroppedTable, selectMaxChangeSeq, updateChangeStampsAfterSeqSql, selectOutboundChangesSql, selectNodeChangesAfterSeqSql, prepareDeleteRowColumnVersionsUpToHlc, canonicaliseForChecksum, prepareUpsertNewerColumnVersion, upsertMetaValue, selectMaxColumnVersionHlcForRow, selectMaxRowChangeHlc, validateIdentifier, prepareDeleteRowColumnVersions, prepareUpsertColumnVersion } from './chunk-ESTZZPT5.mjs';
3
+ import { BatchValidationError, ConflictError } from './chunk-BLMRNSUG.mjs';
4
+ import { CHANGES_TABLE, isReservedIdentifier, decodeTaggedValues } from './chunk-2O3PP4CF.mjs';
5
5
  import { sha256Hex } from './chunk-GVCNMPOS.mjs';
6
6
 
7
7
  // src/core/transaction.ts
@@ -11,21 +11,21 @@ var Transaction = class _Transaction {
11
11
  }
12
12
  _lastInsertRowId = 0;
13
13
  /**
14
- * Runs a read inside this transaction.
14
+ * Executes a read inside this transaction.
15
15
  *
16
- * @param sql - The statement to run.
17
- * @param params - Values bound to the statement, named or positional.
18
- * @returns The rows the statement produced.
16
+ * @param sql - The statement to execute.
17
+ * @param params - The values to bind to the statement, named or positional.
18
+ * @returns The rows that the statement returns.
19
19
  */
20
20
  async query(sql, params) {
21
21
  return query(this.conn, sql, params);
22
22
  }
23
23
  /**
24
- * Runs one write inside this transaction.
24
+ * Executes one write inside this transaction.
25
25
  *
26
- * @param sql - The statement to run.
27
- * @param params - Values bound to the statement, named or positional.
28
- * @returns How many rows changed, and the last inserted row id.
26
+ * @param sql - The statement to execute.
27
+ * @param params - The values to bind to the statement, named or positional.
28
+ * @returns The number of rows that the statement changed, and the row id of the last inserted row.
29
29
  */
30
30
  async execute(sql, params) {
31
31
  const result = await execute(this.conn, sql, params);
@@ -33,10 +33,10 @@ var Transaction = class _Transaction {
33
33
  return result;
34
34
  }
35
35
  /**
36
- * Runs one statement over many parameter sets inside this transaction.
36
+ * Executes one statement once for each parameter set, inside this transaction.
37
37
  *
38
- * @param sql - The statement to run for each parameter set.
39
- * @param paramsBatch - One parameter set per run.
38
+ * @param sql - The statement to execute for each parameter set.
39
+ * @param paramsBatch - One parameter set for each execution.
40
40
  * @returns One result per parameter set, in order.
41
41
  */
42
42
  async executeBatch(sql, paramsBatch) {
@@ -47,110 +47,24 @@ var Transaction = class _Transaction {
47
47
  return results;
48
48
  }
49
49
  /**
50
- * Row id SQLite assigned to the last row this transaction inserted.
50
+ * The row id that SQLite assigned to the last row that this transaction inserted.
51
51
  */
52
52
  get lastInsertRowId() {
53
53
  return this._lastInsertRowId;
54
54
  }
55
55
  /** @internal */
56
56
  static async run(conn, fn) {
57
- return conn.transaction(async (txConn) => {
57
+ return runWriterTransaction(conn, async (txConn) => {
58
58
  const tx = new _Transaction(txConn);
59
59
  return fn(tx);
60
60
  });
61
61
  }
62
62
  };
63
63
 
64
- // src/core/sync/hlc.ts
65
- var MAX_LOGICAL = 65535;
66
- var HLC = class _HLC {
67
- wallMs;
68
- logical;
69
- nodeId;
70
- constructor(nodeId) {
71
- this.nodeId = nodeId;
72
- this.wallMs = 0;
73
- this.logical = 0;
74
- }
75
- /**
76
- * Generate a new HLC timestamp, advancing the clock.
77
- *
78
- * @internal
79
- */
80
- now() {
81
- const physicalMs = Date.now();
82
- if (physicalMs > this.wallMs) {
83
- this.wallMs = physicalMs;
84
- this.logical = 0;
85
- } else {
86
- this.logical += 1;
87
- if (this.logical > MAX_LOGICAL) {
88
- throw new ReplicationError("HLC logical counter overflow");
89
- }
90
- }
91
- return _HLC.encode(this.wallMs, this.logical, this.nodeId);
92
- }
93
- /**
94
- * Merge a remote HLC timestamp into the local clock and return the updated value.
95
- *
96
- * @internal
97
- */
98
- receive(remote) {
99
- const r = _HLC.decode(remote);
100
- const physicalMs = Date.now();
101
- if (physicalMs > this.wallMs && physicalMs > r.wallMs) {
102
- this.wallMs = physicalMs;
103
- this.logical = 0;
104
- } else if (r.wallMs > this.wallMs) {
105
- this.wallMs = r.wallMs;
106
- this.logical = r.logical + 1;
107
- } else if (this.wallMs > r.wallMs) {
108
- this.logical += 1;
109
- } else {
110
- this.logical = Math.max(this.logical, r.logical) + 1;
111
- }
112
- if (this.logical > MAX_LOGICAL) {
113
- throw new ReplicationError("HLC logical counter overflow");
114
- }
115
- return _HLC.encode(this.wallMs, this.logical, this.nodeId);
116
- }
117
- /** Lexicographic comparison of two encoded HLC strings. Returns -1, 0, or 1. */
118
- static compare(a, b) {
119
- if (a < b) return -1;
120
- if (a > b) return 1;
121
- return 0;
122
- }
123
- /** Parse an encoded HLC string into its wall-clock, logical, and nodeId components. */
124
- static decode(hlc) {
125
- const parts = hlc.split("-");
126
- if (parts.length < 3) {
127
- throw new ReplicationError(`Invalid HLC format: ${hlc}`);
128
- }
129
- return {
130
- wallMs: Number.parseInt(parts[0], 16),
131
- logical: Number.parseInt(parts[1], 16),
132
- nodeId: parts.slice(2).join("-")
133
- };
134
- }
135
- /**
136
- * Builds the string form of a clock reading, which sorts in the same order as the reading itself.
137
- *
138
- * @param wallMs - Wall-clock milliseconds since the Unix epoch.
139
- * @param logical - Counter that orders events sharing one millisecond.
140
- * @param nodeId - Identifier of the node taking the reading.
141
- * @returns The encoded stamp.
142
- */
143
- static encode(wallMs, logical, nodeId) {
144
- const wallHex = wallMs.toString(16).padStart(12, "0");
145
- const logicalHex = logical.toString(16).padStart(4, "0");
146
- return `${wallHex}-${logicalHex}-${nodeId}`;
147
- }
148
- };
149
-
150
64
  // src/core/sync/conflict/lww.ts
151
65
  var LWWResolver = class {
152
66
  /**
153
- * Takes the incoming row when its stamp is higher, and keeps the local row otherwise.
67
+ * Returns the incoming row when its stamp is higher, and the local row otherwise.
154
68
  *
155
69
  * @param ctx - The local and incoming versions of one row.
156
70
  * @returns Which version to write.
@@ -344,6 +258,21 @@ var RowWriter = class {
344
258
  };
345
259
 
346
260
  // src/core/sync/batch-applier.ts
261
+ async function resolveConflict(resolver, ctx) {
262
+ try {
263
+ const changeResolver = typeof resolver === "function" ? resolver(ctx.table) : resolver;
264
+ return await changeResolver.resolve(ctx);
265
+ } catch (err) {
266
+ if (err instanceof ConflictError) throw err;
267
+ const reason = err instanceof Error ? err.message : String(err);
268
+ throw new ConflictError(
269
+ `Conflict resolution failed for table '${ctx.table}' row '${ctx.rowId}': ${reason}`,
270
+ ctx.table,
271
+ ctx.rowId,
272
+ err
273
+ );
274
+ }
275
+ }
347
276
  var BatchApplier = class {
348
277
  constructor(conn, localNodeId, hlc, pkResolver, getLastAppliedSeq, tracker, changesTable = CHANGES_TABLE, beforeApply) {
349
278
  this.conn = conn;
@@ -455,10 +384,10 @@ var BatchApplier = class {
455
384
  return { ...result, droppedTables };
456
385
  }
457
386
  /**
458
- * Applies one pulled transaction whose changes are read from disk in
459
- * batches so that a transaction of any size is applied with bounded memory.
460
- * The whole group runs in one local transaction: the batches, the echo
461
- * stamping, and `withinTx` commit together or not at all.
387
+ * Applies one staged transaction, reading its changes from `source` one batch
388
+ * at a time so that memory use stays bounded for a transaction of any size.
389
+ * The method writes every batch, the echo stamps, and the `withinTx` work
390
+ * inside one local transaction, so a failure at any step rolls all of it back.
462
391
  */
463
392
  async applyStagedGroup(options) {
464
393
  const { source, resolver, withinTx } = options;
@@ -539,8 +468,7 @@ var BatchApplier = class {
539
468
  newData: existingRow,
540
469
  oldData: null
541
470
  };
542
- const changeResolver = typeof resolver === "function" ? resolver(change.table) : resolver;
543
- const resolution = await changeResolver.resolve({
471
+ const resolution = await resolveConflict(resolver, {
544
472
  table: change.table,
545
473
  rowId: change.rowId,
546
474
  localChange,
@@ -714,4 +642,4 @@ var StampOps = class {
714
642
  }
715
643
  };
716
644
 
717
- export { BatchApplier, BatchReader, HLC, HLC_CLOCK_META_KEY, LWWResolver, REMOTE_ORIGIN_NODE_ID, StampOps, Transaction, isWellFormedHlc, loadPersistedHlc, recordLocalColumnVersions };
645
+ export { BatchApplier, BatchReader, HLC_CLOCK_META_KEY, LWWResolver, REMOTE_ORIGIN_NODE_ID, StampOps, Transaction, isWellFormedHlc, loadPersistedHlc, recordLocalColumnVersions };
@@ -0,0 +1,299 @@
1
+ import { TransportError } from './chunk-KU32OT4H.mjs';
2
+
3
+ // src/transport/grpc/codec.ts
4
+ var PROTO_UINT32_MAX = 4294967295;
5
+ function toColumnValue(value) {
6
+ if (value === null || value === void 0) {
7
+ return { nullValue: true };
8
+ }
9
+ if (typeof value === "string") {
10
+ return { stringValue: value };
11
+ }
12
+ if (typeof value === "bigint") {
13
+ return { intValue: value };
14
+ }
15
+ if (typeof value === "number") {
16
+ if (Number.isInteger(value)) {
17
+ return { intValue: BigInt(value) };
18
+ }
19
+ return { floatValue: value };
20
+ }
21
+ if (typeof value === "boolean") {
22
+ return { boolValue: value };
23
+ }
24
+ if (value instanceof Uint8Array || Buffer.isBuffer(value)) {
25
+ return { blobValue: Buffer.from(value) };
26
+ }
27
+ throw new TypeError(
28
+ `Unsupported value type for ColumnValue conversion: ${typeof value} (${Object.prototype.toString.call(value)})`
29
+ );
30
+ }
31
+ function fromColumnValue(cv) {
32
+ if (cv.nullValue !== void 0) return null;
33
+ if (cv.stringValue !== void 0) return cv.stringValue;
34
+ if (cv.intValue !== void 0) return cv.intValue;
35
+ if (cv.floatValue !== void 0) return cv.floatValue;
36
+ if (cv.blobValue !== void 0) return cv.blobValue;
37
+ if (cv.boolValue !== void 0) return cv.boolValue;
38
+ return null;
39
+ }
40
+ function toRowData(record) {
41
+ if (record === null || record === void 0) return void 0;
42
+ const fields = {};
43
+ for (const [key, value] of Object.entries(record)) {
44
+ fields[key] = toColumnValue(value);
45
+ }
46
+ return { fields };
47
+ }
48
+ function fromRowData(rowData) {
49
+ if (rowData === void 0) return null;
50
+ const result = {};
51
+ for (const [key, cv] of Object.entries(rowData.fields)) {
52
+ result[key] = fromColumnValue(cv);
53
+ }
54
+ return result;
55
+ }
56
+ function toProtoChange(change) {
57
+ return {
58
+ table: change.table,
59
+ operation: change.operation,
60
+ rowId: change.rowId,
61
+ primaryKey: toRowData(change.primaryKey),
62
+ hlc: change.hlc,
63
+ txId: change.txId,
64
+ nodeId: change.nodeId,
65
+ newData: toRowData(change.newData),
66
+ oldData: toRowData(change.oldData),
67
+ ddlStatement: change.ddlStatement ?? ""
68
+ };
69
+ }
70
+ function fromProtoChange(proto) {
71
+ return {
72
+ table: proto.table,
73
+ operation: proto.operation,
74
+ rowId: proto.rowId,
75
+ primaryKey: fromRowData(proto.primaryKey) ?? {},
76
+ hlc: proto.hlc,
77
+ txId: proto.txId,
78
+ nodeId: proto.nodeId,
79
+ newData: fromRowData(proto.newData),
80
+ oldData: fromRowData(proto.oldData),
81
+ ddlStatement: proto.ddlStatement || void 0
82
+ };
83
+ }
84
+ function toBatchPayload(batch) {
85
+ return {
86
+ sourceNodeId: batch.sourceNodeId,
87
+ batchId: batch.batchId,
88
+ fromSeq: batch.fromSeq,
89
+ toSeq: batch.toSeq,
90
+ hlcRange: { min: batch.hlcRange.min, max: batch.hlcRange.max },
91
+ changes: batch.changes.map(toProtoChange),
92
+ checksum: batch.checksum,
93
+ groupId: batch.groupId ?? "",
94
+ primaryTerm: batch.primaryTerm ?? 0n
95
+ };
96
+ }
97
+ function fromBatchPayload(payload) {
98
+ return {
99
+ sourceNodeId: payload.sourceNodeId,
100
+ batchId: payload.batchId,
101
+ fromSeq: payload.fromSeq,
102
+ toSeq: payload.toSeq,
103
+ hlcRange: {
104
+ min: payload.hlcRange?.min ?? "",
105
+ max: payload.hlcRange?.max ?? ""
106
+ },
107
+ changes: payload.changes.map(fromProtoChange),
108
+ checksum: payload.checksum,
109
+ groupId: payload.groupId || void 0,
110
+ primaryTerm: payload.primaryTerm === 0n ? void 0 : payload.primaryTerm
111
+ };
112
+ }
113
+ function toAckPayload(ack) {
114
+ return {
115
+ batchId: ack.batchId,
116
+ ackedSeq: ack.ackedSeq,
117
+ nodeId: ack.nodeId,
118
+ groupId: ack.groupId ?? "",
119
+ primaryTerm: ack.primaryTerm ?? 0n
120
+ };
121
+ }
122
+ function fromAckPayload(payload) {
123
+ return {
124
+ batchId: payload.batchId,
125
+ ackedSeq: payload.ackedSeq,
126
+ nodeId: payload.nodeId,
127
+ groupId: payload.groupId || void 0,
128
+ primaryTerm: payload.primaryTerm === 0n ? void 0 : payload.primaryTerm
129
+ };
130
+ }
131
+ function toSyncRequestPayload(req) {
132
+ return {
133
+ requestId: req.requestId,
134
+ joinerNodeId: req.joinerNodeId,
135
+ completedTables: req.completedTables,
136
+ groupId: req.groupId ?? "",
137
+ primaryTerm: req.primaryTerm ?? 0n,
138
+ supportsStreamVerification: req.supportsStreamVerification === true
139
+ };
140
+ }
141
+ function fromSyncRequestPayload(p) {
142
+ return {
143
+ requestId: p.requestId,
144
+ joinerNodeId: p.joinerNodeId,
145
+ completedTables: p.completedTables,
146
+ supportsStreamVerification: p.supportsStreamVerification === true ? true : void 0,
147
+ groupId: p.groupId || void 0,
148
+ primaryTerm: p.primaryTerm === 0n ? void 0 : p.primaryTerm
149
+ };
150
+ }
151
+ function toSyncBatchPayload(batch) {
152
+ return {
153
+ requestId: batch.requestId,
154
+ table: batch.table,
155
+ batchIndex: batch.batchIndex,
156
+ rows: batch.rows.map((row) => toRowData(row) ?? { fields: {} }),
157
+ schema: batch.schema ?? [],
158
+ checksum: batch.checksum,
159
+ isLastBatchForTable: batch.isLastBatchForTable,
160
+ groupId: batch.groupId ?? "",
161
+ primaryTerm: batch.primaryTerm ?? 0n,
162
+ totalTables: batch.totalTables ?? 0
163
+ };
164
+ }
165
+ function fromSyncBatchPayload(p) {
166
+ return {
167
+ requestId: p.requestId,
168
+ table: p.table,
169
+ batchIndex: p.batchIndex,
170
+ rows: p.rows.map((r) => fromRowData(r) ?? {}),
171
+ schema: p.schema.length > 0 ? p.schema : void 0,
172
+ checksum: p.checksum,
173
+ isLastBatchForTable: p.isLastBatchForTable,
174
+ groupId: p.groupId || void 0,
175
+ primaryTerm: p.primaryTerm === 0n ? void 0 : p.primaryTerm,
176
+ totalTables: p.totalTables === 0 ? void 0 : p.totalTables
177
+ };
178
+ }
179
+ function toSyncCompletePayload(complete) {
180
+ return {
181
+ requestId: complete.requestId,
182
+ snapshotSeq: complete.snapshotSeq,
183
+ manifests: complete.manifests.map((m) => ({
184
+ table: m.table,
185
+ rowCount: m.rowCount,
186
+ pkHash: m.pkHash ?? "",
187
+ batchDigest: m.batchDigest ?? ""
188
+ })),
189
+ groupId: complete.groupId ?? "",
190
+ primaryTerm: complete.primaryTerm ?? 0n
191
+ };
192
+ }
193
+ function fromSyncCompletePayload(p) {
194
+ return {
195
+ requestId: p.requestId,
196
+ snapshotSeq: p.snapshotSeq,
197
+ manifests: p.manifests.map(
198
+ (m) => ({
199
+ table: m.table,
200
+ rowCount: m.rowCount,
201
+ pkHash: m.pkHash || void 0,
202
+ batchDigest: m.batchDigest || void 0
203
+ })
204
+ ),
205
+ groupId: p.groupId || void 0,
206
+ primaryTerm: p.primaryTerm === 0n ? void 0 : p.primaryTerm
207
+ };
208
+ }
209
+ function toSyncAckPayload(ack) {
210
+ return {
211
+ requestId: ack.requestId,
212
+ joinerNodeId: ack.joinerNodeId,
213
+ table: ack.table,
214
+ batchIndex: ack.batchIndex,
215
+ success: ack.success,
216
+ error: ack.error ?? "",
217
+ groupId: ack.groupId ?? "",
218
+ primaryTerm: ack.primaryTerm ?? 0n
219
+ };
220
+ }
221
+ function fromSyncAckPayload(p) {
222
+ return {
223
+ requestId: p.requestId,
224
+ joinerNodeId: p.joinerNodeId,
225
+ table: p.table,
226
+ batchIndex: p.batchIndex,
227
+ success: p.success,
228
+ error: p.error || void 0,
229
+ groupId: p.groupId || void 0,
230
+ primaryTerm: p.primaryTerm === 0n ? void 0 : p.primaryTerm
231
+ };
232
+ }
233
+ function toForwardRequest(req) {
234
+ const statements = req.statements.map((s) => {
235
+ const namedParams = {};
236
+ const positionalParams = [];
237
+ if (s.params !== void 0 && s.params !== null) {
238
+ if (Array.isArray(s.params)) {
239
+ for (const v of s.params) {
240
+ positionalParams.push(toColumnValue(v));
241
+ }
242
+ } else {
243
+ for (const [k, v] of Object.entries(s.params)) {
244
+ namedParams[k] = toColumnValue(v);
245
+ }
246
+ }
247
+ }
248
+ return { sql: s.sql, namedParams, positionalParams };
249
+ });
250
+ const writeConcernTimeoutMs = req.writeConcern?.timeoutMs ?? 0;
251
+ if (writeConcernTimeoutMs > PROTO_UINT32_MAX) {
252
+ throw new TransportError(
253
+ `Write concern timeoutMs ${writeConcernTimeoutMs} exceeds the ${PROTO_UINT32_MAX} ms that a forwarded write can carry`
254
+ );
255
+ }
256
+ return {
257
+ requestId: req.requestId,
258
+ statements,
259
+ groupId: req.groupId ?? "",
260
+ primaryTerm: req.primaryTerm ?? 0n,
261
+ writeConcernLevel: req.writeConcern?.level ?? "",
262
+ writeConcernTimeoutMs
263
+ };
264
+ }
265
+ function fromForwardWriteConcern(proto) {
266
+ const level = proto.writeConcernLevel;
267
+ if (level === "") return void 0;
268
+ if (level !== "local" && level !== "majority" && level !== "all") {
269
+ throw new TransportError(`Forward request states an unknown write concern level '${level}'`);
270
+ }
271
+ return proto.writeConcernTimeoutMs > 0 ? { level, timeoutMs: proto.writeConcernTimeoutMs } : { level };
272
+ }
273
+ function fromForwardRequest(proto) {
274
+ const statements = proto.statements.map((s) => {
275
+ const hasNamed = Object.keys(s.namedParams).length > 0;
276
+ const hasPositional = s.positionalParams.length > 0;
277
+ let params;
278
+ if (hasNamed) {
279
+ const named = {};
280
+ for (const [k, cv] of Object.entries(s.namedParams)) {
281
+ named[k] = fromColumnValue(cv);
282
+ }
283
+ params = named;
284
+ } else if (hasPositional) {
285
+ params = s.positionalParams.map(fromColumnValue);
286
+ }
287
+ return { sql: s.sql, params };
288
+ });
289
+ const writeConcern = fromForwardWriteConcern(proto);
290
+ return {
291
+ requestId: proto.requestId,
292
+ statements,
293
+ groupId: proto.groupId || void 0,
294
+ primaryTerm: proto.primaryTerm === 0n ? void 0 : proto.primaryTerm,
295
+ ...writeConcern === void 0 ? {} : { writeConcern }
296
+ };
297
+ }
298
+
299
+ export { fromAckPayload, fromBatchPayload, fromColumnValue, fromForwardRequest, fromSyncAckPayload, fromSyncBatchPayload, fromSyncCompletePayload, fromSyncRequestPayload, toAckPayload, toBatchPayload, toColumnValue, toForwardRequest, toSyncAckPayload, toSyncBatchPayload, toSyncCompletePayload, toSyncRequestPayload };