@delali/sirannon-db 0.1.6 → 0.1.8

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 (54) hide show
  1. package/README.md +154 -19
  2. package/dist/backup-scheduler/index.d.ts +18 -3
  3. package/dist/backup-scheduler/index.mjs +2 -2
  4. package/dist/{change-tracker-CFTQ9TSn.d.ts → change-tracker-CbmaMO-N.d.ts} +12 -2
  5. package/dist/chunk-4ISB7XMA.mjs +21 -0
  6. package/dist/chunk-BNUTBHHH.mjs +22 -0
  7. package/dist/chunk-CJLYFDP5.mjs +26 -0
  8. package/dist/chunk-CW6S3WL5.mjs +222 -0
  9. package/dist/chunk-DVWQD3GF.mjs +49 -0
  10. package/dist/chunk-GEZUUIKV.mjs +268 -0
  11. package/dist/{chunk-UVMVN3OT.mjs → chunk-H5AB6NIR.mjs} +1 -1
  12. package/dist/{chunk-UTO3ZAFS.mjs → chunk-HHRMRFFR.mjs} +148 -28
  13. package/dist/chunk-TKGHYWQ6.mjs +35 -0
  14. package/dist/chunk-UXLAO6ZH.mjs +352 -0
  15. package/dist/chunk-VLTICJOD.mjs +470 -0
  16. package/dist/{chunk-O7BHI3CF.mjs → chunk-YPYVQJ4C.mjs} +15 -1
  17. package/dist/client/index.d.ts +107 -15
  18. package/dist/client/index.mjs +348 -37
  19. package/dist/core/index.d.ts +70 -13
  20. package/dist/core/index.mjs +635 -192
  21. package/dist/core/writer-worker.d.ts +2 -0
  22. package/dist/core/writer-worker.mjs +129 -0
  23. package/dist/{database-BVY1GqE7.d.ts → database-DuGp0Rtr.d.ts} +51 -19
  24. package/dist/driver/better-sqlite3.d.ts +1 -1
  25. package/dist/driver/better-sqlite3.mjs +44 -6
  26. package/dist/driver/bun.mjs +35 -6
  27. package/dist/driver/expo.mjs +2 -2
  28. package/dist/driver/node.d.ts +1 -1
  29. package/dist/driver/node.mjs +48 -5
  30. package/dist/driver/wa-sqlite.d.ts +109 -0
  31. package/dist/driver/wa-sqlite.mjs +30 -2
  32. package/dist/{errors-C00ed08Q.d.ts → errors-5Nf5ZAEC.d.ts} +19 -1
  33. package/dist/file-migrations/index.d.ts +2 -3
  34. package/dist/file-migrations/index.mjs +1 -1
  35. package/dist/replication/coordinator/etcd.mjs +2 -2
  36. package/dist/replication/index.d.ts +7 -8
  37. package/dist/replication/index.mjs +79 -78
  38. package/dist/server/index.d.ts +111 -28
  39. package/dist/server/index.mjs +1016 -384
  40. package/dist/{sirannon-Cd-lK6T0.d.ts → sirannon-4SspRvP5.d.ts} +3 -3
  41. package/dist/transport/grpc.d.ts +3 -4
  42. package/dist/transport/grpc.mjs +2 -2
  43. package/dist/{types-Lc7ywFx7.d.ts → types-BsjobKbl.d.ts} +2 -2
  44. package/dist/{types-BeozgNPr.d.ts → types-D4p4UyDK.d.ts} +1 -1
  45. package/dist/types-D_hQW1hr.d.ts +494 -0
  46. package/package.json +3 -23
  47. package/dist/chunk-3MCMONVP.mjs +0 -115
  48. package/dist/chunk-74UN4DIE.mjs +0 -14
  49. package/dist/chunk-FB2U2Q3Y.mjs +0 -21
  50. package/dist/chunk-GS7T5YMI.mjs +0 -51
  51. package/dist/chunk-PXKAKK2V.mjs +0 -124
  52. package/dist/index-CLdNrcPz.d.ts +0 -16
  53. package/dist/types-BFSsG77t.d.ts +0 -29
  54. package/dist/types-D-74JiXb.d.ts +0 -265
@@ -12,12 +12,40 @@ function defineDriver(config) {
12
12
  if (!config.capabilities || typeof config.open !== "function") {
13
13
  throw new SirannonError("Driver must define capabilities and open()", "INVALID_DRIVER");
14
14
  }
15
+ if (config.worker !== void 0 && typeof config.worker.specifier !== "string") {
16
+ throw new SirannonError("Driver worker entry must define a string specifier", "INVALID_DRIVER");
17
+ }
15
18
  return Object.freeze({
16
19
  capabilities: Object.freeze({ ...config.capabilities }),
17
- open: config.open
20
+ open: config.open,
21
+ ...config.worker ? { worker: Object.freeze({ ...config.worker }) } : {},
22
+ ...config.startWriterHost ? { startWriterHost: config.startWriterHost } : {},
23
+ ...config.createWriterContext ? { createWriterContext: config.createWriterContext } : {},
24
+ ...config.createBackupEngine ? { createBackupEngine: config.createBackupEngine } : {},
25
+ ...config.resolveExtensionPath ? { resolveExtensionPath: config.resolveExtensionPath } : {}
18
26
  });
19
27
  }
20
28
 
29
+ // src/core/driver/synchronous.ts
30
+ var DEFAULT_SYNCHRONOUS = "normal";
31
+ var SYNCHRONOUS_PRAGMA_VALUES = {
32
+ off: "OFF",
33
+ normal: "NORMAL",
34
+ full: "FULL",
35
+ extra: "EXTRA"
36
+ };
37
+ function synchronousPragmaValue(level) {
38
+ const resolved = level ?? DEFAULT_SYNCHRONOUS;
39
+ const value = SYNCHRONOUS_PRAGMA_VALUES[resolved];
40
+ if (value === void 0) {
41
+ throw new SirannonError(
42
+ `Invalid synchronous level '${String(resolved)}': expected 'off', 'normal', 'full', or 'extra'`,
43
+ "INVALID_SYNCHRONOUS"
44
+ );
45
+ }
46
+ return value;
47
+ }
48
+
21
49
  // src/drivers/wa-sqlite/index.ts
22
50
  function waSqlite(driverOptions) {
23
51
  return defineDriver({
@@ -46,7 +74,7 @@ function waSqlite(driverOptions) {
46
74
  } catch {
47
75
  }
48
76
  }
49
- await sqlite3.exec(db, "PRAGMA synchronous = NORMAL");
77
+ await sqlite3.exec(db, `PRAGMA synchronous = ${synchronousPragmaValue(options?.synchronous)}`);
50
78
  await sqlite3.exec(db, "PRAGMA foreign_keys = ON");
51
79
  async function execStatements(sql, params) {
52
80
  const columns = [];
@@ -67,6 +67,15 @@ declare class HookDeniedError extends SirannonError {
67
67
  declare class CDCError extends SirannonError {
68
68
  constructor(message: string);
69
69
  }
70
+ /**
71
+ * Thrown when a caller reaches for a table Sirannon reserves for itself. The
72
+ * internal bookkeeping tables and SQLite's own catalogue are off limits to the
73
+ * query API so a caller cannot read or corrupt the change log, replication
74
+ * ledger, or schema catalogue.
75
+ */
76
+ declare class ForbiddenSqlError extends SirannonError {
77
+ constructor(message: string);
78
+ }
70
79
  /**
71
80
  * Thrown when a backup operation fails, whether that is an online backup via
72
81
  * the SQLite backup API or a file-level copy.
@@ -89,6 +98,15 @@ declare class ConnectionPoolError extends SirannonError {
89
98
  declare class MaxDatabasesError extends SirannonError {
90
99
  constructor(max: number);
91
100
  }
101
+ /**
102
+ * Thrown when more writes are pending than the writer-worker limit allows. It
103
+ * signals load shedding, so the server maps it to a 503 with a Retry-After hint.
104
+ */
105
+ declare class WriteOverloadError extends SirannonError {
106
+ readonly limit: number;
107
+ readonly retryAfterMs: number;
108
+ constructor(limit: number, retryAfterMs: number);
109
+ }
92
110
  /**
93
111
  * Thrown when a native SQLite extension cannot be loaded. The `path` argument
94
112
  * is the filesystem path passed to `load_extension`, and the optional `cause`
@@ -98,4 +116,4 @@ declare class ExtensionError extends SirannonError {
98
116
  constructor(path: string, cause?: string);
99
117
  }
100
118
 
101
- export { BackupError as B, CDCError as C, DatabaseAlreadyExistsError as D, ExtensionError as E, HookDeniedError as H, MaxDatabasesError as M, QueryError as Q, ReadOnlyError as R, SirannonError as S, TransactionError as T, ConnectionPoolError as a, DatabaseNotFoundError as b, MigrationError as c };
119
+ export { BackupError as B, CDCError as C, DatabaseAlreadyExistsError as D, ExtensionError as E, ForbiddenSqlError as F, HookDeniedError as H, MaxDatabasesError as M, QueryError as Q, ReadOnlyError as R, SirannonError as S, TransactionError as T, WriteOverloadError as W, ConnectionPoolError as a, DatabaseNotFoundError as b, MigrationError as c };
@@ -1,6 +1,5 @@
1
- import { M as Migration } from '../types-BeozgNPr.js';
2
- import '../types-D-74JiXb.js';
3
- import '../types-BFSsG77t.js';
1
+ import { M as Migration } from '../types-D4p4UyDK.js';
2
+ import '../types-D_hQW1hr.js';
4
3
 
5
4
  interface ScannedMigration {
6
5
  version: number;
@@ -1,4 +1,4 @@
1
- import { MigrationError } from '../chunk-O7BHI3CF.mjs';
1
+ import { MigrationError } from '../chunk-YPYVQJ4C.mjs';
2
2
  import { statSync, readdirSync, readFileSync } from 'fs';
3
3
  import { resolve, join } from 'path';
4
4
 
@@ -1,6 +1,6 @@
1
1
  import { compatibilityAllowsPromotion } from '../../chunk-ER7ODTDA.mjs';
2
- import { CoordinatorError, NoSafePrimaryError } from '../../chunk-UVMVN3OT.mjs';
3
- import '../../chunk-O7BHI3CF.mjs';
2
+ import { CoordinatorError, NoSafePrimaryError } from '../../chunk-H5AB6NIR.mjs';
3
+ import '../../chunk-YPYVQJ4C.mjs';
4
4
  import { Etcd3 } from 'etcd3';
5
5
 
6
6
  var MIN_AUTOMATIC_FAILOVER_VOTERS = 3;
@@ -1,14 +1,13 @@
1
- import { C as ConflictResolver, h as ConflictContext, i as ConflictResolution, H as HLCTimestamp, R as ReplicationBatch, A as ApplyResult, j as SyncPhase, k as SyncTableManifest, I as InFlightBatch, P as PeerState, b as ForwardedTransactionResult, c as SyncBatch, d as SyncComplete, e as SyncAck, S as SyncRequest, l as ReplicationConfig, m as SyncState, n as ReplicationStatus, o as ReplicationErrorEvent, a as ReplicationAck, p as Topology, T as TopologyRole } from '../types-Lc7ywFx7.js';
2
- export { F as ForwardedTransaction, N as NodeInfo, q as ReplicationChange, f as ReplicationTransport, g as TransportConfig } from '../types-Lc7ywFx7.js';
1
+ import { C as ConflictResolver, h as ConflictContext, i as ConflictResolution, H as HLCTimestamp, R as ReplicationBatch, A as ApplyResult, j as SyncPhase, k as SyncTableManifest, I as InFlightBatch, P as PeerState, b as ForwardedTransactionResult, c as SyncBatch, d as SyncComplete, e as SyncAck, S as SyncRequest, l as ReplicationConfig, m as SyncState, n as ReplicationStatus, o as ReplicationErrorEvent, a as ReplicationAck, p as Topology, T as TopologyRole } from '../types-BsjobKbl.js';
2
+ export { F as ForwardedTransaction, N as NodeInfo, q as ReplicationChange, f as ReplicationTransport, g as TransportConfig } from '../types-BsjobKbl.js';
3
3
  import { c as ReplicationGroupState } from '../types-BEu1I_9_.js';
4
4
  export { A as AcquireControllerLeaseInput, a as AcquireControllerLeaseResult, h as AdmitNodeToInSyncSetInput, C as ClusterCoordinator, f as CompareAndAdvancePrimaryTermInput, g as CompareAndAdvancePrimaryTermResult, j as CoordinatorCompatibilityMetadata, k as CoordinatorLease, b as CoordinatorNodeSession, l as CoordinatorPrimary, e as CoordinatorWatchDisposer, P as PromoteEligibleReplicaInput, R as RegisterNodeSessionInput, d as ReplicationGroupWatcher, S as SetReplicationGroupStateInput, U as UpdateInSyncSetInput, i as UpdateNodeMaintenanceInput } from '../types-BEu1I_9_.js';
5
5
  import { EventEmitter } from 'node:events';
6
- import { C as ChangeTracker } from '../change-tracker-CFTQ9TSn.js';
7
- import { D as Database } from '../database-BVY1GqE7.js';
8
- import { a as SQLiteConnection } from '../types-BFSsG77t.js';
9
- import { P as Params, k as QueryOptions, T as Transaction, E as ExecuteResult } from '../types-D-74JiXb.js';
10
- import { S as SirannonError } from '../errors-C00ed08Q.js';
11
- import '../types-BeozgNPr.js';
6
+ import { C as ChangeTracker } from '../change-tracker-CbmaMO-N.js';
7
+ import { D as Database } from '../database-DuGp0Rtr.js';
8
+ import { e as SQLiteConnection, P as Params, p as QueryOptions, T as Transaction, E as ExecuteResult } from '../types-D_hQW1hr.js';
9
+ import { S as SirannonError } from '../errors-5Nf5ZAEC.js';
10
+ import '../types-D4p4UyDK.js';
12
11
 
13
12
  type ColumnVersionGetter = (table: string, rowId: string) => Promise<Map<string, {
14
13
  hlc: string;
@@ -1,9 +1,10 @@
1
- import { Transaction } from '../chunk-3MCMONVP.mjs';
2
- import { decodeTaggedValues } from '../chunk-GS7T5YMI.mjs';
3
1
  import { compatibilityAllowsPromotion } from '../chunk-ER7ODTDA.mjs';
4
- import { ReplicationError, WriteConcernError, AuthorityError, SyncError, TopologyError, CoordinatorError, StalePrimaryError, NodeDrainingError, NodeNotInSyncError, ReadConcernError, ProtocolVersionMismatchError, BatchValidationError } from '../chunk-UVMVN3OT.mjs';
5
- export { AuthorityError, BatchValidationError, ConflictError, CoordinatorError, FailoverError, NoSafePrimaryError, NodeDrainingError, NodeNotInSyncError, ProtocolVersionMismatchError, ReadConcernError, ReplicationError, StalePrimaryError, SyncError, TopologyError, TransportError, UnsafeRecoveryRequiredError, WriteConcernError } from '../chunk-UVMVN3OT.mjs';
6
- import '../chunk-O7BHI3CF.mjs';
2
+ import { ReplicationError, WriteConcernError, AuthorityError, SyncError, TopologyError, CoordinatorError, StalePrimaryError, NodeDrainingError, NodeNotInSyncError, ReadConcernError, ProtocolVersionMismatchError, BatchValidationError } from '../chunk-H5AB6NIR.mjs';
3
+ export { AuthorityError, BatchValidationError, ConflictError, CoordinatorError, FailoverError, NoSafePrimaryError, NodeDrainingError, NodeNotInSyncError, ProtocolVersionMismatchError, ReadConcernError, ReplicationError, StalePrimaryError, SyncError, TopologyError, TransportError, UnsafeRecoveryRequiredError, WriteConcernError } from '../chunk-H5AB6NIR.mjs';
4
+ import { Transaction } from '../chunk-TKGHYWQ6.mjs';
5
+ import '../chunk-CW6S3WL5.mjs';
6
+ import { CHANGES_TABLE, APPLIED_CHANGES_TABLE, COLUMN_VERSIONS_TABLE, PEER_STATE_TABLE, SYNC_STATE_TABLE, INTERNAL_TABLE_PREFIX, decodeTaggedValues } from '../chunk-GEZUUIKV.mjs';
7
+ import '../chunk-YPYVQJ4C.mjs';
7
8
  import { randomBytes, randomUUID, createHash } from 'crypto';
8
9
  import { EventEmitter } from 'events';
9
10
 
@@ -77,6 +78,43 @@ var HLC = class _HLC {
77
78
  }
78
79
  };
79
80
 
81
+ // src/replication/log/canonicalise.ts
82
+ function canonicaliseForChecksum(value) {
83
+ return canonicaliseValue(value);
84
+ }
85
+ function canonicaliseValue(v) {
86
+ if (v === null || v === void 0) return "null";
87
+ if (typeof v === "boolean") return v ? "true" : "false";
88
+ if (typeof v === "string") return JSON.stringify(v);
89
+ if (typeof v === "bigint") return `{"__sint":"${v.toString()}"}`;
90
+ if (typeof v === "number") {
91
+ if (Number.isInteger(v)) return `{"__sint":"${v.toString()}"}`;
92
+ if (Number.isFinite(v)) return JSON.stringify(v);
93
+ return "null";
94
+ }
95
+ if (typeof v === "object") {
96
+ if (v instanceof Uint8Array) {
97
+ const buf = Buffer.isBuffer(v) ? v : Buffer.from(v);
98
+ return `{"__blob":"${buf.toString("base64")}"}`;
99
+ }
100
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(v)) {
101
+ return `{"__blob":"${v.toString("base64")}"}`;
102
+ }
103
+ if (Array.isArray(v)) {
104
+ return `[${v.map(canonicaliseValue).join(",")}]`;
105
+ }
106
+ const obj = v;
107
+ const parts = [];
108
+ for (const key of Object.keys(obj).sort()) {
109
+ const val = obj[key];
110
+ if (val === void 0) continue;
111
+ parts.push(`${JSON.stringify(key)}:${canonicaliseValue(val)}`);
112
+ }
113
+ return `{${parts.join(",")}}`;
114
+ }
115
+ return "null";
116
+ }
117
+
80
118
  // src/replication/conflict/lww.ts
81
119
  var LWWResolver = class {
82
120
  resolve(ctx) {
@@ -115,12 +153,12 @@ var FieldMergeResolver = class {
115
153
  const localChanged = /* @__PURE__ */ new Set();
116
154
  const remoteChanged = /* @__PURE__ */ new Set();
117
155
  for (const key of Object.keys(localData)) {
118
- if (JSON.stringify(localData[key]) !== JSON.stringify(oldData[key])) {
156
+ if (canonicaliseForChecksum(localData[key]) !== canonicaliseForChecksum(oldData[key])) {
119
157
  localChanged.add(key);
120
158
  }
121
159
  }
122
160
  for (const key of Object.keys(remoteData)) {
123
- if (JSON.stringify(remoteData[key]) !== JSON.stringify(oldData[key])) {
161
+ if (canonicaliseForChecksum(remoteData[key]) !== canonicaliseForChecksum(oldData[key])) {
124
162
  remoteChanged.add(key);
125
163
  }
126
164
  }
@@ -189,43 +227,6 @@ var PrimaryWinsResolver = class {
189
227
  }
190
228
  };
191
229
 
192
- // src/replication/log/canonicalise.ts
193
- function canonicaliseForChecksum(value) {
194
- return canonicaliseValue(value);
195
- }
196
- function canonicaliseValue(v) {
197
- if (v === null || v === void 0) return "null";
198
- if (typeof v === "boolean") return v ? "true" : "false";
199
- if (typeof v === "string") return JSON.stringify(v);
200
- if (typeof v === "bigint") return `{"__sint":"${v.toString()}"}`;
201
- if (typeof v === "number") {
202
- if (Number.isInteger(v)) return `{"__sint":"${v.toString()}"}`;
203
- if (Number.isFinite(v)) return JSON.stringify(v);
204
- return "null";
205
- }
206
- if (typeof v === "object") {
207
- if (v instanceof Uint8Array) {
208
- const buf = Buffer.isBuffer(v) ? v : Buffer.from(v);
209
- return `{"__blob":"${buf.toString("base64")}"}`;
210
- }
211
- if (typeof Buffer !== "undefined" && Buffer.isBuffer(v)) {
212
- return `{"__blob":"${v.toString("base64")}"}`;
213
- }
214
- if (Array.isArray(v)) {
215
- return `[${v.map(canonicaliseValue).join(",")}]`;
216
- }
217
- const obj = v;
218
- const parts = [];
219
- for (const key of Object.keys(obj).sort()) {
220
- const val = obj[key];
221
- if (val === void 0) continue;
222
- parts.push(`${JSON.stringify(key)}:${canonicaliseValue(val)}`);
223
- }
224
- return `{${parts.join(",")}}`;
225
- }
226
- return "null";
227
- }
228
-
229
230
  // src/replication/engine/constants.ts
230
231
  var DEFAULT_BATCH_SIZE = 100;
231
232
  var DEFAULT_BATCH_INTERVAL_MS = 100;
@@ -359,7 +360,7 @@ var BatchReader = class {
359
360
  const rows = await selectStmt.all(afterSeq.toString(), this.localNodeId);
360
361
  for (const row of rows) {
361
362
  if (row.operation === "DELETE") {
362
- const delStmt = await tx.prepare("DELETE FROM _sirannon_column_versions WHERE table_name = ? AND row_id = ?");
363
+ const delStmt = await tx.prepare(`DELETE FROM ${COLUMN_VERSIONS_TABLE} WHERE table_name = ? AND row_id = ?`);
363
364
  await delStmt.run(row.table_name, String(row.row_id));
364
365
  continue;
365
366
  }
@@ -378,7 +379,7 @@ var BatchReader = class {
378
379
  }
379
380
  }
380
381
  const upsertStmt = await tx.prepare(
381
- `INSERT INTO _sirannon_column_versions (table_name, row_id, column_name, hlc, node_id)
382
+ `INSERT INTO ${COLUMN_VERSIONS_TABLE} (table_name, row_id, column_name, hlc, node_id)
382
383
  VALUES (?, ?, ?, ?, ?)
383
384
  ON CONFLICT(table_name, row_id, column_name)
384
385
  DO UPDATE SET hlc = excluded.hlc, node_id = excluded.node_id`
@@ -443,7 +444,7 @@ var BatchApplier = class {
443
444
  if (needsPartialDedup) {
444
445
  appliedSeqSet = /* @__PURE__ */ new Set();
445
446
  const checkStmt = await this.conn.prepare(
446
- "SELECT source_seq FROM _sirannon_applied_changes WHERE source_node_id = ? AND source_seq >= ? AND source_seq <= ?"
447
+ `SELECT source_seq FROM ${APPLIED_CHANGES_TABLE} WHERE source_node_id = ? AND source_seq >= ? AND source_seq <= ?`
447
448
  );
448
449
  const applied2 = await checkStmt.all(
449
450
  batch.sourceNodeId,
@@ -548,7 +549,7 @@ var BatchApplier = class {
548
549
  }
549
550
  }
550
551
  const recordStmt = await this.conn.prepare(
551
- "INSERT OR IGNORE INTO _sirannon_applied_changes (source_node_id, source_seq, applied_at) VALUES (?, ?, ?)"
552
+ `INSERT OR IGNORE INTO ${APPLIED_CHANGES_TABLE} (source_node_id, source_seq, applied_at) VALUES (?, ?, ?)`
552
553
  );
553
554
  const nowSec = Date.now() / 1e3;
554
555
  for (let seq = batch.fromSeq; seq <= batch.toSeq; seq += 1n) {
@@ -573,7 +574,7 @@ var BatchApplier = class {
573
574
  }
574
575
  async getLocalHlcForRow(tx, table, rowId) {
575
576
  const stmt = await tx.prepare(
576
- "SELECT MAX(hlc) as max_hlc FROM _sirannon_column_versions WHERE table_name = ? AND row_id = ?"
577
+ `SELECT MAX(hlc) as max_hlc FROM ${COLUMN_VERSIONS_TABLE} WHERE table_name = ? AND row_id = ?`
577
578
  );
578
579
  const row = await stmt.get(table, rowId);
579
580
  return row?.max_hlc ?? null;
@@ -660,13 +661,13 @@ var BatchApplier = class {
660
661
  }
661
662
  async recordColumnVersions(tx, change, data) {
662
663
  if (change.operation === "delete") {
663
- const delStmt = await tx.prepare("DELETE FROM _sirannon_column_versions WHERE table_name = ? AND row_id = ?");
664
+ const delStmt = await tx.prepare(`DELETE FROM ${COLUMN_VERSIONS_TABLE} WHERE table_name = ? AND row_id = ?`);
664
665
  await delStmt.run(change.table, change.rowId);
665
666
  return;
666
667
  }
667
668
  if (!data) return;
668
669
  const upsertStmt = await tx.prepare(
669
- `INSERT INTO _sirannon_column_versions (table_name, row_id, column_name, hlc, node_id)
670
+ `INSERT INTO ${COLUMN_VERSIONS_TABLE} (table_name, row_id, column_name, hlc, node_id)
670
671
  VALUES (?, ?, ?, ?, ?)
671
672
  ON CONFLICT(table_name, row_id, column_name)
672
673
  DO UPDATE SET hlc = excluded.hlc, node_id = excluded.node_id`
@@ -931,21 +932,21 @@ CREATE TABLE IF NOT EXISTS "${this.changesTable}" (
931
932
  hlc TEXT NOT NULL DEFAULT ''
932
933
  )`);
933
934
  await this.conn.exec(`
934
- CREATE TABLE IF NOT EXISTS _sirannon_peer_state (
935
+ CREATE TABLE IF NOT EXISTS ${PEER_STATE_TABLE} (
935
936
  peer_node_id TEXT PRIMARY KEY,
936
937
  last_acked_seq INTEGER NOT NULL DEFAULT 0,
937
938
  last_received_hlc TEXT NOT NULL DEFAULT '',
938
939
  updated_at REAL NOT NULL
939
940
  )`);
940
941
  await this.conn.exec(`
941
- CREATE TABLE IF NOT EXISTS _sirannon_applied_changes (
942
+ CREATE TABLE IF NOT EXISTS ${APPLIED_CHANGES_TABLE} (
942
943
  source_node_id TEXT NOT NULL,
943
944
  source_seq INTEGER NOT NULL,
944
945
  applied_at REAL NOT NULL,
945
946
  PRIMARY KEY (source_node_id, source_seq)
946
947
  )`);
947
948
  await this.conn.exec(`
948
- CREATE TABLE IF NOT EXISTS _sirannon_column_versions (
949
+ CREATE TABLE IF NOT EXISTS ${COLUMN_VERSIONS_TABLE} (
949
950
  table_name TEXT NOT NULL,
950
951
  row_id TEXT NOT NULL,
951
952
  column_name TEXT NOT NULL,
@@ -954,7 +955,7 @@ CREATE TABLE IF NOT EXISTS _sirannon_column_versions (
954
955
  PRIMARY KEY (table_name, row_id, column_name)
955
956
  )`);
956
957
  await this.conn.exec(`
957
- CREATE TABLE IF NOT EXISTS _sirannon_sync_state (
958
+ CREATE TABLE IF NOT EXISTS ${SYNC_STATE_TABLE} (
958
959
  table_name TEXT PRIMARY KEY,
959
960
  status TEXT NOT NULL DEFAULT 'pending',
960
961
  row_count INTEGER NOT NULL DEFAULT 0,
@@ -966,7 +967,7 @@ CREATE TABLE IF NOT EXISTS _sirannon_sync_state (
966
967
  request_id TEXT
967
968
  )`);
968
969
  }
969
- async dumpSchema(conn, excludePrefix = "_sirannon_") {
970
+ async dumpSchema(conn, excludePrefix = INTERNAL_TABLE_PREFIX) {
970
971
  const stmt = await conn.prepare(
971
972
  `SELECT type, name, sql FROM sqlite_master
972
973
  WHERE type IN ('table', 'index') AND name NOT LIKE ? AND name NOT LIKE 'sqlite_%' AND sql IS NOT NULL`
@@ -1003,7 +1004,7 @@ CREATE TABLE IF NOT EXISTS _sirannon_sync_state (
1003
1004
  }
1004
1005
  async getTablesInFkOrder(conn) {
1005
1006
  const stmt = await conn.prepare(
1006
- `SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE '_sirannon_%' AND name NOT LIKE 'sqlite_%'`
1007
+ `SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE '${INTERNAL_TABLE_PREFIX}%' AND name NOT LIKE 'sqlite_%'`
1007
1008
  );
1008
1009
  const tableRows = await stmt.all();
1009
1010
  const tableNames = tableRows.map((r) => r.name);
@@ -1070,7 +1071,7 @@ CREATE TABLE IF NOT EXISTS _sirannon_sync_state (
1070
1071
  for (const table of reversed) {
1071
1072
  await tx.exec(`DELETE FROM "${table}"`);
1072
1073
  }
1073
- await tx.exec(`DELETE FROM _sirannon_sync_state WHERE table_name != '__sync_meta__'`);
1074
+ await tx.exec(`DELETE FROM ${SYNC_STATE_TABLE} WHERE table_name != '__sync_meta__'`);
1074
1075
  });
1075
1076
  } finally {
1076
1077
  await conn.exec("PRAGMA foreign_keys = ON");
@@ -1118,7 +1119,7 @@ var StateOps = class {
1118
1119
  const changesRow = await changesStmt.get();
1119
1120
  best = mergeCandidate(best, changesRow?.max_hlc ?? null);
1120
1121
  const versionsStmt = await this.conn.prepare(
1121
- `SELECT MAX(hlc) AS max_hlc FROM _sirannon_column_versions WHERE hlc != ''`
1122
+ `SELECT MAX(hlc) AS max_hlc FROM ${COLUMN_VERSIONS_TABLE} WHERE hlc != ''`
1122
1123
  );
1123
1124
  const versionsRow = await versionsStmt.get();
1124
1125
  best = mergeCandidate(best, versionsRow?.max_hlc ?? null);
@@ -1126,7 +1127,7 @@ var StateOps = class {
1126
1127
  }
1127
1128
  async getLastAppliedSeq(fromNodeId) {
1128
1129
  const stmt = await this.conn.prepare(
1129
- "SELECT MAX(source_seq) as max_seq FROM _sirannon_applied_changes WHERE source_node_id = ?"
1130
+ `SELECT MAX(source_seq) as max_seq FROM ${APPLIED_CHANGES_TABLE} WHERE source_node_id = ?`
1130
1131
  );
1131
1132
  const row = await stmt.get(fromNodeId);
1132
1133
  if (!row || row.max_seq === null) {
@@ -1136,20 +1137,20 @@ var StateOps = class {
1136
1137
  }
1137
1138
  async setLastAppliedSeq(fromNodeId, seq) {
1138
1139
  const stmt = await this.conn.prepare(
1139
- `INSERT INTO _sirannon_peer_state (peer_node_id, last_acked_seq, updated_at)
1140
+ `INSERT INTO ${PEER_STATE_TABLE} (peer_node_id, last_acked_seq, updated_at)
1140
1141
  VALUES (?, ?, ?)
1141
1142
  ON CONFLICT(peer_node_id)
1142
1143
  DO UPDATE SET
1143
- last_acked_seq = max(_sirannon_peer_state.last_acked_seq, excluded.last_acked_seq),
1144
+ last_acked_seq = max(${PEER_STATE_TABLE}.last_acked_seq, excluded.last_acked_seq),
1144
1145
  updated_at = CASE
1145
- WHEN excluded.last_acked_seq >= _sirannon_peer_state.last_acked_seq THEN excluded.updated_at
1146
- ELSE _sirannon_peer_state.updated_at
1146
+ WHEN excluded.last_acked_seq >= ${PEER_STATE_TABLE}.last_acked_seq THEN excluded.updated_at
1147
+ ELSE ${PEER_STATE_TABLE}.updated_at
1147
1148
  END`
1148
1149
  );
1149
1150
  await stmt.run(fromNodeId, seq.toString(), Date.now() / 1e3);
1150
1151
  }
1151
1152
  async getPeerAckedSeq(peerNodeId) {
1152
- const stmt = await this.conn.prepare("SELECT last_acked_seq FROM _sirannon_peer_state WHERE peer_node_id = ?");
1153
+ const stmt = await this.conn.prepare(`SELECT last_acked_seq FROM ${PEER_STATE_TABLE} WHERE peer_node_id = ?`);
1153
1154
  const row = await stmt.get(peerNodeId);
1154
1155
  if (!row || row.last_acked_seq === null) {
1155
1156
  return 0n;
@@ -1166,7 +1167,7 @@ var StateOps = class {
1166
1167
  }
1167
1168
  async getMinAckedSeq() {
1168
1169
  const stmt = await this.conn.prepare(
1169
- "SELECT MIN(last_acked_seq) as min_seq, COUNT(*) as cnt FROM _sirannon_peer_state"
1170
+ `SELECT MIN(last_acked_seq) as min_seq, COUNT(*) as cnt FROM ${PEER_STATE_TABLE}`
1170
1171
  );
1171
1172
  const row = await stmt.get();
1172
1173
  const hasPeers = row !== void 0 && row.cnt > 0;
@@ -1191,7 +1192,7 @@ var StateOps = class {
1191
1192
  }
1192
1193
  async setSyncTableStatus(table, status, rowCount, pkHash) {
1193
1194
  const stmt = await this.conn.prepare(
1194
- `INSERT INTO _sirannon_sync_state (table_name, status, row_count, pk_hash, completed_at)
1195
+ `INSERT INTO ${SYNC_STATE_TABLE} (table_name, status, row_count, pk_hash, completed_at)
1195
1196
  VALUES (?, ?, ?, ?, ?)
1196
1197
  ON CONFLICT(table_name) DO UPDATE SET
1197
1198
  status = excluded.status,
@@ -1203,7 +1204,7 @@ var StateOps = class {
1203
1204
  }
1204
1205
  async setSyncMeta(phase, snapshotSeq, sourcePeerId, requestId) {
1205
1206
  const stmt = await this.conn.prepare(
1206
- `INSERT INTO _sirannon_sync_state (table_name, status, snapshot_seq, source_peer_id, started_at, request_id)
1207
+ `INSERT INTO ${SYNC_STATE_TABLE} (table_name, status, snapshot_seq, source_peer_id, started_at, request_id)
1207
1208
  VALUES ('__sync_meta__', ?, ?, ?, ?, ?)
1208
1209
  ON CONFLICT(table_name) DO UPDATE SET
1209
1210
  status = excluded.status,
@@ -1222,14 +1223,14 @@ var StateOps = class {
1222
1223
  }
1223
1224
  async getSyncState() {
1224
1225
  const metaStmt = await this.conn.prepare(
1225
- `SELECT status, snapshot_seq, source_peer_id FROM _sirannon_sync_state WHERE table_name = '__sync_meta__'`
1226
+ `SELECT status, snapshot_seq, source_peer_id FROM ${SYNC_STATE_TABLE} WHERE table_name = '__sync_meta__'`
1226
1227
  );
1227
1228
  const meta = await metaStmt.get();
1228
1229
  if (!meta) {
1229
1230
  return { phase: "ready", completedTables: [], snapshotSeq: null, sourcePeerId: null };
1230
1231
  }
1231
1232
  const completedStmt = await this.conn.prepare(
1232
- `SELECT table_name FROM _sirannon_sync_state WHERE table_name != '__sync_meta__' AND status = 'completed'`
1233
+ `SELECT table_name FROM ${SYNC_STATE_TABLE} WHERE table_name != '__sync_meta__' AND status = 'completed'`
1233
1234
  );
1234
1235
  const completedRows = await completedStmt.all();
1235
1236
  return {
@@ -1240,7 +1241,7 @@ var StateOps = class {
1240
1241
  };
1241
1242
  }
1242
1243
  async isSyncCompleted() {
1243
- const stmt = await this.conn.prepare(`SELECT status FROM _sirannon_sync_state WHERE table_name = '__sync_meta__'`);
1244
+ const stmt = await this.conn.prepare(`SELECT status FROM ${SYNC_STATE_TABLE} WHERE table_name = '__sync_meta__'`);
1244
1245
  const row = await stmt.get();
1245
1246
  return row?.status === "ready";
1246
1247
  }
@@ -1267,7 +1268,7 @@ function mergeCandidate(current, candidate) {
1267
1268
 
1268
1269
  // src/replication/log/log.ts
1269
1270
  var ReplicationLog = class {
1270
- constructor(conn, localNodeId, hlc, changesTable = "_sirannon_changes", tracker) {
1271
+ constructor(conn, localNodeId, hlc, changesTable = CHANGES_TABLE, tracker) {
1271
1272
  this.changesTable = changesTable;
1272
1273
  this.pkResolver = new PkResolver(conn);
1273
1274
  this.state = new StateOps(conn);
@@ -1672,7 +1673,7 @@ var LocalExecutor = class {
1672
1673
  const r = await stmt.run(...bindValues);
1673
1674
  if (isDdl) {
1674
1675
  const ddlStmt = await tx.prepare(
1675
- `INSERT INTO "_sirannon_changes" (table_name, operation, row_id, new_data, node_id, tx_id, hlc)
1676
+ `INSERT INTO "${CHANGES_TABLE}" (table_name, operation, row_id, new_data, node_id, tx_id, hlc)
1676
1677
  VALUES ('__ddl__', 'DDL', '', ?, ?, ?, ?)`
1677
1678
  );
1678
1679
  const hlcVal = engine.hlc.now();
@@ -1738,7 +1739,7 @@ var LocalExecutor = class {
1738
1739
  droppedTables.push(droppedTable);
1739
1740
  }
1740
1741
  const ddlStmt = await tx.prepare(
1741
- `INSERT INTO "_sirannon_changes" (table_name, operation, row_id, new_data, node_id, tx_id, hlc)
1742
+ `INSERT INTO "${CHANGES_TABLE}" (table_name, operation, row_id, new_data, node_id, tx_id, hlc)
1742
1743
  VALUES ('__ddl__', 'DDL', '', ?, ?, ?, ?)`
1743
1744
  );
1744
1745
  const hlcVal = engine.hlc.now();
@@ -1789,7 +1790,7 @@ var LocalExecutor = class {
1789
1790
  hooks.droppedTables.push(droppedTable);
1790
1791
  }
1791
1792
  const ddlStmt = await tx.prepare(
1792
- `INSERT INTO "_sirannon_changes" (table_name, operation, row_id, new_data, node_id, tx_id, hlc)
1793
+ `INSERT INTO "${CHANGES_TABLE}" (table_name, operation, row_id, new_data, node_id, tx_id, hlc)
1793
1794
  VALUES ('__ddl__', 'DDL', '', ?, ?, ?, ?)`
1794
1795
  );
1795
1796
  const hlcVal = engine.hlc.now();
@@ -2433,7 +2434,7 @@ var SyncJoiner = class {
2433
2434
  }
2434
2435
  await engine.log.setLastAppliedSeq(fromPeerId, complete.snapshotSeq);
2435
2436
  const recordStmt = await engine.writerConn.prepare(
2436
- "INSERT OR IGNORE INTO _sirannon_applied_changes (source_node_id, source_seq, applied_at) VALUES (?, ?, ?)"
2437
+ `INSERT OR IGNORE INTO ${APPLIED_CHANGES_TABLE} (source_node_id, source_seq, applied_at) VALUES (?, ?, ?)`
2437
2438
  );
2438
2439
  await recordStmt.run(fromPeerId, complete.snapshotSeq.toString(), Date.now() / 1e3);
2439
2440
  const previousApplied = engine.appliedSeqByPeer.get(fromPeerId) ?? 0n;
@@ -2929,7 +2930,7 @@ var ReplicationEngine = class extends EventEmitter {
2929
2930
  this.nodeId = config.nodeId ?? generateNodeId();
2930
2931
  this.hlc = new HLC(this.nodeId);
2931
2932
  this.tracker = config.changeTracker;
2932
- this.log = new ReplicationLog(writerConn, this.nodeId, this.hlc, "_sirannon_changes", this.tracker);
2933
+ this.log = new ReplicationLog(writerConn, this.nodeId, this.hlc, CHANGES_TABLE, this.tracker);
2933
2934
  this.defaultResolver = config.defaultConflictResolver ?? new LWWResolver();
2934
2935
  this.batchSize = config.batchSize ?? DEFAULT_BATCH_SIZE;
2935
2936
  this.batchIntervalMs = config.batchIntervalMs ?? DEFAULT_BATCH_INTERVAL_MS;
@@ -3122,7 +3123,7 @@ var ReplicationEngine = class extends EventEmitter {
3122
3123
  }
3123
3124
  async loadAppliedSeqs() {
3124
3125
  const stmt = await this.writerConn.prepare(
3125
- "SELECT source_node_id, MAX(source_seq) AS max_seq FROM _sirannon_applied_changes GROUP BY source_node_id"
3126
+ `SELECT source_node_id, MAX(source_seq) AS max_seq FROM ${APPLIED_CHANGES_TABLE} GROUP BY source_node_id`
3126
3127
  );
3127
3128
  const rows = await stmt.all();
3128
3129
  for (const row of rows) {