@delali/sirannon-db 0.3.6-next.77 → 0.3.6-next.78

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 (72) hide show
  1. package/dist/backup/index.d.ts +5 -5
  2. package/dist/backup/index.mjs +5 -3
  3. package/dist/backup-scheduler/index.d.ts +2 -2
  4. package/dist/backup-scheduler/index.mjs +2 -2
  5. package/dist/chunk-22TYGH7J.mjs +55 -0
  6. package/dist/{chunk-SB3MIFLG.mjs → chunk-2ZQOZFX7.mjs} +29 -6
  7. package/dist/{chunk-UIDQGPJY.mjs → chunk-4O7WYZHN.mjs} +38 -3
  8. package/dist/{chunk-ZFFUVYYE.mjs → chunk-5DKWHT6K.mjs} +109 -1
  9. package/dist/chunk-5O4J6LQQ.mjs +35 -0
  10. package/dist/{chunk-CZL4D3BM.mjs → chunk-6CABU54O.mjs} +2 -2
  11. package/dist/{chunk-JAQ2I4OT.mjs → chunk-A5ULJVK7.mjs} +11 -11
  12. package/dist/{chunk-RLBKZZ24.mjs → chunk-AC7TKWNH.mjs} +1 -0
  13. package/dist/chunk-BKO4SCEC.mjs +99 -0
  14. package/dist/{chunk-KOTBP4AW.mjs → chunk-C7E4H6UN.mjs} +24 -4
  15. package/dist/{chunk-G4AG3ESO.mjs → chunk-FBA7P4ZA.mjs} +114 -149
  16. package/dist/{chunk-DKML5425.mjs → chunk-GBVB6WSC.mjs} +2 -2
  17. package/dist/chunk-I4JY5QPA.mjs +51 -0
  18. package/dist/{chunk-L6EX6K27.mjs → chunk-MO4APBYG.mjs} +54 -13
  19. package/dist/{chunk-RPWBYASV.mjs → chunk-MTOQ6QIS.mjs} +1 -1
  20. package/dist/chunk-MX77PGEN.mjs +60 -0
  21. package/dist/{chunk-BTUD6XQO.mjs → chunk-N3MHZV3B.mjs} +11 -29
  22. package/dist/{chunk-UHUDPQFI.mjs → chunk-OJXV4L3N.mjs} +1 -3
  23. package/dist/chunk-QF7WF7RJ.mjs +34 -0
  24. package/dist/chunk-RLBV4YTB.mjs +22 -0
  25. package/dist/{chunk-ISDT4RBD.mjs → chunk-ULUZUBON.mjs} +1 -1
  26. package/dist/{chunk-AP4367HK.mjs → chunk-VGRAAQX3.mjs} +4 -4
  27. package/dist/chunk-W74HFQFD.mjs +153 -0
  28. package/dist/chunk-Y4SP7CRF.mjs +9 -0
  29. package/dist/{chunk-JFVEGN7J.mjs → chunk-ZAUHAJ4G.mjs} +2 -1
  30. package/dist/{chunk-DDIHBUEN.mjs → chunk-ZWW53O5P.mjs} +19 -7
  31. package/dist/client/index.d.ts +8 -8
  32. package/dist/client/topology.d.ts +4 -4
  33. package/dist/{client-base-Doaqwrx3.d.ts → client-base-DJZbSY55.d.ts} +2 -2
  34. package/dist/{client-streams-MKPE4MXW.mjs → client-streams-F7IG3PTU.mjs} +13 -16
  35. package/dist/core/index.d.ts +48 -12
  36. package/dist/core/index.mjs +1812 -107
  37. package/dist/core/writer-worker.mjs +28 -4
  38. package/dist/{database-fz-8CJBP.d.ts → database-BZ96ZzAx.d.ts} +411 -184
  39. package/dist/{database-backup-BwTNiBm2.d.ts → database-backup-BhM8yJ1A.d.ts} +32 -2
  40. package/dist/driver/better-sqlite3.d.ts +11 -1
  41. package/dist/driver/better-sqlite3.mjs +212 -23
  42. package/dist/driver/bun.d.ts +1 -1
  43. package/dist/driver/bun.mjs +15 -22
  44. package/dist/driver/expo.d.ts +1 -1
  45. package/dist/driver/expo.mjs +185 -16
  46. package/dist/driver/node.d.ts +1 -1
  47. package/dist/driver/node.mjs +15 -62
  48. package/dist/driver/wa-sqlite.d.ts +1 -1
  49. package/dist/driver/wa-sqlite.mjs +14 -153
  50. package/dist/{change-tracker-Dpo99EI-.d.ts → errors-DLMAGQFb.d.ts} +200 -2
  51. package/dist/file-migrations/index.d.ts +1 -1
  52. package/dist/peer-refusal-RR37UOLV.mjs +1 -0
  53. package/dist/{protocol-DZSYSv0S.d.ts → protocol-DE10taas.d.ts} +1 -1
  54. package/dist/replication/coordinator/etcd.mjs +1 -1
  55. package/dist/replication/index.d.ts +7 -7
  56. package/dist/replication/index.mjs +66 -9
  57. package/dist/{restore-options-CcMukOLO.d.ts → restore-options-B6HV_5XT.d.ts} +6 -1
  58. package/dist/server/index.d.ts +10 -10
  59. package/dist/server/index.mjs +506 -350
  60. package/dist/{server-options-B9CsAZp5.d.ts → server-options-Bhdlvehe.d.ts} +14 -1
  61. package/dist/{server-streams-SBYCHWWM.mjs → server-streams-Q2AZAW3N.mjs} +6 -30
  62. package/dist/{sirannon-D0CjDULa.d.ts → sirannon-Ba1o2HUQ.d.ts} +5 -5
  63. package/dist/transport/grpc.d.ts +15 -4
  64. package/dist/transport/grpc.mjs +34 -37
  65. package/dist/transport/memory.d.ts +4 -3
  66. package/dist/transport/memory.mjs +8 -26
  67. package/dist/{types-DTmi7pXp.d.ts → types-BWyv_N18.d.ts} +189 -1
  68. package/dist/{types-BlZ9GG1a.d.ts → types-CDnlkVRK.d.ts} +8 -2
  69. package/package.json +17 -9
  70. package/dist/chunk-DBN57KVR.mjs +0 -171
  71. package/dist/chunk-W67A4H2E.mjs +0 -23
  72. package/dist/chunk-YW7IKQ7T.mjs +0 -62
@@ -1,34 +1,24 @@
1
1
  import { verifyBackupRecord } from './chunk-DX4WGJL7.mjs';
2
- import { BackupManager, BackupScheduler, DEFAULT_PIECE_BYTES, sendFileInPieces } from './chunk-BTUD6XQO.mjs';
3
- import { DEFAULT_BACKUP_NAME_PREFIX, defaultStagingDir, DEFAULT_CAPTURE_INTERVAL_MS, DEFAULT_FULL_COPY_INTERVAL_MS } from './chunk-JAQ2I4OT.mjs';
2
+ import { BackupManager, BackupScheduler, sendFileInPieces } from './chunk-N3MHZV3B.mjs';
3
+ import { DEFAULT_BACKUP_NAME_PREFIX, defaultStagingDir, DEFAULT_CAPTURE_INTERVAL_MS, DEFAULT_FULL_COPY_INTERVAL_MS } from './chunk-Y4SP7CRF.mjs';
4
+ import { DEFAULT_PIECE_BYTES } from './chunk-I4JY5QPA.mjs';
4
5
  import { withLateGroupOutcomes, withLateRowCounts } from './chunk-K43WJORM.mjs';
5
- import { leftTransactionUnbegun, canCarryBegin, toWorkerGroupStatement, WORKER_CANCELLED_CODE, WORKER_COPY_STOPPED_CODE, deserializeError } from './chunk-75IBRE73.mjs';
6
+ import { leftTransactionUnbegun, canCarryBegin, WORKER_CANCELLED_CODE, WORKER_COPY_STOPPED_CODE, deserializeError, toWorkerGroupStatement } from './chunk-75IBRE73.mjs';
6
7
  import { rowCountsOf, totalRowCounts } from './chunk-E2GRX4ZD.mjs';
7
- import { readCycleState, writeCycleState, removeCycleState, stagedCapturePath, captureAndCheckpointTurn } from './chunk-UHUDPQFI.mjs';
8
+ import { readCycleState, writeCycleState, removeCycleState, stagedCapturePath, captureAndCheckpointTurn } from './chunk-OJXV4L3N.mjs';
8
9
  import { checkpointLog } from './chunk-QGVRRKB2.mjs';
9
10
  import { logPathFor } from './chunk-AHCMO4YL.mjs';
10
11
  import { reportQuietly } from './chunk-BUWJH3D3.mjs';
11
- import { DEFAULT_CHAIN_NAME, readBackupChains, appendChainRecord, appendChainHead, readChainHeadAt, readChainHeads } from './chunk-CZL4D3BM.mjs';
12
+ import { DEFAULT_CHAIN_NAME, readBackupChains, appendChainRecord, appendChainHead, readChainHeadAt, readChainHeads } from './chunk-6CABU54O.mjs';
12
13
  import { destinationWithDeadline, DEFAULT_DESTINATION_TIMEOUT_MS, randomHex } from './chunk-BGJ5JVYL.mjs';
13
- import { SirannonError, ExtensionError } from './chunk-KINDFBQP.mjs';
14
+ import { SirannonError } from './chunk-KINDFBQP.mjs';
14
15
  import { Worker } from 'worker_threads';
15
16
  import { existsSync } from 'fs';
16
17
  import { resolve, dirname, join, sep } from 'path';
17
18
  import { fileURLToPath, pathToFileURL } from 'url';
18
- import { AsyncLocalStorage } from 'async_hooks';
19
19
  import { mkdir, rm, stat } from 'fs/promises';
20
20
  import { createRequire } from 'module';
21
21
 
22
- // src/core/driver/extension.ts
23
- async function loadThroughRuntime(extensionPath, load) {
24
- try {
25
- load();
26
- } catch (err) {
27
- if (err instanceof SirannonError) throw err;
28
- throw new ExtensionError(extensionPath, err instanceof Error ? err.message : String(err));
29
- }
30
- }
31
-
32
22
  // src/core/driver/statement-cache.ts
33
23
  var STATEMENT_CACHE_CAPACITY = 128;
34
24
  function createStatementCache(prepare) {
@@ -50,63 +40,50 @@ function createStatementCache(prepare) {
50
40
  };
51
41
  }
52
42
 
53
- // src/core/driver/values.ts
54
- var SAFE_INT_BOUND = 9007199254740991n;
55
- function narrowSafeBigInt(value) {
56
- if (typeof value === "bigint" && value >= -SAFE_INT_BOUND && value <= SAFE_INT_BOUND) {
57
- return Number(value);
58
- }
59
- return value;
60
- }
61
- function narrowRowIntegers(row) {
62
- if (row === null || typeof row !== "object") return row;
63
- const obj = row;
64
- for (const key of Object.keys(obj)) {
65
- const value = obj[key];
66
- if (typeof value === "bigint" && value >= -SAFE_INT_BOUND && value <= SAFE_INT_BOUND) {
67
- obj[key] = Number(value);
68
- }
69
- }
70
- return row;
71
- }
72
- function narrowRowsIntegers(rows) {
73
- for (const row of rows) {
74
- narrowRowIntegers(row);
75
- }
76
- return rows;
77
- }
78
- var EMPTY_BLOB_WITH_ALLOCATED_STORAGE = new Uint8Array(new ArrayBuffer(1), 0, 0);
79
- function bindableValue(value) {
80
- if (typeof value === "boolean") return value ? 1n : 0n;
81
- if (ArrayBuffer.isView(value) && value.byteLength === 0) return EMPTY_BLOB_WITH_ALLOCATED_STORAGE;
82
- return value;
83
- }
84
- function isNamedParams(value) {
85
- if (value === null || typeof value !== "object") return false;
86
- const prototype = Object.getPrototypeOf(value);
87
- return prototype === Object.prototype || prototype === null;
88
- }
89
- function bindableNamedParams(params) {
90
- let bound;
91
- for (const key of Object.keys(params)) {
92
- const value = params[key];
93
- const replacement = bindableValue(value);
94
- if (replacement === value) continue;
95
- bound ??= { ...params };
96
- bound[key] = replacement;
97
- }
98
- return bound ?? params;
43
+ // src/core/worker/host-connection.ts
44
+ function countedCalls(request, sql) {
45
+ return {
46
+ allCounted: (...params) => request({ kind: "allCounted", sql, params }),
47
+ getCounted: (...params) => request({ kind: "getCounted", sql, params }),
48
+ allRawCounted: (...params) => request({ kind: "allRawCounted", sql, params })
49
+ };
99
50
  }
100
- function bindableParams(params) {
101
- let bound;
102
- for (let index = 0; index < params.length; index++) {
103
- const value = params[index];
104
- const replacement = isNamedParams(value) ? bindableNamedParams(value) : bindableValue(value);
105
- if (replacement === value) continue;
106
- bound ??= [...params];
107
- bound[index] = replacement;
108
- }
109
- return bound ?? params;
51
+ function workerHostConnection(request, countsRows, ownCalls) {
52
+ return {
53
+ exec: (sql) => request({ kind: "exec", sql }),
54
+ prepare: async (sql) => ({
55
+ all: (...params) => request({ kind: "all", sql, params }),
56
+ get: (...params) => request({ kind: "get", sql, params }),
57
+ run: (...params) => request({ kind: "run", sql, params }),
58
+ allRaw: (...params) => request({ kind: "allRaw", sql, params }),
59
+ ...countsRows ? countedCalls(request, sql) : {}
60
+ }),
61
+ runBatch: (sql, paramsBatch) => request({ kind: "runBatch", sql, paramsBatch }),
62
+ runBatchSummary: (sql, paramsBatch) => request({ kind: "runBatchSummary", sql, paramsBatch }),
63
+ runGroup: (units, opening) => request({
64
+ kind: "runGroup",
65
+ units: units.map((unit) => ({ statements: unit.statements.map(toWorkerGroupStatement) })),
66
+ ...opening === void 0 ? {} : { opening: toWorkerGroupStatement(opening) }
67
+ }),
68
+ copyRunsOffCallerThread: true,
69
+ copyDatabase: (copyRequest) => request(
70
+ {
71
+ kind: "copyDatabase",
72
+ destPath: copyRequest.destPath,
73
+ pagesPerStep: copyRequest.pagesPerStep,
74
+ ...copyRequest.stallTimeoutMs === void 0 ? {} : { stallTimeoutMs: copyRequest.stallTimeoutMs }
75
+ },
76
+ copyRequest.onStep
77
+ ),
78
+ startChangeCapture: async () => {
79
+ await request({ kind: "startChangeCapture" });
80
+ },
81
+ takeCapturedChanges: () => request({ kind: "takeCapturedChanges" }),
82
+ stopChangeCapture: async () => {
83
+ await request({ kind: "stopChangeCapture" });
84
+ },
85
+ ...ownCalls
86
+ };
110
87
  }
111
88
 
112
89
  // src/core/worker/late-replies.ts
@@ -233,11 +210,12 @@ var DEFAULT_WRITE_TIMEOUT_MS = 3e4;
233
210
  var DEFAULT_MAX_RESTARTS = 5;
234
211
  var CLOSE_GRACE_MS = 1e3;
235
212
  var WriterWorker = class _WriterWorker {
236
- constructor(openRequest, timeoutMs, maxRestarts, countsRows) {
213
+ constructor(openRequest, timeoutMs, maxRestarts, countsRows, encryptionKey) {
237
214
  this.openRequest = openRequest;
238
215
  this.timeoutMs = timeoutMs;
239
216
  this.maxRestarts = maxRestarts;
240
217
  this.countsRows = countsRows;
218
+ this.encryptionKey = encryptionKey;
241
219
  this.connection = this.makeConnection();
242
220
  }
243
221
  worker = null;
@@ -260,7 +238,8 @@ var WriterWorker = class _WriterWorker {
260
238
  { kind: "open", entry, path, options },
261
239
  workerOptions?.writeTimeoutMs ?? DEFAULT_WRITE_TIMEOUT_MS,
262
240
  workerOptions?.maxRestarts ?? DEFAULT_MAX_RESTARTS,
263
- workerOptions?.countsRows === true
241
+ workerOptions?.countsRows === true,
242
+ workerOptions?.encryptionKey
264
243
  );
265
244
  worker.spawn();
266
245
  try {
@@ -287,10 +266,23 @@ var WriterWorker = class _WriterWorker {
287
266
  this.fault(new SirannonError(`Writer worker exited with code ${code}`, "WRITER_WORKER_EXIT"));
288
267
  }
289
268
  });
290
- this.ready = this.send(this.openRequest).then(() => this.reloadExtensionsOntoRestartedConnection());
269
+ this.ready = this.openOnWorker().then(() => this.reloadExtensionsOntoRestartedConnection());
291
270
  this.ready.catch(() => {
292
271
  });
293
272
  }
273
+ async openOnWorker() {
274
+ if (!this.encryptionKey) {
275
+ await this.send(this.openRequest);
276
+ return;
277
+ }
278
+ const key = await this.encryptionKey();
279
+ try {
280
+ const options = { ...this.openRequest.options, encryption: { key } };
281
+ await this.send({ ...this.openRequest, options }, void 0, [key.buffer]);
282
+ } finally {
283
+ if (key.buffer.byteLength > 0) key.fill(0);
284
+ }
285
+ }
294
286
  async reloadExtensionsOntoRestartedConnection() {
295
287
  for (const path of this.loadedExtensions) {
296
288
  await this.send({ kind: "loadExtension", path });
@@ -413,7 +405,7 @@ var WriterWorker = class _WriterWorker {
413
405
  }
414
406
  this.spawn();
415
407
  }
416
- send(request, onStep) {
408
+ send(request, onStep, transfer = []) {
417
409
  const worker = this.worker;
418
410
  if (!worker) {
419
411
  return Promise.reject(
@@ -446,7 +438,7 @@ var WriterWorker = class _WriterWorker {
446
438
  ...onStep ? { onStep } : {}
447
439
  });
448
440
  try {
449
- worker.postMessage(message);
441
+ worker.postMessage(message, transfer);
450
442
  } catch (err) {
451
443
  this.pending.delete(id);
452
444
  if (timer) clearTimeout(timer);
@@ -468,70 +460,38 @@ var WriterWorker = class _WriterWorker {
468
460
  return value;
469
461
  });
470
462
  }
471
- countedCalls(sql) {
472
- return {
473
- allCounted: (...params) => this.request({ kind: "allCounted", sql, params }),
474
- getCounted: (...params) => this.request({ kind: "getCounted", sql, params }),
475
- allRawCounted: (...params) => this.request({ kind: "allRawCounted", sql, params })
476
- };
477
- }
478
463
  makeConnection() {
479
- const conn = {
480
- exec: (sql) => this.request({ kind: "exec", sql }),
481
- prepare: async (sql) => ({
482
- all: (...params) => this.request({ kind: "all", sql, params }),
483
- get: (...params) => this.request({ kind: "get", sql, params }),
484
- run: (...params) => this.request({ kind: "run", sql, params }),
485
- allRaw: (...params) => this.request({ kind: "allRaw", sql, params }),
486
- ...this.countsRows ? this.countedCalls(sql) : {}
487
- }),
488
- runBatch: (sql, paramsBatch) => this.request({ kind: "runBatch", sql, paramsBatch }),
489
- runBatchSummary: (sql, paramsBatch) => this.request({
490
- kind: "runBatchSummary",
491
- sql,
492
- paramsBatch
493
- }),
494
- runGroup: (units, opening) => this.request({
495
- kind: "runGroup",
496
- units: units.map((unit) => ({ statements: unit.statements.map(toWorkerGroupStatement) })),
497
- ...opening === void 0 ? {} : { opening: toWorkerGroupStatement(opening) }
498
- }),
499
- copyRunsOffCallerThread: true,
500
- copyDatabase: (request) => this.request(
501
- {
502
- kind: "copyDatabase",
503
- destPath: request.destPath,
504
- pagesPerStep: request.pagesPerStep,
505
- ...request.stallTimeoutMs === void 0 ? {} : { stallTimeoutMs: request.stallTimeoutMs }
464
+ const conn = workerHostConnection(
465
+ (request, onStep) => this.request(request, onStep),
466
+ this.countsRows,
467
+ {
468
+ loadExtension: async (extensionPath) => {
469
+ await this.request({ kind: "loadExtension", path: extensionPath });
470
+ if (!this.loadedExtensions.includes(extensionPath)) this.loadedExtensions.push(extensionPath);
506
471
  },
507
- request.onStep
508
- ),
509
- loadExtension: async (extensionPath) => {
510
- await this.request({ kind: "loadExtension", path: extensionPath });
511
- if (!this.loadedExtensions.includes(extensionPath)) this.loadedExtensions.push(extensionPath);
512
- },
513
- transaction: async (fn) => {
514
- this.transactionOpen = true;
515
- this.beginPending = true;
516
- try {
517
- const result = await fn(conn);
518
- if (!this.beginPending) await conn.exec("COMMIT");
519
- return result;
520
- } catch (err) {
472
+ transaction: async (fn) => {
473
+ this.transactionOpen = true;
474
+ this.beginPending = true;
521
475
  try {
522
- if (!this.beginPending) await conn.exec("ROLLBACK");
523
- } catch {
476
+ const result = await fn(conn);
477
+ if (!this.beginPending) await conn.exec("COMMIT");
478
+ return result;
479
+ } catch (err) {
480
+ try {
481
+ if (!this.beginPending) await conn.exec("ROLLBACK");
482
+ } catch {
483
+ }
484
+ throw err;
485
+ } finally {
486
+ this.transactionOpen = false;
487
+ this.beginPending = false;
488
+ this.beginCarrier = null;
489
+ this.lostTransaction = null;
524
490
  }
525
- throw err;
526
- } finally {
527
- this.transactionOpen = false;
528
- this.beginPending = false;
529
- this.beginCarrier = null;
530
- this.lostTransaction = null;
531
- }
532
- },
533
- close: () => this.close()
534
- };
491
+ },
492
+ close: () => this.close()
493
+ }
494
+ );
535
495
  return conn;
536
496
  }
537
497
  async close() {
@@ -561,14 +521,6 @@ var WriterWorker = class _WriterWorker {
561
521
  this.pending.clear();
562
522
  }
563
523
  };
564
- function asyncLocalWriterContext() {
565
- const held = new AsyncLocalStorage();
566
- return {
567
- run: (operation) => held.run(true, operation),
568
- isActive: () => held.getStore() === true,
569
- exit: (operation) => held.exit(operation)
570
- };
571
- }
572
524
 
573
525
  // src/core/backup/preferred-node.ts
574
526
  function preferredBackupNode(membership, preference) {
@@ -928,6 +880,10 @@ async function confirmChainStillListed(grip) {
928
880
  grip.report(lost);
929
881
  return false;
930
882
  }
883
+ function keyIdField(request) {
884
+ const keyId = request.keyId?.();
885
+ return keyId === void 0 ? {} : { keyId };
886
+ }
931
887
  async function startChain(request, chainName, namePrefix, previousChainId) {
932
888
  const chainId = randomHex(8);
933
889
  const startedAt = Date.now();
@@ -954,7 +910,8 @@ async function startChain(request, chainName, namePrefix, previousChainId) {
954
910
  pieceCount: report.pieceCount,
955
911
  pieceBytes: report.pieceBytes,
956
912
  bytesWritten: report.bytesWritten,
957
- ...report.fingerprint ? { fingerprint: report.fingerprint } : {}
913
+ ...report.fingerprint ? { fingerprint: report.fingerprint } : {},
914
+ ...keyIdField(request)
958
915
  };
959
916
  await appendChainRecord(request.destination, chainName, base, 0);
960
917
  const head = {
@@ -998,7 +955,8 @@ async function transferCapture(request, chainName, chainId, pending, recordIndex
998
955
  pieceBytes,
999
956
  bytesWritten: sent.bytesWritten,
1000
957
  checkpointed: pending.cursor.checkpointed,
1001
- ...sent.fingerprint ? { fingerprint: sent.fingerprint } : {}
958
+ ...sent.fingerprint ? { fingerprint: sent.fingerprint } : {},
959
+ ...keyIdField(request)
1002
960
  };
1003
961
  await appendChainRecord(request.destination, chainName, record, recordIndex);
1004
962
  return {
@@ -1286,6 +1244,13 @@ var BackupCycle = class {
1286
1244
  report: (err) => this.report(err)
1287
1245
  });
1288
1246
  }
1247
+ /** Drops the chain that this stopped cycle extended, so that the next cycle starts a new chain with a full copy. */
1248
+ forgetChain() {
1249
+ return this.runTurn(async () => {
1250
+ await this.discardState();
1251
+ await removeCycleState(this.stagingDir);
1252
+ });
1253
+ }
1289
1254
  async discardState() {
1290
1255
  const pending = this.state?.pending;
1291
1256
  if (pending) await rm(stagedCapturePath(this.stagingDir, pending.sequence), { force: true });
@@ -1466,4 +1431,4 @@ function nodeBackupEngine(streaming) {
1466
1431
  };
1467
1432
  }
1468
1433
 
1469
- export { WriterWorker, asyncLocalWriterContext, bindableParams, createStatementCache, loadThroughRuntime, narrowRowIntegers, narrowRowsIntegers, narrowSafeBigInt, nodeBackupEngine, nodeResolveExtensionPath, nodeStreamingSupport };
1434
+ export { WriterWorker, createStatementCache, nodeBackupEngine, nodeResolveExtensionPath, nodeStreamingSupport };
@@ -1,6 +1,6 @@
1
- import { IDENTIFIER_RE, selectCountTableRows, canonicaliseForChecksum, selectUserSchemaObjects, validateDdlSafety, selectUserTableNames, referencedTables } from './chunk-RPWBYASV.mjs';
1
+ import { IDENTIFIER_RE, selectCountTableRows, canonicaliseForChecksum, selectUserSchemaObjects, validateDdlSafety, selectUserTableNames, referencedTables } from './chunk-MTOQ6QIS.mjs';
2
2
  import { ReplicationError } from './chunk-BLMRNSUG.mjs';
3
- import { INTERNAL_TABLE_PREFIX } from './chunk-JFVEGN7J.mjs';
3
+ import { INTERNAL_TABLE_PREFIX } from './chunk-ZAUHAJ4G.mjs';
4
4
  import { createHash } from 'crypto';
5
5
 
6
6
  var DumpOps = class {
@@ -0,0 +1,51 @@
1
+ import { SirannonError } from './chunk-KINDFBQP.mjs';
2
+
3
+ // src/core/backup/start-guard.ts
4
+ async function startCopyWithoutHoldingWriter(runExclusive, start) {
5
+ let begin;
6
+ let release;
7
+ const writerHeld = new Promise((resolve) => {
8
+ begin = resolve;
9
+ });
10
+ const firstStepDone = new Promise((resolve) => {
11
+ release = resolve;
12
+ });
13
+ const run = (async () => {
14
+ await writerHeld;
15
+ return start(release);
16
+ })();
17
+ run.then(release, release);
18
+ await runExclusive(async () => {
19
+ begin();
20
+ await firstStepDone;
21
+ });
22
+ return run;
23
+ }
24
+
25
+ // src/core/backup/report.ts
26
+ var DEFAULT_PIECE_BYTES = 16 * 1024 * 1024;
27
+ function defaultDestinationName() {
28
+ return `backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.db`;
29
+ }
30
+ function assertPieceBytes(pieceBytes) {
31
+ if (!Number.isInteger(pieceBytes) || pieceBytes <= 0) {
32
+ throw new SirannonError(
33
+ `Piece size must be a positive whole number of bytes, and it was ${pieceBytes}`,
34
+ "BACKUP_ERROR"
35
+ );
36
+ }
37
+ }
38
+ async function readPageSize(conn) {
39
+ const stmt = await conn.prepare("PRAGMA page_size");
40
+ const row = await stmt.get();
41
+ const [pageSize] = row === void 0 ? [] : Object.values(row);
42
+ return pageSize === void 0 ? 0 : Number(pageSize);
43
+ }
44
+ async function readMainDatabasePath(conn) {
45
+ const stmt = await conn.prepare("PRAGMA database_list");
46
+ const rows = await stmt.all();
47
+ const main = rows.find((row) => row.name === "main");
48
+ return main?.file ?? "";
49
+ }
50
+
51
+ export { DEFAULT_PIECE_BYTES, assertPieceBytes, defaultDestinationName, readMainDatabasePath, readPageSize, startCopyWithoutHoldingWriter };
@@ -1,8 +1,9 @@
1
- import { planBackupRestore } from './chunk-YW7IKQ7T.mjs';
1
+ import { planBackupRestore, EncryptionKeyring } from './chunk-W74HFQFD.mjs';
2
2
  import { listStoredFilePieces, fetchStoredFile, checkpointLog } from './chunk-QGVRRKB2.mjs';
3
3
  import { reportQuietly, LOG_HEADER_BYTES, LOG_FRAME_HEADER_BYTES, readLogHeader, foldLogChecksum, MAGIC_WITH_LITTLE_ENDIAN_CONTENT, LOG_FORMAT_VERSION } from './chunk-BUWJH3D3.mjs';
4
- import { readBackupChains, DEFAULT_CHAIN_NAME } from './chunk-CZL4D3BM.mjs';
4
+ import { readBackupChains, DEFAULT_CHAIN_NAME } from './chunk-6CABU54O.mjs';
5
5
  import { destinationWithDeadline, DEFAULT_DESTINATION_TIMEOUT_MS, randomHex } from './chunk-BGJ5JVYL.mjs';
6
+ import { EncryptionError } from './chunk-5O4J6LQQ.mjs';
6
7
  import { SirannonError } from './chunk-KINDFBQP.mjs';
7
8
  import { rename, open, rm, stat } from 'fs/promises';
8
9
 
@@ -53,6 +54,7 @@ function assembleFromDestination(destination, report, destPath) {
53
54
  return assembleStoredFile(destination, { ...report, name: report.destinationName }, destPath);
54
55
  }
55
56
  var DATABASE_HEADER_BYTES = 100;
57
+ var PLAINTEXT_HEADER = "SQLite format 3\0";
56
58
  var PAGE_SIZE_OFFSET = 16;
57
59
  var PAGE_SIZE_FOR_65536 = 1;
58
60
  var SMALLEST_PAGE_BYTES = 512;
@@ -71,6 +73,7 @@ async function readDatabaseHeader(path) {
71
73
  if (bytesRead < DATABASE_HEADER_BYTES) {
72
74
  throw restoreError(`The full copy assembled into '${path}' is too short to be a SQLite database`);
73
75
  }
76
+ if (buffer.toString("latin1", 0, PLAINTEXT_HEADER.length) !== PLAINTEXT_HEADER) return null;
74
77
  const stated = buffer.readUInt16BE(PAGE_SIZE_OFFSET);
75
78
  const pageSize = stated === PAGE_SIZE_FOR_65536 ? LARGEST_PAGE_BYTES : stated;
76
79
  if (pageSize < SMALLEST_PAGE_BYTES || pageSize > LARGEST_PAGE_BYTES || (pageSize & pageSize - 1) !== 0) {
@@ -332,16 +335,16 @@ async function readPageCount(conn) {
332
335
  const row = await stmt.get();
333
336
  return row ? Number(row.page_count) : 0;
334
337
  }
335
- async function countDatabasePages(driver, destPath) {
336
- const conn = await driver.open(destPath, { walMode: false, walAutoCheckpoint: 0 });
338
+ async function countDatabasePages(openRebuilt, destPath) {
339
+ const conn = await openRebuilt(destPath);
337
340
  try {
338
341
  return await readPageCount(conn);
339
342
  } finally {
340
343
  await conn.close();
341
344
  }
342
345
  }
343
- async function foldLogIntoDatabase(driver, destPath, databasePages) {
344
- const conn = await driver.open(destPath, { walMode: false, walAutoCheckpoint: 0 });
346
+ async function foldLogIntoDatabase(openRebuilt, destPath, databasePages) {
347
+ const conn = await openRebuilt(destPath);
345
348
  try {
346
349
  const reached = await readPageCount(conn);
347
350
  if (reached !== databasePages) {
@@ -375,7 +378,7 @@ async function applyChangeBatch(request) {
375
378
  });
376
379
  }
377
380
  const written = await writer.finish();
378
- await foldLogIntoDatabase(request.driver, request.destPath, written.databasePages);
381
+ await foldLogIntoDatabase(request.openRebuilt, request.destPath, written.databasePages);
379
382
  return written.frameCount;
380
383
  } catch (err) {
381
384
  await writer.abandon();
@@ -385,6 +388,43 @@ async function applyChangeBatch(request) {
385
388
  }
386
389
  }
387
390
 
391
+ // src/core/backup/restore-keyed.ts
392
+ var KEY_FAILURE_CODES = /* @__PURE__ */ new Set(["ENCRYPTION_KEY_REJECTED", "ENCRYPTION_KEY_MISSING"]);
393
+ function rebuiltDatabaseOpener(driver, encryption) {
394
+ const keyring = new EncryptionKeyring(encryption?.key ?? null);
395
+ return (path) => keyring.openWith((keyed) => driver.open(path, { walMode: false, walAutoCheckpoint: 0, ...keyed }));
396
+ }
397
+ function explainKeyFailure(err, base) {
398
+ if (!(err instanceof EncryptionError) || !KEY_FAILURE_CODES.has(err.code)) return err;
399
+ const heldUnder = base.keyId === void 0 ? "a key that came with no identifier" : `the key '${base.keyId}'`;
400
+ return new EncryptionError(
401
+ `The key that the restore opened with failed on the full copy '${base.name}', which holds ciphertext under ${heldUnder}`,
402
+ err.code === "ENCRYPTION_KEY_MISSING" ? "ENCRYPTION_KEY_MISSING" : "ENCRYPTION_KEY_REJECTED",
403
+ err
404
+ );
405
+ }
406
+ async function readText(conn, sql) {
407
+ const row = await (await conn.prepare(sql)).get();
408
+ return row === void 0 ? void 0 : Object.values(row)[0];
409
+ }
410
+ async function readRebuiltHeader(path, openRebuilt, base) {
411
+ const plaintext = await readDatabaseHeader(path);
412
+ if (plaintext !== null) return plaintext;
413
+ let conn;
414
+ try {
415
+ conn = await openRebuilt(path);
416
+ } catch (err) {
417
+ throw explainKeyFailure(err, base);
418
+ }
419
+ try {
420
+ const pageSize = Number(await readText(conn, "PRAGMA page_size"));
421
+ const journalMode = await readText(conn, "PRAGMA journal_mode");
422
+ return { pageSize, walMode: typeof journalMode === "string" && journalMode.toLowerCase() === "wal" };
423
+ } finally {
424
+ await conn.close();
425
+ }
426
+ }
427
+
388
428
  // src/core/backup/restore-options.ts
389
429
  var DEFAULT_RESTORE_BATCH_SIZE = 16;
390
430
  var MAX_RESTORE_BATCH_SIZE = 4096;
@@ -410,12 +450,12 @@ async function removeDatabaseAndLog(path) {
410
450
  });
411
451
  await removeLogBeside(path);
412
452
  }
413
- async function foldLogIntoDatabase2(driver, path) {
453
+ async function foldLogIntoDatabase2(openRebuilt, path) {
414
454
  const existing = await stat(path).catch(() => void 0);
415
455
  if (!existing) return true;
416
456
  const log = await stat(`${path}-wal`).catch(() => void 0);
417
457
  if (!log || log.size === 0) return true;
418
- const conn = await driver.open(path, { walMode: false, walAutoCheckpoint: 0 }).catch(() => void 0);
458
+ const conn = await openRebuilt(path).catch(() => void 0);
419
459
  if (!conn) return false;
420
460
  const folded = await checkpointLog(conn).catch(() => void 0);
421
461
  await conn.close().catch(() => void 0);
@@ -452,6 +492,7 @@ async function restoreBackup(options) {
452
492
  await assertPathIsFree(destPath, options.replaceExisting === true);
453
493
  const buildPath = `${destPath}${BUILD_SUFFIX}`;
454
494
  await removeDatabaseAndLog(buildPath);
495
+ const openRebuilt = rebuiltDatabaseOpener(options.driver, options.encryption);
455
496
  let piecesFetched = 0;
456
497
  let bytesFetched = 0;
457
498
  let changesApplied = 0;
@@ -470,7 +511,7 @@ async function restoreBackup(options) {
470
511
  bytesFetched = bytes;
471
512
  reportProgress("full-copy");
472
513
  });
473
- const header = await readDatabaseHeader(buildPath);
514
+ const header = await readRebuiltHeader(buildPath, openRebuilt, plan.base);
474
515
  if (!header.walMode && plan.changes.length > 0) {
475
516
  throw new SirannonError(
476
517
  `The full copy assembled into '${buildPath}' records no write-ahead log, so SQLite would ignore the change pieces rather than replay them`,
@@ -482,7 +523,7 @@ async function restoreBackup(options) {
482
523
  logSequence++;
483
524
  framesApplied += await applyChangeBatch({
484
525
  destination,
485
- driver: options.driver,
526
+ openRebuilt,
486
527
  destPath: buildPath,
487
528
  pageSize: header.pageSize,
488
529
  logSequence,
@@ -496,8 +537,8 @@ async function restoreBackup(options) {
496
537
  changesApplied += batch.length;
497
538
  reportProgress("changes");
498
539
  }
499
- await countDatabasePages(options.driver, buildPath);
500
- const logFoldedIn = await foldLogIntoDatabase2(options.driver, destPath);
540
+ await countDatabasePages(openRebuilt, buildPath);
541
+ const logFoldedIn = await foldLogIntoDatabase2(openRebuilt, destPath);
501
542
  if (logFoldedIn) await removeLogBeside(destPath);
502
543
  else await removeDatabaseAndLog(destPath);
503
544
  await rename(buildPath, destPath);
@@ -1,5 +1,5 @@
1
1
  import { ReplicationError, BatchValidationError } from './chunk-BLMRNSUG.mjs';
2
- import { CHANGES_TABLE, META_TABLE, MIGRATIONS_TABLE, REPLACED_ROWS_TABLE, APPLIED_CHANGES_TABLE, STAGED_PUSH_CHANGES_TABLE, COLUMN_VERSIONS_TABLE, DEVICE_CURSORS_TABLE, PEER_STATE_TABLE, SYNC_STATE_TABLE, STAGED_CHANGES_TABLE, isReservedIdentifier } from './chunk-JFVEGN7J.mjs';
2
+ import { CHANGES_TABLE, META_TABLE, MIGRATIONS_TABLE, REPLACED_ROWS_TABLE, APPLIED_CHANGES_TABLE, STAGED_PUSH_CHANGES_TABLE, COLUMN_VERSIONS_TABLE, DEVICE_CURSORS_TABLE, PEER_STATE_TABLE, SYNC_STATE_TABLE, STAGED_CHANGES_TABLE, isReservedIdentifier } from './chunk-ZAUHAJ4G.mjs';
3
3
  import { SirannonError } from './chunk-KINDFBQP.mjs';
4
4
 
5
5
  // src/core/sync/hlc.ts
@@ -0,0 +1,60 @@
1
+ // src/core/driver/values.ts
2
+ var SAFE_INT_BOUND = 9007199254740991n;
3
+ function narrowSafeBigInt(value) {
4
+ if (typeof value === "bigint" && value >= -SAFE_INT_BOUND && value <= SAFE_INT_BOUND) {
5
+ return Number(value);
6
+ }
7
+ return value;
8
+ }
9
+ function narrowRowIntegers(row) {
10
+ if (row === null || typeof row !== "object") return row;
11
+ const obj = row;
12
+ for (const key of Object.keys(obj)) {
13
+ const value = obj[key];
14
+ if (typeof value === "bigint" && value >= -SAFE_INT_BOUND && value <= SAFE_INT_BOUND) {
15
+ obj[key] = Number(value);
16
+ }
17
+ }
18
+ return row;
19
+ }
20
+ function narrowRowsIntegers(rows) {
21
+ for (const row of rows) {
22
+ narrowRowIntegers(row);
23
+ }
24
+ return rows;
25
+ }
26
+ var EMPTY_BLOB_WITH_ALLOCATED_STORAGE = new Uint8Array(new ArrayBuffer(1), 0, 0);
27
+ function bindableValue(value) {
28
+ if (typeof value === "boolean") return value ? 1n : 0n;
29
+ if (ArrayBuffer.isView(value) && value.byteLength === 0) return EMPTY_BLOB_WITH_ALLOCATED_STORAGE;
30
+ return value;
31
+ }
32
+ function isNamedParams(value) {
33
+ if (value === null || typeof value !== "object") return false;
34
+ const prototype = Object.getPrototypeOf(value);
35
+ return prototype === Object.prototype || prototype === null;
36
+ }
37
+ function bindableNamedParams(params) {
38
+ let bound;
39
+ for (const key of Object.keys(params)) {
40
+ const value = params[key];
41
+ const replacement = bindableValue(value);
42
+ if (replacement === value) continue;
43
+ bound ??= { ...params };
44
+ bound[key] = replacement;
45
+ }
46
+ return bound ?? params;
47
+ }
48
+ function bindableParams(params) {
49
+ let bound;
50
+ for (let index = 0; index < params.length; index++) {
51
+ const value = params[index];
52
+ const replacement = isNamedParams(value) ? bindableNamedParams(value) : bindableValue(value);
53
+ if (replacement === value) continue;
54
+ bound ??= [...params];
55
+ bound[index] = replacement;
56
+ }
57
+ return bound ?? params;
58
+ }
59
+
60
+ export { bindableParams, isNamedParams, narrowRowIntegers, narrowRowsIntegers, narrowSafeBigInt };