@minnowdb/core 0.3.0 → 0.4.1
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/README.md +4 -2
- package/dist/block-format/block.d.ts +32 -13
- package/dist/block-format/block.js +179 -55
- package/dist/block-format/checksum.d.ts +2 -1
- package/dist/block-format/checksum.js +5 -2
- package/dist/block-format/codecs.d.ts +7 -2
- package/dist/block-format/codecs.js +53 -12
- package/dist/block-format/column.d.ts +3 -2
- package/dist/block-format/column.js +96 -30
- package/dist/block-format/index.d.ts +2 -2
- package/dist/block-format/index.js +2 -2
- package/dist/block-format/physical.d.ts +8 -2
- package/dist/block-format/physical.js +14 -6
- package/dist/block-format/types.d.ts +7 -3
- package/dist/block-format/types.js +0 -1
- package/dist/block-format/unicode.d.ts +11 -0
- package/dist/block-format/unicode.js +46 -0
- package/dist/date-value.d.ts +15 -0
- package/dist/date-value.js +64 -0
- package/dist/engine/artifact-cache.d.ts +0 -1
- package/dist/engine/artifact-cache.js +0 -1
- package/dist/engine/batch.d.ts +5 -2
- package/dist/engine/batch.js +0 -1
- package/dist/engine/buffered-writer.d.ts +2 -1
- package/dist/engine/buffered-writer.js +34 -9
- package/dist/engine/cache-limits.d.ts +25 -0
- package/dist/engine/cache-limits.js +25 -0
- package/dist/engine/catalog.d.ts +5 -6
- package/dist/engine/catalog.js +5 -5
- package/dist/engine/client.d.ts +27 -7
- package/dist/engine/client.js +197 -31
- package/dist/engine/database.d.ts +161 -38
- package/dist/engine/database.js +4326 -1523
- package/dist/engine/defaults.d.ts +0 -1
- package/dist/engine/defaults.js +2 -4
- package/dist/engine/errors.d.ts +19 -1
- package/dist/engine/errors.js +32 -2
- package/dist/engine/fts.d.ts +0 -6
- package/dist/engine/fts.js +39 -14
- package/dist/engine/group-index.d.ts +0 -1
- package/dist/engine/group-index.js +6 -11
- package/dist/engine/index.d.ts +16 -11
- package/dist/engine/index.js +12 -10
- package/dist/engine/join-index.d.ts +0 -1
- package/dist/engine/join-index.js +2 -2
- package/dist/engine/keyed-live.d.ts +57 -0
- package/dist/engine/keyed-live.js +226 -0
- package/dist/engine/live-api.d.ts +4 -0
- package/dist/engine/live-api.js +4 -0
- package/dist/engine/live.d.ts +43 -26
- package/dist/engine/live.js +499 -129
- package/dist/engine/memory.d.ts +2 -1
- package/dist/engine/memory.js +3 -2
- package/dist/engine/optimizer.d.ts +0 -1
- package/dist/engine/optimizer.js +4 -4
- package/dist/engine/query-api.d.ts +3 -0
- package/dist/engine/query-api.js +3 -0
- package/dist/engine/query-cache.d.ts +0 -1
- package/dist/engine/query-cache.js +4 -4
- package/dist/engine/query.d.ts +4 -7
- package/dist/engine/query.js +348 -155
- package/dist/engine/result-wire.d.ts +0 -1
- package/dist/engine/result-wire.js +2 -2
- package/dist/engine/schema-wire.d.ts +1 -2
- package/dist/engine/schema-wire.js +0 -1
- package/dist/engine/schema.d.ts +3 -8
- package/dist/engine/schema.js +41 -24
- package/dist/engine/sort-keys.d.ts +0 -1
- package/dist/engine/sort-keys.js +2 -2
- package/dist/engine/sql-domains.d.ts +6 -1
- package/dist/engine/sql-domains.js +209 -45
- package/dist/engine/sql-driver.d.ts +2 -2
- package/dist/engine/sql-driver.js +0 -1
- package/dist/engine/sql-json.d.ts +0 -1
- package/dist/engine/sql-json.js +53 -7
- package/dist/engine/sql-semantics.d.ts +8 -5
- package/dist/engine/sql-semantics.js +427 -65
- package/dist/engine/typed-live.d.ts +67 -0
- package/dist/engine/typed-live.js +349 -0
- package/dist/engine/vector.d.ts +7 -1
- package/dist/engine/vector.js +202 -58
- package/dist/engine/worker-host.d.ts +8 -2
- package/dist/engine/worker-host.js +616 -152
- package/dist/engine/worker.d.ts +0 -1
- package/dist/engine/worker.js +3 -3
- package/dist/engine/write-block-planner.d.ts +18 -0
- package/dist/engine/write-block-planner.js +112 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/plan/index.d.ts +0 -1
- package/dist/plan/index.js +0 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.js +1 -1
- package/dist/storage/indexeddb.d.ts +74 -65
- package/dist/storage/indexeddb.js +12301 -2076
- package/dist/storage/memory.d.ts +56 -66
- package/dist/storage/memory.js +1004 -139
- package/dist/storage/opfs/files.d.ts +18 -7
- package/dist/storage/opfs/files.js +115 -28
- package/dist/storage/opfs/index.d.ts +1 -1
- package/dist/storage/opfs/index.js +1 -1
- package/dist/storage/opfs/leader.d.ts +403 -91
- package/dist/storage/opfs/leader.js +4318 -602
- package/dist/storage/opfs/rpc.d.ts +15 -3
- package/dist/storage/opfs/rpc.js +223 -2
- package/dist/storage/opfs/snapshot-ledger.d.ts +40 -0
- package/dist/storage/opfs/snapshot-ledger.js +281 -0
- package/dist/storage/opfs/store.d.ts +26 -121
- package/dist/storage/opfs/store.js +436 -388
- package/dist/storage/persistence.d.ts +37 -0
- package/dist/storage/persistence.js +78 -0
- package/dist/storage/snapshot-stream.d.ts +24 -0
- package/dist/storage/snapshot-stream.js +897 -0
- package/dist/storage/snapshot.d.ts +9 -99
- package/dist/storage/snapshot.js +33 -309
- package/dist/storage/toolkit/extents.d.ts +41 -7
- package/dist/storage/toolkit/extents.js +402 -39
- package/dist/storage/toolkit/index.d.ts +3 -4
- package/dist/storage/toolkit/index.js +27 -3
- package/dist/storage/toolkit/record-core.d.ts +174 -61
- package/dist/storage/toolkit/record-core.js +5401 -849
- package/dist/storage/toolkit/sync-file.d.ts +10 -1
- package/dist/storage/toolkit/sync-file.js +50 -2
- package/dist/storage/toolkit/wal.d.ts +23 -5
- package/dist/storage/toolkit/wal.js +89 -27
- package/dist/storage/toolkit/wire.d.ts +4 -6
- package/dist/storage/toolkit/wire.js +80 -25
- package/dist/storage/types.d.ts +1271 -218
- package/dist/storage/types.js +1471 -138
- package/dist/testing/block-store-conformance.d.ts +0 -1
- package/dist/testing/block-store-conformance.js +877 -138
- package/dist/testing/index.d.ts +42 -31
- package/dist/testing/index.js +100 -68
- package/dist/testing/opfs-shim.d.ts +13 -3
- package/dist/testing/opfs-shim.js +40 -6
- package/dist/testing/simulator.d.ts +0 -1
- package/dist/testing/simulator.js +43 -12
- package/dist/testing/sqllogictest.d.ts +0 -1
- package/dist/testing/sqllogictest.js +0 -1
- package/dist/transactions/index.d.ts +81 -20
- package/dist/transactions/index.js +1050 -268
- package/dist/worker-protocol/index.d.ts +3 -2
- package/dist/worker-protocol/index.js +2 -5
- package/package.json +49 -1
- package/dist/block-format/block.d.ts.map +0 -1
- package/dist/block-format/block.js.map +0 -1
- package/dist/block-format/checksum.d.ts.map +0 -1
- package/dist/block-format/checksum.js.map +0 -1
- package/dist/block-format/codecs.d.ts.map +0 -1
- package/dist/block-format/codecs.js.map +0 -1
- package/dist/block-format/column.d.ts.map +0 -1
- package/dist/block-format/column.js.map +0 -1
- package/dist/block-format/index.d.ts.map +0 -1
- package/dist/block-format/index.js.map +0 -1
- package/dist/block-format/physical.d.ts.map +0 -1
- package/dist/block-format/physical.js.map +0 -1
- package/dist/block-format/types.d.ts.map +0 -1
- package/dist/block-format/types.js.map +0 -1
- package/dist/engine/artifact-cache.d.ts.map +0 -1
- package/dist/engine/artifact-cache.js.map +0 -1
- package/dist/engine/batch.d.ts.map +0 -1
- package/dist/engine/batch.js.map +0 -1
- package/dist/engine/buffered-writer.d.ts.map +0 -1
- package/dist/engine/buffered-writer.js.map +0 -1
- package/dist/engine/catalog.d.ts.map +0 -1
- package/dist/engine/catalog.js.map +0 -1
- package/dist/engine/client.d.ts.map +0 -1
- package/dist/engine/client.js.map +0 -1
- package/dist/engine/coordinator.d.ts +0 -17
- package/dist/engine/coordinator.d.ts.map +0 -1
- package/dist/engine/coordinator.js +0 -60
- package/dist/engine/coordinator.js.map +0 -1
- package/dist/engine/database.d.ts.map +0 -1
- package/dist/engine/database.js.map +0 -1
- package/dist/engine/defaults.d.ts.map +0 -1
- package/dist/engine/defaults.js.map +0 -1
- package/dist/engine/errors.d.ts.map +0 -1
- package/dist/engine/errors.js.map +0 -1
- package/dist/engine/fts.d.ts.map +0 -1
- package/dist/engine/fts.js.map +0 -1
- package/dist/engine/group-index.d.ts.map +0 -1
- package/dist/engine/group-index.js.map +0 -1
- package/dist/engine/index.d.ts.map +0 -1
- package/dist/engine/index.js.map +0 -1
- package/dist/engine/join-index.d.ts.map +0 -1
- package/dist/engine/join-index.js.map +0 -1
- package/dist/engine/live.d.ts.map +0 -1
- package/dist/engine/live.js.map +0 -1
- package/dist/engine/memory.d.ts.map +0 -1
- package/dist/engine/memory.js.map +0 -1
- package/dist/engine/optimizer.d.ts.map +0 -1
- package/dist/engine/optimizer.js.map +0 -1
- package/dist/engine/query-cache.d.ts.map +0 -1
- package/dist/engine/query-cache.js.map +0 -1
- package/dist/engine/query.d.ts.map +0 -1
- package/dist/engine/query.js.map +0 -1
- package/dist/engine/result-wire.d.ts.map +0 -1
- package/dist/engine/result-wire.js.map +0 -1
- package/dist/engine/schema-wire.d.ts.map +0 -1
- package/dist/engine/schema-wire.js.map +0 -1
- package/dist/engine/schema.d.ts.map +0 -1
- package/dist/engine/schema.js.map +0 -1
- package/dist/engine/sort-keys.d.ts.map +0 -1
- package/dist/engine/sort-keys.js.map +0 -1
- package/dist/engine/sql-domains.d.ts.map +0 -1
- package/dist/engine/sql-domains.js.map +0 -1
- package/dist/engine/sql-driver.d.ts.map +0 -1
- package/dist/engine/sql-driver.js.map +0 -1
- package/dist/engine/sql-json.d.ts.map +0 -1
- package/dist/engine/sql-json.js.map +0 -1
- package/dist/engine/sql-semantics.d.ts.map +0 -1
- package/dist/engine/sql-semantics.js.map +0 -1
- package/dist/engine/vector.d.ts.map +0 -1
- package/dist/engine/vector.js.map +0 -1
- package/dist/engine/worker-host.d.ts.map +0 -1
- package/dist/engine/worker-host.js.map +0 -1
- package/dist/engine/worker.d.ts.map +0 -1
- package/dist/engine/worker.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/plan/index.d.ts.map +0 -1
- package/dist/plan/index.js.map +0 -1
- package/dist/storage/fixture-shape.d.ts +0 -42
- package/dist/storage/fixture-shape.d.ts.map +0 -1
- package/dist/storage/fixture-shape.js +0 -146
- package/dist/storage/fixture-shape.js.map +0 -1
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/index.js.map +0 -1
- package/dist/storage/indexeddb.d.ts.map +0 -1
- package/dist/storage/indexeddb.js.map +0 -1
- package/dist/storage/memory.d.ts.map +0 -1
- package/dist/storage/memory.js.map +0 -1
- package/dist/storage/opfs/files.d.ts.map +0 -1
- package/dist/storage/opfs/files.js.map +0 -1
- package/dist/storage/opfs/index.d.ts.map +0 -1
- package/dist/storage/opfs/index.js.map +0 -1
- package/dist/storage/opfs/leader.d.ts.map +0 -1
- package/dist/storage/opfs/leader.js.map +0 -1
- package/dist/storage/opfs/rpc.d.ts.map +0 -1
- package/dist/storage/opfs/rpc.js.map +0 -1
- package/dist/storage/opfs/store.d.ts.map +0 -1
- package/dist/storage/opfs/store.js.map +0 -1
- package/dist/storage/snapshot.d.ts.map +0 -1
- package/dist/storage/snapshot.js.map +0 -1
- package/dist/storage/toolkit/extents.d.ts.map +0 -1
- package/dist/storage/toolkit/extents.js.map +0 -1
- package/dist/storage/toolkit/index.d.ts.map +0 -1
- package/dist/storage/toolkit/index.js.map +0 -1
- package/dist/storage/toolkit/record-core.d.ts.map +0 -1
- package/dist/storage/toolkit/record-core.js.map +0 -1
- package/dist/storage/toolkit/sync-file.d.ts.map +0 -1
- package/dist/storage/toolkit/sync-file.js.map +0 -1
- package/dist/storage/toolkit/wal.d.ts.map +0 -1
- package/dist/storage/toolkit/wal.js.map +0 -1
- package/dist/storage/toolkit/wire.d.ts.map +0 -1
- package/dist/storage/toolkit/wire.js.map +0 -1
- package/dist/storage/types.d.ts.map +0 -1
- package/dist/storage/types.js.map +0 -1
- package/dist/testing/block-store-conformance.d.ts.map +0 -1
- package/dist/testing/block-store-conformance.js.map +0 -1
- package/dist/testing/index.d.ts.map +0 -1
- package/dist/testing/index.js.map +0 -1
- package/dist/testing/opfs-shim.d.ts.map +0 -1
- package/dist/testing/opfs-shim.js.map +0 -1
- package/dist/testing/seeds.d.ts +0 -11
- package/dist/testing/seeds.d.ts.map +0 -1
- package/dist/testing/seeds.js +0 -50
- package/dist/testing/seeds.js.map +0 -1
- package/dist/testing/simulator.d.ts.map +0 -1
- package/dist/testing/simulator.js.map +0 -1
- package/dist/testing/sqllogictest.d.ts.map +0 -1
- package/dist/testing/sqllogictest.js.map +0 -1
- package/dist/transactions/index.d.ts.map +0 -1
- package/dist/transactions/index.js.map +0 -1
- package/dist/worker-protocol/index.d.ts.map +0 -1
- package/dist/worker-protocol/index.js.map +0 -1
|
@@ -1,122 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
/** Public snapshot API over the bounded framed v1 container. */
|
|
2
|
+
export * from "./snapshot-stream.js";
|
|
2
3
|
export declare const SNAPSHOT_FORMAT_VERSION = 1;
|
|
3
|
-
/**
|
|
4
|
-
export
|
|
5
|
-
columnId: string;
|
|
6
|
-
coversVersion: number;
|
|
7
|
-
totalTokens: number;
|
|
8
|
-
/** Term-range partitioned, term-sorted within each chunk. */
|
|
9
|
-
chunks: FtsPosting[][];
|
|
10
|
-
}
|
|
11
|
-
export interface SnapshotTable {
|
|
12
|
-
record: TableRecord;
|
|
13
|
-
/** The next row ID a write should reserve; below it, IDs may already be in use. */
|
|
14
|
-
nextRowId: bigint;
|
|
15
|
-
autoIncrement: Array<{
|
|
16
|
-
columnId: string;
|
|
17
|
-
next: bigint;
|
|
18
|
-
}>;
|
|
19
|
-
/** Complete unique-key membership, so a write after loading still conflicts correctly. */
|
|
20
|
-
uniqueKeyTokens: string[];
|
|
21
|
-
/** Complete membership for every durable UNIQUE secondary index. */
|
|
22
|
-
secondaryUniqueKeys?: Array<{
|
|
23
|
-
indexId: string;
|
|
24
|
-
keyTokens: string[];
|
|
25
|
-
}>;
|
|
26
|
-
fts: SnapshotFtsIndex[];
|
|
27
|
-
}
|
|
28
|
-
export interface DatabaseSnapshot {
|
|
29
|
-
/** The manifest version this snapshot captured, which the restored database keeps. */
|
|
30
|
-
version: number;
|
|
31
|
-
createdAt: string;
|
|
32
|
-
tables: SnapshotTable[];
|
|
33
|
-
segments: SegmentRecord[];
|
|
34
|
-
/** Committed transactions only; segment visibility resolves through `committedVersion`. */
|
|
35
|
-
transactions: TransactionRecord[];
|
|
36
|
-
blocks: Array<{
|
|
37
|
-
id: string;
|
|
38
|
-
bytes: Uint8Array;
|
|
39
|
-
}>;
|
|
40
|
-
}
|
|
41
|
-
/** Validates catalog identities and complete uniqueness membership before a snapshot is loaded. */
|
|
42
|
-
export declare function validateSnapshotCatalog(tables: ReadonlyArray<{
|
|
43
|
-
record: TableRecord;
|
|
44
|
-
uniqueKeyTokens: readonly string[];
|
|
45
|
-
secondaryUniqueKeys?: ReadonlyArray<{
|
|
46
|
-
indexId: string;
|
|
47
|
-
keyTokens: readonly string[];
|
|
48
|
-
}>;
|
|
49
|
-
}>): void;
|
|
50
|
-
/** Where a snapshot load has got to, for a progress bar over a multi-megabyte file. */
|
|
4
|
+
/** Export and worker-transport chunk ceiling; direct imports accept any non-empty source chunk. */
|
|
5
|
+
export declare const MAX_SNAPSHOT_STREAM_CHUNK_BYTES: number;
|
|
51
6
|
export interface SnapshotLoadProgress {
|
|
52
7
|
phase: "blocks" | "catalog" | "done";
|
|
53
8
|
writtenBytes: number;
|
|
54
9
|
totalBytes: number;
|
|
55
10
|
}
|
|
56
|
-
/**
|
|
57
|
-
* Where a snapshot export has got to. `reading` covers copying the version out of the store and
|
|
58
|
-
* encoding it, and reports no byte counts because the encoded length is not known until it is
|
|
59
|
-
* finished. `transfer` happens only when the database lives in a worker and the bytes are handed
|
|
60
|
-
* back a chunk at a time; an in-page database goes straight from `reading` to `done`.
|
|
61
|
-
*/
|
|
62
11
|
export interface SnapshotExportProgress {
|
|
63
12
|
phase: "reading" | "transfer" | "done";
|
|
64
13
|
transferredBytes: number;
|
|
65
|
-
/** Zero while
|
|
14
|
+
/** Zero while streaming; the exact encoded length is known by the byte-array wrapper. */
|
|
66
15
|
totalBytes: number;
|
|
67
16
|
}
|
|
68
|
-
/**
|
|
17
|
+
/** Header summary used by restore confirmation UIs without retaining database metadata. */
|
|
69
18
|
export interface SnapshotSummary {
|
|
70
19
|
formatVersion: number;
|
|
71
20
|
version: number;
|
|
72
21
|
createdAt: string;
|
|
73
22
|
tableCount: number;
|
|
74
23
|
blockCount: number;
|
|
75
|
-
/**
|
|
24
|
+
/** Stored body payload bytes, excluding frame envelopes, header, and footer. */
|
|
76
25
|
payloadBytes: number;
|
|
77
26
|
byteLength: number;
|
|
78
27
|
}
|
|
79
28
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* because it is JSON and its bulk is full-text posting lists and segment records, which compress
|
|
83
|
-
* by roughly four to one.
|
|
84
|
-
*/
|
|
85
|
-
export declare function encodeSnapshot(snapshot: DatabaseSnapshot): Promise<Uint8Array>;
|
|
86
|
-
/**
|
|
87
|
-
* Reads only the header. Cheap enough to run against a multi-hundred-megabyte file before
|
|
88
|
-
* deciding whether to load it.
|
|
29
|
+
* Reads and validates the canonical header from a materialized snapshot. The decoder closes its
|
|
30
|
+
* source immediately after the header; body checks run during import, where frames are consumed.
|
|
89
31
|
*/
|
|
90
32
|
export declare function readSnapshotSummary(bytes: Uint8Array): Promise<SnapshotSummary>;
|
|
91
|
-
/**
|
|
92
|
-
* Parses a snapshot and authenticates every block. `inspectBlock` verifies each payload's magic,
|
|
93
|
-
* version, declared lengths, and envelope checksum without decompressing it, so a corrupt file
|
|
94
|
-
* fails here rather than mid-query.
|
|
95
|
-
*/
|
|
96
|
-
export declare function decodeSnapshot(bytes: Uint8Array): Promise<DatabaseSnapshot>;
|
|
97
|
-
/**
|
|
98
|
-
* Shared by both stores' exporters: given the manifest's live block set, decide which segments
|
|
99
|
-
* and transactions the snapshot needs.
|
|
100
|
-
*
|
|
101
|
-
* A segment is live when every block it references survives in the manifest — compaction
|
|
102
|
-
* supersedes a segment by dropping its blocks, so that test is exactly the reader's. A
|
|
103
|
-
* transaction is needed when a live segment names it, and only committed transactions qualify:
|
|
104
|
-
* an active one's artifacts are unpublished and would dangle. Their pending lists are cleared,
|
|
105
|
-
* because after the restore the manifest is the only root anything needs.
|
|
106
|
-
*
|
|
107
|
-
* `committedVersion` is preserved exactly, and the restored database keeps the captured version
|
|
108
|
-
* number rather than renumbering to 1. Segments sort by `logicalOrder ?? committedVersion`, so
|
|
109
|
-
* flattening those versions would let an update fold ahead of the insert it patches; and change
|
|
110
|
-
* detection compares committed versions against a snapshot version, which would misread any
|
|
111
|
-
* transaction numbered above the manifest.
|
|
112
|
-
*/
|
|
113
|
-
export declare function selectLiveRecords(input: {
|
|
114
|
-
liveBlockIds: ReadonlySet<string>;
|
|
115
|
-
segments: readonly SegmentRecord[];
|
|
116
|
-
transactions: readonly TransactionRecord[];
|
|
117
|
-
version: number;
|
|
118
|
-
}): {
|
|
119
|
-
segments: SegmentRecord[];
|
|
120
|
-
transactions: TransactionRecord[];
|
|
121
|
-
};
|
|
122
|
-
//# sourceMappingURL=snapshot.d.ts.map
|
package/dist/storage/snapshot.js
CHANGED
|
@@ -1,320 +1,44 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* A snapshot is a *published* view, not a backup of a store's internals. It carries the block
|
|
6
|
-
* bytes the current manifest points at, the catalog needed to read them (tables, segments, and
|
|
7
|
-
* the committed transactions segment visibility resolves through), the counters a later write
|
|
8
|
-
* has to continue from, and the unique-key membership a later write has to conflict against.
|
|
9
|
-
* It deliberately drops everything transient — leases, spill pages, garbage-collection and
|
|
10
|
-
* compaction jobs, in-flight transactions — and it drops version history: the snapshot restores
|
|
11
|
-
* as a single checkpoint, so a 10,000-commit database loads as one clean version.
|
|
12
|
-
*
|
|
13
|
-
* The container is a small JSON header followed by the block payloads laid end to end. Blocks
|
|
14
|
-
* are already self-describing and doubly CRC-checked (see `../block-format/block.ts`), so the
|
|
15
|
-
* container adds only a checksum over its own header and lets `inspectBlock` authenticate each
|
|
16
|
-
* payload without decompressing any of them.
|
|
17
|
-
*/
|
|
18
|
-
import { crc32, gzipCodec, inspectBlock } from "../block-format/index.js";
|
|
19
|
-
import { validateSecondaryIndexes, validateTableColumns } from "./types.js";
|
|
20
|
-
/** ASCII `MNWSNAP1`, matching the block format's habit of a readable magic. */
|
|
21
|
-
const MAGIC = Uint8Array.from([0x4d, 0x4e, 0x57, 0x53, 0x4e, 0x41, 0x50, 0x31]);
|
|
22
|
-
/**
|
|
23
|
-
* magic(8) + formatVersion(4) + storedHeaderLength(4) + headerChecksum(4) + headerLength(4)
|
|
24
|
-
* + headerCodec(4). All little-endian, matching the block format.
|
|
25
|
-
*/
|
|
26
|
-
const PREFIX_LENGTH = 28;
|
|
27
|
-
const HEADER_CODEC_NONE = 0;
|
|
28
|
-
const HEADER_CODEC_GZIP = 1;
|
|
1
|
+
/** Public snapshot API over the bounded framed v1 container. */
|
|
2
|
+
export * from "./snapshot-stream.js";
|
|
3
|
+
import { SNAPSHOT_FRAME_KINDS } from "./types.js";
|
|
4
|
+
import { decodeSnapshotFrameStream } from "./snapshot-stream.js";
|
|
29
5
|
export const SNAPSHOT_FORMAT_VERSION = 1;
|
|
30
|
-
/**
|
|
31
|
-
export
|
|
32
|
-
const indexNames = new Set();
|
|
33
|
-
for (const table of tables) {
|
|
34
|
-
validateTableColumns(table.record.columns);
|
|
35
|
-
validateSecondaryIndexes(table.record);
|
|
36
|
-
if (new Set(table.uniqueKeyTokens).size !== table.uniqueKeyTokens.length) {
|
|
37
|
-
throw new Error(`Snapshot repeats a table unique key: ${table.record.name}`);
|
|
38
|
-
}
|
|
39
|
-
if (table.record.uniqueKeyColumnId === undefined && table.uniqueKeyTokens.length > 0) {
|
|
40
|
-
throw new Error(`Snapshot has orphaned table unique keys: ${table.record.name}`);
|
|
41
|
-
}
|
|
42
|
-
const memberships = new Set();
|
|
43
|
-
for (const entry of table.secondaryUniqueKeys ?? []) {
|
|
44
|
-
if (memberships.has(entry.indexId)) {
|
|
45
|
-
throw new Error(`Snapshot repeats UNIQUE-index membership: ${entry.indexId}`);
|
|
46
|
-
}
|
|
47
|
-
memberships.add(entry.indexId);
|
|
48
|
-
const index = table.record.secondaryIndexes?.[entry.indexId];
|
|
49
|
-
if (index?.unique !== true || index.uniqueEnforced !== true) {
|
|
50
|
-
throw new Error(`Snapshot has orphaned UNIQUE-index membership: ${entry.indexId}`);
|
|
51
|
-
}
|
|
52
|
-
if (new Set(entry.keyTokens).size !== entry.keyTokens.length) {
|
|
53
|
-
throw new Error(`Snapshot repeats a UNIQUE-index key: ${index.name}`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
for (const [indexId, index] of Object.entries(table.record.secondaryIndexes ?? {})) {
|
|
57
|
-
if (indexNames.has(index.name))
|
|
58
|
-
throw new TypeError(`Index already exists: ${index.name}`);
|
|
59
|
-
indexNames.add(index.name);
|
|
60
|
-
if (index.uniqueEnforced === true && !memberships.has(indexId)) {
|
|
61
|
-
throw new Error(`Snapshot is missing UNIQUE-index membership: ${index.name}`);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
function encodeSegment(segment) {
|
|
67
|
-
const { rowIdStart, rowIdEndExclusive, rowIdSpans, ...rest } = segment;
|
|
68
|
-
return {
|
|
69
|
-
...rest,
|
|
70
|
-
rowIdStart: rowIdStart.toString(),
|
|
71
|
-
rowIdEndExclusive: rowIdEndExclusive.toString(),
|
|
72
|
-
...(rowIdSpans === undefined
|
|
73
|
-
? {}
|
|
74
|
-
: {
|
|
75
|
-
rowIdSpans: rowIdSpans.map((span) => ({
|
|
76
|
-
rowStart: span.rowStart,
|
|
77
|
-
rowCount: span.rowCount,
|
|
78
|
-
rowIdStart: span.rowIdStart.toString(),
|
|
79
|
-
})),
|
|
80
|
-
}),
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
function decodeSegment(segment) {
|
|
84
|
-
const { rowIdStart, rowIdEndExclusive, rowIdSpans, ...rest } = segment;
|
|
85
|
-
const spans = rowIdSpans?.map((span) => ({
|
|
86
|
-
rowStart: span.rowStart,
|
|
87
|
-
rowCount: span.rowCount,
|
|
88
|
-
rowIdStart: parseBigInt(span.rowIdStart, "segment row-ID span"),
|
|
89
|
-
}));
|
|
90
|
-
return {
|
|
91
|
-
...rest,
|
|
92
|
-
rowIdStart: parseBigInt(rowIdStart, "segment row-ID start"),
|
|
93
|
-
rowIdEndExclusive: parseBigInt(rowIdEndExclusive, "segment row-ID end"),
|
|
94
|
-
...(spans === undefined ? {} : { rowIdSpans: spans }),
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
function encodePosting(posting) {
|
|
98
|
-
const [first, ...rest] = posting.rowIds;
|
|
99
|
-
if (first === undefined)
|
|
100
|
-
return { term: posting.term, first: "0", deltas: [], tf: posting.tf };
|
|
101
|
-
const deltas = [];
|
|
102
|
-
let previous = first;
|
|
103
|
-
for (const rowId of rest) {
|
|
104
|
-
const gap = rowId - previous;
|
|
105
|
-
if (gap > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
106
|
-
throw new RangeError("Full-text row-ID gap is too large to snapshot");
|
|
107
|
-
}
|
|
108
|
-
deltas.push(Number(gap));
|
|
109
|
-
previous = rowId;
|
|
110
|
-
}
|
|
111
|
-
return { term: posting.term, first: first.toString(), deltas, tf: posting.tf };
|
|
112
|
-
}
|
|
113
|
-
function decodePosting(posting) {
|
|
114
|
-
const rowIds = [];
|
|
115
|
-
if (posting.tf.length > 0) {
|
|
116
|
-
let current = parseBigInt(posting.first, "full-text row ID");
|
|
117
|
-
rowIds.push(current);
|
|
118
|
-
for (const delta of posting.deltas) {
|
|
119
|
-
current += BigInt(delta);
|
|
120
|
-
rowIds.push(current);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return { term: posting.term, rowIds, tf: posting.tf };
|
|
124
|
-
}
|
|
125
|
-
function parseBigInt(value, context) {
|
|
126
|
-
if (!/^-?\d+$/.test(value))
|
|
127
|
-
throw new Error(`Snapshot has an invalid ${context}: ${value}`);
|
|
128
|
-
return BigInt(value);
|
|
129
|
-
}
|
|
6
|
+
/** Export and worker-transport chunk ceiling; direct imports accept any non-empty source chunk. */
|
|
7
|
+
export const MAX_SNAPSHOT_STREAM_CHUNK_BYTES = 1024 * 1024;
|
|
130
8
|
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* because it is JSON and its bulk is full-text posting lists and segment records, which compress
|
|
134
|
-
* by roughly four to one.
|
|
135
|
-
*/
|
|
136
|
-
export async function encodeSnapshot(snapshot) {
|
|
137
|
-
const header = {
|
|
138
|
-
formatVersion: SNAPSHOT_FORMAT_VERSION,
|
|
139
|
-
version: snapshot.version,
|
|
140
|
-
createdAt: snapshot.createdAt,
|
|
141
|
-
blocks: snapshot.blocks.map((block) => ({ id: block.id, length: block.bytes.byteLength })),
|
|
142
|
-
tables: snapshot.tables.map((table) => ({
|
|
143
|
-
record: table.record,
|
|
144
|
-
nextRowId: table.nextRowId.toString(),
|
|
145
|
-
autoIncrement: table.autoIncrement.map((entry) => ({
|
|
146
|
-
columnId: entry.columnId,
|
|
147
|
-
next: entry.next.toString(),
|
|
148
|
-
})),
|
|
149
|
-
uniqueKeyTokens: table.uniqueKeyTokens,
|
|
150
|
-
...(table.secondaryUniqueKeys === undefined
|
|
151
|
-
? {}
|
|
152
|
-
: { secondaryUniqueKeys: table.secondaryUniqueKeys }),
|
|
153
|
-
fts: table.fts.map((index) => ({
|
|
154
|
-
columnId: index.columnId,
|
|
155
|
-
coversVersion: index.coversVersion,
|
|
156
|
-
totalTokens: index.totalTokens,
|
|
157
|
-
chunks: index.chunks.map((chunk) => chunk.map(encodePosting)),
|
|
158
|
-
})),
|
|
159
|
-
})),
|
|
160
|
-
segments: snapshot.segments.map(encodeSegment),
|
|
161
|
-
transactions: snapshot.transactions,
|
|
162
|
-
};
|
|
163
|
-
const json = new TextEncoder().encode(JSON.stringify(header));
|
|
164
|
-
const stored = await gzipCodec.compress(json);
|
|
165
|
-
const payloadBytes = snapshot.blocks.reduce((total, block) => total + block.bytes.byteLength, 0);
|
|
166
|
-
const bytes = new Uint8Array(PREFIX_LENGTH + stored.byteLength + payloadBytes);
|
|
167
|
-
bytes.set(MAGIC, 0);
|
|
168
|
-
const view = new DataView(bytes.buffer);
|
|
169
|
-
view.setUint32(8, SNAPSHOT_FORMAT_VERSION, true);
|
|
170
|
-
view.setUint32(12, stored.byteLength, true);
|
|
171
|
-
view.setUint32(16, crc32(stored), true);
|
|
172
|
-
view.setUint32(20, json.byteLength, true);
|
|
173
|
-
view.setUint32(24, HEADER_CODEC_GZIP, true);
|
|
174
|
-
bytes.set(stored, PREFIX_LENGTH);
|
|
175
|
-
let offset = PREFIX_LENGTH + stored.byteLength;
|
|
176
|
-
for (const block of snapshot.blocks) {
|
|
177
|
-
bytes.set(block.bytes, offset);
|
|
178
|
-
offset += block.bytes.byteLength;
|
|
179
|
-
}
|
|
180
|
-
return bytes;
|
|
181
|
-
}
|
|
182
|
-
async function readHeader(bytes) {
|
|
183
|
-
if (bytes.byteLength < PREFIX_LENGTH)
|
|
184
|
-
throw new Error("Truncated snapshot header");
|
|
185
|
-
if (!MAGIC.every((byte, index) => bytes[index] === byte)) {
|
|
186
|
-
throw new Error("Not a Minnow snapshot");
|
|
187
|
-
}
|
|
188
|
-
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
189
|
-
const formatVersion = view.getUint32(8, true);
|
|
190
|
-
if (formatVersion !== SNAPSHOT_FORMAT_VERSION) {
|
|
191
|
-
throw new Error(`Unsupported snapshot version ${String(formatVersion)}`);
|
|
192
|
-
}
|
|
193
|
-
const storedLength = view.getUint32(12, true);
|
|
194
|
-
const payloadOffset = PREFIX_LENGTH + storedLength;
|
|
195
|
-
if (payloadOffset > bytes.byteLength)
|
|
196
|
-
throw new Error("Truncated snapshot header");
|
|
197
|
-
const stored = bytes.subarray(PREFIX_LENGTH, payloadOffset);
|
|
198
|
-
// Checked before the header is decompressed, let alone parsed: a damaged header must fail
|
|
199
|
-
// as a checksum mismatch rather than as whatever gzip or JSON happens to say about it.
|
|
200
|
-
if (crc32(stored) !== view.getUint32(16, true)) {
|
|
201
|
-
throw new Error("Snapshot header checksum mismatch");
|
|
202
|
-
}
|
|
203
|
-
const codec = view.getUint32(24, true);
|
|
204
|
-
if (codec !== HEADER_CODEC_NONE && codec !== HEADER_CODEC_GZIP) {
|
|
205
|
-
throw new Error(`Unsupported snapshot header codec ${String(codec)}`);
|
|
206
|
-
}
|
|
207
|
-
const json = codec === HEADER_CODEC_GZIP
|
|
208
|
-
? await gzipCodec.decompress(stored, view.getUint32(20, true))
|
|
209
|
-
: stored;
|
|
210
|
-
const header = JSON.parse(new TextDecoder().decode(json));
|
|
211
|
-
return { header, payloadOffset };
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Reads only the header. Cheap enough to run against a multi-hundred-megabyte file before
|
|
215
|
-
* deciding whether to load it.
|
|
9
|
+
* Reads and validates the canonical header from a materialized snapshot. The decoder closes its
|
|
10
|
+
* source immediately after the header; body checks run during import, where frames are consumed.
|
|
216
11
|
*/
|
|
217
12
|
export async function readSnapshotSummary(bytes) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
formatVersion: header.formatVersion,
|
|
221
|
-
version: header.version,
|
|
222
|
-
createdAt: header.createdAt,
|
|
223
|
-
tableCount: header.tables.length,
|
|
224
|
-
blockCount: header.blocks.length,
|
|
225
|
-
payloadBytes: bytes.byteLength - payloadOffset,
|
|
226
|
-
byteLength: bytes.byteLength,
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* Parses a snapshot and authenticates every block. `inspectBlock` verifies each payload's magic,
|
|
231
|
-
* version, declared lengths, and envelope checksum without decompressing it, so a corrupt file
|
|
232
|
-
* fails here rather than mid-query.
|
|
233
|
-
*/
|
|
234
|
-
export async function decodeSnapshot(bytes) {
|
|
235
|
-
const { header, payloadOffset } = await readHeader(bytes);
|
|
236
|
-
const declared = header.blocks.reduce((total, block) => total + block.length, 0);
|
|
237
|
-
if (payloadOffset + declared !== bytes.byteLength) {
|
|
238
|
-
throw new Error("Snapshot length mismatch");
|
|
13
|
+
if (!(bytes instanceof Uint8Array) || bytes.byteLength === 0) {
|
|
14
|
+
throw new TypeError("Snapshot bytes must be a non-empty Uint8Array");
|
|
239
15
|
}
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const payload = bytes.subarray(offset, offset + block.length);
|
|
244
|
-
inspectBlock(payload);
|
|
245
|
-
blocks.push({ id: block.id, bytes: payload });
|
|
246
|
-
offset += block.length;
|
|
247
|
-
}
|
|
248
|
-
const present = new Set(blocks.map((block) => block.id));
|
|
249
|
-
validateSnapshotCatalog(header.tables);
|
|
250
|
-
const segments = header.segments.map(decodeSegment);
|
|
251
|
-
for (const segment of segments) {
|
|
252
|
-
for (const ids of Object.values(segment.columnBlockIds)) {
|
|
253
|
-
for (const id of ids) {
|
|
254
|
-
if (!present.has(id))
|
|
255
|
-
throw new Error(`Snapshot segment references a missing block: ${id}`);
|
|
256
|
-
}
|
|
16
|
+
const entries = decodeSnapshotFrameStream((async function* () {
|
|
17
|
+
for (let offset = 0; offset < bytes.byteLength; offset += MAX_SNAPSHOT_STREAM_CHUNK_BYTES) {
|
|
18
|
+
yield bytes.subarray(offset, offset + MAX_SNAPSHOT_STREAM_CHUNK_BYTES);
|
|
257
19
|
}
|
|
20
|
+
})());
|
|
21
|
+
const first = await entries.next();
|
|
22
|
+
await entries.return(undefined);
|
|
23
|
+
if (first.done || first.value.type !== "header")
|
|
24
|
+
throw new Error("Snapshot header is missing");
|
|
25
|
+
return summaryFromHeader(first.value.header, bytes.byteLength);
|
|
26
|
+
}
|
|
27
|
+
function summaryFromHeader(header, byteLength) {
|
|
28
|
+
let payloadBytes = 0;
|
|
29
|
+
for (const kind of SNAPSHOT_FRAME_KINDS) {
|
|
30
|
+
const next = payloadBytes + header.kinds[kind].storedBytes;
|
|
31
|
+
if (!Number.isSafeInteger(next))
|
|
32
|
+
throw new RangeError("Snapshot payload is too large");
|
|
33
|
+
payloadBytes = next;
|
|
258
34
|
}
|
|
259
35
|
return {
|
|
260
|
-
|
|
36
|
+
formatVersion: header.formatVersion,
|
|
37
|
+
version: header.databaseVersion,
|
|
261
38
|
createdAt: header.createdAt,
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
columnId: entry.columnId,
|
|
267
|
-
next: parseBigInt(entry.next, "auto-increment counter"),
|
|
268
|
-
})),
|
|
269
|
-
uniqueKeyTokens: table.uniqueKeyTokens,
|
|
270
|
-
...(table.secondaryUniqueKeys === undefined
|
|
271
|
-
? {}
|
|
272
|
-
: { secondaryUniqueKeys: table.secondaryUniqueKeys }),
|
|
273
|
-
fts: table.fts.map((index) => ({
|
|
274
|
-
columnId: index.columnId,
|
|
275
|
-
coversVersion: index.coversVersion,
|
|
276
|
-
totalTokens: index.totalTokens,
|
|
277
|
-
chunks: index.chunks.map((chunk) => chunk.map(decodePosting)),
|
|
278
|
-
})),
|
|
279
|
-
})),
|
|
280
|
-
segments,
|
|
281
|
-
transactions: header.transactions,
|
|
282
|
-
blocks,
|
|
39
|
+
tableCount: header.kinds["catalog-page"].itemCount,
|
|
40
|
+
blockCount: header.kinds.block.itemCount,
|
|
41
|
+
payloadBytes,
|
|
42
|
+
byteLength,
|
|
283
43
|
};
|
|
284
44
|
}
|
|
285
|
-
/**
|
|
286
|
-
* Shared by both stores' exporters: given the manifest's live block set, decide which segments
|
|
287
|
-
* and transactions the snapshot needs.
|
|
288
|
-
*
|
|
289
|
-
* A segment is live when every block it references survives in the manifest — compaction
|
|
290
|
-
* supersedes a segment by dropping its blocks, so that test is exactly the reader's. A
|
|
291
|
-
* transaction is needed when a live segment names it, and only committed transactions qualify:
|
|
292
|
-
* an active one's artifacts are unpublished and would dangle. Their pending lists are cleared,
|
|
293
|
-
* because after the restore the manifest is the only root anything needs.
|
|
294
|
-
*
|
|
295
|
-
* `committedVersion` is preserved exactly, and the restored database keeps the captured version
|
|
296
|
-
* number rather than renumbering to 1. Segments sort by `logicalOrder ?? committedVersion`, so
|
|
297
|
-
* flattening those versions would let an update fold ahead of the insert it patches; and change
|
|
298
|
-
* detection compares committed versions against a snapshot version, which would misread any
|
|
299
|
-
* transaction numbered above the manifest.
|
|
300
|
-
*/
|
|
301
|
-
export function selectLiveRecords(input) {
|
|
302
|
-
const committed = new Map(input.transactions
|
|
303
|
-
.filter((record) => record.status === "committed" &&
|
|
304
|
-
record.committedVersion !== null &&
|
|
305
|
-
record.committedVersion <= input.version)
|
|
306
|
-
.map((record) => [record.id, record]));
|
|
307
|
-
const segments = input.segments.filter((segment) => {
|
|
308
|
-
if (!committed.has(segment.transactionId))
|
|
309
|
-
return false;
|
|
310
|
-
return Object.values(segment.columnBlockIds)
|
|
311
|
-
.flat()
|
|
312
|
-
.every((id) => input.liveBlockIds.has(id));
|
|
313
|
-
});
|
|
314
|
-
const usedTransactionIds = new Set(segments.map((segment) => segment.transactionId));
|
|
315
|
-
const transactions = [...committed.values()]
|
|
316
|
-
.filter((record) => usedTransactionIds.has(record.id))
|
|
317
|
-
.map((record) => ({ ...record, pendingBlockIds: [], pendingSegmentIds: [] }));
|
|
318
|
-
return { segments, transactions };
|
|
319
|
-
}
|
|
320
|
-
//# sourceMappingURL=snapshot.js.map
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type SyncFileHandle } from "./sync-file.js";
|
|
2
2
|
/**
|
|
3
3
|
* Packed, append-only extent files for bulk bytes — blocks and full-text base chunks.
|
|
4
4
|
*
|
|
5
5
|
* The leader appends into one tail extent through a handle it keeps open (microseconds per
|
|
6
6
|
* write) and seals it at a size threshold; sealed extents are immutable. Every payload's home
|
|
7
|
-
* is a placement — extent id, offset, length — recorded by the WAL
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* next append.
|
|
7
|
+
* is a placement — extent id, offset, length, and whole-payload checksum — recorded by the WAL
|
|
8
|
+
* entry that publishes it. Recovery verifies each new placement before applying its entry,
|
|
9
|
+
* re-derives the tail position from the consistent prefix, and overwrites any unpublished bytes
|
|
10
|
+
* beyond it on the next append.
|
|
11
11
|
*
|
|
12
12
|
* Extents assume a single writer (the OPFS adapter's leader), so holding handles — the tail's
|
|
13
13
|
* permanently, sealed ones' in a small read cache — never contends with anyone.
|
|
@@ -22,10 +22,16 @@ export interface ExtentFiles {
|
|
|
22
22
|
}): Promise<SyncFileHandle>;
|
|
23
23
|
deleteFile(path: readonly string[]): Promise<boolean>;
|
|
24
24
|
}
|
|
25
|
+
export interface ExtentPoolOptions {
|
|
26
|
+
/** @internal Deterministic checksum instrumentation for performance/correctness tests. */
|
|
27
|
+
_checksumForTests?: (bytes: Uint8Array) => number;
|
|
28
|
+
}
|
|
25
29
|
export interface Placement {
|
|
26
30
|
extent: number;
|
|
27
31
|
offset: number;
|
|
28
32
|
length: number;
|
|
33
|
+
/** CRC32 of every stored payload byte, independent of the payload's own format. */
|
|
34
|
+
checksum: number;
|
|
29
35
|
}
|
|
30
36
|
/** Persisted in checkpoints; placements in replayed WAL entries refine `tailOffset`. */
|
|
31
37
|
export interface ExtentMeta {
|
|
@@ -35,11 +41,31 @@ export interface ExtentMeta {
|
|
|
35
41
|
/** Live payload bytes per extent id; an extent at zero that is not the tail is deletable. */
|
|
36
42
|
liveBytes: Array<readonly [number, number]>;
|
|
37
43
|
}
|
|
44
|
+
/** Opaque O(1) rollback point for bytes not yet published by a WAL frame. */
|
|
45
|
+
export declare class ExtentBatchMark {
|
|
46
|
+
readonly tailExtentId: number;
|
|
47
|
+
readonly nextExtentId: number;
|
|
48
|
+
readonly tailOffset: number;
|
|
49
|
+
/** @internal Original counters are captured lazily only for extents the batch touches. */
|
|
50
|
+
readonly _liveBytesBefore: Map<number, number | undefined>;
|
|
51
|
+
constructor(tailExtentId: number, nextExtentId: number, tailOffset: number);
|
|
52
|
+
}
|
|
38
53
|
export declare class ExtentPool {
|
|
39
54
|
#private;
|
|
40
55
|
private constructor();
|
|
41
56
|
/** Opens (or creates) the tail extent. `meta` comes from the checkpoint, if any. */
|
|
42
|
-
static open(tree: ExtentFiles, meta: ExtentMeta | undefined): Promise<ExtentPool>;
|
|
57
|
+
static open(tree: ExtentFiles, meta: ExtentMeta | undefined, options?: ExtentPoolOptions): Promise<ExtentPool>;
|
|
58
|
+
/** Captures the exact append/accounting state before an unpublished multi-payload batch. */
|
|
59
|
+
markBatch(): ExtentBatchMark;
|
|
60
|
+
/** Publishes a successful batch; constant-time and allocation-free. */
|
|
61
|
+
commitBatch(mark: ExtentBatchMark): void;
|
|
62
|
+
/**
|
|
63
|
+
* Rolls back every append and accounting mutation since `mark`. This is deliberately an
|
|
64
|
+
* async slow path; successful appends retain the allocation-free hot path.
|
|
65
|
+
*/
|
|
66
|
+
rollbackBatch(mark: ExtentBatchMark): Promise<void>;
|
|
67
|
+
/** Removes an unpublished physical suffix left by a crash before its WAL publication. */
|
|
68
|
+
truncateTailToPublishedOffset(): void;
|
|
43
69
|
/**
|
|
44
70
|
* Replay hook: a WAL entry recorded this placement after the checkpoint was taken. Advances
|
|
45
71
|
* the tail position and live-byte accounting to match what the writer had done.
|
|
@@ -51,12 +77,18 @@ export declare class ExtentPool {
|
|
|
51
77
|
/** Appends synchronously; seals and rolls the tail first when it is full (the async part). */
|
|
52
78
|
append(bytes: Uint8Array, flush: boolean): Promise<Placement>;
|
|
53
79
|
read(placement: Placement): Promise<Uint8Array>;
|
|
80
|
+
/** Reads and verifies one complete opaque payload with exactly one checksum scan. */
|
|
81
|
+
readVerified(placement: Placement): Promise<Uint8Array>;
|
|
82
|
+
/** True when the complete placement currently exists; used to bound crash replay. */
|
|
83
|
+
contains(placement: Placement): Promise<boolean>;
|
|
54
84
|
/**
|
|
55
85
|
* Returns the sealed extent with the most dead space once less than half its physical bytes
|
|
56
86
|
* remain live. Relocating that extent keeps sealed-file bytes below twice live payload bytes;
|
|
57
87
|
* the append tail contributes only one bounded extent beyond that ratio.
|
|
58
88
|
*/
|
|
59
89
|
fragmentedExtentId(): Promise<number | undefined>;
|
|
90
|
+
/** Actual bytes of every checkpoint-known extent, including the held tail handle. */
|
|
91
|
+
physicalByteLengths(): Promise<Map<number, number>>;
|
|
60
92
|
/** Live-byte accounting for reclaimed payloads; reports extents now safe to delete. */
|
|
61
93
|
release(placements: Iterable<Placement>): number[];
|
|
62
94
|
/** Removes a drained extent's file and forgets it. Idempotent. */
|
|
@@ -66,4 +98,6 @@ export declare class ExtentPool {
|
|
|
66
98
|
close(): void;
|
|
67
99
|
}
|
|
68
100
|
export declare function extentPath(id: number): string[];
|
|
69
|
-
|
|
101
|
+
export declare function validPlacement(placement: Placement): boolean;
|
|
102
|
+
export declare function assertValidPlacement(placement: Placement): void;
|
|
103
|
+
export declare function assertValidExtentMeta(meta: ExtentMeta): void;
|