@delali/sirannon-db 0.2.3-next.38 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/backup/index.d.ts +5 -5
  2. package/dist/backup/index.mjs +4 -4
  3. package/dist/backup-scheduler/index.d.ts +58 -8
  4. package/dist/backup-scheduler/index.mjs +3 -2
  5. package/dist/{change-tracker-BLKRX8ic.d.ts → change-tracker-uG9gWvsY.d.ts} +1 -1
  6. package/dist/chunk-2QCPVE67.mjs +143 -0
  7. package/dist/{chunk-P53I2G6J.mjs → chunk-63VBFSCS.mjs} +2 -2
  8. package/dist/{chunk-CR6HTXSC.mjs → chunk-7SC2TZAS.mjs} +1 -1
  9. package/dist/chunk-DVS4QSIZ.mjs +124 -0
  10. package/dist/{chunk-IPDHHIG2.mjs → chunk-DXXCLTFF.mjs} +205 -45
  11. package/dist/{chunk-G7AXGAEA.mjs → chunk-OJQHM4MT.mjs} +202 -100
  12. package/dist/{chunk-KSYFFYAP.mjs → chunk-U445A3C4.mjs} +1 -70
  13. package/dist/{chunk-GJ6SX53Z.mjs → chunk-W77EEVOT.mjs} +4 -120
  14. package/dist/client/index.d.ts +8 -8
  15. package/dist/client/topology.d.ts +4 -4
  16. package/dist/{client-base-DldBgJWS.d.ts → client-base-C-B8u5Lm.d.ts} +2 -2
  17. package/dist/core/index.d.ts +9 -9
  18. package/dist/core/index.mjs +14 -5
  19. package/dist/{database-DSbJl-k5.d.ts → database-BlhUdJAO.d.ts} +7 -6
  20. package/dist/{database-backup-CliAhaEm.d.ts → database-backup-C090lMHc.d.ts} +2 -2
  21. package/dist/driver/better-sqlite3.d.ts +1 -1
  22. package/dist/driver/better-sqlite3.mjs +7 -6
  23. package/dist/driver/bun.d.ts +1 -1
  24. package/dist/driver/expo.d.ts +1 -1
  25. package/dist/driver/node.d.ts +1 -1
  26. package/dist/driver/node.mjs +7 -6
  27. package/dist/driver/wa-sqlite.d.ts +1 -1
  28. package/dist/file-migrations/index.d.ts +1 -1
  29. package/dist/{protocol-BK0Yxu5P.d.ts → protocol-QekTGoKn.d.ts} +1 -1
  30. package/dist/replication/index.d.ts +7 -7
  31. package/dist/{restore-options-O5gYlr3n.d.ts → restore-options-C9MW9gpm.d.ts} +1 -1
  32. package/dist/server/index.d.ts +9 -9
  33. package/dist/server/index.mjs +5 -4
  34. package/dist/{server-options-CFgBQDLz.d.ts → server-options-DeK6ZG8u.d.ts} +1 -1
  35. package/dist/{sirannon-Bo0PX2Cj.d.ts → sirannon-C9cTgFrw.d.ts} +2 -2
  36. package/dist/transport/grpc.d.ts +3 -3
  37. package/dist/transport/memory.d.ts +3 -3
  38. package/dist/{types-CL8uxXIV.d.ts → types-C1yBGTdx.d.ts} +2 -2
  39. package/dist/{types-CopTgHG3.d.ts → types-DApb7dvw.d.ts} +151 -5
  40. package/package.json +8 -7
  41. package/dist/chunk-GBM4AU24.mjs +0 -42
@@ -1,10 +1,11 @@
1
- import { verifyBackupRecord } from './chunk-CR6HTXSC.mjs';
2
- import { BackupManager, BackupScheduler, DEFAULT_PIECE_BYTES, sendFileInPieces } from './chunk-IPDHHIG2.mjs';
1
+ import { verifyBackupRecord } from './chunk-7SC2TZAS.mjs';
2
+ import { BackupManager, BackupScheduler, DEFAULT_PIECE_BYTES, sendFileInPieces } from './chunk-DXXCLTFF.mjs';
3
3
  import { DEFAULT_BACKUP_NAME_PREFIX, defaultStagingDir, DEFAULT_CAPTURE_INTERVAL_MS, DEFAULT_FULL_COPY_INTERVAL_MS } from './chunk-LXCGPZPA.mjs';
4
4
  import { WORKER_CANCELLED_CODE, deserializeError } from './chunk-YLAVDZCS.mjs';
5
- import { readCycleState, writeCycleState, removeCycleState, stagedCapturePath, captureAndCheckpointTurn } from './chunk-GJ6SX53Z.mjs';
6
- import { checkpointLog } from './chunk-KSYFFYAP.mjs';
7
- import { destinationWithDeadline, DEFAULT_DESTINATION_TIMEOUT_MS } from './chunk-GBM4AU24.mjs';
5
+ import { readCycleState, writeCycleState, removeCycleState, stagedCapturePath, captureAndCheckpointTurn } from './chunk-W77EEVOT.mjs';
6
+ import { checkpointLog } from './chunk-U445A3C4.mjs';
7
+ import { logPathFor } from './chunk-2QCPVE67.mjs';
8
+ import { destinationWithDeadline, DEFAULT_DESTINATION_TIMEOUT_MS } from './chunk-DVS4QSIZ.mjs';
8
9
  import { DEFAULT_CHAIN_NAME, readBackupChains, appendChainRecord, appendChainHead, readChainHeadAt, readChainHeads } from './chunk-YVF2ZJTE.mjs';
9
10
  import { randomHex } from './chunk-V5FUJEYY.mjs';
10
11
  import { SirannonError, ExtensionError } from './chunk-PBRXXISQ.mjs';
@@ -407,15 +408,6 @@ function clearPendingTimers(entry) {
407
408
  if (entry.graceTimer) clearTimeout(entry.graceTimer);
408
409
  }
409
410
 
410
- // src/core/backup/cycle-callbacks.ts
411
- function reportQuietly(callback, value) {
412
- if (!callback) return;
413
- try {
414
- callback(value);
415
- } catch {
416
- }
417
- }
418
-
419
411
  // src/core/backup/preferred-node.ts
420
412
  function preferredBackupNode(membership, preference) {
421
413
  if (typeof preference !== "string") {
@@ -552,6 +544,162 @@ async function chainLostFromList(request, chainName, state) {
552
544
  return chainMissingFromList(request.destination, chainName, state.chainId, request.databaseId, state.headIndex);
553
545
  }
554
546
 
547
+ // src/core/backup/cycle-callbacks.ts
548
+ function reportQuietly(callback, value) {
549
+ if (!callback) return;
550
+ try {
551
+ callback(value);
552
+ } catch {
553
+ }
554
+ }
555
+
556
+ // src/core/backup/cycle-status.ts
557
+ var BackupCycleStatusRecorder = class {
558
+ turnRunning = false;
559
+ turnStartedAt = 0;
560
+ latestProgress;
561
+ latestRun;
562
+ latestSkip;
563
+ latestError;
564
+ /** Marks a turn as under way and clears the progress the previous turn recorded. */
565
+ turnStarted() {
566
+ this.turnRunning = true;
567
+ this.turnStartedAt = Date.now();
568
+ this.latestProgress = void 0;
569
+ }
570
+ /** Marks the turn as finished. A caller then reads the outcome from the last run, skip, or failure. */
571
+ turnFinished() {
572
+ this.turnRunning = false;
573
+ this.turnStartedAt = 0;
574
+ this.latestProgress = void 0;
575
+ }
576
+ /** Records how far the turn under way has got. */
577
+ progressed(progress) {
578
+ this.latestProgress = progress;
579
+ }
580
+ /** Records what a finished turn wrote. */
581
+ ran(report) {
582
+ this.latestRun = report;
583
+ }
584
+ /** Records a turn the cycle passed over. */
585
+ skipped(skip) {
586
+ this.latestSkip = skip;
587
+ }
588
+ /**
589
+ * Records a failure, under the code a caller matches on, along with how far
590
+ * the run had got and which chain it was extending.
591
+ *
592
+ * @param err - What the turn failed with.
593
+ * @param chainId - The chain the cycle was extending, where it held one.
594
+ */
595
+ failed(err, chainId) {
596
+ const at = Date.now();
597
+ this.latestError = {
598
+ code: err instanceof SirannonError ? err.code : "BACKUP_ERROR",
599
+ message: err.message,
600
+ at,
601
+ durationMs: this.turnStartedAt === 0 ? 0 : at - this.turnStartedAt,
602
+ ...chainId === void 0 ? {} : { chainId },
603
+ ...this.latestProgress === void 0 ? {} : { progress: this.latestProgress }
604
+ };
605
+ }
606
+ /**
607
+ * Reads everything recorded so far.
608
+ *
609
+ * @param chainId - The chain the cycle is extending, where it has one.
610
+ * @returns What the cycle is doing, and what its recent turns produced.
611
+ */
612
+ read(chainId) {
613
+ return {
614
+ running: this.turnRunning,
615
+ ...chainId === void 0 ? {} : { chainId },
616
+ ...this.latestProgress === void 0 ? {} : { progress: this.latestProgress },
617
+ ...this.latestRun === void 0 ? {} : { lastRun: this.latestRun },
618
+ ...this.latestSkip === void 0 ? {} : { lastSkip: this.latestSkip },
619
+ ...this.latestError === void 0 ? {} : { lastError: this.latestError }
620
+ };
621
+ }
622
+ };
623
+
624
+ // src/core/backup/cycle-reporting.ts
625
+ var BackupTurnLog = class {
626
+ constructor(audience, logPath) {
627
+ this.audience = audience;
628
+ this.logPath = logPath;
629
+ }
630
+ record = new BackupCycleStatusRecorder();
631
+ announced = /* @__PURE__ */ new Set();
632
+ /**
633
+ * Records a finished run and passes it to the caller.
634
+ *
635
+ * @param report - What that run produced.
636
+ */
637
+ ran(report) {
638
+ this.record.ran(report);
639
+ reportQuietly(this.audience.onRun, report);
640
+ }
641
+ /**
642
+ * Records how far the run under way has reached and passes that to the caller.
643
+ *
644
+ * @param progress - The counters that run has reached.
645
+ */
646
+ progressed(progress) {
647
+ this.record.progressed(progress);
648
+ reportQuietly(this.audience.onProgress, progress);
649
+ }
650
+ /** Marks a turn as under way, and forgets whatever failures the previous turn announced. */
651
+ turnStarted() {
652
+ this.announced.clear();
653
+ this.record.turnStarted();
654
+ }
655
+ /** Marks the turn as finished. */
656
+ turnFinished() {
657
+ this.record.turnFinished();
658
+ }
659
+ /**
660
+ * Records a failure as the turn's outcome and passes it to the caller.
661
+ *
662
+ * @param err - What the turn failed with.
663
+ * @param chainId - The chain to record it against, where the cycle holds one.
664
+ */
665
+ failed(err, chainId) {
666
+ this.announced.add(err);
667
+ const failure = toBackupError(err);
668
+ this.record.failed(failure, chainId);
669
+ reportQuietly(this.audience.onError, failure);
670
+ }
671
+ /**
672
+ * Answers whether this turn has already passed one failure to the caller.
673
+ *
674
+ * @param err - The failure to look for.
675
+ * @returns Whether the caller has already heard about that failure.
676
+ */
677
+ hasAnnounced(err) {
678
+ return this.announced.has(err);
679
+ }
680
+ /**
681
+ * Records a turn the cycle passed over and passes it to the caller, stating
682
+ * how much log this node was still holding as it skipped.
683
+ *
684
+ * @param skip - Why the cycle passed the turn over.
685
+ */
686
+ async skipped(skip) {
687
+ const held = await uncapturedLogBytes(this.logPath);
688
+ const passed = held === void 0 ? skip : { ...skip, uncapturedLogBytes: held };
689
+ this.record.skipped(passed);
690
+ reportQuietly(this.audience.onSkip, passed);
691
+ }
692
+ /**
693
+ * Reads everything recorded so far.
694
+ *
695
+ * @param chainId - The chain the cycle is extending, where it has one.
696
+ * @returns What the cycle is doing, and what its recent turns produced.
697
+ */
698
+ read(chainId) {
699
+ return this.record.read(chainId);
700
+ }
701
+ };
702
+
555
703
  // src/core/backup/cycle-scheduling.ts
556
704
  var BackupCycleTimer = class {
557
705
  handle = null;
@@ -630,61 +778,6 @@ async function confirmChainStillListed(grip) {
630
778
  grip.report(lost);
631
779
  return false;
632
780
  }
633
-
634
- // src/core/backup/cycle-status.ts
635
- var BackupCycleStatusRecorder = class {
636
- turnRunning = false;
637
- latestProgress;
638
- latestRun;
639
- latestSkip;
640
- latestError;
641
- /** Marks a turn as under way and clears the progress the previous turn recorded. */
642
- turnStarted() {
643
- this.turnRunning = true;
644
- this.latestProgress = void 0;
645
- }
646
- /** Marks the turn as finished. A caller then reads the outcome from the last run, skip, or failure. */
647
- turnFinished() {
648
- this.turnRunning = false;
649
- this.latestProgress = void 0;
650
- }
651
- /** Records how far the turn under way has got. */
652
- progressed(progress) {
653
- this.latestProgress = progress;
654
- }
655
- /** Records what a finished turn wrote. */
656
- ran(report) {
657
- this.latestRun = report;
658
- }
659
- /** Records a turn the cycle passed over. */
660
- skipped(skip) {
661
- this.latestSkip = skip;
662
- }
663
- /** Records a failure, under the code a caller matches on. */
664
- failed(err) {
665
- this.latestError = {
666
- code: err instanceof SirannonError ? err.code : "BACKUP_ERROR",
667
- message: err.message,
668
- at: Date.now()
669
- };
670
- }
671
- /**
672
- * Reads everything recorded so far.
673
- *
674
- * @param chainId - The chain the cycle is extending, where it has one.
675
- * @returns What the cycle is doing, and what its recent turns produced.
676
- */
677
- read(chainId) {
678
- return {
679
- running: this.turnRunning,
680
- ...chainId === void 0 ? {} : { chainId },
681
- ...this.latestProgress === void 0 ? {} : { progress: this.latestProgress },
682
- ...this.latestRun === void 0 ? {} : { lastRun: this.latestRun },
683
- ...this.latestSkip === void 0 ? {} : { lastSkip: this.latestSkip },
684
- ...this.latestError === void 0 ? {} : { lastError: this.latestError }
685
- };
686
- }
687
- };
688
781
  async function startChain(request, chainName, namePrefix, previousChainId) {
689
782
  const chainId = randomHex(8);
690
783
  const startedAt = Date.now();
@@ -836,24 +929,19 @@ var BackupCycle = class {
836
929
  sendRefused = false;
837
930
  stopped = false;
838
931
  request;
839
- statusRecord = new BackupCycleStatusRecorder();
932
+ turnLog;
840
933
  /** @internal */
841
934
  constructor(request) {
935
+ this.logPath = logPathFor(request.sourcePath);
936
+ this.turnLog = new BackupTurnLog(request, this.logPath);
842
937
  this.request = {
843
938
  ...request,
844
- onRun: (report) => {
845
- this.statusRecord.ran(report);
846
- reportQuietly(request.onRun, report);
847
- },
848
- onProgress: (progress) => {
849
- this.statusRecord.progressed(progress);
850
- reportQuietly(request.onProgress, progress);
851
- }
939
+ onRun: (report) => this.turnLog.ran(report),
940
+ onProgress: (progress) => this.turnLog.progressed(progress)
852
941
  };
853
942
  this.chainName = request.chainName ?? DEFAULT_CHAIN_NAME;
854
943
  this.namePrefix = request.namePrefix ?? DEFAULT_BACKUP_NAME_PREFIX;
855
944
  this.stagingDir = request.stagingDir ?? defaultStagingDir(request.sourcePath);
856
- this.logPath = `${request.sourcePath}-wal`;
857
945
  this.intervalMs = request.intervalMs ?? DEFAULT_CAPTURE_INTERVAL_MS;
858
946
  this.fullCopyIntervalMs = request.fullCopyIntervalMs ?? DEFAULT_FULL_COPY_INTERVAL_MS;
859
947
  this.preferredNode = request.preferredNode ?? "replica";
@@ -945,7 +1033,7 @@ var BackupCycle = class {
945
1033
  * @returns Whether a turn is under way, how far it has got, and the last run, skip, and failure.
946
1034
  */
947
1035
  status() {
948
- return this.statusRecord.read(this.state?.chainId);
1036
+ return this.turnLog.read(this.state?.chainId);
949
1037
  }
950
1038
  /**
951
1039
  * Reads one of this chain's backups back out of the destination and compares
@@ -957,21 +1045,29 @@ var BackupCycle = class {
957
1045
  async verify(name) {
958
1046
  return verifyBackupRecord(this.request.destination, await this.chains(), name);
959
1047
  }
1048
+ /**
1049
+ * Runs one turn and passes whatever broke it to the operator. A deeper step
1050
+ * announces some failures itself and then raises them, so this announces a
1051
+ * failure only where no step has announced it already. The operator therefore
1052
+ * hears each failure exactly once. Sirannon empties a log the turn has
1053
+ * outgrown once all of that is done, and the report of writes reaching no
1054
+ * backup replaces whatever failure the turn recorded.
1055
+ */
960
1056
  runTurn(op) {
961
1057
  return this.turns.run(async () => {
962
1058
  this.busy = true;
963
1059
  this.captured = false;
964
1060
  this.sendRefused = false;
965
- this.statusRecord.turnStarted();
1061
+ this.turnLog.turnStarted();
966
1062
  try {
967
1063
  return await op();
968
1064
  } catch (err) {
969
- this.statusRecord.failed(toBackupError(err));
1065
+ if (!this.turnLog.hasAnnounced(err)) this.report(err);
970
1066
  throw err;
971
1067
  } finally {
972
1068
  if (!this.captured) await this.releaseLogPastLimit();
973
1069
  this.busy = false;
974
- this.statusRecord.turnFinished();
1070
+ this.turnLog.turnFinished();
975
1071
  }
976
1072
  });
977
1073
  }
@@ -1018,13 +1114,9 @@ var BackupCycle = class {
1018
1114
  }
1019
1115
  return false;
1020
1116
  }
1021
- /** Passes a skipped turn on, counting the log this node was holding as it skipped. */
1117
+ /** Passes a skipped turn on, where the cycle has a reason to give for it. */
1022
1118
  async reportSkip(skip) {
1023
- if (!skip) return;
1024
- const held = await uncapturedLogBytes(this.logPath);
1025
- const passed = held === void 0 ? skip : { ...skip, uncapturedLogBytes: held };
1026
- this.statusRecord.skipped(passed);
1027
- reportQuietly(this.request.onSkip, passed);
1119
+ if (skip) await this.turnLog.skipped(skip);
1028
1120
  }
1029
1121
  async tick() {
1030
1122
  if (this.stopped) return;
@@ -1032,11 +1124,8 @@ var BackupCycle = class {
1032
1124
  await this.reportSkip(previousRunStillActive());
1033
1125
  return;
1034
1126
  }
1035
- try {
1036
- await this.runOnce();
1037
- } catch (err) {
1038
- this.report(err);
1039
- }
1127
+ await this.runOnce().catch(() => {
1128
+ });
1040
1129
  }
1041
1130
  async verifyChain() {
1042
1131
  this.verified = await confirmChainStillListed({
@@ -1057,14 +1146,19 @@ var BackupCycle = class {
1057
1146
  * Runs one turn. A log that restarted before the capture reached it leaves a
1058
1147
  * chain nothing can extend, so this starts a fresh one. The caller still gets
1059
1148
  * the error: those writes are in no backup, and an operator has to hear that.
1149
+ * Where the replacement chain also fails to start, Sirannon records that
1150
+ * second failure against the chain the restart broke, because the cycle holds
1151
+ * no chain of its own by then and an operator reading the status has to know
1152
+ * which one they have lost.
1060
1153
  */
1061
1154
  async turnOrStartOver() {
1062
1155
  try {
1063
1156
  return await this.turn();
1064
1157
  } catch (err) {
1065
1158
  if (startsAFreshChain(err)) {
1066
- this.report(err);
1067
- await this.replaceChain().catch((chainErr) => this.report(chainErr));
1159
+ const broken = this.state?.chainId;
1160
+ this.reportAgainstChain(err, broken);
1161
+ await this.replaceChain().catch((chainErr) => this.reportAgainstChain(chainErr, broken));
1068
1162
  }
1069
1163
  throw err;
1070
1164
  }
@@ -1082,10 +1176,18 @@ var BackupCycle = class {
1082
1176
  last = await this.sendWaitingCapture() ?? last;
1083
1177
  return last;
1084
1178
  }
1179
+ /**
1180
+ * Passes a failure to the operator and records it as the turn's outcome.
1181
+ *
1182
+ * @param err - What the turn failed with.
1183
+ * @param chainId - The chain to record it against, which defaults to the one the cycle holds now.
1184
+ */
1085
1185
  report(err) {
1086
- const failure = toBackupError(err);
1087
- this.statusRecord.failed(failure);
1088
- reportQuietly(this.request.onError, failure);
1186
+ this.turnLog.failed(err, this.state?.chainId);
1187
+ }
1188
+ /** Records a failure against a chain the cycle may already have let go of. */
1189
+ reportAgainstChain(err, chainId) {
1190
+ this.turnLog.failed(err, chainId);
1089
1191
  }
1090
1192
  async replaceChain() {
1091
1193
  const previousChainId = this.state?.chainId;
@@ -1220,7 +1322,7 @@ function nodeBackupEngine(streaming) {
1220
1322
  copyToDestination: (conn, request) => manager.copyToDestination(conn, request),
1221
1323
  streamsToDestination: () => manager.streamsToDestination(),
1222
1324
  createCycle: (request) => createBackupCycle(request),
1223
- schedule: (conn, options, runExclusive) => scheduler.schedule(conn, options, runExclusive)
1325
+ schedule: (conn, request) => scheduler.schedule(conn, request)
1224
1326
  };
1225
1327
  }
1226
1328
 
@@ -85,73 +85,4 @@ async function fetchStoredFile(destination, file, pieces, take) {
85
85
  return { bytesFetched, pieceCount: pieces.length, ...fingerprint === void 0 ? {} : { fingerprint } };
86
86
  }
87
87
 
88
- // src/core/backup/wal-format.ts
89
- var MAGIC_WITH_LITTLE_ENDIAN_CONTENT = 931071618;
90
- var MAGIC_WITH_BIG_ENDIAN_CONTENT = 931071619;
91
- var LOG_FORMAT_VERSION = 3007e3;
92
- var SMALLEST_PAGE_BYTES = 512;
93
- var LARGEST_PAGE_BYTES = 65536;
94
- var LOG_HEADER_BYTES = 32;
95
- var LOG_FRAME_HEADER_BYTES = 24;
96
- function foldLogChecksum(view, offset, byteLength, bigEndianContent, seed) {
97
- const littleEndian = !bigEndianContent;
98
- const end = offset + byteLength;
99
- let first = seed.first;
100
- let second = seed.second;
101
- for (let at = offset; at < end; at += 8) {
102
- first = first + view.getUint32(at, littleEndian) + second >>> 0;
103
- second = second + view.getUint32(at + 4, littleEndian) + first >>> 0;
104
- }
105
- return { first, second };
106
- }
107
- function readLogHeader(bytes) {
108
- if (bytes.byteLength < LOG_HEADER_BYTES) return void 0;
109
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
110
- const magic = view.getUint32(0, false);
111
- if (magic !== MAGIC_WITH_LITTLE_ENDIAN_CONTENT && magic !== MAGIC_WITH_BIG_ENDIAN_CONTENT) return void 0;
112
- const bigEndianContent = magic === MAGIC_WITH_BIG_ENDIAN_CONTENT;
113
- const pageSize = view.getUint32(8, false);
114
- if (pageSize < SMALLEST_PAGE_BYTES || pageSize > LARGEST_PAGE_BYTES || (pageSize & pageSize - 1) !== 0) {
115
- return void 0;
116
- }
117
- const stored = { first: view.getUint32(24, false), second: view.getUint32(28, false) };
118
- const computed = foldLogChecksum(view, 0, 24, bigEndianContent, { first: 0, second: 0 });
119
- if (computed.first !== stored.first || computed.second !== stored.second) return void 0;
120
- return {
121
- pageSize,
122
- frameBytes: LOG_FRAME_HEADER_BYTES + pageSize,
123
- logSequence: view.getUint32(12, false),
124
- salt1: view.getUint32(16, false),
125
- salt2: view.getUint32(20, false),
126
- checksum: stored,
127
- bigEndianContent
128
- };
129
- }
130
- function readLogFrameHeader(view, offset) {
131
- return {
132
- pageNumber: view.getUint32(offset, false),
133
- databasePages: view.getUint32(offset + 4, false),
134
- salt1: view.getUint32(offset + 8, false),
135
- salt2: view.getUint32(offset + 12, false),
136
- checksum: { first: view.getUint32(offset + 16, false), second: view.getUint32(offset + 20, false) }
137
- };
138
- }
139
- function readValidLogFrame(view, offset, header, seed) {
140
- const frame = readLogFrameHeader(view, offset);
141
- if (frame.salt1 !== header.salt1 || frame.salt2 !== header.salt2) return void 0;
142
- const overHeader = foldLogChecksum(view, offset, 8, header.bigEndianContent, seed);
143
- const checksum = foldLogChecksum(
144
- view,
145
- offset + LOG_FRAME_HEADER_BYTES,
146
- header.pageSize,
147
- header.bigEndianContent,
148
- overHeader
149
- );
150
- if (checksum.first !== frame.checksum.first || checksum.second !== frame.checksum.second) return void 0;
151
- return { frame, checksum };
152
- }
153
- function logFrameOffset(frameNumber, frameBytes) {
154
- return LOG_HEADER_BYTES + (frameNumber - 1) * frameBytes;
155
- }
156
-
157
- export { LOG_FORMAT_VERSION, LOG_FRAME_HEADER_BYTES, LOG_HEADER_BYTES, MAGIC_WITH_LITTLE_ENDIAN_CONTENT, checkpointLog, fetchStoredFile, foldLogChecksum, listStoredFilePieces, logFrameOffset, readLogHeader, readValidLogFrame };
88
+ export { checkpointLog, fetchStoredFile, listStoredFilePieces };
@@ -1,8 +1,10 @@
1
- import { checkpointLog, logFrameOffset, LOG_HEADER_BYTES, readLogHeader, readValidLogFrame } from './chunk-KSYFFYAP.mjs';
1
+ import { checkpointLog } from './chunk-U445A3C4.mjs';
2
+ import { readLogFileHeader, sameLog, cursorChecksum, scanLogFrames, copyLogRange } from './chunk-2QCPVE67.mjs';
3
+ import { logFrameOffset } from './chunk-DVS4QSIZ.mjs';
2
4
  import { isBackupChainPosition } from './chunk-YVF2ZJTE.mjs';
3
5
  import { randomHex } from './chunk-V5FUJEYY.mjs';
4
6
  import { SirannonError } from './chunk-PBRXXISQ.mjs';
5
- import { readFile, mkdir, writeFile, rename, rm, readdir, open } from 'fs/promises';
7
+ import { readFile, mkdir, writeFile, rename, rm, readdir } from 'fs/promises';
6
8
  import { dirname, join } from 'path';
7
9
 
8
10
  var STATE_FILE_NAME = "cycle.json";
@@ -43,124 +45,6 @@ async function writeCycleState(stagingDir, state) {
43
45
  async function removeCycleState(stagingDir) {
44
46
  await rm(cycleStatePath(stagingDir), { force: true });
45
47
  }
46
- var READ_CHUNK_BYTES = 4 * 1024 * 1024;
47
- async function openForReading(path) {
48
- try {
49
- return await open(path, "r");
50
- } catch (err) {
51
- if (err.code === "ENOENT") return void 0;
52
- throw err;
53
- }
54
- }
55
- async function readLogFileHeader(logPath) {
56
- const file = await openForReading(logPath);
57
- if (!file) return void 0;
58
- try {
59
- const buffer = Buffer.allocUnsafe(LOG_HEADER_BYTES);
60
- const { bytesRead } = await file.read(buffer, 0, LOG_HEADER_BYTES, 0);
61
- if (bytesRead < LOG_HEADER_BYTES) return void 0;
62
- return readLogHeader(buffer);
63
- } finally {
64
- await file.close();
65
- }
66
- }
67
- async function scanLogFrames(logPath, header, from) {
68
- const stopped = {
69
- lastCommitFrame: from.frame,
70
- endOffset: logFrameOffset(from.frame + 1, header.frameBytes),
71
- checksum: from.checksum
72
- };
73
- const file = await openForReading(logPath);
74
- if (!file) return stopped;
75
- try {
76
- const { size } = await file.stat();
77
- const framesInFile = Math.max(0, Math.floor((size - LOG_HEADER_BYTES) / header.frameBytes));
78
- const framesPerChunk = Math.max(1, Math.floor(READ_CHUNK_BYTES / header.frameBytes));
79
- const buffer = Buffer.allocUnsafe(framesPerChunk * header.frameBytes);
80
- const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
81
- let running = from.checksum;
82
- let found = stopped;
83
- let frame = from.frame + 1;
84
- while (frame <= framesInFile) {
85
- const frames = Math.min(framesPerChunk, framesInFile - frame + 1);
86
- const wanted = frames * header.frameBytes;
87
- let filled = 0;
88
- while (filled < wanted) {
89
- const { bytesRead } = await file.read(
90
- buffer,
91
- filled,
92
- wanted - filled,
93
- logFrameOffset(frame, header.frameBytes) + filled
94
- );
95
- if (bytesRead === 0) break;
96
- filled += bytesRead;
97
- }
98
- const readable = Math.floor(filled / header.frameBytes);
99
- if (readable === 0) break;
100
- for (let inChunk = 0; inChunk < readable; inChunk++) {
101
- const read = readValidLogFrame(view, inChunk * header.frameBytes, header, running);
102
- if (!read) return found;
103
- running = read.checksum;
104
- if (read.frame.databasePages !== 0) {
105
- found = {
106
- lastCommitFrame: frame + inChunk,
107
- endOffset: logFrameOffset(frame + inChunk + 1, header.frameBytes),
108
- checksum: running
109
- };
110
- }
111
- }
112
- frame += readable;
113
- }
114
- return found;
115
- } finally {
116
- await file.close();
117
- }
118
- }
119
- async function copyLogRange(logPath, startOffset, endOffset, destPath) {
120
- const source = await open(logPath, "r");
121
- try {
122
- const dest = await open(destPath, "w");
123
- try {
124
- const buffer = Buffer.allocUnsafe(Math.min(READ_CHUNK_BYTES, Math.max(endOffset - startOffset, 1)));
125
- let at = startOffset;
126
- let written = 0;
127
- while (at < endOffset) {
128
- const wanted = Math.min(buffer.byteLength, endOffset - at);
129
- const { bytesRead } = await source.read(buffer, 0, wanted, at);
130
- if (bytesRead === 0) {
131
- throw new SirannonError(
132
- `The write-ahead log '${logPath}' ends at byte ${at} while the frames captured from it run to byte ${endOffset}, so it lost frames while Sirannon was reading it. Another connection checkpointed the log. Route every write and every checkpoint through Sirannon, and take a fresh full copy so a new chain starts from a known state.`,
133
- "BACKUP_LOG_REWOUND"
134
- );
135
- }
136
- let put = 0;
137
- while (put < bytesRead) {
138
- const { bytesWritten } = await dest.write(buffer, put, bytesRead - put);
139
- if (bytesWritten === 0) {
140
- throw new SirannonError(
141
- `The capture of '${logPath}' could write no more than ${written + put} of its ${endOffset - startOffset} bytes into '${destPath}'. Check the free space and the permissions on that directory.`,
142
- "BACKUP_ERROR"
143
- );
144
- }
145
- put += bytesWritten;
146
- }
147
- at += bytesRead;
148
- written += bytesRead;
149
- }
150
- return written;
151
- } finally {
152
- await dest.close();
153
- }
154
- } finally {
155
- await source.close();
156
- }
157
- }
158
- function cursorChecksum(cursor) {
159
- return { first: cursor.checksum1, second: cursor.checksum2 };
160
- }
161
- function sameLog(header, cursor) {
162
- return header.salt1 === cursor.salt1 && header.salt2 === cursor.salt2;
163
- }
164
48
 
165
49
  // src/core/backup/cycle-capture.ts
166
50
  var STAGED_CAPTURE_PREFIX = "capture-";
@@ -1,17 +1,17 @@
1
1
  export { F as FieldMergeResolver, L as LWWResolver, P as PrimaryWinsResolver } from '../primary-wins-B0np8JS3.js';
2
2
  import { C as ConflictResolver, R as ReplicationBatch } from '../types-CjhxcjhA.js';
3
3
  export { a as ConflictContext, b as ConflictResolution } from '../types-CjhxcjhA.js';
4
- import { C as ClientOptions, B as BulkLoadDurability } from '../server-options-CFgBQDLz.js';
5
- import { D as DatabaseClient, T as Transport, L as LiveHandlers, R as RemoteSubscription, a as RemoteSubscriptionBuilder, S as SubscribeOptions, b as RegistryDigestSource } from '../client-base-DldBgJWS.js';
6
- export { c as LoadAllOptions, d as RemoteDatabase, e as RemoteError, f as SQL_REFUSED_MESSAGE, g as ServerCapabilities, h as ServerCapabilityCheck } from '../client-base-DldBgJWS.js';
4
+ import { C as ClientOptions, B as BulkLoadDurability } from '../server-options-DeK6ZG8u.js';
5
+ import { D as DatabaseClient, T as Transport, L as LiveHandlers, R as RemoteSubscription, a as RemoteSubscriptionBuilder, S as SubscribeOptions, b as RegistryDigestSource } from '../client-base-C-B8u5Lm.js';
6
+ export { c as LoadAllOptions, d as RemoteDatabase, e as RemoteError, f as SQL_REFUSED_MESSAGE, g as ServerCapabilities, h as ServerCapabilityCheck } from '../client-base-C-B8u5Lm.js';
7
7
  import { a as LiveQuery, b as LiveQueryState, c as LiveUpdate } from '../types-BCejqzNA.js';
8
- import { k as DeviceSyncPort, D as Database } from '../database-DSbJl-k5.js';
8
+ import { k as DeviceSyncPort, D as Database } from '../database-BlhUdJAO.js';
9
9
  import { C as ChangeEvent, P as Params, R as ReadConcern, W as WriteConcern } from '../query-types-Cv8N7pXj.js';
10
- import { Q as QueryResponse, E as ExecuteResponse, T as TransactionResponse, B as BatchResponse, L as LoadResponse, A as AckResponse } from '../protocol-BK0Yxu5P.js';
10
+ import { Q as QueryResponse, E as ExecuteResponse, T as TransactionResponse, B as BatchResponse, L as LoadResponse, A as AckResponse } from '../protocol-QekTGoKn.js';
11
11
  import '../operation-registry-C1FmwQWR.js';
12
- import '../types-CopTgHG3.js';
13
- import '../database-backup-CliAhaEm.js';
14
- import '../change-tracker-BLKRX8ic.js';
12
+ import '../types-DApb7dvw.js';
13
+ import '../database-backup-C090lMHc.js';
14
+ import '../change-tracker-uG9gWvsY.js';
15
15
 
16
16
  /**
17
17
  * @public
@@ -1,11 +1,11 @@
1
1
  import { a as ReadConcernLevel } from '../query-types-Cv8N7pXj.js';
2
- import { C as ClientOptions } from '../server-options-CFgBQDLz.js';
3
- import { T as Transport, D as DatabaseClient } from '../client-base-DldBgJWS.js';
2
+ import { C as ClientOptions } from '../server-options-DeK6ZG8u.js';
3
+ import { T as Transport, D as DatabaseClient } from '../client-base-C-B8u5Lm.js';
4
4
  import '../operation-registry-C1FmwQWR.js';
5
5
  import '../types-CjhxcjhA.js';
6
- import '../types-CopTgHG3.js';
6
+ import '../types-DApb7dvw.js';
7
7
  import '../types-BCejqzNA.js';
8
- import '../protocol-BK0Yxu5P.js';
8
+ import '../protocol-QekTGoKn.js';
9
9
 
10
10
  interface TopologyRouting {
11
11
  _getReadEndpoint(databaseId?: string, readConcern?: ReadConcernLevel): Promise<string>;
@@ -1,8 +1,8 @@
1
- import { B as BulkLoadDurability, a as BulkLoadResult, C as ClientOptions } from './server-options-CFgBQDLz.js';
1
+ import { B as BulkLoadDurability, a as BulkLoadResult, C as ClientOptions } from './server-options-DeK6ZG8u.js';
2
2
  import { R as ResultOp, a as LiveQuery } from './types-BCejqzNA.js';
3
3
  import { O as OperationRef, b as OperationArguments } from './operation-registry-C1FmwQWR.js';
4
4
  import { P as Params, R as ReadConcern, W as WriteConcern, C as ChangeEvent, Q as QueryOptions } from './query-types-Cv8N7pXj.js';
5
- import { Q as QueryResponse, E as ExecuteResponse, T as TransactionResponse, B as BatchResponse, L as LoadResponse } from './protocol-BK0Yxu5P.js';
5
+ import { Q as QueryResponse, E as ExecuteResponse, T as TransactionResponse, B as BatchResponse, L as LoadResponse } from './protocol-QekTGoKn.js';
6
6
 
7
7
  /**
8
8
  * Message a client raises when the server accepts no SQL over the network.