@delali/sirannon-db 0.2.3-next.31 → 0.2.3-next.32

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 (42) hide show
  1. package/dist/backup/index.d.ts +4 -3
  2. package/dist/backup/index.mjs +2 -0
  3. package/dist/backup-scheduler/index.d.ts +1 -2
  4. package/dist/backup-scheduler/index.mjs +1 -1
  5. package/dist/chain-queries-CL_R_DuV.d.ts +91 -0
  6. package/dist/{change-tracker-7wXnBjUN.d.ts → change-tracker-NPJjE1Sh.d.ts} +1 -1
  7. package/dist/chunk-4H45GMYI.mjs +118 -0
  8. package/dist/chunk-C33OO6KQ.mjs +1090 -0
  9. package/dist/{chunk-7X6E7UUF.mjs → chunk-ECEGYTVF.mjs} +1 -1
  10. package/dist/{chunk-23HKRYSM.mjs → chunk-MZM74XYK.mjs} +46 -43
  11. package/dist/chunk-YNTDOFTN.mjs +62 -0
  12. package/dist/client/index.d.ts +8 -9
  13. package/dist/client/topology.d.ts +4 -5
  14. package/dist/{client-base-TJj2LTmq.d.ts → client-base-BDCz2KB1.d.ts} +2 -2
  15. package/dist/core/index.d.ts +15 -10
  16. package/dist/core/index.mjs +194 -54
  17. package/dist/{database-rRTtNLyK.d.ts → database-Cy4ewMCg.d.ts} +111 -42
  18. package/dist/driver/better-sqlite3.d.ts +1 -2
  19. package/dist/driver/better-sqlite3.mjs +6 -2
  20. package/dist/driver/bun.d.ts +1 -2
  21. package/dist/driver/bun.mjs +3 -0
  22. package/dist/driver/expo.d.ts +1 -2
  23. package/dist/driver/expo.mjs +3 -0
  24. package/dist/driver/node.d.ts +1 -2
  25. package/dist/driver/node.mjs +6 -2
  26. package/dist/driver/wa-sqlite.d.ts +1 -2
  27. package/dist/driver/wa-sqlite.mjs +3 -0
  28. package/dist/file-migrations/index.d.ts +1 -2
  29. package/dist/{protocol-b7bwu6s4.d.ts → protocol-D9Yt7YOR.d.ts} +1 -1
  30. package/dist/replication/index.d.ts +7 -8
  31. package/dist/server/index.d.ts +8 -9
  32. package/dist/server/index.mjs +1 -1
  33. package/dist/{server-options-Dgvo1Fhv.d.ts → server-options-CosjHEle.d.ts} +1 -1
  34. package/dist/{sirannon-Xu6bjxsA.d.ts → sirannon-BMw6cs63.d.ts} +2 -2
  35. package/dist/transport/grpc.d.ts +3 -4
  36. package/dist/transport/memory.d.ts +3 -4
  37. package/dist/{types-B1wQuNXV.d.ts → types-CwIiWpVr.d.ts} +440 -2
  38. package/dist/{types-B8dVULSQ.d.ts → types-Dm_POacb.d.ts} +2 -2
  39. package/package.json +1 -1
  40. package/dist/capabilities-DagBnGk8.d.ts +0 -21
  41. package/dist/chunk-ADBGZA6L.mjs +0 -420
  42. package/dist/report-BFsHdkKt.d.ts +0 -132
@@ -1,6 +1,7 @@
1
- import { B as BackupDestination, a as BackupRunReport } from '../report-BFsHdkKt.js';
2
- export { b as BackupPiece, c as BackupProgress, d as BackupToDestinationOptions } from '../report-BFsHdkKt.js';
3
- export { B as BackupCapabilities } from '../capabilities-DagBnGk8.js';
1
+ import { e as BackupDestination, f as BackupRunReport } from '../types-CwIiWpVr.js';
2
+ export { g as BackupChain, h as BackupChainBase, i as BackupChainChange, j as BackupChainPosition, k as BackupChainRecord, l as BackupCycle, m as BackupCycleOptions, n as BackupPiece, o as BackupProgress, p as BackupToDestinationOptions, q as DEFAULT_CHAIN_NAME, r as chainLogName, s as readBackupChains } from '../types-CwIiWpVr.js';
3
+ export { B as BackupCapabilities, a as BackupRestorePlan, b as BackupSafeToDeleteOptions, c as backupPiecesSafeToDelete, p as planBackupRestore } from '../chain-queries-CL_R_DuV.js';
4
+ import '../query-types-Cv8N7pXj.js';
4
5
 
5
6
  /** What one assembled file took to build.
6
7
  * @public
@@ -1,3 +1,5 @@
1
+ export { backupPiecesSafeToDelete, planBackupRestore } from '../chunk-YNTDOFTN.mjs';
2
+ export { DEFAULT_CHAIN_NAME, chainLogName, readBackupChains } from '../chunk-4H45GMYI.mjs';
1
3
  import { SirannonError } from '../chunk-PBRXXISQ.mjs';
2
4
  import { createHash } from 'crypto';
3
5
  import { open, rm } from 'fs/promises';
@@ -1,5 +1,4 @@
1
- import { e as SQLiteConnection, f as BackupScheduleOptions } from '../types-B1wQuNXV.js';
2
- import { e as BackupRunRequest, a as BackupRunReport } from '../report-BFsHdkKt.js';
1
+ import { t as SQLiteConnection, u as BackupRunRequest, f as BackupRunReport, v as BackupScheduleOptions } from '../types-CwIiWpVr.js';
3
2
  import '../query-types-Cv8N7pXj.js';
4
3
 
5
4
  declare class BackupManager {
@@ -1,4 +1,4 @@
1
- export { BackupScheduler } from '../chunk-23HKRYSM.mjs';
1
+ export { BackupScheduler } from '../chunk-MZM74XYK.mjs';
2
2
  import '../chunk-W67A4H2E.mjs';
3
3
  import '../chunk-V5FUJEYY.mjs';
4
4
  import '../chunk-PBRXXISQ.mjs';
@@ -0,0 +1,91 @@
1
+ import { h as BackupChainBase, i as BackupChainChange, g as BackupChain, k as BackupChainRecord } from './types-CwIiWpVr.js';
2
+
3
+ /**
4
+ * What one runtime supports of the backup operations, so a caller learns
5
+ * before a run rather than at failure time. A runtime that hands over whole
6
+ * databases only reports no full copy at all.
7
+ *
8
+ * @public
9
+ */
10
+ interface BackupCapabilities {
11
+ /** Whether this runtime copies an open database while writes continue. */
12
+ fullCopy: boolean;
13
+ /** Whether a full copy reaches the destination without a local file. */
14
+ streamedCopy: boolean;
15
+ /** Whether a full copy writes a local file and sends that file on. */
16
+ stagedCopy: boolean;
17
+ /** Local disk a full copy needs, which the staged route sets to the size of the backup. */
18
+ localDiskRequired: 'none' | 'equal-to-backup';
19
+ /** Whether this runtime repeats a full copy on a schedule. */
20
+ schedule: boolean;
21
+ }
22
+
23
+ /** What one restore has to read, and what it will produce.
24
+ * @public
25
+ */
26
+ interface BackupRestorePlan {
27
+ /** The chain to read. */
28
+ chainId: string;
29
+ /** The full copy to start from. */
30
+ base: BackupChainBase;
31
+ /** The change pieces to apply on top of it, oldest first. */
32
+ changes: BackupChainChange[];
33
+ /**
34
+ * Epoch milliseconds the restored database will reflect. This is when the
35
+ * last piece in the plan was captured, which is at or before the moment you
36
+ * asked for.
37
+ */
38
+ restoresTo: number;
39
+ }
40
+ /** How far back you still want to be able to restore.
41
+ * @public
42
+ */
43
+ interface BackupSafeToDeleteOptions {
44
+ /**
45
+ * Epoch milliseconds of the earliest moment a restore must still reach.
46
+ * Leave it out and the answer covers only the backups no restore could ever
47
+ * use.
48
+ */
49
+ restorableFrom?: number;
50
+ }
51
+ /**
52
+ * Works out what a restore to a given moment has to read: the newest full copy
53
+ * finished at or before it, then every change piece captured from that copy up
54
+ * to it.
55
+ *
56
+ * You get back the moment the result will actually reflect, which is when the
57
+ * last piece was captured. One piece covers every write in the interval it was
58
+ * taken over, so a restore arrives at a piece boundary, not at the exact
59
+ * millisecond you named.
60
+ *
61
+ * A gap in the chain fails the plan with `BACKUP_CHAIN_BROKEN` and names the
62
+ * missing piece, because a plan that stopped part-way through would leave a
63
+ * database nobody could trust.
64
+ *
65
+ * @param chains - The chains a destination holds, as {@link readBackupChains} returns them.
66
+ * @param moment - Epoch milliseconds you want back.
67
+ * @returns The full copy, the change pieces to apply, and the moment the result reflects.
68
+ *
69
+ * @public
70
+ */
71
+ declare function planBackupRestore(chains: readonly BackupChain[], moment: number): BackupRestorePlan;
72
+ /**
73
+ * Tells you which backups no restore still needs, so you can delete them
74
+ * knowing exactly what you give up.
75
+ *
76
+ * Two kinds are dead whatever you ask for: a chain whose full copy has gone,
77
+ * and every change piece after a gap, since nothing can be replayed past the
78
+ * gap. Name the earliest moment you still want to reach and the answer also
79
+ * covers the older chains a newer full copy already spans.
80
+ *
81
+ * Sirannon lists them and deletes nothing. The destination is yours.
82
+ *
83
+ * @param chains - The chains a destination holds, as {@link readBackupChains} returns them.
84
+ * @param options - How far back you still want to be able to restore.
85
+ * @returns The records you may delete, oldest first.
86
+ *
87
+ * @public
88
+ */
89
+ declare function backupPiecesSafeToDelete(chains: readonly BackupChain[], options?: BackupSafeToDeleteOptions): BackupChainRecord[];
90
+
91
+ export { type BackupCapabilities as B, type BackupRestorePlan as a, type BackupSafeToDeleteOptions as b, backupPiecesSafeToDelete as c, planBackupRestore as p };
@@ -1,4 +1,4 @@
1
- import { e as SQLiteConnection } from './types-B1wQuNXV.js';
1
+ import { t as SQLiteConnection } from './types-CwIiWpVr.js';
2
2
  import { C as ChangeEvent } from './query-types-Cv8N7pXj.js';
3
3
 
4
4
  /**
@@ -0,0 +1,118 @@
1
+ import { SirannonError } from './chunk-PBRXXISQ.mjs';
2
+
3
+ // src/core/backup/chain-records.ts
4
+ function isWholeNumber(value) {
5
+ return typeof value === "number" && Number.isInteger(value) && value >= 0;
6
+ }
7
+ function isAtLeastOne(value) {
8
+ return isWholeNumber(value) && value >= 1;
9
+ }
10
+ function isTextOrAbsent(value) {
11
+ return value === void 0 || typeof value === "string";
12
+ }
13
+ function isBackupChainPosition(value) {
14
+ const position = value;
15
+ return isWholeNumber(position?.logSequence) && isWholeNumber(position.salt1) && isWholeNumber(position.salt2) && isAtLeastOne(position.firstFrame) && isAtLeastOne(position.lastFrame) && position.lastFrame >= position.firstFrame;
16
+ }
17
+ function isBackupChainHead(value) {
18
+ const head = value;
19
+ return typeof head?.chainId === "string" && isWholeNumber(head.startedAt) && isTextOrAbsent(head.previousChainId);
20
+ }
21
+ function isBackupChainBase(value) {
22
+ const base = value;
23
+ return base?.kind === "full" && typeof base.chainId === "string" && typeof base.name === "string" && typeof base.runId === "string" && isWholeNumber(base.finishedAt) && isWholeNumber(base.pieceCount) && isAtLeastOne(base.pieceBytes) && isWholeNumber(base.bytesWritten) && isTextOrAbsent(base.fingerprint);
24
+ }
25
+ function isBackupChainChange(value) {
26
+ const change = value;
27
+ return change?.kind === "change" && typeof change.chainId === "string" && typeof change.name === "string" && typeof change.runId === "string" && isAtLeastOne(change.sequence) && isBackupChainPosition(change.position) && isWholeNumber(change.capturedAt) && isAtLeastOne(change.frameCount) && isWholeNumber(change.pieceCount) && isAtLeastOne(change.pieceBytes) && isWholeNumber(change.bytesWritten) && typeof change.checkpointed === "boolean" && isTextOrAbsent(change.fingerprint);
28
+ }
29
+
30
+ // src/core/backup/chain.ts
31
+ var DEFAULT_CHAIN_NAME = "sirannon-backup-chain";
32
+ var encoder = new TextEncoder();
33
+ var decoder = new TextDecoder();
34
+ function destinationError(message, err) {
35
+ if (err instanceof SirannonError) return err;
36
+ const detail = err instanceof Error ? `: ${err.message}` : "";
37
+ return new SirannonError(`${message}${detail}`, "BACKUP_DESTINATION_ERROR");
38
+ }
39
+ function chainLogName(chainName, chainId) {
40
+ return `${chainName}.${chainId}`;
41
+ }
42
+ async function appendRecord(destination, name, index, record) {
43
+ try {
44
+ await destination.writePiece(name, index, encoder.encode(JSON.stringify(record)));
45
+ } catch (err) {
46
+ throw destinationError(`The destination refused record ${index} of '${name}'`, err);
47
+ }
48
+ }
49
+ async function readRecords(destination, name) {
50
+ let listed;
51
+ try {
52
+ listed = await destination.listPieces(name);
53
+ } catch (err) {
54
+ throw destinationError(`The destination could not list the records of '${name}'`, err);
55
+ }
56
+ const ordered = [...listed].sort((left, right) => left.index - right.index);
57
+ const records = [];
58
+ for (const piece of ordered) {
59
+ let bytes;
60
+ try {
61
+ bytes = await destination.readPiece(name, piece.index);
62
+ } catch (err) {
63
+ throw destinationError(`The destination could not return record ${piece.index} of '${name}'`, err);
64
+ }
65
+ try {
66
+ records.push(JSON.parse(decoder.decode(bytes)));
67
+ } catch (err) {
68
+ throw destinationError(`Record ${piece.index} of '${name}' is not a record Sirannon wrote`, err);
69
+ }
70
+ }
71
+ return records;
72
+ }
73
+ function chainRecords(records, name) {
74
+ const kept = [];
75
+ for (const record of records) {
76
+ const kind = record?.kind;
77
+ if (kind !== "full" && kind !== "change") continue;
78
+ if (kind === "full" ? isBackupChainBase(record) : isBackupChainChange(record)) {
79
+ kept.push(record);
80
+ continue;
81
+ }
82
+ const label = kind === "full" ? "full copy" : "change piece";
83
+ throw destinationError(
84
+ `A ${label} record of '${name}' is missing fields Sirannon writes into every record it stores, so no restore can use this chain until you put that record back`
85
+ );
86
+ }
87
+ return kept;
88
+ }
89
+ async function appendChainHead(destination, chainName, head, index) {
90
+ await appendRecord(destination, chainName, index, head);
91
+ }
92
+ async function appendChainRecord(destination, chainName, record, index) {
93
+ await appendRecord(destination, chainLogName(chainName, record.chainId), index, record);
94
+ }
95
+ async function readChainHeads(destination, chainName) {
96
+ const records = await readRecords(destination, chainName);
97
+ return records.filter(isBackupChainHead).reverse();
98
+ }
99
+ async function readBackupChains(destination, chainName = DEFAULT_CHAIN_NAME) {
100
+ const heads = await readChainHeads(destination, chainName);
101
+ const chains = [];
102
+ for (const head of heads) {
103
+ const logName = chainLogName(chainName, head.chainId);
104
+ const records = chainRecords(await readRecords(destination, logName), logName);
105
+ const changes = records.filter((record) => record.kind === "change").sort((left, right) => left.sequence - right.sequence);
106
+ const base = records.find((record) => record.kind === "full");
107
+ chains.push({
108
+ chainId: head.chainId,
109
+ startedAt: head.startedAt,
110
+ ...head.previousChainId ? { previousChainId: head.previousChainId } : {},
111
+ ...base ? { base } : {},
112
+ changes
113
+ });
114
+ }
115
+ return chains;
116
+ }
117
+
118
+ export { DEFAULT_CHAIN_NAME, appendChainHead, appendChainRecord, chainLogName, isBackupChainPosition, readBackupChains, readChainHeads };