@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,95 +1,245 @@
|
|
|
1
|
+
import { readFully, writeFully } from "./sync-file.js";
|
|
2
|
+
import { crc32 } from "../../block-format/index.js";
|
|
3
|
+
/** Opaque O(1) rollback point for bytes not yet published by a WAL frame. */
|
|
4
|
+
export class ExtentBatchMark {
|
|
5
|
+
tailExtentId;
|
|
6
|
+
nextExtentId;
|
|
7
|
+
tailOffset;
|
|
8
|
+
/** @internal Original counters are captured lazily only for extents the batch touches. */
|
|
9
|
+
_liveBytesBefore = new Map();
|
|
10
|
+
constructor(tailExtentId, nextExtentId, tailOffset) {
|
|
11
|
+
this.tailExtentId = tailExtentId;
|
|
12
|
+
this.nextExtentId = nextExtentId;
|
|
13
|
+
this.tailOffset = tailOffset;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
1
16
|
const SEAL_BYTES = 8 * 1024 * 1024;
|
|
2
17
|
const READ_HANDLE_CACHE = 12;
|
|
3
18
|
export class ExtentPool {
|
|
4
19
|
#tree;
|
|
20
|
+
#checksum;
|
|
5
21
|
#tailId;
|
|
6
22
|
#nextId;
|
|
7
23
|
#tailOffset;
|
|
8
24
|
#tailHandle;
|
|
25
|
+
#tailDirty = false;
|
|
9
26
|
#liveBytes = new Map();
|
|
10
27
|
/** Sealed-extent read handles, LRU by insertion order. The tail is never in here. */
|
|
11
28
|
#readHandles = new Map();
|
|
12
|
-
|
|
29
|
+
#openingReadHandles = new Map();
|
|
30
|
+
#deletingExtents = new Set();
|
|
31
|
+
#activeBatch;
|
|
32
|
+
#closed = false;
|
|
33
|
+
constructor(tree, meta, tailHandle, checksum) {
|
|
13
34
|
this.#tree = tree;
|
|
35
|
+
this.#checksum = checksum;
|
|
14
36
|
this.#tailId = meta.tailId;
|
|
15
37
|
this.#nextId = meta.nextId;
|
|
16
38
|
this.#tailOffset = meta.tailOffset;
|
|
17
39
|
this.#tailHandle = tailHandle;
|
|
18
40
|
}
|
|
19
41
|
/** Opens (or creates) the tail extent. `meta` comes from the checkpoint, if any. */
|
|
20
|
-
static async open(tree, meta) {
|
|
42
|
+
static async open(tree, meta, options = {}) {
|
|
43
|
+
if (meta !== undefined)
|
|
44
|
+
assertValidExtentMeta(meta);
|
|
21
45
|
const tailId = meta?.tailExtentId ?? 0;
|
|
22
46
|
const nextId = meta?.nextExtentId ?? 1;
|
|
23
47
|
const tailHandle = await tree.openHandle(extentPath(tailId), { create: true });
|
|
24
|
-
const pool = new ExtentPool(tree, { tailId, nextId, tailOffset: meta?.tailOffset ?? 0 }, tailHandle);
|
|
48
|
+
const pool = new ExtentPool(tree, { tailId, nextId, tailOffset: meta?.tailOffset ?? 0 }, tailHandle, options._checksumForTests ?? crc32);
|
|
25
49
|
for (const [id, bytes] of meta?.liveBytes ?? [])
|
|
26
50
|
pool.#liveBytes.set(id, bytes);
|
|
51
|
+
if (tailHandle.getSize() < pool.#tailOffset) {
|
|
52
|
+
pool.close();
|
|
53
|
+
throw new Error(`OPFS tail extent ${String(tailId)} is shorter than its checkpointed offset: ` +
|
|
54
|
+
`${String(tailHandle.getSize())} < ${String(pool.#tailOffset)}`);
|
|
55
|
+
}
|
|
27
56
|
return pool;
|
|
28
57
|
}
|
|
58
|
+
/** Captures the exact append/accounting state before an unpublished multi-payload batch. */
|
|
59
|
+
markBatch() {
|
|
60
|
+
if (this.#activeBatch !== undefined)
|
|
61
|
+
throw new Error("An extent append batch is already active");
|
|
62
|
+
const mark = new ExtentBatchMark(this.#tailId, this.#nextId, this.#tailOffset);
|
|
63
|
+
this.#activeBatch = mark;
|
|
64
|
+
return mark;
|
|
65
|
+
}
|
|
66
|
+
/** Publishes a successful batch; constant-time and allocation-free. */
|
|
67
|
+
commitBatch(mark) {
|
|
68
|
+
if (this.#activeBatch !== mark)
|
|
69
|
+
throw new Error("Extent batch mark is not active");
|
|
70
|
+
this.#activeBatch = undefined;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Rolls back every append and accounting mutation since `mark`. This is deliberately an
|
|
74
|
+
* async slow path; successful appends retain the allocation-free hot path.
|
|
75
|
+
*/
|
|
76
|
+
async rollbackBatch(mark) {
|
|
77
|
+
if (this.#activeBatch !== mark)
|
|
78
|
+
throw new Error("Extent batch mark is not active");
|
|
79
|
+
if (mark.nextExtentId > this.#nextId) {
|
|
80
|
+
throw new Error("Cannot roll an extent pool forward to a future batch mark");
|
|
81
|
+
}
|
|
82
|
+
this.#tailHandle.close();
|
|
83
|
+
// Every id allocated after the mark is unpublished by definition. Delete it before
|
|
84
|
+
// restoring the former tail so repeated refused batches cannot accumulate sealed files.
|
|
85
|
+
for (let id = mark.nextExtentId; id < this.#nextId; id += 1) {
|
|
86
|
+
await this.#closeCachedReadHandle(id);
|
|
87
|
+
await this.#tree.deleteFile(extentPath(id));
|
|
88
|
+
}
|
|
89
|
+
await this.#closeCachedReadHandle(mark.tailExtentId);
|
|
90
|
+
this.#tailHandle = await this.#tree.openHandle(extentPath(mark.tailExtentId), { create: true });
|
|
91
|
+
this.#tailHandle.truncate(mark.tailOffset);
|
|
92
|
+
this.#tailId = mark.tailExtentId;
|
|
93
|
+
this.#nextId = mark.nextExtentId;
|
|
94
|
+
this.#tailOffset = mark.tailOffset;
|
|
95
|
+
this.#tailDirty = false;
|
|
96
|
+
for (const [id, bytes] of mark._liveBytesBefore) {
|
|
97
|
+
if (bytes === undefined)
|
|
98
|
+
this.#liveBytes.delete(id);
|
|
99
|
+
else
|
|
100
|
+
this.#liveBytes.set(id, bytes);
|
|
101
|
+
}
|
|
102
|
+
this.#activeBatch = undefined;
|
|
103
|
+
}
|
|
104
|
+
async #closeCachedReadHandle(id) {
|
|
105
|
+
const opening = this.#openingReadHandles.get(id);
|
|
106
|
+
if (opening !== undefined)
|
|
107
|
+
await opening.promise.catch(() => undefined);
|
|
108
|
+
const cached = this.#readHandles.get(id);
|
|
109
|
+
if (cached !== undefined && cached.readers > 0) {
|
|
110
|
+
await new Promise((resolve) => cached.drained.push(resolve));
|
|
111
|
+
}
|
|
112
|
+
this.#readHandles.get(id)?.handle.close();
|
|
113
|
+
this.#readHandles.delete(id);
|
|
114
|
+
}
|
|
115
|
+
/** Removes an unpublished physical suffix left by a crash before its WAL publication. */
|
|
116
|
+
truncateTailToPublishedOffset() {
|
|
117
|
+
const size = this.#tailHandle.getSize();
|
|
118
|
+
if (size < this.#tailOffset) {
|
|
119
|
+
throw new Error(`OPFS tail extent ${String(this.#tailId)} is shorter than its published offset: ` +
|
|
120
|
+
`${String(size)} < ${String(this.#tailOffset)}`);
|
|
121
|
+
}
|
|
122
|
+
if (size > this.#tailOffset)
|
|
123
|
+
this.#tailHandle.truncate(this.#tailOffset);
|
|
124
|
+
}
|
|
29
125
|
/**
|
|
30
126
|
* Replay hook: a WAL entry recorded this placement after the checkpoint was taken. Advances
|
|
31
127
|
* the tail position and live-byte accounting to match what the writer had done.
|
|
32
128
|
*/
|
|
33
129
|
restorePlacement(placement) {
|
|
34
|
-
|
|
130
|
+
assertValidPlacement(placement);
|
|
131
|
+
const liveBytes = safeExtentSum(this.#liveBytes.get(placement.extent) ?? 0, placement.length, "Extent live bytes");
|
|
132
|
+
this.#liveBytes.set(placement.extent, liveBytes);
|
|
35
133
|
if (placement.extent === this.#tailId) {
|
|
36
|
-
this.#tailOffset = Math.max(this.#tailOffset, placement.offset
|
|
134
|
+
this.#tailOffset = Math.max(this.#tailOffset, safeExtentSum(placement.offset, placement.length, "Extent tail offset"));
|
|
37
135
|
}
|
|
38
136
|
if (placement.extent >= this.#nextId) {
|
|
39
137
|
// The writer sealed past the checkpointed tail; adopt its numbering.
|
|
40
|
-
this.#nextId = placement.extent
|
|
138
|
+
this.#nextId = safeExtentSuccessor(placement.extent, "Extent ID");
|
|
41
139
|
}
|
|
42
140
|
}
|
|
43
141
|
/** Replay hook: the tail recorded by placements may belong to a newer extent than ours. */
|
|
44
142
|
async adoptTail(extentId, offset) {
|
|
143
|
+
assertValidExtentCoordinate(extentId, "Extent ID");
|
|
144
|
+
assertValidExtentCoordinate(offset, "Extent tail offset");
|
|
145
|
+
const nextId = safeExtentSuccessor(extentId, "Extent ID");
|
|
45
146
|
if (extentId === this.#tailId) {
|
|
46
147
|
this.#tailOffset = Math.max(this.#tailOffset, offset);
|
|
47
148
|
return;
|
|
48
149
|
}
|
|
49
150
|
this.#tailHandle.close();
|
|
151
|
+
// Recovery validation may have opened the newer extent through the read cache before it
|
|
152
|
+
// was known to be the tail. Release that shared-read role before taking the tail lock.
|
|
153
|
+
this.#readHandles.get(extentId)?.handle.close();
|
|
154
|
+
this.#readHandles.delete(extentId);
|
|
50
155
|
this.#tailHandle = await this.#tree.openHandle(extentPath(extentId), { create: true });
|
|
51
156
|
this.#tailId = extentId;
|
|
52
157
|
this.#tailOffset = offset;
|
|
53
|
-
this.#nextId = Math.max(this.#nextId,
|
|
158
|
+
this.#nextId = Math.max(this.#nextId, nextId);
|
|
54
159
|
}
|
|
55
160
|
get tailExtentId() {
|
|
56
161
|
return this.#tailId;
|
|
57
162
|
}
|
|
58
163
|
/** Appends synchronously; seals and rolls the tail first when it is full (the async part). */
|
|
59
164
|
async append(bytes, flush) {
|
|
60
|
-
|
|
165
|
+
const initialEnd = safeExtentSum(this.#tailOffset, bytes.byteLength, "Extent tail offset");
|
|
166
|
+
if (this.#tailOffset > 0 && initialEnd > SEAL_BYTES) {
|
|
61
167
|
await this.#seal();
|
|
62
168
|
}
|
|
169
|
+
const nextOffset = safeExtentSum(this.#tailOffset, bytes.byteLength, "Extent tail offset");
|
|
170
|
+
const nextLiveBytes = safeExtentSum(this.#liveBytes.get(this.#tailId) ?? 0, bytes.byteLength, "Extent live bytes");
|
|
63
171
|
const placement = {
|
|
64
172
|
extent: this.#tailId,
|
|
65
173
|
offset: this.#tailOffset,
|
|
66
174
|
length: bytes.byteLength,
|
|
175
|
+
checksum: this.#checksum(bytes),
|
|
67
176
|
};
|
|
68
|
-
this.#tailHandle
|
|
177
|
+
writeFully(this.#tailHandle, bytes, placement.offset, `appending extent ${String(placement.extent)}`);
|
|
178
|
+
this.#tailDirty = true;
|
|
69
179
|
if (flush)
|
|
70
|
-
this
|
|
71
|
-
this.#tailOffset
|
|
72
|
-
this.#
|
|
180
|
+
this.flush();
|
|
181
|
+
this.#tailOffset = nextOffset;
|
|
182
|
+
this.#rememberLiveBytes(this.#tailId);
|
|
183
|
+
this.#liveBytes.set(this.#tailId, nextLiveBytes);
|
|
73
184
|
return placement;
|
|
74
185
|
}
|
|
75
186
|
async #seal() {
|
|
187
|
+
const nextId = safeExtentSuccessor(this.#nextId, "Extent ID");
|
|
188
|
+
// Relaxed durability batches flushes instead of flushing every append. Sealing is the last
|
|
189
|
+
// chance to make this immutable extent durable before its held handle is released.
|
|
190
|
+
this.flush();
|
|
76
191
|
this.#tailHandle.close();
|
|
77
192
|
// The now-sealed extent may still be read; it re-opens on demand through the cache.
|
|
78
193
|
this.#tailId = this.#nextId;
|
|
79
|
-
this.#nextId
|
|
194
|
+
this.#nextId = nextId;
|
|
80
195
|
this.#tailOffset = 0;
|
|
81
196
|
this.#tailHandle = await this.#tree.openHandle(extentPath(this.#tailId), { create: true });
|
|
82
197
|
}
|
|
83
198
|
async read(placement) {
|
|
84
|
-
|
|
85
|
-
if (placement.extent === this.#tailId)
|
|
86
|
-
this.#
|
|
87
|
-
|
|
199
|
+
assertValidPlacement(placement);
|
|
200
|
+
if (placement.extent === this.#tailId)
|
|
201
|
+
return this.#readFromHandle(placement, this.#tailHandle);
|
|
202
|
+
const acquired = this.#acquireReadHandle(placement.extent);
|
|
203
|
+
const handle = isHandlePromise(acquired) ? await acquired : acquired;
|
|
204
|
+
try {
|
|
205
|
+
return this.#readFromHandle(placement, handle);
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
this.#releaseReadHandle(placement.extent);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/** Reads and verifies one complete opaque payload with exactly one checksum scan. */
|
|
212
|
+
async readVerified(placement) {
|
|
213
|
+
const bytes = await this.read(placement);
|
|
214
|
+
if (this.#checksum(bytes) !== placement.checksum) {
|
|
215
|
+
throw new Error(`Extent placement checksum mismatch: ${String(placement.extent)}:` +
|
|
216
|
+
`${String(placement.offset)}+${String(placement.length)}`);
|
|
88
217
|
}
|
|
89
|
-
const handle = await this.#readHandle(placement.extent);
|
|
90
|
-
handle.read(bytes, { at: placement.offset });
|
|
91
218
|
return bytes;
|
|
92
219
|
}
|
|
220
|
+
/** True when the complete placement currently exists; used to bound crash replay. */
|
|
221
|
+
async contains(placement) {
|
|
222
|
+
if (!validPlacement(placement))
|
|
223
|
+
return false;
|
|
224
|
+
try {
|
|
225
|
+
if (placement.extent === this.#tailId) {
|
|
226
|
+
return placement.offset + placement.length <= this.#tailHandle.getSize();
|
|
227
|
+
}
|
|
228
|
+
const acquired = this.#acquireReadHandle(placement.extent);
|
|
229
|
+
const handle = isHandlePromise(acquired) ? await acquired : acquired;
|
|
230
|
+
try {
|
|
231
|
+
return placement.offset + placement.length <= handle.getSize();
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
this.#releaseReadHandle(placement.extent);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
if (error instanceof DOMException && error.name === "NotFoundError")
|
|
239
|
+
return false;
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
93
243
|
/**
|
|
94
244
|
* Returns the sealed extent with the most dead space once less than half its physical bytes
|
|
95
245
|
* remain live. Relocating that extent keeps sealed-file bytes below twice live payload bytes;
|
|
@@ -100,7 +250,15 @@ export class ExtentPool {
|
|
|
100
250
|
for (const [id, liveBytes] of this.#liveBytes) {
|
|
101
251
|
if (id === this.#tailId || liveBytes === 0)
|
|
102
252
|
continue;
|
|
103
|
-
const
|
|
253
|
+
const acquired = this.#acquireReadHandle(id);
|
|
254
|
+
const handle = isHandlePromise(acquired) ? await acquired : acquired;
|
|
255
|
+
let size;
|
|
256
|
+
try {
|
|
257
|
+
size = handle.getSize();
|
|
258
|
+
}
|
|
259
|
+
finally {
|
|
260
|
+
this.#releaseReadHandle(id);
|
|
261
|
+
}
|
|
104
262
|
if (size === 0 || liveBytes * 2 >= size)
|
|
105
263
|
continue;
|
|
106
264
|
const wasteRatio = (size - liveBytes) / size;
|
|
@@ -110,30 +268,141 @@ export class ExtentPool {
|
|
|
110
268
|
}
|
|
111
269
|
return candidate?.id;
|
|
112
270
|
}
|
|
113
|
-
|
|
271
|
+
/** Actual bytes of every checkpoint-known extent, including the held tail handle. */
|
|
272
|
+
async physicalByteLengths() {
|
|
273
|
+
const sizes = new Map();
|
|
274
|
+
const ids = new Set([...this.#liveBytes.keys(), this.#tailId]);
|
|
275
|
+
for (const id of ids) {
|
|
276
|
+
if (id === this.#tailId) {
|
|
277
|
+
sizes.set(id, this.#tailHandle.getSize());
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
const acquired = this.#acquireReadHandle(id);
|
|
281
|
+
const handle = isHandlePromise(acquired) ? await acquired : acquired;
|
|
282
|
+
try {
|
|
283
|
+
sizes.set(id, handle.getSize());
|
|
284
|
+
}
|
|
285
|
+
finally {
|
|
286
|
+
this.#releaseReadHandle(id);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return sizes;
|
|
290
|
+
}
|
|
291
|
+
/** Cached hits return the handle directly: no promise allocation or extra await on reads. */
|
|
292
|
+
#acquireReadHandle(extent) {
|
|
293
|
+
if (this.#closed)
|
|
294
|
+
throw new Error("This extent pool is closed");
|
|
295
|
+
if (this.#deletingExtents.has(extent)) {
|
|
296
|
+
throw new Error(`Extent ${String(extent)} is being deleted`);
|
|
297
|
+
}
|
|
114
298
|
const cached = this.#readHandles.get(extent);
|
|
115
299
|
if (cached !== undefined) {
|
|
116
300
|
// Refresh recency.
|
|
117
301
|
this.#readHandles.delete(extent);
|
|
118
302
|
this.#readHandles.set(extent, cached);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
this.#
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
303
|
+
cached.readers += 1;
|
|
304
|
+
return cached.handle;
|
|
305
|
+
}
|
|
306
|
+
const opening = this.#openingReadHandles.get(extent);
|
|
307
|
+
if (opening !== undefined) {
|
|
308
|
+
opening.reservations += 1;
|
|
309
|
+
return opening.promise;
|
|
310
|
+
}
|
|
311
|
+
const pending = { reservations: 1 };
|
|
312
|
+
pending.promise = this.#tree.openHandle(extentPath(extent), { create: false }).then((handle) => {
|
|
313
|
+
this.#openingReadHandles.delete(extent);
|
|
314
|
+
// An open already in flight owns reader reservations. A deletion that starts later
|
|
315
|
+
// blocks new acquisitions and awaits this promise plus those readers; rejecting the
|
|
316
|
+
// earlier reservations here would let relocation invalidate a selected placement.
|
|
317
|
+
if (this.#closed) {
|
|
318
|
+
handle.close();
|
|
319
|
+
throw new Error(`Extent ${String(extent)} became unavailable while opening`);
|
|
128
320
|
}
|
|
321
|
+
this.#readHandles.set(extent, {
|
|
322
|
+
handle,
|
|
323
|
+
readers: pending.reservations,
|
|
324
|
+
drained: [],
|
|
325
|
+
});
|
|
326
|
+
this.#evictReadHandles();
|
|
327
|
+
return handle;
|
|
328
|
+
}, (error) => {
|
|
329
|
+
this.#openingReadHandles.delete(extent);
|
|
330
|
+
throw error;
|
|
331
|
+
});
|
|
332
|
+
this.#openingReadHandles.set(extent, pending);
|
|
333
|
+
return pending.promise;
|
|
334
|
+
}
|
|
335
|
+
#releaseReadHandle(extent) {
|
|
336
|
+
const cached = this.#readHandles.get(extent);
|
|
337
|
+
if (cached === undefined)
|
|
338
|
+
return;
|
|
339
|
+
cached.readers -= 1;
|
|
340
|
+
if (cached.readers < 0)
|
|
341
|
+
throw new Error(`Extent ${String(extent)} read lease underflow`);
|
|
342
|
+
if (cached.readers === 0) {
|
|
343
|
+
for (const resolve of cached.drained.splice(0))
|
|
344
|
+
resolve();
|
|
345
|
+
this.#evictReadHandles();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
#evictReadHandles() {
|
|
349
|
+
while (this.#readHandles.size > READ_HANDLE_CACHE) {
|
|
350
|
+
const idle = [...this.#readHandles].find(([, cached]) => cached.readers === 0);
|
|
351
|
+
if (idle === undefined)
|
|
352
|
+
return;
|
|
353
|
+
const [extent, cached] = idle;
|
|
354
|
+
cached.handle.close();
|
|
355
|
+
this.#readHandles.delete(extent);
|
|
129
356
|
}
|
|
130
|
-
|
|
357
|
+
}
|
|
358
|
+
#readFromHandle(placement, handle) {
|
|
359
|
+
const end = placement.offset + placement.length;
|
|
360
|
+
const size = handle.getSize();
|
|
361
|
+
if (end > size) {
|
|
362
|
+
throw new Error(`Extent placement exceeds its file: ${String(placement.extent)}:` +
|
|
363
|
+
`${String(placement.offset)}+${String(placement.length)} > ${String(size)}`);
|
|
364
|
+
}
|
|
365
|
+
const bytes = new Uint8Array(placement.length);
|
|
366
|
+
readFully(handle, bytes, placement.offset, `reading extent ${String(placement.extent)} placement`);
|
|
367
|
+
return bytes;
|
|
131
368
|
}
|
|
132
369
|
/** Live-byte accounting for reclaimed payloads; reports extents now safe to delete. */
|
|
133
370
|
release(placements) {
|
|
371
|
+
const decrements = new Map();
|
|
372
|
+
const ranges = new Map();
|
|
134
373
|
for (const placement of placements) {
|
|
135
|
-
|
|
136
|
-
|
|
374
|
+
assertValidPlacement(placement);
|
|
375
|
+
decrements.set(placement.extent, (decrements.get(placement.extent) ?? 0) + placement.length);
|
|
376
|
+
if (placement.length > 0) {
|
|
377
|
+
const extentRanges = ranges.get(placement.extent) ?? [];
|
|
378
|
+
extentRanges.push({ start: placement.offset, end: placement.offset + placement.length });
|
|
379
|
+
ranges.set(placement.extent, extentRanges);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
for (const [id, extentRanges] of ranges) {
|
|
383
|
+
extentRanges.sort((left, right) => left.start - right.start || left.end - right.end);
|
|
384
|
+
for (let index = 1; index < extentRanges.length; index += 1) {
|
|
385
|
+
const previous = extentRanges[index - 1];
|
|
386
|
+
const current = extentRanges[index];
|
|
387
|
+
if (previous !== undefined && current !== undefined && current.start < previous.end) {
|
|
388
|
+
throw new Error(`Extent ${String(id)} release contains overlapping or duplicate placements: ` +
|
|
389
|
+
`${String(previous.start)}..${String(previous.end)} and ` +
|
|
390
|
+
`${String(current.start)}..${String(current.end)}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// Validate the entire batch before mutating one counter. Underflow is an invariant
|
|
395
|
+
// violation, never evidence that an extent is empty and safe to delete.
|
|
396
|
+
for (const [id, decrement] of decrements) {
|
|
397
|
+
const live = this.#liveBytes.get(id);
|
|
398
|
+
if (live === undefined || decrement > live) {
|
|
399
|
+
throw new Error(`Extent ${String(id)} live-byte accounting underflow: release ${String(decrement)} ` +
|
|
400
|
+
`from ${live === undefined ? "an unknown extent" : String(live)}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
for (const [id, decrement] of decrements) {
|
|
404
|
+
this.#rememberLiveBytes(id);
|
|
405
|
+
this.#liveBytes.set(id, (this.#liveBytes.get(id) ?? 0) - decrement);
|
|
137
406
|
}
|
|
138
407
|
const deletable = [];
|
|
139
408
|
for (const [id, bytes] of this.#liveBytes) {
|
|
@@ -144,12 +413,30 @@ export class ExtentPool {
|
|
|
144
413
|
}
|
|
145
414
|
/** Removes a drained extent's file and forgets it. Idempotent. */
|
|
146
415
|
async deleteExtent(id) {
|
|
416
|
+
if (this.#activeBatch !== undefined) {
|
|
417
|
+
throw new Error("Cannot delete an extent while an unpublished batch is active");
|
|
418
|
+
}
|
|
147
419
|
if (id === this.#tailId)
|
|
148
420
|
return;
|
|
149
|
-
this.#
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
421
|
+
this.#deletingExtents.add(id);
|
|
422
|
+
try {
|
|
423
|
+
const opening = this.#openingReadHandles.get(id);
|
|
424
|
+
if (opening !== undefined)
|
|
425
|
+
await opening.promise.catch(() => undefined);
|
|
426
|
+
const cached = this.#readHandles.get(id);
|
|
427
|
+
if (cached !== undefined && cached.readers > 0) {
|
|
428
|
+
await new Promise((resolve) => cached.drained.push(resolve));
|
|
429
|
+
}
|
|
430
|
+
this.#readHandles.get(id)?.handle.close();
|
|
431
|
+
this.#readHandles.delete(id);
|
|
432
|
+
await this.#tree.deleteFile(extentPath(id));
|
|
433
|
+
// Preserve the zero-live entry when the physical delete is refused. The caller can then
|
|
434
|
+
// retry it and account the cleanup debt instead of silently orphaning an untracked file.
|
|
435
|
+
this.#liveBytes.delete(id);
|
|
436
|
+
}
|
|
437
|
+
finally {
|
|
438
|
+
this.#deletingExtents.delete(id);
|
|
439
|
+
}
|
|
153
440
|
}
|
|
154
441
|
meta() {
|
|
155
442
|
return {
|
|
@@ -160,16 +447,92 @@ export class ExtentPool {
|
|
|
160
447
|
};
|
|
161
448
|
}
|
|
162
449
|
flush() {
|
|
450
|
+
if (!this.#tailDirty)
|
|
451
|
+
return;
|
|
163
452
|
this.#tailHandle.flush();
|
|
453
|
+
this.#tailDirty = false;
|
|
164
454
|
}
|
|
165
455
|
close() {
|
|
456
|
+
this.#closed = true;
|
|
166
457
|
this.#tailHandle.close();
|
|
167
|
-
for (const handle of this.#readHandles.values())
|
|
458
|
+
for (const { handle } of this.#readHandles.values())
|
|
168
459
|
handle.close();
|
|
169
460
|
this.#readHandles.clear();
|
|
170
461
|
}
|
|
462
|
+
#rememberLiveBytes(id) {
|
|
463
|
+
const batch = this.#activeBatch;
|
|
464
|
+
if (batch === undefined || batch._liveBytesBefore.has(id))
|
|
465
|
+
return;
|
|
466
|
+
batch._liveBytesBefore.set(id, this.#liveBytes.get(id));
|
|
467
|
+
}
|
|
171
468
|
}
|
|
172
469
|
export function extentPath(id) {
|
|
173
470
|
return ["extents", String(id).padStart(6, "0")];
|
|
174
471
|
}
|
|
175
|
-
|
|
472
|
+
export function validPlacement(placement) {
|
|
473
|
+
return (Number.isSafeInteger(placement.extent) &&
|
|
474
|
+
placement.extent >= 0 &&
|
|
475
|
+
placement.extent < Number.MAX_SAFE_INTEGER &&
|
|
476
|
+
Number.isSafeInteger(placement.offset) &&
|
|
477
|
+
placement.offset >= 0 &&
|
|
478
|
+
Number.isSafeInteger(placement.length) &&
|
|
479
|
+
placement.length >= 0 &&
|
|
480
|
+
Number.isSafeInteger(placement.checksum) &&
|
|
481
|
+
placement.checksum >= 0 &&
|
|
482
|
+
placement.checksum <= 0xffff_ffff &&
|
|
483
|
+
placement.offset + placement.length <= Number.MAX_SAFE_INTEGER);
|
|
484
|
+
}
|
|
485
|
+
export function assertValidPlacement(placement) {
|
|
486
|
+
if (!validPlacement(placement)) {
|
|
487
|
+
throw new TypeError(`Invalid extent placement: ${String(placement.extent)}:${String(placement.offset)}+` +
|
|
488
|
+
`${String(placement.length)} checksum ${String(placement.checksum)}`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
export function assertValidExtentMeta(meta) {
|
|
492
|
+
if (!Number.isSafeInteger(meta.nextExtentId) ||
|
|
493
|
+
meta.nextExtentId < 1 ||
|
|
494
|
+
!Number.isSafeInteger(meta.tailExtentId) ||
|
|
495
|
+
meta.tailExtentId < 0 ||
|
|
496
|
+
meta.tailExtentId >= meta.nextExtentId ||
|
|
497
|
+
!Number.isSafeInteger(meta.tailOffset) ||
|
|
498
|
+
meta.tailOffset < 0 ||
|
|
499
|
+
!Array.isArray(meta.liveBytes)) {
|
|
500
|
+
throw new TypeError("Invalid OPFS extent checkpoint metadata");
|
|
501
|
+
}
|
|
502
|
+
const ids = new Set();
|
|
503
|
+
for (const entry of meta.liveBytes) {
|
|
504
|
+
const [id, bytes] = entry;
|
|
505
|
+
if (!Number.isSafeInteger(id) ||
|
|
506
|
+
id < 0 ||
|
|
507
|
+
id >= meta.nextExtentId ||
|
|
508
|
+
ids.has(id) ||
|
|
509
|
+
!Number.isSafeInteger(bytes) ||
|
|
510
|
+
bytes < 0) {
|
|
511
|
+
throw new TypeError(`Invalid OPFS extent live-byte entry: ${String(id)} => ${String(bytes)}`);
|
|
512
|
+
}
|
|
513
|
+
ids.add(id);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function isHandlePromise(value) {
|
|
517
|
+
return typeof value.then === "function";
|
|
518
|
+
}
|
|
519
|
+
function assertValidExtentCoordinate(value, label) {
|
|
520
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
521
|
+
throw new RangeError(`${label} is out of range`);
|
|
522
|
+
}
|
|
523
|
+
function safeExtentSuccessor(value, label) {
|
|
524
|
+
if (!Number.isSafeInteger(value) || value < 0 || value >= Number.MAX_SAFE_INTEGER) {
|
|
525
|
+
throw new RangeError(`${label} cannot exceed the safe integer range`);
|
|
526
|
+
}
|
|
527
|
+
return value + 1;
|
|
528
|
+
}
|
|
529
|
+
function safeExtentSum(left, right, label) {
|
|
530
|
+
if (!Number.isSafeInteger(left) ||
|
|
531
|
+
left < 0 ||
|
|
532
|
+
!Number.isSafeInteger(right) ||
|
|
533
|
+
right < 0 ||
|
|
534
|
+
left > Number.MAX_SAFE_INTEGER - right) {
|
|
535
|
+
throw new RangeError(`${label} cannot exceed the safe integer range`);
|
|
536
|
+
}
|
|
537
|
+
return left + right;
|
|
538
|
+
}
|
|
@@ -22,9 +22,8 @@
|
|
|
22
22
|
* `FileSystemSyncAccessHandle` satisfies it as-is, and so does anything with positioned
|
|
23
23
|
* synchronous I/O. See `/docs/storage/custom` for the guide.
|
|
24
24
|
*/
|
|
25
|
-
export type
|
|
25
|
+
export { readFully, writeFully, type SyncFileHandle } from "./sync-file.js";
|
|
26
26
|
export { RecordCore, type PhysicalBlocks, type RecordCoreState } from "./record-core.js";
|
|
27
|
-
export { WalWriter, replayWalFrames } from "./wal.js";
|
|
28
|
-
export { ExtentPool, extentPath, type ExtentFiles, type ExtentMeta, type Placement, } from "./extents.js";
|
|
27
|
+
export { MAX_WAL_FRAME_BYTES, WalWriter, iterateWalFrames, replayWalFrames, type ReplayedWalFrame, } from "./wal.js";
|
|
28
|
+
export { ExtentPool, assertValidExtentMeta, assertValidPlacement, extentPath, validPlacement, type ExtentBatchMark, type ExtentFiles, type ExtentMeta, type ExtentPoolOptions, type Placement, } from "./extents.js";
|
|
29
29
|
export { LOG_FORMAT_VERSION, decodeChunk, decodePostingChunk, decodeRecordJson, decodeSyncCheckpoint, encodeChunk, encodePostingChunk, encodeRecordJson, encodeSyncCheckpoint, } from "./wire.js";
|
|
30
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,5 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The storage adapter toolkit: optional building blocks for implementing `BlockStore` against
|
|
3
|
+
* a new substrate. Nothing here is part of the storage contract — the contract is the
|
|
4
|
+
* `BlockStore` capability interfaces, their documented rules, and the conformance kit in
|
|
5
|
+
* `@minnowdb/core/testing` — and the engine never references this module. An adapter is free
|
|
6
|
+
* to ignore all of it and store records however its platform does best.
|
|
7
|
+
*
|
|
8
|
+
* What it offers, and which shipped adapter proves each piece:
|
|
9
|
+
*
|
|
10
|
+
* - `RecordCore` — the synchronous record-state machine behind `MemoryBlockStore` and
|
|
11
|
+
* `OpfsBlockStore`: every record family, every validation order, every typed conflict the
|
|
12
|
+
* contract requires, with `dump()`/`load()` for checkpointing. An adapter that owns a
|
|
13
|
+
* single-writer view of the records (a leader, a lone process) can wrap it and spend its
|
|
14
|
+
* own code purely on persistence.
|
|
15
|
+
* - `WalWriter` / `replayWalFrames` — checksummed write-ahead-log frames over any
|
|
16
|
+
* `SyncFileHandle`.
|
|
17
|
+
* - `ExtentPool` — packed append-only files for bulk bytes, addressed by `Placement`.
|
|
18
|
+
* - `encodeRecordJson` and the envelope codecs — JSON with bigint support, checksummed and
|
|
19
|
+
* versioned, for frames, checkpoints, and immutable chunks.
|
|
20
|
+
*
|
|
21
|
+
* `SyncFileHandle` is the only substrate assumption the file-shaped pieces make; the browser's
|
|
22
|
+
* `FileSystemSyncAccessHandle` satisfies it as-is, and so does anything with positioned
|
|
23
|
+
* synchronous I/O. See `/docs/storage/custom` for the guide.
|
|
24
|
+
*/
|
|
25
|
+
export { readFully, writeFully } from "./sync-file.js";
|
|
1
26
|
export { RecordCore } from "./record-core.js";
|
|
2
|
-
export { WalWriter, replayWalFrames } from "./wal.js";
|
|
3
|
-
export { ExtentPool, extentPath, } from "./extents.js";
|
|
27
|
+
export { MAX_WAL_FRAME_BYTES, WalWriter, iterateWalFrames, replayWalFrames, } from "./wal.js";
|
|
28
|
+
export { ExtentPool, assertValidExtentMeta, assertValidPlacement, extentPath, validPlacement, } from "./extents.js";
|
|
4
29
|
export { LOG_FORMAT_VERSION, decodeChunk, decodePostingChunk, decodeRecordJson, decodeSyncCheckpoint, encodeChunk, encodePostingChunk, encodeRecordJson, encodeSyncCheckpoint, } from "./wire.js";
|
|
5
|
-
//# sourceMappingURL=index.js.map
|