@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.
- package/dist/backup/index.d.ts +5 -5
- package/dist/backup/index.mjs +4 -4
- package/dist/backup-scheduler/index.d.ts +58 -8
- package/dist/backup-scheduler/index.mjs +3 -2
- package/dist/{change-tracker-BLKRX8ic.d.ts → change-tracker-uG9gWvsY.d.ts} +1 -1
- package/dist/chunk-2QCPVE67.mjs +143 -0
- package/dist/{chunk-P53I2G6J.mjs → chunk-63VBFSCS.mjs} +2 -2
- package/dist/{chunk-CR6HTXSC.mjs → chunk-7SC2TZAS.mjs} +1 -1
- package/dist/chunk-DVS4QSIZ.mjs +124 -0
- package/dist/{chunk-IPDHHIG2.mjs → chunk-DXXCLTFF.mjs} +205 -45
- package/dist/{chunk-G7AXGAEA.mjs → chunk-OJQHM4MT.mjs} +202 -100
- package/dist/{chunk-KSYFFYAP.mjs → chunk-U445A3C4.mjs} +1 -70
- package/dist/{chunk-GJ6SX53Z.mjs → chunk-W77EEVOT.mjs} +4 -120
- package/dist/client/index.d.ts +8 -8
- package/dist/client/topology.d.ts +4 -4
- package/dist/{client-base-DldBgJWS.d.ts → client-base-C-B8u5Lm.d.ts} +2 -2
- package/dist/core/index.d.ts +9 -9
- package/dist/core/index.mjs +14 -5
- package/dist/{database-DSbJl-k5.d.ts → database-BlhUdJAO.d.ts} +7 -6
- package/dist/{database-backup-CliAhaEm.d.ts → database-backup-C090lMHc.d.ts} +2 -2
- package/dist/driver/better-sqlite3.d.ts +1 -1
- package/dist/driver/better-sqlite3.mjs +7 -6
- package/dist/driver/bun.d.ts +1 -1
- package/dist/driver/expo.d.ts +1 -1
- package/dist/driver/node.d.ts +1 -1
- package/dist/driver/node.mjs +7 -6
- package/dist/driver/wa-sqlite.d.ts +1 -1
- package/dist/file-migrations/index.d.ts +1 -1
- package/dist/{protocol-BK0Yxu5P.d.ts → protocol-QekTGoKn.d.ts} +1 -1
- package/dist/replication/index.d.ts +7 -7
- package/dist/{restore-options-O5gYlr3n.d.ts → restore-options-C9MW9gpm.d.ts} +1 -1
- package/dist/server/index.d.ts +9 -9
- package/dist/server/index.mjs +5 -4
- package/dist/{server-options-CFgBQDLz.d.ts → server-options-DeK6ZG8u.d.ts} +1 -1
- package/dist/{sirannon-Bo0PX2Cj.d.ts → sirannon-C9cTgFrw.d.ts} +2 -2
- package/dist/transport/grpc.d.ts +3 -3
- package/dist/transport/memory.d.ts +3 -3
- package/dist/{types-CL8uxXIV.d.ts → types-C1yBGTdx.d.ts} +2 -2
- package/dist/{types-CopTgHG3.d.ts → types-DApb7dvw.d.ts} +151 -5
- package/package.json +8 -7
- package/dist/chunk-GBM4AU24.mjs +0 -42
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { verifyBackupRecord } from './chunk-
|
|
2
|
-
import { BackupManager, BackupScheduler, DEFAULT_PIECE_BYTES, sendFileInPieces } from './chunk-
|
|
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-
|
|
6
|
-
import { checkpointLog } from './chunk-
|
|
7
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
1061
|
+
this.turnLog.turnStarted();
|
|
966
1062
|
try {
|
|
967
1063
|
return await op();
|
|
968
1064
|
} catch (err) {
|
|
969
|
-
this.
|
|
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.
|
|
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,
|
|
1117
|
+
/** Passes a skipped turn on, where the cycle has a reason to give for it. */
|
|
1022
1118
|
async reportSkip(skip) {
|
|
1023
|
-
if (
|
|
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
|
-
|
|
1036
|
-
|
|
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.
|
|
1067
|
-
|
|
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
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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-";
|
package/dist/client/index.d.ts
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
13
|
-
import '../database-backup-
|
|
14
|
-
import '../change-tracker-
|
|
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-
|
|
3
|
-
import { T as Transport, D as DatabaseClient } from '../client-base-
|
|
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-
|
|
6
|
+
import '../types-DApb7dvw.js';
|
|
7
7
|
import '../types-BCejqzNA.js';
|
|
8
|
-
import '../protocol-
|
|
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-
|
|
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-
|
|
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.
|