@minnowdb/core 0.7.0 → 0.7.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/dist/block-format/block.js +254 -325
- package/dist/block-format/checksum.js +30 -52
- package/dist/block-format/codecs.js +112 -125
- package/dist/block-format/column.js +187 -205
- package/dist/block-format/index.js +5 -1
- package/dist/block-format/physical.js +371 -387
- package/dist/block-format/types.js +4 -1
- package/dist/block-format/unicode.js +30 -39
- package/dist/date-value.js +67 -78
- package/dist/engine/artifact-cache.js +106 -116
- package/dist/engine/batch.js +36 -44
- package/dist/engine/buffered-writer.js +156 -158
- package/dist/engine/byte-estimates.js +20 -22
- package/dist/engine/cache-limits.js +24 -25
- package/dist/engine/cancellation.js +5 -3
- package/dist/engine/catalog.js +64 -73
- package/dist/engine/client.js +715 -780
- package/dist/engine/database.js +15941 -19313
- package/dist/engine/defaults.js +104 -111
- package/dist/engine/errors.js +113 -128
- package/dist/engine/fts.js +233 -280
- package/dist/engine/group-index.js +287 -321
- package/dist/engine/index.js +23 -12
- package/dist/engine/join-index.js +177 -186
- package/dist/engine/keyed-live.js +168 -173
- package/dist/engine/live-api.js +0 -1
- package/dist/engine/live-equal.js +32 -37
- package/dist/engine/live.js +603 -647
- package/dist/engine/memory.js +109 -111
- package/dist/engine/optimizer.d.ts +19 -1
- package/dist/engine/optimizer.js +2633 -2934
- package/dist/engine/point-read.js +122 -182
- package/dist/engine/query-api.js +12 -3
- package/dist/engine/query-cache.js +61 -69
- package/dist/engine/query.d.ts +6 -0
- package/dist/engine/query.js +8503 -9980
- package/dist/engine/result-wire.js +221 -252
- package/dist/engine/schema-wire.js +97 -112
- package/dist/engine/schema.js +1099 -1254
- package/dist/engine/sort-keys.js +315 -389
- package/dist/engine/sql-domains.js +646 -769
- package/dist/engine/sql-driver.js +0 -1
- package/dist/engine/sql-functions.js +1031 -1125
- package/dist/engine/sql-json.js +199 -218
- package/dist/engine/sql-semantics.js +572 -639
- package/dist/engine/typed-live.js +271 -293
- package/dist/engine/vector.js +4484 -5145
- package/dist/engine/worker-host.d.ts +8 -63
- package/dist/engine/worker-host.js +28 -1130
- package/dist/engine/worker-indexeddb.d.ts +1 -0
- package/dist/engine/worker-indexeddb.js +3 -0
- package/dist/engine/worker-memory.d.ts +1 -0
- package/dist/engine/worker-memory.js +3 -0
- package/dist/engine/worker-opfs.d.ts +1 -0
- package/dist/engine/worker-opfs.js +3 -0
- package/dist/engine/worker-server.d.ts +93 -0
- package/dist/engine/worker-server.js +999 -0
- package/dist/engine/worker-store-indexeddb.d.ts +2 -0
- package/dist/engine/worker-store-indexeddb.js +11 -0
- package/dist/engine/worker-store-memory.d.ts +2 -0
- package/dist/engine/worker-store-memory.js +6 -0
- package/dist/engine/worker-store-opfs.d.ts +2 -0
- package/dist/engine/worker-store-opfs.js +9 -0
- package/dist/engine/worker.js +0 -12
- package/dist/engine/write-block-planner.js +93 -100
- package/dist/plan/index.js +15 -16
- package/dist/plan/model.d.ts +6 -0
- package/dist/plan/model.js +20 -27
- package/dist/storage/index.js +0 -12
- package/dist/storage/indexeddb.js +11776 -13171
- package/dist/storage/memory.js +1198 -1241
- package/dist/storage/opfs/files.js +238 -295
- package/dist/storage/opfs/index.js +9 -3
- package/dist/storage/opfs/leader.js +4386 -4856
- package/dist/storage/opfs/rpc.js +223 -259
- package/dist/storage/opfs/snapshot-ledger.js +219 -260
- package/dist/storage/opfs/store.js +884 -1003
- package/dist/storage/persistence.js +59 -74
- package/dist/storage/snapshot-stream.js +788 -826
- package/dist/storage/snapshot.js +36 -37
- package/dist/storage/toolkit/extents.js +444 -520
- package/dist/storage/toolkit/index.js +28 -29
- package/dist/storage/toolkit/record-core.js +5792 -6377
- package/dist/storage/toolkit/sync-file.js +34 -42
- package/dist/storage/toolkit/wal.js +92 -127
- package/dist/storage/toolkit/wire.js +232 -256
- package/dist/storage/types.js +2948 -3250
- package/dist/testing/block-store-conformance.js +1556 -1629
- package/dist/testing/index.js +310 -291
- package/dist/testing/opfs-shim.js +280 -312
- package/dist/testing/simulator.js +496 -534
- package/dist/testing/sqllogictest.js +425 -365
- package/dist/transactions/index.js +1464 -1712
- package/dist/worker-protocol/index.js +70 -69
- package/package.json +20 -2
package/dist/storage/memory.js
CHANGED
|
@@ -1,1294 +1,1251 @@
|
|
|
1
|
-
import { BlockReadBatchTooLargeError, MAX_SNAPSHOT_SESSION_TTL_MS, MAX_POSTING_BUILD_TTL_MS, MAX_SNAPSHOT_FRAME_BATCH_BYTES, MAX_SNAPSHOT_FRAME_BATCH_ITEMS, MAX_SNAPSHOT_METADATA_BATCH_BYTES, SNAPSHOT_FRAME_KINDS, MAX_TEMP_BYTES_PER_OWNER, MAX_TEMP_BYTES_TOTAL, MAX_TEMP_PAGES_PER_OWNER, MAX_TEMP_PAGES_TOTAL, MAX_TEMP_RUNS_PER_OWNER, MAX_TEMP_RUNS_TOTAL, PostingBuildConflictError, StorageResourceLimitError, MAX_BLOCK_READ_BATCH_BYTES, SnapshotImportConflictError, StorageCorruptionError, activePostingStorageColumnIds, assertStorageBulkReadItems, validateStorageId, assertTempRunPageBatchLimits
|
|
1
|
+
import { BlockReadBatchTooLargeError, MAX_SNAPSHOT_SESSION_TTL_MS, MAX_POSTING_BUILD_TTL_MS, MAX_SNAPSHOT_FRAME_BATCH_BYTES, MAX_SNAPSHOT_FRAME_BATCH_ITEMS, MAX_SNAPSHOT_METADATA_BATCH_BYTES, SNAPSHOT_FRAME_KINDS, MAX_TEMP_BYTES_PER_OWNER, MAX_TEMP_BYTES_TOTAL, MAX_TEMP_PAGES_PER_OWNER, MAX_TEMP_PAGES_TOTAL, MAX_TEMP_RUNS_PER_OWNER, MAX_TEMP_RUNS_TOTAL, PostingBuildConflictError, StorageResourceLimitError, MAX_BLOCK_READ_BATCH_BYTES, SnapshotImportConflictError, StorageCorruptionError, activePostingStorageColumnIds, assertStorageBulkReadItems, validateStorageId, assertTempRunPageBatchLimits } from "./types.js";
|
|
2
2
|
import { verifyStoredBlock } from "../block-format/index.js";
|
|
3
3
|
import { crc32 } from "../block-format/checksum.js";
|
|
4
|
-
import { decodeSnapshotMetadataItems, encodeSnapshotFrameStreamFooter, encodeSnapshotFrameStreamHeader, encodeSnapshotMetadataPage, extendSnapshotFrameStreamChecksum, prepareSnapshotFrameStreamHeader, snapshotFrameEnvelopeParts, snapshotFrameStreamHeaderIdentity
|
|
5
|
-
import { RecordCore, validateFtsPostingChunks, validateId, validateTempRunPage, validateTempRunPageIdentity
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
4
|
+
import { decodeSnapshotMetadataItems, encodeSnapshotFrameStreamFooter, encodeSnapshotFrameStreamHeader, encodeSnapshotMetadataPage, extendSnapshotFrameStreamChecksum, prepareSnapshotFrameStreamHeader, snapshotFrameEnvelopeParts, snapshotFrameStreamHeaderIdentity } from "./snapshot-stream.js";
|
|
5
|
+
import { RecordCore, validateFtsPostingChunks, validateId, validateTempRunPage, validateTempRunPageIdentity } from "./toolkit/record-core.js";
|
|
6
|
+
class MemoryBlockStore {
|
|
7
|
+
#blocks = /* @__PURE__ */ new Map();
|
|
8
|
+
#blockChecksums = /* @__PURE__ */ new Map();
|
|
9
|
+
#physicalBlockBytes = 0;
|
|
10
|
+
#tempRunPages = /* @__PURE__ */ new Map();
|
|
11
|
+
#tempUsageByOwner = /* @__PURE__ */ new Map();
|
|
12
|
+
#tempPageKeysByRun = /* @__PURE__ */ new Map();
|
|
13
|
+
#tempBytesTotal = 0;
|
|
14
|
+
#tempPagesTotal = 0;
|
|
15
|
+
#tempRunsTotal = 0;
|
|
16
|
+
#ftsBaseBuilds = /* @__PURE__ */ new Map();
|
|
17
|
+
#snapshotFrameExport;
|
|
18
|
+
#snapshotFrameImport;
|
|
19
|
+
#completedSnapshotFrameImport;
|
|
20
|
+
#core = new RecordCore({
|
|
21
|
+
hasBlock: (id) => this.#blocks.has(id),
|
|
22
|
+
blockByteLength: (id) => this.#blocks.get(id)?.byteLength,
|
|
23
|
+
blockChecksum: (id) => this.#blockChecksums.get(id)
|
|
24
|
+
});
|
|
25
|
+
#commitQueue = Promise.resolve();
|
|
26
|
+
async getBlock(id) {
|
|
27
|
+
validateStorageId(id, "Block ID");
|
|
28
|
+
const bytes = this.#blocks.get(id);
|
|
29
|
+
return bytes === void 0 ? void 0 : new Uint8Array(bytes);
|
|
30
|
+
}
|
|
31
|
+
async getBlocks(ids) {
|
|
32
|
+
assertStorageBulkReadItems(ids, "Block read batch");
|
|
33
|
+
for (const id of ids)
|
|
34
|
+
validateStorageId(id, "Block ID");
|
|
35
|
+
let bytes = 0;
|
|
36
|
+
for (const id of ids) {
|
|
37
|
+
bytes += this.#blocks.get(id)?.byteLength ?? 0;
|
|
38
|
+
if (!Number.isSafeInteger(bytes) || bytes > MAX_BLOCK_READ_BATCH_BYTES) {
|
|
39
|
+
throw new BlockReadBatchTooLargeError(bytes);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return ids.map((id) => {
|
|
43
|
+
const bytes2 = this.#blocks.get(id);
|
|
44
|
+
return bytes2 === void 0 ? void 0 : new Uint8Array(bytes2);
|
|
30
45
|
});
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
46
|
+
}
|
|
47
|
+
async readManifestBlock(version, id) {
|
|
48
|
+
validateStorageId(id, "Block ID");
|
|
49
|
+
return this.#runAtomic(() => {
|
|
50
|
+
if (!this.#core.hasManifestBlocks(version, [id])[0])
|
|
51
|
+
return void 0;
|
|
52
|
+
const bytes = this.#blocks.get(id);
|
|
53
|
+
if (bytes === void 0) {
|
|
54
|
+
throw new StorageCorruptionError("memory", id, "manifest member payload is missing");
|
|
55
|
+
}
|
|
56
|
+
return new Uint8Array(bytes);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async hasManifestBlocks(version, ids) {
|
|
60
|
+
assertStorageBulkReadItems(ids, "Manifest membership batch");
|
|
61
|
+
return this.#runAtomic(() => this.#core.hasManifestBlocks(version, ids));
|
|
62
|
+
}
|
|
63
|
+
async putTempRunPage(page) {
|
|
64
|
+
assertTempRunPageBatchLimits([page]);
|
|
65
|
+
validateTempRunPage(page);
|
|
66
|
+
return this.#runAtomic(() => this.#putTempRunPages([page]));
|
|
67
|
+
}
|
|
68
|
+
async putTempRunPages(pages) {
|
|
69
|
+
assertTempRunPageBatchLimits(pages);
|
|
70
|
+
for (const page of pages)
|
|
71
|
+
validateTempRunPage(page);
|
|
72
|
+
return this.#runAtomic(() => this.#putTempRunPages(pages));
|
|
73
|
+
}
|
|
74
|
+
async getTempRunPage(ownerId, runId, pageIndex) {
|
|
75
|
+
validateTempRunPageIdentity(ownerId, runId, pageIndex);
|
|
76
|
+
return this.#tempRunPages.get(tempRunPageKey(ownerId, runId, pageIndex))?.slice();
|
|
77
|
+
}
|
|
78
|
+
async removeTempRun(ownerId, runId) {
|
|
79
|
+
validateTempRunPageIdentity(ownerId, runId, 0);
|
|
80
|
+
return this.#runAtomic(() => this.#removeTempRunPages(ownerId, runId));
|
|
81
|
+
}
|
|
82
|
+
async removeTempOwner(ownerId) {
|
|
83
|
+
validateId(ownerId);
|
|
84
|
+
return this.#runAtomic(() => {
|
|
85
|
+
this.#removeTempOwnerPages(ownerId);
|
|
86
|
+
this.#core.removeTempOwner(ownerId);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async createTempOwner(record) {
|
|
90
|
+
return this.#runAtomic(() => {
|
|
91
|
+
this.#core.createTempOwner(record);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async getTempOwner(ownerId) {
|
|
95
|
+
return this.#core.getTempOwner(ownerId);
|
|
96
|
+
}
|
|
97
|
+
async renewTempOwner(input) {
|
|
98
|
+
return this.#runAtomic(() => this.#core.renewTempOwner(input));
|
|
99
|
+
}
|
|
100
|
+
async removeTempOwnerIfExpired(ownerId, expiresAtCutoff) {
|
|
101
|
+
return this.#runAtomic(() => {
|
|
102
|
+
const removed = this.#core.removeTempOwnerIfExpired(ownerId, expiresAtCutoff);
|
|
103
|
+
if (removed)
|
|
104
|
+
this.#removeTempOwnerPages(ownerId);
|
|
105
|
+
return removed;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
async listTempOwnerIdsPage(afterOwnerId, limit) {
|
|
109
|
+
const pageOwnerIds = [];
|
|
110
|
+
for (const key of this.#tempRunPages.keys()) {
|
|
111
|
+
const separator = key.indexOf(":");
|
|
112
|
+
const length = Number(key.slice(0, separator));
|
|
113
|
+
pageOwnerIds.push(key.slice(separator + 1, separator + 1 + length));
|
|
114
|
+
}
|
|
115
|
+
return this.#core.listTempOwnerIdsPage(afterOwnerId, limit, pageOwnerIds);
|
|
116
|
+
}
|
|
117
|
+
async listExpiredTempOwnerPage(expiresAtCutoff, afterCursor, limit) {
|
|
118
|
+
return this.#core.listExpiredTempOwnerPage(expiresAtCutoff, afterCursor, limit);
|
|
119
|
+
}
|
|
120
|
+
#putTempRunPages(pages) {
|
|
121
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
122
|
+
const deltas = /* @__PURE__ */ new Map();
|
|
123
|
+
let totalBytesDelta = 0;
|
|
124
|
+
let totalPagesDelta = 0;
|
|
125
|
+
let totalRunsDelta = 0;
|
|
126
|
+
for (const page of pages) {
|
|
127
|
+
if (this.#core.getTempOwner(page.ownerId) === void 0) {
|
|
128
|
+
throw new Error(`Temp owner does not exist: ${page.ownerId}`);
|
|
129
|
+
}
|
|
130
|
+
const key = tempRunPageKey(page.ownerId, page.runId, page.pageIndex);
|
|
131
|
+
if (seenKeys.has(key))
|
|
132
|
+
throw new TypeError(`Temp run page batch repeats ${key}`);
|
|
133
|
+
seenKeys.add(key);
|
|
134
|
+
const previousBytes = this.#tempRunPages.get(key)?.byteLength ?? 0;
|
|
135
|
+
const isNewPage = !this.#tempRunPages.has(key);
|
|
136
|
+
const delta = deltas.get(page.ownerId) ?? { bytes: 0, pages: 0, newRuns: /* @__PURE__ */ new Set() };
|
|
137
|
+
delta.bytes += page.bytes.byteLength - previousBytes;
|
|
138
|
+
if (isNewPage)
|
|
139
|
+
delta.pages += 1;
|
|
140
|
+
const usage = this.#tempUsageByOwner.get(page.ownerId);
|
|
141
|
+
if (usage?.runs.has(page.runId) !== true && !delta.newRuns.has(page.runId)) {
|
|
142
|
+
delta.newRuns.add(page.runId);
|
|
143
|
+
}
|
|
144
|
+
deltas.set(page.ownerId, delta);
|
|
145
|
+
totalBytesDelta += page.bytes.byteLength - previousBytes;
|
|
146
|
+
if (isNewPage)
|
|
147
|
+
totalPagesDelta += 1;
|
|
148
|
+
}
|
|
149
|
+
for (const [ownerId, delta] of deltas) {
|
|
150
|
+
const usage = this.#tempUsageByOwner.get(ownerId);
|
|
151
|
+
const nextBytes = (usage?.bytes ?? 0) + delta.bytes;
|
|
152
|
+
const nextPages = (usage?.pages ?? 0) + delta.pages;
|
|
153
|
+
const nextRuns = (usage?.runs.size ?? 0) + delta.newRuns.size;
|
|
154
|
+
assertTempResourceLimit("temp owner byte", nextBytes, MAX_TEMP_BYTES_PER_OWNER);
|
|
155
|
+
assertTempResourceLimit("temp page", nextPages, MAX_TEMP_PAGES_PER_OWNER);
|
|
156
|
+
assertTempResourceLimit("temp run", nextRuns, MAX_TEMP_RUNS_PER_OWNER);
|
|
157
|
+
totalRunsDelta += delta.newRuns.size;
|
|
158
|
+
}
|
|
159
|
+
assertTempResourceLimit("temporary byte", this.#tempBytesTotal + totalBytesDelta, MAX_TEMP_BYTES_TOTAL);
|
|
160
|
+
assertTempResourceLimit("temporary page total", this.#tempPagesTotal + totalPagesDelta, MAX_TEMP_PAGES_TOTAL);
|
|
161
|
+
assertTempResourceLimit("temporary run total", this.#tempRunsTotal + totalRunsDelta, MAX_TEMP_RUNS_TOTAL);
|
|
162
|
+
for (const page of pages) {
|
|
163
|
+
const key = tempRunPageKey(page.ownerId, page.runId, page.pageIndex);
|
|
164
|
+
const previousBytes = this.#tempRunPages.get(key)?.byteLength ?? 0;
|
|
165
|
+
const isNewPage = !this.#tempRunPages.has(key);
|
|
166
|
+
const usage = this.#tempUsageByOwner.get(page.ownerId) ?? {
|
|
167
|
+
bytes: 0,
|
|
168
|
+
pages: 0,
|
|
169
|
+
runs: /* @__PURE__ */ new Map()
|
|
170
|
+
};
|
|
171
|
+
const previousRunPages = usage.runs.get(page.runId) ?? 0;
|
|
172
|
+
this.#tempRunPages.set(key, page.bytes.slice());
|
|
173
|
+
const runKey = tempRunPagePrefix(page.ownerId, page.runId);
|
|
174
|
+
const runKeys = this.#tempPageKeysByRun.get(runKey) ?? /* @__PURE__ */ new Set();
|
|
175
|
+
runKeys.add(key);
|
|
176
|
+
this.#tempPageKeysByRun.set(runKey, runKeys);
|
|
177
|
+
usage.bytes += page.bytes.byteLength - previousBytes;
|
|
178
|
+
this.#tempBytesTotal += page.bytes.byteLength - previousBytes;
|
|
179
|
+
if (isNewPage) {
|
|
180
|
+
usage.pages += 1;
|
|
181
|
+
this.#tempPagesTotal += 1;
|
|
182
|
+
usage.runs.set(page.runId, previousRunPages + 1);
|
|
183
|
+
if (previousRunPages === 0)
|
|
184
|
+
this.#tempRunsTotal += 1;
|
|
185
|
+
}
|
|
186
|
+
this.#tempUsageByOwner.set(page.ownerId, usage);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
#removeTempRunPages(ownerId, runId) {
|
|
190
|
+
const runKey = tempRunPagePrefix(ownerId, runId);
|
|
191
|
+
const keys = this.#tempPageKeysByRun.get(runKey);
|
|
192
|
+
if (keys === void 0)
|
|
193
|
+
return;
|
|
194
|
+
const usage = this.#tempUsageByOwner.get(ownerId);
|
|
195
|
+
let removedBytes = 0;
|
|
196
|
+
for (const key of keys) {
|
|
197
|
+
removedBytes += this.#tempRunPages.get(key)?.byteLength ?? 0;
|
|
198
|
+
this.#tempRunPages.delete(key);
|
|
199
|
+
}
|
|
200
|
+
this.#tempPageKeysByRun.delete(runKey);
|
|
201
|
+
this.#tempBytesTotal -= removedBytes;
|
|
202
|
+
this.#tempPagesTotal -= keys.size;
|
|
203
|
+
this.#tempRunsTotal -= 1;
|
|
204
|
+
if (usage !== void 0) {
|
|
205
|
+
usage.bytes -= removedBytes;
|
|
206
|
+
usage.pages -= keys.size;
|
|
207
|
+
usage.runs.delete(runId);
|
|
208
|
+
if (usage.pages === 0)
|
|
209
|
+
this.#tempUsageByOwner.delete(ownerId);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
#removeTempOwnerPages(ownerId) {
|
|
213
|
+
const usage = this.#tempUsageByOwner.get(ownerId);
|
|
214
|
+
if (usage === void 0)
|
|
215
|
+
return;
|
|
216
|
+
for (const runId of [...usage.runs.keys()])
|
|
217
|
+
this.#removeTempRunPages(ownerId, runId);
|
|
218
|
+
}
|
|
219
|
+
async addTable(record, options = {}) {
|
|
220
|
+
this.#core.addTable(record, options);
|
|
221
|
+
}
|
|
222
|
+
async getTable(id) {
|
|
223
|
+
return this.#core.getTable(id);
|
|
224
|
+
}
|
|
225
|
+
async updateTable(id, expectedRevision, update) {
|
|
226
|
+
return this.#runAtomic(() => {
|
|
227
|
+
const before = this.#core.getTable(id);
|
|
228
|
+
const updated = this.#core.updateTable(id, expectedRevision, update);
|
|
229
|
+
if (before !== void 0)
|
|
230
|
+
this.#removeRetiredPostingBuilds(before, updated);
|
|
231
|
+
return updated;
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
async removeTable(id, expectedRevision, options = {}) {
|
|
235
|
+
return this.#runAtomic(() => {
|
|
236
|
+
this.#core.removeTable(id, expectedRevision, options);
|
|
237
|
+
this.#removeTablePostingBuilds(id);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
async dropTable(input) {
|
|
241
|
+
return this.#runAtomic(() => {
|
|
242
|
+
const summary = this.#core.dropTable(input);
|
|
243
|
+
this.#removeTablePostingBuilds(input.tableId);
|
|
244
|
+
return summary;
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
async dropTableColumn(input) {
|
|
248
|
+
return this.#runAtomic(() => {
|
|
249
|
+
const summary = this.#core.dropTableColumn(input);
|
|
250
|
+
this.#ftsBaseBuilds.delete(`${input.tableId}/${input.columnId}`);
|
|
251
|
+
return summary;
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
#removeRetiredPostingBuilds(before, after) {
|
|
255
|
+
const retained = /* @__PURE__ */ new Set([
|
|
256
|
+
...Object.keys(after.ftsColumns ?? {}),
|
|
257
|
+
...Object.values(after.secondaryIndexes ?? {}).map((index) => index.storageColumnId)
|
|
258
|
+
]);
|
|
259
|
+
for (const columnId of [
|
|
260
|
+
...Object.keys(before.ftsColumns ?? {}),
|
|
261
|
+
...Object.values(before.secondaryIndexes ?? {}).map((index) => index.storageColumnId)
|
|
262
|
+
]) {
|
|
263
|
+
if (!retained.has(columnId))
|
|
264
|
+
this.#ftsBaseBuilds.delete(`${before.id}/${columnId}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
#removeTablePostingBuilds(tableId) {
|
|
268
|
+
const prefix = `${tableId}/`;
|
|
269
|
+
for (const key of [...this.#ftsBaseBuilds.keys()]) {
|
|
270
|
+
if (key.startsWith(prefix))
|
|
271
|
+
this.#ftsBaseBuilds.delete(key);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
async writeFtsBase(tableId, columnId, input) {
|
|
275
|
+
return this.#runAtomic(() => {
|
|
276
|
+
this.#core.writeFtsBase(tableId, columnId, input);
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
async removeFtsColumn(tableId, columnId) {
|
|
280
|
+
return this.#runAtomic(() => {
|
|
281
|
+
this.#core.removeFtsColumn(tableId, columnId);
|
|
282
|
+
this.#ftsBaseBuilds.delete(`${tableId}/${columnId}`);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
async beginFtsBaseBuild(input) {
|
|
286
|
+
validatePostingBuildLifetime(input.createdAt, input.expiresAt);
|
|
287
|
+
validateId(input.tableId);
|
|
288
|
+
validateId(input.columnId);
|
|
289
|
+
validateId(input.buildId);
|
|
290
|
+
validateId(input.ownerId);
|
|
291
|
+
return this.#runAtomic(() => {
|
|
292
|
+
const key = `${input.tableId}/${input.columnId}`;
|
|
293
|
+
const current = this.#ftsBaseBuilds.get(key);
|
|
294
|
+
if (current !== void 0 && Date.parse(current.expiresAt) > Date.parse(input.createdAt)) {
|
|
295
|
+
if (current.buildId === input.buildId && current.ownerId === input.ownerId)
|
|
296
|
+
return;
|
|
297
|
+
throw new PostingBuildConflictError(current.buildId, current.ownerId, "another live build owns the column");
|
|
298
|
+
}
|
|
299
|
+
const table = this.#core.getTable(input.tableId);
|
|
300
|
+
if (table === void 0 || !activePostingStorageColumnIds(table).has(input.columnId)) {
|
|
301
|
+
throw new Error(`Postings index is no longer active: ${key}`);
|
|
302
|
+
}
|
|
303
|
+
this.#ftsBaseBuilds.set(key, {
|
|
304
|
+
buildId: input.buildId,
|
|
305
|
+
ownerId: input.ownerId,
|
|
306
|
+
createdAt: input.createdAt,
|
|
307
|
+
expiresAt: input.expiresAt,
|
|
308
|
+
chunks: /* @__PURE__ */ new Map()
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
async renewFtsBaseBuild(input) {
|
|
313
|
+
validatePostingBuildLifetime(input.expiresAtCutoff, input.expiresAt);
|
|
314
|
+
return this.#runAtomic(() => {
|
|
315
|
+
const build = this.#requirePostingBuild(input);
|
|
316
|
+
build.expiresAt = input.expiresAt;
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
async writeFtsBaseBuildChunk(input) {
|
|
320
|
+
validatePostingBuildLifetime(input.expiresAtCutoff, input.expiresAt);
|
|
321
|
+
return this.#runAtomic(() => {
|
|
322
|
+
const build = this.#requirePostingBuild(input);
|
|
323
|
+
if (!Number.isSafeInteger(input.ordinal) || input.ordinal < 0) {
|
|
324
|
+
throw new RangeError(`Full-text base chunk ordinal is invalid: ${String(input.ordinal)}`);
|
|
325
|
+
}
|
|
326
|
+
validateFtsPostingChunks([input.chunk], "Full-text base build");
|
|
327
|
+
const replay = build.chunks.get(input.ordinal);
|
|
328
|
+
if (replay !== void 0) {
|
|
329
|
+
if (!samePostingChunk(replay, input.chunk)) {
|
|
330
|
+
throw new PostingBuildConflictError(input.buildId, input.ownerId, "chunk replay changed");
|
|
205
331
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
332
|
+
build.expiresAt = input.expiresAt;
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (input.ordinal !== build.chunks.size) {
|
|
336
|
+
throw new Error(`Full-text base chunk is out of order: ${String(input.ordinal)}`);
|
|
337
|
+
}
|
|
338
|
+
build.chunks.set(input.ordinal, structuredClone(input.chunk));
|
|
339
|
+
build.expiresAt = input.expiresAt;
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
async finishFtsBaseBuild(input) {
|
|
343
|
+
return this.#runAtomic(() => {
|
|
344
|
+
const key = `${input.tableId}/${input.columnId}`;
|
|
345
|
+
const build = this.#requirePostingBuild(input);
|
|
346
|
+
if (!Number.isSafeInteger(input.coversVersion) || input.coversVersion < -1 || !Number.isSafeInteger(input.chunkCount) || input.chunkCount < 0 || !Number.isSafeInteger(input.totalTokens) || input.totalTokens < 0) {
|
|
347
|
+
throw new TypeError("Full-text base build metadata is invalid");
|
|
348
|
+
}
|
|
349
|
+
if (input.chunkCount !== build.chunks.size) {
|
|
350
|
+
throw new Error(`Full-text base build is incomplete: ${input.buildId}`);
|
|
351
|
+
}
|
|
352
|
+
const table = this.#core.getTable(input.tableId);
|
|
353
|
+
if (table === void 0 || !activePostingStorageColumnIds(table).has(input.columnId)) {
|
|
354
|
+
this.#ftsBaseBuilds.delete(key);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const chunks = Array.from({ length: input.chunkCount }, (_, ordinal) => {
|
|
358
|
+
const chunk = build.chunks.get(ordinal);
|
|
359
|
+
if (chunk === void 0) {
|
|
360
|
+
throw new Error(`Full-text base chunk is missing: ${String(ordinal)}`);
|
|
216
361
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
return
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
362
|
+
return chunk;
|
|
363
|
+
});
|
|
364
|
+
this.#core.writeFtsBase(input.tableId, input.columnId, {
|
|
365
|
+
coversVersion: input.coversVersion,
|
|
366
|
+
chunks,
|
|
367
|
+
totalTokens: input.totalTokens
|
|
368
|
+
});
|
|
369
|
+
this.#ftsBaseBuilds.delete(key);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
async abortFtsBaseBuild(input) {
|
|
373
|
+
return this.#runAtomic(() => {
|
|
374
|
+
const key = `${input.tableId}/${input.columnId}`;
|
|
375
|
+
const build = this.#ftsBaseBuilds.get(key);
|
|
376
|
+
if (build === void 0)
|
|
377
|
+
return;
|
|
378
|
+
if (build.buildId !== input.buildId || build.ownerId !== input.ownerId && Date.parse(build.expiresAt) > Date.parse(input.expiresAtCutoff)) {
|
|
379
|
+
throw new PostingBuildConflictError(build.buildId, build.ownerId, "abort ownership changed");
|
|
380
|
+
}
|
|
381
|
+
this.#ftsBaseBuilds.delete(key);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
#requirePostingBuild(input) {
|
|
385
|
+
const build = this.#ftsBaseBuilds.get(`${input.tableId}/${input.columnId}`);
|
|
386
|
+
if (build?.buildId !== input.buildId || build.ownerId !== input.ownerId) {
|
|
387
|
+
throw new PostingBuildConflictError(build?.buildId ?? input.buildId, build?.ownerId ?? input.ownerId, "session is missing or owned by another caller");
|
|
388
|
+
}
|
|
389
|
+
if (Date.parse(build.expiresAt) <= Date.parse(input.expiresAtCutoff)) {
|
|
390
|
+
throw new PostingBuildConflictError(build.buildId, build.ownerId, "session is expired");
|
|
391
|
+
}
|
|
392
|
+
return build;
|
|
393
|
+
}
|
|
394
|
+
async readFtsCandidates(tableId, columnId, terms, upToVersion, maxRowIds) {
|
|
395
|
+
return this.#core.readFtsCandidates(tableId, columnId, terms, upToVersion, maxRowIds);
|
|
396
|
+
}
|
|
397
|
+
async readFtsPostings(tableId, columnId, upToVersion, maxRowIds, maxRetainedBytes) {
|
|
398
|
+
return this.#core.readFtsPostings(tableId, columnId, upToVersion, maxRowIds, maxRetainedBytes);
|
|
399
|
+
}
|
|
400
|
+
async getTableByName(name) {
|
|
401
|
+
const id = this.#core.getTableIdByName(name);
|
|
402
|
+
return id === void 0 ? void 0 : this.getTable(id);
|
|
403
|
+
}
|
|
404
|
+
async listTables() {
|
|
405
|
+
return this.#core.listTables();
|
|
406
|
+
}
|
|
407
|
+
async getSegment(id) {
|
|
408
|
+
return this.#core.getSegment(id);
|
|
409
|
+
}
|
|
410
|
+
async listSegmentPage(afterId, limit) {
|
|
411
|
+
return this.#core.listSegmentPage(afterId, limit);
|
|
412
|
+
}
|
|
413
|
+
async listTableSegmentPage(tableId, afterId, limit) {
|
|
414
|
+
return this.#core.listTableSegmentPage(tableId, afterId, limit);
|
|
415
|
+
}
|
|
416
|
+
async removeAbortedSegment(id, expectedTransactionId) {
|
|
417
|
+
return this.#runAtomic(() => this.#core.removeAbortedSegment(id, expectedTransactionId));
|
|
418
|
+
}
|
|
419
|
+
async adoptAbortedSegment(input) {
|
|
420
|
+
return this.#runAtomic(() => this.#core.adoptAbortedSegment(input));
|
|
421
|
+
}
|
|
422
|
+
async reserveRowIds(tableId, count) {
|
|
423
|
+
return this.#core.reserveRowIds(tableId, count);
|
|
424
|
+
}
|
|
425
|
+
async reserveAutoIncrement(tableId, columnId, count, atLeast) {
|
|
426
|
+
return this.#runAtomic(() => this.#core.reserveAutoIncrement(tableId, columnId, count, atLeast));
|
|
427
|
+
}
|
|
428
|
+
async getExistingUniqueKeys(tableId, keyTokens) {
|
|
429
|
+
assertStorageBulkReadItems(keyTokens, "Unique-key lookup batch");
|
|
430
|
+
return this.#core.getExistingUniqueKeys(tableId, keyTokens);
|
|
431
|
+
}
|
|
432
|
+
async beginUniqueKeyBuild(input) {
|
|
433
|
+
return this.#runAtomic(() => this.#core.beginUniqueKeyBuild(input));
|
|
434
|
+
}
|
|
435
|
+
async getUniqueKeyBuild(buildId) {
|
|
436
|
+
return this.#core.getUniqueKeyBuild(buildId);
|
|
437
|
+
}
|
|
438
|
+
async renewUniqueKeyBuild(input) {
|
|
439
|
+
return this.#runAtomic(() => this.#core.renewUniqueKeyBuild(input));
|
|
440
|
+
}
|
|
441
|
+
async appendUniqueKeyBuildChunk(input) {
|
|
442
|
+
return this.#runAtomic(() => this.#core.appendUniqueKeyBuildChunk(input));
|
|
443
|
+
}
|
|
444
|
+
async finishUniqueKeyBuild(input) {
|
|
445
|
+
return this.#runAtomic(() => this.#core.finishUniqueKeyBuild(input));
|
|
446
|
+
}
|
|
447
|
+
async abortUniqueKeyBuild(input) {
|
|
448
|
+
return this.#runAtomic(() => this.#core.abortUniqueKeyBuild(input));
|
|
449
|
+
}
|
|
450
|
+
async getCurrentManifestVersion() {
|
|
451
|
+
return this.#core.getCurrentManifestVersion();
|
|
452
|
+
}
|
|
453
|
+
async getCatalogProbe() {
|
|
454
|
+
return this.#core.getCatalogProbe();
|
|
455
|
+
}
|
|
456
|
+
async getQueryCatalogState(names) {
|
|
457
|
+
assertStorageBulkReadItems(names, "Query catalog table batch");
|
|
458
|
+
return this.#runAtomic(() => {
|
|
459
|
+
const probe = this.#core.getCatalogProbe();
|
|
460
|
+
const tables = names.map((name) => {
|
|
461
|
+
const id = this.#core.getTableIdByName(name);
|
|
462
|
+
return id === void 0 ? void 0 : this.#core.getTable(id);
|
|
463
|
+
});
|
|
464
|
+
const segments = [];
|
|
465
|
+
for (const table of tables) {
|
|
466
|
+
if (table === void 0)
|
|
467
|
+
continue;
|
|
468
|
+
let cursor = null;
|
|
469
|
+
do {
|
|
470
|
+
const page = this.#core.listTableSegmentPage(table.id, cursor, 1024);
|
|
471
|
+
segments.push(...page.records);
|
|
472
|
+
cursor = page.nextCursor;
|
|
473
|
+
} while (cursor !== null);
|
|
474
|
+
}
|
|
475
|
+
segments.sort((left, right) => left.id.localeCompare(right.id));
|
|
476
|
+
const transactionIds = [...new Set(segments.map((segment) => segment.transactionId))];
|
|
477
|
+
const transactions = [];
|
|
478
|
+
for (let start = 0; start < transactionIds.length; start += 64) {
|
|
479
|
+
for (const record of this.#core.getTransactions(transactionIds.slice(start, start + 64))) {
|
|
480
|
+
if (record !== void 0)
|
|
481
|
+
transactions.push(record);
|
|
271
482
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
manifestVersion: probe.manifestVersion,
|
|
486
|
+
tables,
|
|
487
|
+
segments,
|
|
488
|
+
transactions,
|
|
489
|
+
catalogEpoch: probe.catalogEpoch
|
|
490
|
+
};
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
async getCurrentManifest() {
|
|
494
|
+
return this.#core.getCurrentManifest();
|
|
495
|
+
}
|
|
496
|
+
async getManifest(version) {
|
|
497
|
+
return this.#core.getManifest(version);
|
|
498
|
+
}
|
|
499
|
+
async listManifestBlockPage(input) {
|
|
500
|
+
return this.#core.listManifestBlockPage(input);
|
|
501
|
+
}
|
|
502
|
+
async listRetiredManifestBlockPage(input) {
|
|
503
|
+
return this.#core.listRetiredManifestBlockPage(input);
|
|
504
|
+
}
|
|
505
|
+
async listManifestPage(afterVersion, limit) {
|
|
506
|
+
return this.#core.listManifestPage(afterVersion, limit);
|
|
507
|
+
}
|
|
508
|
+
async beginTransaction(input) {
|
|
509
|
+
return this.#runAtomic(() => this.#core.beginTransaction(input));
|
|
510
|
+
}
|
|
511
|
+
async createTransaction(record) {
|
|
512
|
+
return this.#runAtomic(() => {
|
|
513
|
+
this.#core.createTransaction(record);
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
async getTransaction(id) {
|
|
517
|
+
return this.#core.getTransaction(id);
|
|
518
|
+
}
|
|
519
|
+
async getTransactions(ids) {
|
|
520
|
+
assertStorageBulkReadItems(ids, "Transaction read batch");
|
|
521
|
+
return this.#core.getTransactions(ids);
|
|
522
|
+
}
|
|
523
|
+
async listTransactionPage(afterId, limit) {
|
|
524
|
+
return this.#core.listTransactionPage(afterId, limit);
|
|
525
|
+
}
|
|
526
|
+
async updateTransaction(id, expectedRevision, update) {
|
|
527
|
+
return this.#runAtomic(() => this.#core.updateTransaction(id, expectedRevision, update));
|
|
528
|
+
}
|
|
529
|
+
async renewTransaction(input) {
|
|
530
|
+
return this.#runAtomic(() => this.#core.renewTransaction(input));
|
|
531
|
+
}
|
|
532
|
+
async abortTransactionIfExpired(input) {
|
|
533
|
+
return this.#runAtomic(() => this.#core.abortTransactionIfExpired(input));
|
|
534
|
+
}
|
|
535
|
+
async stageTransactionArtifacts(input) {
|
|
536
|
+
return this.#runAtomic(() => {
|
|
537
|
+
const updated = this.#core.stageTransactionArtifacts(input);
|
|
538
|
+
for (const block of input.blocks)
|
|
539
|
+
this.#putBlock(block.id, block.bytes);
|
|
540
|
+
return updated;
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
async rollbackTransactionArtifacts(input) {
|
|
544
|
+
return this.#runAtomic(() => {
|
|
545
|
+
const updated = this.#core.rollbackTransactionArtifacts(input);
|
|
546
|
+
for (const id of input.removeBlockIds)
|
|
547
|
+
this.#deleteBlock(id);
|
|
548
|
+
return updated;
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
async commitTransaction(input) {
|
|
552
|
+
return this.#runAtomic(() => this.#core.commitTransaction(input));
|
|
553
|
+
}
|
|
554
|
+
async writeTransaction(input) {
|
|
555
|
+
return this.#runAtomic(() => {
|
|
556
|
+
const summary = this.#core.writeTransaction(input);
|
|
557
|
+
for (const block of input.blocks)
|
|
558
|
+
this.#putBlock(block.id, block.bytes);
|
|
559
|
+
return summary;
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
async createLease(record) {
|
|
563
|
+
return this.#runAtomic(() => {
|
|
564
|
+
this.#core.createLease(record);
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
async getLease(id) {
|
|
568
|
+
return this.#core.getLease(id);
|
|
569
|
+
}
|
|
570
|
+
async listLeases() {
|
|
571
|
+
return this.#core.listLeases();
|
|
572
|
+
}
|
|
573
|
+
async listExpiredLeasePage(expiresAtCutoff, afterCursor, limit) {
|
|
574
|
+
return this.#core.listExpiredLeasePage(expiresAtCutoff, afterCursor, limit);
|
|
575
|
+
}
|
|
576
|
+
async renewLease(input) {
|
|
577
|
+
return this.#runAtomic(() => this.#core.renewLease(input));
|
|
578
|
+
}
|
|
579
|
+
async moveLease(input) {
|
|
580
|
+
return this.#runAtomic(() => this.#core.moveLease(input));
|
|
581
|
+
}
|
|
582
|
+
async removeLeaseIfExpired(id, expectedRevision, expiresAtCutoff) {
|
|
583
|
+
return this.#runAtomic(() => this.#core.removeLeaseIfExpired(id, expectedRevision, expiresAtCutoff));
|
|
584
|
+
}
|
|
585
|
+
async removeLease(input) {
|
|
586
|
+
return this.#runAtomic(() => this.#core.removeLease(input));
|
|
587
|
+
}
|
|
588
|
+
async createCompactionJob(record) {
|
|
589
|
+
this.#core.createCompactionJob(record);
|
|
590
|
+
}
|
|
591
|
+
async getCompactionJob(id) {
|
|
592
|
+
return this.#core.getCompactionJob(id);
|
|
593
|
+
}
|
|
594
|
+
async listCompactionJobs(tableId) {
|
|
595
|
+
return this.#core.listCompactionJobs(tableId);
|
|
596
|
+
}
|
|
597
|
+
async listCompactionJobPage(afterId, limit) {
|
|
598
|
+
return this.#core.listCompactionJobPage(afterId, limit);
|
|
599
|
+
}
|
|
600
|
+
async updateCompactionJob(id, expectedRevision, update) {
|
|
601
|
+
return this.#core.updateCompactionJob(id, expectedRevision, update);
|
|
602
|
+
}
|
|
603
|
+
async cancelCompactionJob(id, expectedRevision, cancelledAt) {
|
|
604
|
+
return this.#runAtomic(() => this.#core.cancelCompactionJob(id, expectedRevision, cancelledAt));
|
|
605
|
+
}
|
|
606
|
+
async removeCompactionJob(id) {
|
|
607
|
+
return this.#core.removeCompactionJob(id);
|
|
608
|
+
}
|
|
609
|
+
async createGarbageCollectionJob(input) {
|
|
610
|
+
return this.#runAtomic(() => this.#core.createGarbageCollectionJob(input));
|
|
611
|
+
}
|
|
612
|
+
async updateGarbageCollectionPlanning(input) {
|
|
613
|
+
return this.#runAtomic(() => this.#core.updateGarbageCollectionPlanning(input));
|
|
614
|
+
}
|
|
615
|
+
async getGarbageCollectionJob(id) {
|
|
616
|
+
return this.#core.getGarbageCollectionJob(id);
|
|
617
|
+
}
|
|
618
|
+
async listGarbageCollectionJobs() {
|
|
619
|
+
return this.#core.listGarbageCollectionJobs();
|
|
620
|
+
}
|
|
621
|
+
async listGarbageCollectionJobPage(afterId, limit) {
|
|
622
|
+
return this.#core.listGarbageCollectionJobPage(afterId, limit);
|
|
623
|
+
}
|
|
624
|
+
async runGarbageCollectionStep(input) {
|
|
625
|
+
return this.#runAtomic(() => {
|
|
626
|
+
const result = this.#core.runGarbageCollectionStep(input);
|
|
627
|
+
result.reclaimedBlockIds.forEach((id) => this.#deleteBlock(id));
|
|
628
|
+
return result;
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
async removePrunedManifestRecords(maxItems) {
|
|
632
|
+
return this.#runAtomic(() => this.#core.removePrunedManifestRecords(maxItems));
|
|
633
|
+
}
|
|
634
|
+
async removeGarbageCollectionJob(id) {
|
|
635
|
+
return this.#runAtomic(() => {
|
|
636
|
+
this.#core.removeGarbageCollectionJob(id);
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
async beginSnapshotFrameExport(input) {
|
|
640
|
+
validateSnapshotSessionLifetime(input.createdAt, input.expiresAt);
|
|
641
|
+
validateId(input.ownerId);
|
|
642
|
+
return this.#runAtomic(() => {
|
|
643
|
+
const current = this.#snapshotFrameExport;
|
|
644
|
+
if (current !== void 0) {
|
|
645
|
+
if (Date.parse(current.session.expiresAt) > Date.parse(input.createdAt)) {
|
|
646
|
+
throw new SnapshotImportConflictError(current.session.sessionId, current.session.ownerId, "another snapshot export is still live");
|
|
278
647
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
this.#core.writeFtsBase(tableId, columnId, input);
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
async removeFtsColumn(tableId, columnId) {
|
|
286
|
-
return this.#runAtomic(() => {
|
|
287
|
-
this.#core.removeFtsColumn(tableId, columnId);
|
|
288
|
-
this.#ftsBaseBuilds.delete(`${tableId}/${columnId}`);
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
async beginFtsBaseBuild(input) {
|
|
292
|
-
validatePostingBuildLifetime(input.createdAt, input.expiresAt);
|
|
293
|
-
validateId(input.tableId);
|
|
294
|
-
validateId(input.columnId);
|
|
295
|
-
validateId(input.buildId);
|
|
296
|
-
validateId(input.ownerId);
|
|
297
|
-
return this.#runAtomic(() => {
|
|
298
|
-
const key = `${input.tableId}/${input.columnId}`;
|
|
299
|
-
const current = this.#ftsBaseBuilds.get(key);
|
|
300
|
-
if (current !== undefined && Date.parse(current.expiresAt) > Date.parse(input.createdAt)) {
|
|
301
|
-
if (current.buildId === input.buildId && current.ownerId === input.ownerId)
|
|
302
|
-
return;
|
|
303
|
-
throw new PostingBuildConflictError(current.buildId, current.ownerId, "another live build owns the column");
|
|
304
|
-
}
|
|
305
|
-
const table = this.#core.getTable(input.tableId);
|
|
306
|
-
if (table === undefined || !activePostingStorageColumnIds(table).has(input.columnId)) {
|
|
307
|
-
throw new Error(`Postings index is no longer active: ${key}`);
|
|
308
|
-
}
|
|
309
|
-
this.#ftsBaseBuilds.set(key, {
|
|
310
|
-
buildId: input.buildId,
|
|
311
|
-
ownerId: input.ownerId,
|
|
312
|
-
createdAt: input.createdAt,
|
|
313
|
-
expiresAt: input.expiresAt,
|
|
314
|
-
chunks: new Map(),
|
|
315
|
-
});
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
async renewFtsBaseBuild(input) {
|
|
319
|
-
validatePostingBuildLifetime(input.expiresAtCutoff, input.expiresAt);
|
|
320
|
-
return this.#runAtomic(() => {
|
|
321
|
-
const build = this.#requirePostingBuild(input);
|
|
322
|
-
build.expiresAt = input.expiresAt;
|
|
648
|
+
this.#core.removeLease({
|
|
649
|
+
id: current.session.sessionId,
|
|
650
|
+
ownerId: current.session.ownerId
|
|
323
651
|
});
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
652
|
+
this.#snapshotFrameExport = void 0;
|
|
653
|
+
}
|
|
654
|
+
const manifest = this.#core.snapshotFrameManifest();
|
|
655
|
+
const metadataFrames = [];
|
|
656
|
+
const summaries = emptySnapshotObservedSummaries();
|
|
657
|
+
const append = (item) => {
|
|
658
|
+
const payload = encodeSnapshotMetadataPage([item]);
|
|
659
|
+
const kind = snapshotMetadataFrameKind(item);
|
|
660
|
+
const frame = {
|
|
661
|
+
sequence: metadataFrames.length,
|
|
662
|
+
kind,
|
|
663
|
+
itemCount: 1,
|
|
664
|
+
key: null,
|
|
665
|
+
payload,
|
|
666
|
+
checksum: crc32(payload)
|
|
667
|
+
};
|
|
668
|
+
metadataFrames.push(frame);
|
|
669
|
+
const summary = summaries[kind];
|
|
670
|
+
summary.frameCount += 1;
|
|
671
|
+
summary.itemCount += 1;
|
|
672
|
+
summary.storedBytes = safeSnapshotByteSum(summary.storedBytes, payload.byteLength);
|
|
673
|
+
};
|
|
674
|
+
for (const item of this.#core.snapshotFrameMetadataItems())
|
|
675
|
+
append(item);
|
|
676
|
+
for (const item of this.#core.snapshotFramePostingItems())
|
|
677
|
+
append(item);
|
|
678
|
+
summaries.block = {
|
|
679
|
+
frameCount: manifest.liveBlockCount,
|
|
680
|
+
itemCount: manifest.liveBlockCount,
|
|
681
|
+
storedBytes: manifest.liveBlockBytes
|
|
682
|
+
};
|
|
683
|
+
const header = prepareSnapshotFrameStreamHeader({
|
|
684
|
+
formatVersion: 1,
|
|
685
|
+
databaseVersion: manifest.version,
|
|
686
|
+
createdAt: input.createdAt,
|
|
687
|
+
kinds: summaries
|
|
688
|
+
});
|
|
689
|
+
const sessionId = `snapshot-export/${crypto.randomUUID()}`;
|
|
690
|
+
this.#core.createLease({
|
|
691
|
+
id: sessionId,
|
|
692
|
+
kind: "backup",
|
|
693
|
+
manifestVersion: manifest.version,
|
|
694
|
+
ownerId: input.ownerId,
|
|
695
|
+
createdAt: input.createdAt,
|
|
696
|
+
expiresAt: input.expiresAt,
|
|
697
|
+
revision: 0
|
|
698
|
+
});
|
|
699
|
+
const session = { sessionId, ownerId: input.ownerId, expiresAt: input.expiresAt, header };
|
|
700
|
+
this.#snapshotFrameExport = {
|
|
701
|
+
session,
|
|
702
|
+
manifestVersion: manifest.version,
|
|
703
|
+
metadataFrames,
|
|
704
|
+
nextSequence: 0,
|
|
705
|
+
lastBlockId: null
|
|
706
|
+
};
|
|
707
|
+
return structuredClone(session);
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
async readSnapshotExportFrame(input) {
|
|
711
|
+
validateSnapshotSessionLifetime(input.expiresAtCutoff, input.expiresAt);
|
|
712
|
+
if (!Number.isSafeInteger(input.sequence) || input.sequence < 0) {
|
|
713
|
+
throw new RangeError("Snapshot export sequence is invalid");
|
|
714
|
+
}
|
|
715
|
+
return this.#runAtomic(() => {
|
|
716
|
+
const state = this.#snapshotFrameExport;
|
|
717
|
+
if (state?.session.sessionId !== input.sessionId || state.session.ownerId !== input.ownerId) {
|
|
718
|
+
throw new SnapshotImportConflictError(input.sessionId, input.ownerId, "export session is missing or owned by another caller");
|
|
719
|
+
}
|
|
720
|
+
if (input.sequence === state.nextSequence - 1 && state.lastFrame !== void 0) {
|
|
721
|
+
return copySnapshotFrame(state.lastFrame);
|
|
722
|
+
}
|
|
723
|
+
if (input.sequence !== state.nextSequence) {
|
|
724
|
+
throw new RangeError("Snapshot export frames must be pulled in contiguous order");
|
|
725
|
+
}
|
|
726
|
+
const lease = this.#core.getLease(input.sessionId);
|
|
727
|
+
if (lease === void 0) {
|
|
728
|
+
throw new SnapshotImportConflictError(input.sessionId, input.ownerId, "lease is missing");
|
|
729
|
+
}
|
|
730
|
+
this.#core.renewLease({
|
|
731
|
+
id: lease.id,
|
|
732
|
+
expectedRevision: lease.revision,
|
|
733
|
+
expiresAtCutoff: input.expiresAtCutoff,
|
|
734
|
+
expiresAt: input.expiresAt
|
|
735
|
+
});
|
|
736
|
+
state.session = { ...state.session, expiresAt: input.expiresAt };
|
|
737
|
+
const metadata = state.metadataFrames[input.sequence];
|
|
738
|
+
let frame;
|
|
739
|
+
if (metadata !== void 0) {
|
|
740
|
+
frame = metadata;
|
|
741
|
+
} else {
|
|
742
|
+
const blockPage = this.#core.listManifestBlockPage({
|
|
743
|
+
version: state.manifestVersion,
|
|
744
|
+
afterBlockId: state.lastBlockId,
|
|
745
|
+
limit: 1
|
|
397
746
|
});
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
747
|
+
const record = blockPage.records[0];
|
|
748
|
+
if (record === void 0)
|
|
749
|
+
return void 0;
|
|
750
|
+
const bytes = this.#blocks.get(record.blockId);
|
|
751
|
+
if (bytes?.byteLength !== record.byteLength || crc32(bytes) !== record.checksum) {
|
|
752
|
+
throw new StorageCorruptionError("memory", record.blockId, "snapshot block disagrees with its manifest provenance");
|
|
403
753
|
}
|
|
404
|
-
|
|
405
|
-
|
|
754
|
+
frame = {
|
|
755
|
+
sequence: input.sequence,
|
|
756
|
+
kind: "block",
|
|
757
|
+
itemCount: 1,
|
|
758
|
+
key: record.blockId,
|
|
759
|
+
payload: bytes.slice(),
|
|
760
|
+
checksum: record.checksum
|
|
761
|
+
};
|
|
762
|
+
state.lastBlockId = record.blockId;
|
|
763
|
+
}
|
|
764
|
+
state.lastFrame = copySnapshotFrame(frame);
|
|
765
|
+
state.nextSequence += 1;
|
|
766
|
+
return copySnapshotFrame(frame);
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
async closeSnapshotFrameExport(input) {
|
|
770
|
+
return this.#runAtomic(() => {
|
|
771
|
+
const state = this.#snapshotFrameExport;
|
|
772
|
+
if (state === void 0)
|
|
773
|
+
return false;
|
|
774
|
+
if (state.session.sessionId !== input.sessionId || state.session.ownerId !== input.ownerId) {
|
|
775
|
+
throw new SnapshotImportConflictError(input.sessionId, input.ownerId, "export session belongs to another caller");
|
|
776
|
+
}
|
|
777
|
+
this.#core.removeLease({ id: input.sessionId, ownerId: input.ownerId });
|
|
778
|
+
this.#snapshotFrameExport = void 0;
|
|
779
|
+
return true;
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
async beginSnapshotFrameImport(input) {
|
|
783
|
+
validateSnapshotSessionLifetime(input.createdAt, input.expiresAt);
|
|
784
|
+
validateId(input.ownerId);
|
|
785
|
+
const header = prepareSnapshotFrameStreamHeader(input.header);
|
|
786
|
+
if (snapshotFrameStreamHeaderIdentity(header) !== input.identity) {
|
|
787
|
+
throw new TypeError("Snapshot import identity does not match its canonical header");
|
|
788
|
+
}
|
|
789
|
+
return this.#runAtomic(() => {
|
|
790
|
+
const current = this.#snapshotFrameImport;
|
|
791
|
+
if (current !== void 0) {
|
|
792
|
+
const expired = Date.parse(current.session.expiresAt) <= Date.parse(input.createdAt);
|
|
793
|
+
const sameIdentity = current.session.identity === input.identity;
|
|
794
|
+
const sameHeader = sameSnapshotFrameHeader(current.header, header);
|
|
795
|
+
if (expired && (!sameIdentity || !sameHeader)) {
|
|
796
|
+
if (!current.completedReplay)
|
|
797
|
+
this.#clearPhysicalBlocks();
|
|
798
|
+
this.#snapshotFrameImport = void 0;
|
|
799
|
+
} else {
|
|
800
|
+
if (!expired && current.session.identity !== input.identity) {
|
|
801
|
+
throw new SnapshotImportConflictError(current.session.identity, current.session.ownerId, "another snapshot import is still live");
|
|
802
|
+
}
|
|
803
|
+
if (!expired && current.session.ownerId !== input.ownerId) {
|
|
804
|
+
throw new SnapshotImportConflictError(current.session.identity, current.session.ownerId, "snapshot import is owned by another caller");
|
|
805
|
+
}
|
|
806
|
+
if (!sameHeader) {
|
|
807
|
+
throw new SnapshotImportConflictError(current.session.identity, current.session.ownerId, "snapshot import header changed");
|
|
808
|
+
}
|
|
809
|
+
current.session = {
|
|
810
|
+
...current.session,
|
|
811
|
+
ownerId: input.ownerId,
|
|
812
|
+
expiresAt: input.expiresAt
|
|
813
|
+
};
|
|
814
|
+
return structuredClone(current.session);
|
|
406
815
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
468
|
-
async getCatalogProbe() {
|
|
469
|
-
return this.#core.getCatalogProbe();
|
|
470
|
-
}
|
|
471
|
-
async getQueryCatalogState(names) {
|
|
472
|
-
assertStorageBulkReadItems(names, "Query catalog table batch");
|
|
473
|
-
return this.#runAtomic(() => {
|
|
474
|
-
const probe = this.#core.getCatalogProbe();
|
|
475
|
-
const tables = names.map((name) => {
|
|
476
|
-
const id = this.#core.getTableIdByName(name);
|
|
477
|
-
return id === undefined ? undefined : this.#core.getTable(id);
|
|
478
|
-
});
|
|
479
|
-
const segments = [];
|
|
480
|
-
for (const table of tables) {
|
|
481
|
-
if (table === undefined)
|
|
482
|
-
continue;
|
|
483
|
-
let cursor = null;
|
|
484
|
-
do {
|
|
485
|
-
const page = this.#core.listTableSegmentPage(table.id, cursor, 1_024);
|
|
486
|
-
segments.push(...page.records);
|
|
487
|
-
cursor = page.nextCursor;
|
|
488
|
-
} while (cursor !== null);
|
|
489
|
-
}
|
|
490
|
-
segments.sort((left, right) => left.id.localeCompare(right.id));
|
|
491
|
-
const transactionIds = [...new Set(segments.map((segment) => segment.transactionId))];
|
|
492
|
-
const transactions = [];
|
|
493
|
-
for (let start = 0; start < transactionIds.length; start += 64) {
|
|
494
|
-
for (const record of this.#core.getTransactions(transactionIds.slice(start, start + 64))) {
|
|
495
|
-
if (record !== undefined)
|
|
496
|
-
transactions.push(record);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
return {
|
|
500
|
-
manifestVersion: probe.manifestVersion,
|
|
501
|
-
tables,
|
|
502
|
-
segments,
|
|
503
|
-
transactions,
|
|
504
|
-
catalogEpoch: probe.catalogEpoch,
|
|
505
|
-
};
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
async getCurrentManifest() {
|
|
509
|
-
return this.#core.getCurrentManifest();
|
|
510
|
-
}
|
|
511
|
-
async getManifest(version) {
|
|
512
|
-
return this.#core.getManifest(version);
|
|
513
|
-
}
|
|
514
|
-
async listManifestBlockPage(input) {
|
|
515
|
-
return this.#core.listManifestBlockPage(input);
|
|
516
|
-
}
|
|
517
|
-
async listRetiredManifestBlockPage(input) {
|
|
518
|
-
return this.#core.listRetiredManifestBlockPage(input);
|
|
519
|
-
}
|
|
520
|
-
async listManifestPage(afterVersion, limit) {
|
|
521
|
-
return this.#core.listManifestPage(afterVersion, limit);
|
|
522
|
-
}
|
|
523
|
-
async beginTransaction(input) {
|
|
524
|
-
return this.#runAtomic(() => this.#core.beginTransaction(input));
|
|
525
|
-
}
|
|
526
|
-
async createTransaction(record) {
|
|
527
|
-
return this.#runAtomic(() => {
|
|
528
|
-
this.#core.createTransaction(record);
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
async getTransaction(id) {
|
|
532
|
-
return this.#core.getTransaction(id);
|
|
533
|
-
}
|
|
534
|
-
async getTransactions(ids) {
|
|
535
|
-
assertStorageBulkReadItems(ids, "Transaction read batch");
|
|
536
|
-
return this.#core.getTransactions(ids);
|
|
537
|
-
}
|
|
538
|
-
async listTransactionPage(afterId, limit) {
|
|
539
|
-
return this.#core.listTransactionPage(afterId, limit);
|
|
540
|
-
}
|
|
541
|
-
async updateTransaction(id, expectedRevision, update) {
|
|
542
|
-
return this.#runAtomic(() => this.#core.updateTransaction(id, expectedRevision, update));
|
|
543
|
-
}
|
|
544
|
-
async renewTransaction(input) {
|
|
545
|
-
return this.#runAtomic(() => this.#core.renewTransaction(input));
|
|
546
|
-
}
|
|
547
|
-
async abortTransactionIfExpired(input) {
|
|
548
|
-
return this.#runAtomic(() => this.#core.abortTransactionIfExpired(input));
|
|
549
|
-
}
|
|
550
|
-
async stageTransactionArtifacts(input) {
|
|
551
|
-
return this.#runAtomic(() => {
|
|
552
|
-
const updated = this.#core.stageTransactionArtifacts(input);
|
|
553
|
-
// The record half validated everything; the bytes land in this same atomic step.
|
|
554
|
-
for (const block of input.blocks)
|
|
555
|
-
this.#putBlock(block.id, block.bytes);
|
|
556
|
-
return updated;
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
async rollbackTransactionArtifacts(input) {
|
|
560
|
-
return this.#runAtomic(() => {
|
|
561
|
-
const updated = this.#core.rollbackTransactionArtifacts(input);
|
|
562
|
-
for (const id of input.removeBlockIds)
|
|
563
|
-
this.#deleteBlock(id);
|
|
564
|
-
return updated;
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
async commitTransaction(input) {
|
|
568
|
-
return this.#runAtomic(() => this.#core.commitTransaction(input));
|
|
569
|
-
}
|
|
570
|
-
async writeTransaction(input) {
|
|
571
|
-
return this.#runAtomic(() => {
|
|
572
|
-
const summary = this.#core.writeTransaction(input);
|
|
573
|
-
// The record half validated everything; the bytes land in this same atomic step.
|
|
574
|
-
for (const block of input.blocks)
|
|
575
|
-
this.#putBlock(block.id, block.bytes);
|
|
576
|
-
return summary;
|
|
577
|
-
});
|
|
578
|
-
}
|
|
579
|
-
async createLease(record) {
|
|
580
|
-
return this.#runAtomic(() => {
|
|
581
|
-
this.#core.createLease(record);
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
async getLease(id) {
|
|
585
|
-
return this.#core.getLease(id);
|
|
586
|
-
}
|
|
587
|
-
async listLeases() {
|
|
588
|
-
return this.#core.listLeases();
|
|
589
|
-
}
|
|
590
|
-
async listExpiredLeasePage(expiresAtCutoff, afterCursor, limit) {
|
|
591
|
-
return this.#core.listExpiredLeasePage(expiresAtCutoff, afterCursor, limit);
|
|
592
|
-
}
|
|
593
|
-
async renewLease(input) {
|
|
594
|
-
return this.#runAtomic(() => this.#core.renewLease(input));
|
|
595
|
-
}
|
|
596
|
-
async moveLease(input) {
|
|
597
|
-
return this.#runAtomic(() => this.#core.moveLease(input));
|
|
598
|
-
}
|
|
599
|
-
async removeLeaseIfExpired(id, expectedRevision, expiresAtCutoff) {
|
|
600
|
-
return this.#runAtomic(() => this.#core.removeLeaseIfExpired(id, expectedRevision, expiresAtCutoff));
|
|
601
|
-
}
|
|
602
|
-
async removeLease(input) {
|
|
603
|
-
return this.#runAtomic(() => this.#core.removeLease(input));
|
|
604
|
-
}
|
|
605
|
-
async createCompactionJob(record) {
|
|
606
|
-
this.#core.createCompactionJob(record);
|
|
607
|
-
}
|
|
608
|
-
async getCompactionJob(id) {
|
|
609
|
-
return this.#core.getCompactionJob(id);
|
|
610
|
-
}
|
|
611
|
-
async listCompactionJobs(tableId) {
|
|
612
|
-
return this.#core.listCompactionJobs(tableId);
|
|
613
|
-
}
|
|
614
|
-
async listCompactionJobPage(afterId, limit) {
|
|
615
|
-
return this.#core.listCompactionJobPage(afterId, limit);
|
|
616
|
-
}
|
|
617
|
-
async updateCompactionJob(id, expectedRevision, update) {
|
|
618
|
-
return this.#core.updateCompactionJob(id, expectedRevision, update);
|
|
619
|
-
}
|
|
620
|
-
async cancelCompactionJob(id, expectedRevision, cancelledAt) {
|
|
621
|
-
return this.#runAtomic(() => this.#core.cancelCompactionJob(id, expectedRevision, cancelledAt));
|
|
622
|
-
}
|
|
623
|
-
async removeCompactionJob(id) {
|
|
624
|
-
return this.#core.removeCompactionJob(id);
|
|
625
|
-
}
|
|
626
|
-
async createGarbageCollectionJob(input) {
|
|
627
|
-
return this.#runAtomic(() => this.#core.createGarbageCollectionJob(input));
|
|
628
|
-
}
|
|
629
|
-
async updateGarbageCollectionPlanning(input) {
|
|
630
|
-
return this.#runAtomic(() => this.#core.updateGarbageCollectionPlanning(input));
|
|
631
|
-
}
|
|
632
|
-
async getGarbageCollectionJob(id) {
|
|
633
|
-
return this.#core.getGarbageCollectionJob(id);
|
|
634
|
-
}
|
|
635
|
-
async listGarbageCollectionJobs() {
|
|
636
|
-
return this.#core.listGarbageCollectionJobs();
|
|
637
|
-
}
|
|
638
|
-
async listGarbageCollectionJobPage(afterId, limit) {
|
|
639
|
-
return this.#core.listGarbageCollectionJobPage(afterId, limit);
|
|
640
|
-
}
|
|
641
|
-
async runGarbageCollectionStep(input) {
|
|
642
|
-
return this.#runAtomic(() => {
|
|
643
|
-
const result = this.#core.runGarbageCollectionStep(input);
|
|
644
|
-
result.reclaimedBlockIds.forEach((id) => this.#deleteBlock(id));
|
|
645
|
-
return result;
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
async removePrunedManifestRecords(maxItems) {
|
|
649
|
-
return this.#runAtomic(() => this.#core.removePrunedManifestRecords(maxItems));
|
|
650
|
-
}
|
|
651
|
-
async removeGarbageCollectionJob(id) {
|
|
652
|
-
return this.#runAtomic(() => {
|
|
653
|
-
this.#core.removeGarbageCollectionJob(id);
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
async beginSnapshotFrameExport(input) {
|
|
657
|
-
validateSnapshotSessionLifetime(input.createdAt, input.expiresAt);
|
|
658
|
-
validateId(input.ownerId);
|
|
659
|
-
return this.#runAtomic(() => {
|
|
660
|
-
const current = this.#snapshotFrameExport;
|
|
661
|
-
if (current !== undefined) {
|
|
662
|
-
if (Date.parse(current.session.expiresAt) > Date.parse(input.createdAt)) {
|
|
663
|
-
throw new SnapshotImportConflictError(current.session.sessionId, current.session.ownerId, "another snapshot export is still live");
|
|
664
|
-
}
|
|
665
|
-
this.#core.removeLease({
|
|
666
|
-
id: current.session.sessionId,
|
|
667
|
-
ownerId: current.session.ownerId,
|
|
668
|
-
});
|
|
669
|
-
this.#snapshotFrameExport = undefined;
|
|
670
|
-
}
|
|
671
|
-
const manifest = this.#core.snapshotFrameManifest();
|
|
672
|
-
const metadataFrames = [];
|
|
673
|
-
const summaries = emptySnapshotObservedSummaries();
|
|
674
|
-
const append = (item) => {
|
|
675
|
-
const payload = encodeSnapshotMetadataPage([item]);
|
|
676
|
-
const kind = snapshotMetadataFrameKind(item);
|
|
677
|
-
const frame = {
|
|
678
|
-
sequence: metadataFrames.length,
|
|
679
|
-
kind,
|
|
680
|
-
itemCount: 1,
|
|
681
|
-
key: null,
|
|
682
|
-
payload,
|
|
683
|
-
checksum: crc32(payload),
|
|
684
|
-
};
|
|
685
|
-
metadataFrames.push(frame);
|
|
686
|
-
const summary = summaries[kind];
|
|
687
|
-
summary.frameCount += 1;
|
|
688
|
-
summary.itemCount += 1;
|
|
689
|
-
summary.storedBytes = safeSnapshotByteSum(summary.storedBytes, payload.byteLength);
|
|
690
|
-
};
|
|
691
|
-
for (const item of this.#core.snapshotFrameMetadataItems())
|
|
692
|
-
append(item);
|
|
693
|
-
for (const item of this.#core.snapshotFramePostingItems())
|
|
694
|
-
append(item);
|
|
695
|
-
summaries.block = {
|
|
696
|
-
frameCount: manifest.liveBlockCount,
|
|
697
|
-
itemCount: manifest.liveBlockCount,
|
|
698
|
-
storedBytes: manifest.liveBlockBytes,
|
|
699
|
-
};
|
|
700
|
-
const header = prepareSnapshotFrameStreamHeader({
|
|
701
|
-
formatVersion: 1,
|
|
702
|
-
databaseVersion: manifest.version,
|
|
703
|
-
createdAt: input.createdAt,
|
|
704
|
-
kinds: summaries,
|
|
705
|
-
});
|
|
706
|
-
const sessionId = `snapshot-export/${crypto.randomUUID()}`;
|
|
707
|
-
this.#core.createLease({
|
|
708
|
-
id: sessionId,
|
|
709
|
-
kind: "backup",
|
|
710
|
-
manifestVersion: manifest.version,
|
|
711
|
-
ownerId: input.ownerId,
|
|
712
|
-
createdAt: input.createdAt,
|
|
713
|
-
expiresAt: input.expiresAt,
|
|
714
|
-
revision: 0,
|
|
715
|
-
});
|
|
716
|
-
const session = { sessionId, ownerId: input.ownerId, expiresAt: input.expiresAt, header };
|
|
717
|
-
this.#snapshotFrameExport = {
|
|
718
|
-
session,
|
|
719
|
-
manifestVersion: manifest.version,
|
|
720
|
-
metadataFrames,
|
|
721
|
-
nextSequence: 0,
|
|
722
|
-
lastBlockId: null,
|
|
723
|
-
};
|
|
724
|
-
return structuredClone(session);
|
|
725
|
-
});
|
|
726
|
-
}
|
|
727
|
-
async readSnapshotExportFrame(input) {
|
|
728
|
-
validateSnapshotSessionLifetime(input.expiresAtCutoff, input.expiresAt);
|
|
729
|
-
if (!Number.isSafeInteger(input.sequence) || input.sequence < 0) {
|
|
730
|
-
throw new RangeError("Snapshot export sequence is invalid");
|
|
816
|
+
}
|
|
817
|
+
const completed = this.#completedSnapshotFrameImport;
|
|
818
|
+
const completedReplay = completed?.identity === input.identity && completed.version === this.#core.getCurrentManifestVersion();
|
|
819
|
+
if (completedReplay && !sameSnapshotFrameHeader(completed.header, header)) {
|
|
820
|
+
throw new SnapshotImportConflictError(input.identity, input.ownerId, "completed snapshot header changed");
|
|
821
|
+
}
|
|
822
|
+
if (!completedReplay && this.#core.getCurrentManifestVersion() !== null) {
|
|
823
|
+
throw new Error("Snapshot store already holds a database");
|
|
824
|
+
}
|
|
825
|
+
if (!completedReplay && this.#blocks.size > 0) {
|
|
826
|
+
throw new StorageCorruptionError("memory", "snapshot-import", "empty target contains unowned payloads");
|
|
827
|
+
}
|
|
828
|
+
const session = {
|
|
829
|
+
identity: input.identity,
|
|
830
|
+
ownerId: input.ownerId,
|
|
831
|
+
version: header.databaseVersion,
|
|
832
|
+
createdAt: input.createdAt,
|
|
833
|
+
expiresAt: input.expiresAt,
|
|
834
|
+
nextSequence: 0,
|
|
835
|
+
stagedBytes: 0
|
|
836
|
+
};
|
|
837
|
+
this.#snapshotFrameImport = {
|
|
838
|
+
session,
|
|
839
|
+
header,
|
|
840
|
+
metadataFrames: completedReplay ? this.#captureSnapshotMetadataFrames() : [],
|
|
841
|
+
observed: emptySnapshotObservedSummaries(),
|
|
842
|
+
checksum: 0,
|
|
843
|
+
itemCount: 0,
|
|
844
|
+
previousKindIndex: 0,
|
|
845
|
+
lastBlockId: null,
|
|
846
|
+
lastBatchFrames: [],
|
|
847
|
+
completedReplay
|
|
848
|
+
};
|
|
849
|
+
return structuredClone(session);
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
async renewSnapshotFrameImport(input) {
|
|
853
|
+
validateSnapshotSessionLifetime(input.expiresAtCutoff, input.expiresAt);
|
|
854
|
+
return this.#runAtomic(() => {
|
|
855
|
+
const state = this.#requireLiveSnapshotFrameImport(input);
|
|
856
|
+
state.session = { ...state.session, expiresAt: input.expiresAt };
|
|
857
|
+
return structuredClone(state.session);
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
async appendSnapshotImportFrames(input) {
|
|
861
|
+
validateSnapshotSessionLifetime(input.expiresAtCutoff, input.expiresAt);
|
|
862
|
+
if (input.frames.length < 1 || input.frames.length > MAX_SNAPSHOT_FRAME_BATCH_ITEMS) {
|
|
863
|
+
throw new RangeError("Snapshot import frame batch item count is out of bounds");
|
|
864
|
+
}
|
|
865
|
+
let batchBytes = 0;
|
|
866
|
+
let metadataBatchBytes = 0;
|
|
867
|
+
for (const frame of input.frames) {
|
|
868
|
+
batchBytes = safeSnapshotByteSum(batchBytes, frame.payload.byteLength);
|
|
869
|
+
if (batchBytes > MAX_SNAPSHOT_FRAME_BATCH_BYTES) {
|
|
870
|
+
throw new RangeError("Snapshot import frame batch exceeds its byte limit");
|
|
871
|
+
}
|
|
872
|
+
if (frame.kind !== "block") {
|
|
873
|
+
metadataBatchBytes = safeSnapshotByteSum(metadataBatchBytes, frame.payload.byteLength);
|
|
874
|
+
if (metadataBatchBytes > MAX_SNAPSHOT_METADATA_BATCH_BYTES) {
|
|
875
|
+
throw new RangeError("Snapshot metadata frame batch exceeds its byte limit");
|
|
731
876
|
}
|
|
732
|
-
|
|
733
|
-
const state = this.#snapshotFrameExport;
|
|
734
|
-
if (state?.session.sessionId !== input.sessionId || state.session.ownerId !== input.ownerId) {
|
|
735
|
-
throw new SnapshotImportConflictError(input.sessionId, input.ownerId, "export session is missing or owned by another caller");
|
|
736
|
-
}
|
|
737
|
-
if (input.sequence === state.nextSequence - 1 && state.lastFrame !== undefined) {
|
|
738
|
-
return copySnapshotFrame(state.lastFrame);
|
|
739
|
-
}
|
|
740
|
-
if (input.sequence !== state.nextSequence) {
|
|
741
|
-
throw new RangeError("Snapshot export frames must be pulled in contiguous order");
|
|
742
|
-
}
|
|
743
|
-
const lease = this.#core.getLease(input.sessionId);
|
|
744
|
-
if (lease === undefined) {
|
|
745
|
-
throw new SnapshotImportConflictError(input.sessionId, input.ownerId, "lease is missing");
|
|
746
|
-
}
|
|
747
|
-
this.#core.renewLease({
|
|
748
|
-
id: lease.id,
|
|
749
|
-
expectedRevision: lease.revision,
|
|
750
|
-
expiresAtCutoff: input.expiresAtCutoff,
|
|
751
|
-
expiresAt: input.expiresAt,
|
|
752
|
-
});
|
|
753
|
-
state.session = { ...state.session, expiresAt: input.expiresAt };
|
|
754
|
-
const metadata = state.metadataFrames[input.sequence];
|
|
755
|
-
let frame;
|
|
756
|
-
if (metadata !== undefined) {
|
|
757
|
-
frame = metadata;
|
|
758
|
-
}
|
|
759
|
-
else {
|
|
760
|
-
const blockPage = this.#core.listManifestBlockPage({
|
|
761
|
-
version: state.manifestVersion,
|
|
762
|
-
afterBlockId: state.lastBlockId,
|
|
763
|
-
limit: 1,
|
|
764
|
-
});
|
|
765
|
-
const record = blockPage.records[0];
|
|
766
|
-
if (record === undefined)
|
|
767
|
-
return undefined;
|
|
768
|
-
const bytes = this.#blocks.get(record.blockId);
|
|
769
|
-
if (bytes?.byteLength !== record.byteLength || crc32(bytes) !== record.checksum) {
|
|
770
|
-
throw new StorageCorruptionError("memory", record.blockId, "snapshot block disagrees with its manifest provenance");
|
|
771
|
-
}
|
|
772
|
-
frame = {
|
|
773
|
-
sequence: input.sequence,
|
|
774
|
-
kind: "block",
|
|
775
|
-
itemCount: 1,
|
|
776
|
-
key: record.blockId,
|
|
777
|
-
payload: bytes.slice(),
|
|
778
|
-
checksum: record.checksum,
|
|
779
|
-
};
|
|
780
|
-
state.lastBlockId = record.blockId;
|
|
781
|
-
}
|
|
782
|
-
state.lastFrame = copySnapshotFrame(frame);
|
|
783
|
-
state.nextSequence += 1;
|
|
784
|
-
return copySnapshotFrame(frame);
|
|
785
|
-
});
|
|
877
|
+
}
|
|
786
878
|
}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
if (state.session.sessionId !== input.sessionId || state.session.ownerId !== input.ownerId) {
|
|
793
|
-
throw new SnapshotImportConflictError(input.sessionId, input.ownerId, "export session belongs to another caller");
|
|
794
|
-
}
|
|
795
|
-
this.#core.removeLease({ id: input.sessionId, ownerId: input.ownerId });
|
|
796
|
-
this.#snapshotFrameExport = undefined;
|
|
797
|
-
return true;
|
|
798
|
-
});
|
|
799
|
-
}
|
|
800
|
-
async beginSnapshotFrameImport(input) {
|
|
801
|
-
validateSnapshotSessionLifetime(input.createdAt, input.expiresAt);
|
|
802
|
-
validateId(input.ownerId);
|
|
803
|
-
const header = prepareSnapshotFrameStreamHeader(input.header);
|
|
804
|
-
if (snapshotFrameStreamHeaderIdentity(header) !== input.identity) {
|
|
805
|
-
throw new TypeError("Snapshot import identity does not match its canonical header");
|
|
879
|
+
return this.#runAtomic(() => {
|
|
880
|
+
const state = this.#requireLiveSnapshotFrameImport(input);
|
|
881
|
+
if (state.lastBatchFrames.length === input.frames.length && input.frames[0]?.sequence === state.session.nextSequence - state.lastBatchFrames.length) {
|
|
882
|
+
if (!state.lastBatchFrames.every((frame, index) => sameSnapshotFrame(frame, input.frames[index]))) {
|
|
883
|
+
throw new SnapshotImportConflictError(input.identity, input.ownerId, "replayed snapshot frame bytes changed");
|
|
806
884
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
if (!sameHeader) {
|
|
826
|
-
throw new SnapshotImportConflictError(current.session.identity, current.session.ownerId, "snapshot import header changed");
|
|
827
|
-
}
|
|
828
|
-
current.session = {
|
|
829
|
-
...current.session,
|
|
830
|
-
ownerId: input.ownerId,
|
|
831
|
-
expiresAt: input.expiresAt,
|
|
832
|
-
};
|
|
833
|
-
return structuredClone(current.session);
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
const completed = this.#completedSnapshotFrameImport;
|
|
837
|
-
const completedReplay = completed?.identity === input.identity &&
|
|
838
|
-
completed.version === this.#core.getCurrentManifestVersion();
|
|
839
|
-
if (completedReplay && !sameSnapshotFrameHeader(completed.header, header)) {
|
|
840
|
-
throw new SnapshotImportConflictError(input.identity, input.ownerId, "completed snapshot header changed");
|
|
841
|
-
}
|
|
842
|
-
if (!completedReplay && this.#core.getCurrentManifestVersion() !== null) {
|
|
843
|
-
throw new Error("Snapshot store already holds a database");
|
|
844
|
-
}
|
|
845
|
-
if (!completedReplay && this.#blocks.size > 0) {
|
|
846
|
-
throw new StorageCorruptionError("memory", "snapshot-import", "empty target contains unowned payloads");
|
|
847
|
-
}
|
|
848
|
-
const session = {
|
|
849
|
-
identity: input.identity,
|
|
850
|
-
ownerId: input.ownerId,
|
|
851
|
-
version: header.databaseVersion,
|
|
852
|
-
createdAt: input.createdAt,
|
|
853
|
-
expiresAt: input.expiresAt,
|
|
854
|
-
nextSequence: 0,
|
|
855
|
-
stagedBytes: 0,
|
|
856
|
-
};
|
|
857
|
-
this.#snapshotFrameImport = {
|
|
858
|
-
session,
|
|
859
|
-
header,
|
|
860
|
-
metadataFrames: completedReplay ? this.#captureSnapshotMetadataFrames() : [],
|
|
861
|
-
observed: emptySnapshotObservedSummaries(),
|
|
862
|
-
checksum: 0,
|
|
863
|
-
itemCount: 0,
|
|
864
|
-
previousKindIndex: 0,
|
|
865
|
-
lastBlockId: null,
|
|
866
|
-
lastBatchFrames: [],
|
|
867
|
-
completedReplay,
|
|
868
|
-
};
|
|
869
|
-
return structuredClone(session);
|
|
870
|
-
});
|
|
871
|
-
}
|
|
872
|
-
async renewSnapshotFrameImport(input) {
|
|
873
|
-
validateSnapshotSessionLifetime(input.expiresAtCutoff, input.expiresAt);
|
|
874
|
-
return this.#runAtomic(() => {
|
|
875
|
-
const state = this.#requireLiveSnapshotFrameImport(input);
|
|
876
|
-
state.session = { ...state.session, expiresAt: input.expiresAt };
|
|
877
|
-
return structuredClone(state.session);
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
async appendSnapshotImportFrames(input) {
|
|
881
|
-
validateSnapshotSessionLifetime(input.expiresAtCutoff, input.expiresAt);
|
|
882
|
-
if (input.frames.length < 1 || input.frames.length > MAX_SNAPSHOT_FRAME_BATCH_ITEMS) {
|
|
883
|
-
throw new RangeError("Snapshot import frame batch item count is out of bounds");
|
|
885
|
+
state.session = { ...state.session, expiresAt: input.expiresAt };
|
|
886
|
+
return structuredClone(state.session);
|
|
887
|
+
}
|
|
888
|
+
if (input.frames[0]?.sequence !== state.session.nextSequence) {
|
|
889
|
+
throw new RangeError("Snapshot import frame batch is not the next contiguous sequence");
|
|
890
|
+
}
|
|
891
|
+
const observed = structuredClone(state.observed);
|
|
892
|
+
let checksum = state.checksum;
|
|
893
|
+
let itemCount = state.itemCount;
|
|
894
|
+
let previousKindIndex = state.previousKindIndex;
|
|
895
|
+
let lastBlockId = state.lastBlockId;
|
|
896
|
+
let nextSequence = state.session.nextSequence;
|
|
897
|
+
let stagedBytes = state.session.stagedBytes;
|
|
898
|
+
const metadataFrames = [];
|
|
899
|
+
const blockFrames = [];
|
|
900
|
+
for (const frame of input.frames) {
|
|
901
|
+
if (frame.sequence !== nextSequence || crc32(frame.payload) !== frame.checksum) {
|
|
902
|
+
throw new Error("Snapshot import frame sequence or checksum is invalid");
|
|
884
903
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
904
|
+
const envelope = snapshotFrameEnvelopeParts(frame);
|
|
905
|
+
const kindIndex = SNAPSHOT_FRAME_KINDS.indexOf(frame.kind);
|
|
906
|
+
if (kindIndex < previousKindIndex)
|
|
907
|
+
throw new Error("Snapshot frame kinds are out of order");
|
|
908
|
+
previousKindIndex = kindIndex;
|
|
909
|
+
if (frame.kind === "block") {
|
|
910
|
+
if (frame.key === null || lastBlockId !== null && frame.key <= lastBlockId) {
|
|
911
|
+
throw new Error("Snapshot block frames are not in canonical ID order");
|
|
912
|
+
}
|
|
913
|
+
verifyStoredBlock(frame.payload);
|
|
914
|
+
if (state.completedReplay) {
|
|
915
|
+
const current = this.#blocks.get(frame.key);
|
|
916
|
+
if (current === void 0 || !sameSnapshotBytes(current, frame.payload)) {
|
|
917
|
+
throw new SnapshotImportConflictError(input.identity, input.ownerId, `completed snapshot block changed: ${frame.key}`);
|
|
891
918
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
919
|
+
} else
|
|
920
|
+
blockFrames.push(copySnapshotFrame(frame));
|
|
921
|
+
lastBlockId = frame.key;
|
|
922
|
+
} else {
|
|
923
|
+
if (frame.itemCount !== 1) {
|
|
924
|
+
throw new TypeError("Snapshot v1 metadata frames contain exactly one item");
|
|
925
|
+
}
|
|
926
|
+
const items = decodeSnapshotMetadataItems(frame.kind, frame.payload);
|
|
927
|
+
if (items.length !== frame.itemCount) {
|
|
928
|
+
throw new Error("Snapshot metadata item count disagrees with its frame");
|
|
929
|
+
}
|
|
930
|
+
if (state.completedReplay) {
|
|
931
|
+
const expected2 = state.metadataFrames[frame.sequence];
|
|
932
|
+
if (!sameSnapshotFrame(expected2, frame)) {
|
|
933
|
+
throw new SnapshotImportConflictError(input.identity, input.ownerId, "completed snapshot metadata changed");
|
|
897
934
|
}
|
|
935
|
+
} else
|
|
936
|
+
metadataFrames.push(copySnapshotFrame(frame));
|
|
898
937
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
if (input.frames[0]?.sequence !== state.session.nextSequence) {
|
|
910
|
-
throw new RangeError("Snapshot import frame batch is not the next contiguous sequence");
|
|
911
|
-
}
|
|
912
|
-
const observed = structuredClone(state.observed);
|
|
913
|
-
let checksum = state.checksum;
|
|
914
|
-
let itemCount = state.itemCount;
|
|
915
|
-
let previousKindIndex = state.previousKindIndex;
|
|
916
|
-
let lastBlockId = state.lastBlockId;
|
|
917
|
-
let nextSequence = state.session.nextSequence;
|
|
918
|
-
let stagedBytes = state.session.stagedBytes;
|
|
919
|
-
const metadataFrames = [];
|
|
920
|
-
const blockFrames = [];
|
|
921
|
-
for (const frame of input.frames) {
|
|
922
|
-
if (frame.sequence !== nextSequence || crc32(frame.payload) !== frame.checksum) {
|
|
923
|
-
throw new Error("Snapshot import frame sequence or checksum is invalid");
|
|
924
|
-
}
|
|
925
|
-
const envelope = snapshotFrameEnvelopeParts(frame);
|
|
926
|
-
const kindIndex = SNAPSHOT_FRAME_KINDS.indexOf(frame.kind);
|
|
927
|
-
if (kindIndex < previousKindIndex)
|
|
928
|
-
throw new Error("Snapshot frame kinds are out of order");
|
|
929
|
-
previousKindIndex = kindIndex;
|
|
930
|
-
if (frame.kind === "block") {
|
|
931
|
-
if (frame.key === null || (lastBlockId !== null && frame.key <= lastBlockId)) {
|
|
932
|
-
throw new Error("Snapshot block frames are not in canonical ID order");
|
|
933
|
-
}
|
|
934
|
-
verifyStoredBlock(frame.payload);
|
|
935
|
-
if (state.completedReplay) {
|
|
936
|
-
const current = this.#blocks.get(frame.key);
|
|
937
|
-
if (current === undefined || !sameSnapshotBytes(current, frame.payload)) {
|
|
938
|
-
throw new SnapshotImportConflictError(input.identity, input.ownerId, `completed snapshot block changed: ${frame.key}`);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
else
|
|
942
|
-
blockFrames.push(copySnapshotFrame(frame));
|
|
943
|
-
lastBlockId = frame.key;
|
|
944
|
-
}
|
|
945
|
-
else {
|
|
946
|
-
if (frame.itemCount !== 1) {
|
|
947
|
-
throw new TypeError("Snapshot v1 metadata frames contain exactly one item");
|
|
948
|
-
}
|
|
949
|
-
const items = decodeSnapshotMetadataItems(frame.kind, frame.payload);
|
|
950
|
-
if (items.length !== frame.itemCount) {
|
|
951
|
-
throw new Error("Snapshot metadata item count disagrees with its frame");
|
|
952
|
-
}
|
|
953
|
-
if (state.completedReplay) {
|
|
954
|
-
const expected = state.metadataFrames[frame.sequence];
|
|
955
|
-
if (!sameSnapshotFrame(expected, frame)) {
|
|
956
|
-
throw new SnapshotImportConflictError(input.identity, input.ownerId, "completed snapshot metadata changed");
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
else
|
|
960
|
-
metadataFrames.push(copySnapshotFrame(frame));
|
|
961
|
-
}
|
|
962
|
-
checksum = extendSnapshotFrameStreamChecksum(checksum, envelope);
|
|
963
|
-
itemCount = safeSnapshotByteSum(itemCount, frame.itemCount);
|
|
964
|
-
stagedBytes = safeSnapshotByteSum(stagedBytes, frame.payload.byteLength);
|
|
965
|
-
const summary = observed[frame.kind];
|
|
966
|
-
summary.frameCount += 1;
|
|
967
|
-
summary.itemCount = safeSnapshotByteSum(summary.itemCount, frame.itemCount);
|
|
968
|
-
summary.storedBytes = safeSnapshotByteSum(summary.storedBytes, frame.payload.byteLength);
|
|
969
|
-
const expected = state.header.kinds[frame.kind];
|
|
970
|
-
if (summary.frameCount > expected.frameCount ||
|
|
971
|
-
summary.itemCount > expected.itemCount ||
|
|
972
|
-
summary.storedBytes > expected.storedBytes) {
|
|
973
|
-
throw new Error(`Snapshot ${frame.kind} frames exceed their header summary`);
|
|
974
|
-
}
|
|
975
|
-
nextSequence += 1;
|
|
976
|
-
}
|
|
977
|
-
for (const frame of metadataFrames)
|
|
978
|
-
state.metadataFrames.push(frame);
|
|
979
|
-
for (const frame of blockFrames) {
|
|
980
|
-
if (frame.key === null)
|
|
981
|
-
throw new Error("Snapshot block frame lost its ID");
|
|
982
|
-
this.#putBlock(frame.key, frame.payload);
|
|
983
|
-
}
|
|
984
|
-
state.observed = observed;
|
|
985
|
-
state.checksum = checksum;
|
|
986
|
-
state.itemCount = itemCount;
|
|
987
|
-
state.previousKindIndex = previousKindIndex;
|
|
988
|
-
state.lastBlockId = lastBlockId;
|
|
989
|
-
state.lastBatchFrames = input.frames.map(copySnapshotFrame);
|
|
990
|
-
state.session = {
|
|
991
|
-
...state.session,
|
|
992
|
-
expiresAt: input.expiresAt,
|
|
993
|
-
nextSequence,
|
|
994
|
-
stagedBytes,
|
|
995
|
-
};
|
|
996
|
-
return structuredClone(state.session);
|
|
997
|
-
});
|
|
998
|
-
}
|
|
999
|
-
async finishSnapshotFrameImport(input) {
|
|
1000
|
-
encodeSnapshotFrameStreamFooter(input.footer);
|
|
1001
|
-
return this.#runAtomic(() => {
|
|
1002
|
-
const state = this.#requireLiveSnapshotFrameImport(input);
|
|
1003
|
-
const expectedFrameCount = snapshotFrameCount(state.header);
|
|
1004
|
-
if (input.footer.frameCount !== expectedFrameCount ||
|
|
1005
|
-
input.footer.frameCount !== state.session.nextSequence ||
|
|
1006
|
-
input.footer.itemCount !== state.itemCount ||
|
|
1007
|
-
input.footer.storedBytes !== state.session.stagedBytes ||
|
|
1008
|
-
input.footer.checksum !== state.checksum) {
|
|
1009
|
-
throw new Error("Snapshot import footer does not match its staged frames");
|
|
1010
|
-
}
|
|
1011
|
-
for (const kind of SNAPSHOT_FRAME_KINDS) {
|
|
1012
|
-
const actual = state.observed[kind];
|
|
1013
|
-
const expected = state.header.kinds[kind];
|
|
1014
|
-
if (actual.frameCount !== expected.frameCount ||
|
|
1015
|
-
actual.itemCount !== expected.itemCount ||
|
|
1016
|
-
actual.storedBytes !== expected.storedBytes) {
|
|
1017
|
-
throw new Error(`Snapshot import ${kind} generation is incomplete`);
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
if (!state.completedReplay) {
|
|
1021
|
-
const items = function* () {
|
|
1022
|
-
for (const frame of state.metadataFrames) {
|
|
1023
|
-
if (frame.kind === "block")
|
|
1024
|
-
continue;
|
|
1025
|
-
yield* decodeSnapshotMetadataItems(frame.kind, frame.payload);
|
|
1026
|
-
}
|
|
1027
|
-
};
|
|
1028
|
-
const blocks = function* (store) {
|
|
1029
|
-
for (const [blockId, bytes] of store.#blocks) {
|
|
1030
|
-
yield { blockId, byteLength: bytes.byteLength, checksum: crc32(bytes) };
|
|
1031
|
-
}
|
|
1032
|
-
};
|
|
1033
|
-
this.#core.loadSnapshotFrameItems(state.header, items(), blocks(this));
|
|
1034
|
-
}
|
|
1035
|
-
this.#completedSnapshotFrameImport = {
|
|
1036
|
-
identity: state.session.identity,
|
|
1037
|
-
version: state.header.databaseVersion,
|
|
1038
|
-
header: structuredClone(state.header),
|
|
1039
|
-
};
|
|
1040
|
-
this.#snapshotFrameImport = undefined;
|
|
1041
|
-
});
|
|
1042
|
-
}
|
|
1043
|
-
async cancelSnapshotFrameImport(input) {
|
|
1044
|
-
return this.#runAtomic(() => {
|
|
1045
|
-
const state = this.#snapshotFrameImport;
|
|
1046
|
-
if (state?.session.identity !== input.identity || state.session.ownerId !== input.ownerId) {
|
|
1047
|
-
throw new SnapshotImportConflictError(input.identity, input.ownerId, "session is missing or owned by another caller");
|
|
1048
|
-
}
|
|
1049
|
-
const result = {
|
|
1050
|
-
identity: input.identity,
|
|
1051
|
-
removedBlockCount: state.completedReplay ? 0 : state.observed.block.itemCount,
|
|
1052
|
-
removedBytes: state.completedReplay ? 0 : state.observed.block.storedBytes,
|
|
1053
|
-
};
|
|
1054
|
-
if (!state.completedReplay)
|
|
1055
|
-
this.#clearPhysicalBlocks();
|
|
1056
|
-
this.#snapshotFrameImport = undefined;
|
|
1057
|
-
return result;
|
|
1058
|
-
});
|
|
1059
|
-
}
|
|
1060
|
-
#requireLiveSnapshotFrameImport(input) {
|
|
1061
|
-
const state = this.#snapshotFrameImport;
|
|
1062
|
-
if (state?.session.identity !== input.identity || state.session.ownerId !== input.ownerId) {
|
|
1063
|
-
throw new SnapshotImportConflictError(input.identity, input.ownerId, "session is missing or owned by another caller");
|
|
938
|
+
checksum = extendSnapshotFrameStreamChecksum(checksum, envelope);
|
|
939
|
+
itemCount = safeSnapshotByteSum(itemCount, frame.itemCount);
|
|
940
|
+
stagedBytes = safeSnapshotByteSum(stagedBytes, frame.payload.byteLength);
|
|
941
|
+
const summary = observed[frame.kind];
|
|
942
|
+
summary.frameCount += 1;
|
|
943
|
+
summary.itemCount = safeSnapshotByteSum(summary.itemCount, frame.itemCount);
|
|
944
|
+
summary.storedBytes = safeSnapshotByteSum(summary.storedBytes, frame.payload.byteLength);
|
|
945
|
+
const expected = state.header.kinds[frame.kind];
|
|
946
|
+
if (summary.frameCount > expected.frameCount || summary.itemCount > expected.itemCount || summary.storedBytes > expected.storedBytes) {
|
|
947
|
+
throw new Error(`Snapshot ${frame.kind} frames exceed their header summary`);
|
|
1064
948
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
949
|
+
nextSequence += 1;
|
|
950
|
+
}
|
|
951
|
+
for (const frame of metadataFrames)
|
|
952
|
+
state.metadataFrames.push(frame);
|
|
953
|
+
for (const frame of blockFrames) {
|
|
954
|
+
if (frame.key === null)
|
|
955
|
+
throw new Error("Snapshot block frame lost its ID");
|
|
956
|
+
this.#putBlock(frame.key, frame.payload);
|
|
957
|
+
}
|
|
958
|
+
state.observed = observed;
|
|
959
|
+
state.checksum = checksum;
|
|
960
|
+
state.itemCount = itemCount;
|
|
961
|
+
state.previousKindIndex = previousKindIndex;
|
|
962
|
+
state.lastBlockId = lastBlockId;
|
|
963
|
+
state.lastBatchFrames = input.frames.map(copySnapshotFrame);
|
|
964
|
+
state.session = {
|
|
965
|
+
...state.session,
|
|
966
|
+
expiresAt: input.expiresAt,
|
|
967
|
+
nextSequence,
|
|
968
|
+
stagedBytes
|
|
969
|
+
};
|
|
970
|
+
return structuredClone(state.session);
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
async finishSnapshotFrameImport(input) {
|
|
974
|
+
encodeSnapshotFrameStreamFooter(input.footer);
|
|
975
|
+
return this.#runAtomic(() => {
|
|
976
|
+
const state = this.#requireLiveSnapshotFrameImport(input);
|
|
977
|
+
const expectedFrameCount = snapshotFrameCount(state.header);
|
|
978
|
+
if (input.footer.frameCount !== expectedFrameCount || input.footer.frameCount !== state.session.nextSequence || input.footer.itemCount !== state.itemCount || input.footer.storedBytes !== state.session.stagedBytes || input.footer.checksum !== state.checksum) {
|
|
979
|
+
throw new Error("Snapshot import footer does not match its staged frames");
|
|
980
|
+
}
|
|
981
|
+
for (const kind of SNAPSHOT_FRAME_KINDS) {
|
|
982
|
+
const actual = state.observed[kind];
|
|
983
|
+
const expected = state.header.kinds[kind];
|
|
984
|
+
if (actual.frameCount !== expected.frameCount || actual.itemCount !== expected.itemCount || actual.storedBytes !== expected.storedBytes) {
|
|
985
|
+
throw new Error(`Snapshot import ${kind} generation is incomplete`);
|
|
1067
986
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
kind: snapshotMetadataFrameKind(item),
|
|
1077
|
-
itemCount: 1,
|
|
1078
|
-
key: null,
|
|
1079
|
-
payload,
|
|
1080
|
-
checksum: crc32(payload),
|
|
1081
|
-
});
|
|
987
|
+
}
|
|
988
|
+
if (!state.completedReplay) {
|
|
989
|
+
const items = function* () {
|
|
990
|
+
for (const frame of state.metadataFrames) {
|
|
991
|
+
if (frame.kind === "block")
|
|
992
|
+
continue;
|
|
993
|
+
yield* decodeSnapshotMetadataItems(frame.kind, frame.payload);
|
|
994
|
+
}
|
|
1082
995
|
};
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
this.#
|
|
1169
|
-
}
|
|
1170
|
-
close() {
|
|
1171
|
-
// The in-memory implementation owns no external resources.
|
|
1172
|
-
}
|
|
1173
|
-
#runAtomic(operation) {
|
|
1174
|
-
let resolveResult;
|
|
1175
|
-
let rejectResult;
|
|
1176
|
-
const result = new Promise((resolve, reject) => {
|
|
1177
|
-
resolveResult = resolve;
|
|
1178
|
-
rejectResult = reject;
|
|
1179
|
-
});
|
|
1180
|
-
this.#commitQueue = this.#commitQueue.then(() => {
|
|
1181
|
-
try {
|
|
1182
|
-
if (this.#completedSnapshotFrameImport !== undefined &&
|
|
1183
|
-
this.#core.getCurrentManifestVersion() !== this.#completedSnapshotFrameImport.version) {
|
|
1184
|
-
this.#completedSnapshotFrameImport = undefined;
|
|
1185
|
-
}
|
|
1186
|
-
resolveResult(operation());
|
|
1187
|
-
}
|
|
1188
|
-
catch (error) {
|
|
1189
|
-
rejectResult(error);
|
|
1190
|
-
}
|
|
1191
|
-
});
|
|
996
|
+
const blocks = function* (store) {
|
|
997
|
+
for (const [blockId, bytes] of store.#blocks) {
|
|
998
|
+
yield { blockId, byteLength: bytes.byteLength, checksum: crc32(bytes) };
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
1001
|
+
this.#core.loadSnapshotFrameItems(state.header, items(), blocks(this));
|
|
1002
|
+
}
|
|
1003
|
+
this.#completedSnapshotFrameImport = {
|
|
1004
|
+
identity: state.session.identity,
|
|
1005
|
+
version: state.header.databaseVersion,
|
|
1006
|
+
header: structuredClone(state.header)
|
|
1007
|
+
};
|
|
1008
|
+
this.#snapshotFrameImport = void 0;
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
async cancelSnapshotFrameImport(input) {
|
|
1012
|
+
return this.#runAtomic(() => {
|
|
1013
|
+
const state = this.#snapshotFrameImport;
|
|
1014
|
+
if (state?.session.identity !== input.identity || state.session.ownerId !== input.ownerId) {
|
|
1015
|
+
throw new SnapshotImportConflictError(input.identity, input.ownerId, "session is missing or owned by another caller");
|
|
1016
|
+
}
|
|
1017
|
+
const result = {
|
|
1018
|
+
identity: input.identity,
|
|
1019
|
+
removedBlockCount: state.completedReplay ? 0 : state.observed.block.itemCount,
|
|
1020
|
+
removedBytes: state.completedReplay ? 0 : state.observed.block.storedBytes
|
|
1021
|
+
};
|
|
1022
|
+
if (!state.completedReplay)
|
|
1023
|
+
this.#clearPhysicalBlocks();
|
|
1024
|
+
this.#snapshotFrameImport = void 0;
|
|
1025
|
+
return result;
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
#requireLiveSnapshotFrameImport(input) {
|
|
1029
|
+
const state = this.#snapshotFrameImport;
|
|
1030
|
+
if (state?.session.identity !== input.identity || state.session.ownerId !== input.ownerId) {
|
|
1031
|
+
throw new SnapshotImportConflictError(input.identity, input.ownerId, "session is missing or owned by another caller");
|
|
1032
|
+
}
|
|
1033
|
+
if (Date.parse(state.session.expiresAt) <= Date.parse(input.expiresAtCutoff)) {
|
|
1034
|
+
throw new SnapshotImportConflictError(input.identity, input.ownerId, "session is expired");
|
|
1035
|
+
}
|
|
1036
|
+
return state;
|
|
1037
|
+
}
|
|
1038
|
+
#captureSnapshotMetadataFrames() {
|
|
1039
|
+
const frames = [];
|
|
1040
|
+
const append = (item) => {
|
|
1041
|
+
const payload = encodeSnapshotMetadataPage([item]);
|
|
1042
|
+
frames.push({
|
|
1043
|
+
sequence: frames.length,
|
|
1044
|
+
kind: snapshotMetadataFrameKind(item),
|
|
1045
|
+
itemCount: 1,
|
|
1046
|
+
key: null,
|
|
1047
|
+
payload,
|
|
1048
|
+
checksum: crc32(payload)
|
|
1049
|
+
});
|
|
1050
|
+
};
|
|
1051
|
+
for (const item of this.#core.snapshotFrameMetadataItems())
|
|
1052
|
+
append(item);
|
|
1053
|
+
for (const item of this.#core.snapshotFramePostingItems())
|
|
1054
|
+
append(item);
|
|
1055
|
+
return frames;
|
|
1056
|
+
}
|
|
1057
|
+
async inspectInterruptedImport() {
|
|
1058
|
+
const framed = this.#snapshotFrameImport;
|
|
1059
|
+
if (framed !== void 0) {
|
|
1060
|
+
return {
|
|
1061
|
+
identity: framed.session.identity,
|
|
1062
|
+
version: framed.session.version,
|
|
1063
|
+
createdAt: framed.session.createdAt,
|
|
1064
|
+
stagedBlockCount: framed.observed.block.itemCount,
|
|
1065
|
+
stagedBytes: framed.session.stagedBytes
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
return null;
|
|
1069
|
+
}
|
|
1070
|
+
async abortInterruptedImport(identity) {
|
|
1071
|
+
return this.#runAtomic(() => {
|
|
1072
|
+
const framed = this.#snapshotFrameImport;
|
|
1073
|
+
if (framed?.session.identity === identity) {
|
|
1074
|
+
const result = {
|
|
1075
|
+
identity,
|
|
1076
|
+
removedBlockCount: framed.completedReplay ? 0 : framed.observed.block.itemCount,
|
|
1077
|
+
removedBytes: framed.completedReplay ? 0 : framed.observed.block.storedBytes
|
|
1078
|
+
};
|
|
1079
|
+
if (!framed.completedReplay)
|
|
1080
|
+
this.#clearPhysicalBlocks();
|
|
1081
|
+
this.#snapshotFrameImport = void 0;
|
|
1192
1082
|
return result;
|
|
1193
|
-
|
|
1083
|
+
}
|
|
1084
|
+
throw new Error(`Interrupted snapshot import not found: ${identity}`);
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
async getLogicalStorageBytes() {
|
|
1088
|
+
return this.#physicalBlockBytes + this.#tempBytesTotal;
|
|
1089
|
+
}
|
|
1090
|
+
async getStorageStats() {
|
|
1091
|
+
return this.#runAtomic(() => {
|
|
1092
|
+
const current = this.#core.getCurrentManifest();
|
|
1093
|
+
const temporaryBytes = this.#tempBytesTotal;
|
|
1094
|
+
const manifestCount = countRecordPages((cursor) => this.#core.listManifestPage(cursor, 1024));
|
|
1095
|
+
const transactionCount = countRecordPages((cursor) => this.#core.listTransactionPage(cursor, 1024));
|
|
1096
|
+
const segmentCount = countRecordPages((cursor) => this.#core.listSegmentPage(cursor, 1024));
|
|
1097
|
+
const liveBlockCount = current?.liveBlockCount ?? 0;
|
|
1098
|
+
const liveBlockBytes = current?.liveBlockBytes ?? 0;
|
|
1099
|
+
if (liveBlockCount > this.#blocks.size || liveBlockBytes > this.#physicalBlockBytes) {
|
|
1100
|
+
throw new StorageCorruptionError("memory", "manifest", "live block summary exceeds physical payloads");
|
|
1101
|
+
}
|
|
1102
|
+
return {
|
|
1103
|
+
backend: "memory",
|
|
1104
|
+
logicalBytes: this.#physicalBlockBytes + temporaryBytes,
|
|
1105
|
+
physicalBytes: this.#physicalBlockBytes + temporaryBytes,
|
|
1106
|
+
liveBlockCount,
|
|
1107
|
+
obsoleteBlockCount: this.#blocks.size - liveBlockCount,
|
|
1108
|
+
liveBlockBytes,
|
|
1109
|
+
obsoleteBlockBytes: this.#physicalBlockBytes - liveBlockBytes,
|
|
1110
|
+
temporaryBytes,
|
|
1111
|
+
walBytes: null,
|
|
1112
|
+
checkpointBytes: null,
|
|
1113
|
+
orphanBytes: null,
|
|
1114
|
+
manifestCount,
|
|
1115
|
+
transactionCount,
|
|
1116
|
+
segmentCount
|
|
1117
|
+
};
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
#putBlock(id, bytes) {
|
|
1121
|
+
const stored = new Uint8Array(bytes);
|
|
1122
|
+
const previous = this.#blocks.get(id);
|
|
1123
|
+
this.#physicalBlockBytes += stored.byteLength - (previous?.byteLength ?? 0);
|
|
1124
|
+
this.#blocks.set(id, stored);
|
|
1125
|
+
this.#blockChecksums.set(id, crc32(stored));
|
|
1126
|
+
}
|
|
1127
|
+
#deleteBlock(id) {
|
|
1128
|
+
this.#physicalBlockBytes -= this.#blocks.get(id)?.byteLength ?? 0;
|
|
1129
|
+
this.#blocks.delete(id);
|
|
1130
|
+
this.#blockChecksums.delete(id);
|
|
1131
|
+
}
|
|
1132
|
+
#clearPhysicalBlocks() {
|
|
1133
|
+
this.#blocks.clear();
|
|
1134
|
+
this.#blockChecksums.clear();
|
|
1135
|
+
this.#physicalBlockBytes = 0;
|
|
1136
|
+
}
|
|
1137
|
+
close() {
|
|
1138
|
+
}
|
|
1139
|
+
#runAtomic(operation) {
|
|
1140
|
+
let resolveResult;
|
|
1141
|
+
let rejectResult;
|
|
1142
|
+
const result = new Promise((resolve, reject) => {
|
|
1143
|
+
resolveResult = resolve;
|
|
1144
|
+
rejectResult = reject;
|
|
1145
|
+
});
|
|
1146
|
+
this.#commitQueue = this.#commitQueue.then(() => {
|
|
1147
|
+
try {
|
|
1148
|
+
if (this.#completedSnapshotFrameImport !== void 0 && this.#core.getCurrentManifestVersion() !== this.#completedSnapshotFrameImport.version) {
|
|
1149
|
+
this.#completedSnapshotFrameImport = void 0;
|
|
1150
|
+
}
|
|
1151
|
+
resolveResult(operation());
|
|
1152
|
+
} catch (error) {
|
|
1153
|
+
rejectResult(error);
|
|
1154
|
+
}
|
|
1155
|
+
});
|
|
1156
|
+
return result;
|
|
1157
|
+
}
|
|
1194
1158
|
}
|
|
1195
1159
|
function tempRunPageKey(ownerId, runId, pageIndex) {
|
|
1196
|
-
|
|
1160
|
+
return `${String(ownerId.length)}:${ownerId}:${String(runId.length)}:${runId}:${String(pageIndex)}`;
|
|
1197
1161
|
}
|
|
1198
1162
|
function tempRunPagePrefix(ownerId, runId) {
|
|
1199
|
-
|
|
1163
|
+
return `${String(ownerId.length)}:${ownerId}:${String(runId.length)}:${runId}:`;
|
|
1200
1164
|
}
|
|
1201
1165
|
function countRecordPages(read) {
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1166
|
+
let count = 0;
|
|
1167
|
+
let cursor = null;
|
|
1168
|
+
do {
|
|
1169
|
+
const page = read(cursor);
|
|
1170
|
+
count += page.records.length;
|
|
1171
|
+
if (!Number.isSafeInteger(count))
|
|
1172
|
+
throw new RangeError("Storage record count overflow");
|
|
1173
|
+
cursor = page.nextCursor;
|
|
1174
|
+
} while (cursor !== null);
|
|
1175
|
+
return count;
|
|
1212
1176
|
}
|
|
1213
1177
|
function assertTempResourceLimit(resource, actual, limit) {
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1178
|
+
if (!Number.isSafeInteger(actual) || actual < 0 || actual > limit) {
|
|
1179
|
+
throw new StorageResourceLimitError(resource, actual, limit);
|
|
1180
|
+
}
|
|
1217
1181
|
}
|
|
1218
1182
|
function validateSnapshotSessionLifetime(cutoff, expiresAt) {
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1183
|
+
const cutoffMs = Date.parse(cutoff);
|
|
1184
|
+
const expiresAtMs = Date.parse(expiresAt);
|
|
1185
|
+
if (!Number.isFinite(cutoffMs) || !Number.isFinite(expiresAtMs)) {
|
|
1186
|
+
throw new TypeError("Snapshot session timestamps are invalid");
|
|
1187
|
+
}
|
|
1188
|
+
if (expiresAtMs <= cutoffMs) {
|
|
1189
|
+
throw new RangeError("Snapshot session expiration must be after its cutoff");
|
|
1190
|
+
}
|
|
1191
|
+
if (expiresAtMs - cutoffMs > MAX_SNAPSHOT_SESSION_TTL_MS) {
|
|
1192
|
+
throw new RangeError("Snapshot session exceeds the maximum lifetime");
|
|
1193
|
+
}
|
|
1230
1194
|
}
|
|
1231
1195
|
function validatePostingBuildLifetime(cutoff, expiresAt) {
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1196
|
+
const cutoffMs = Date.parse(cutoff);
|
|
1197
|
+
const expiresAtMs = Date.parse(expiresAt);
|
|
1198
|
+
if (!Number.isFinite(cutoffMs) || !Number.isFinite(expiresAtMs)) {
|
|
1199
|
+
throw new TypeError("Posting build timestamps are invalid");
|
|
1200
|
+
}
|
|
1201
|
+
if (expiresAtMs <= cutoffMs || expiresAtMs - cutoffMs > MAX_POSTING_BUILD_TTL_MS) {
|
|
1202
|
+
throw new RangeError("Posting build expiration is outside its bounded lifetime");
|
|
1203
|
+
}
|
|
1240
1204
|
}
|
|
1241
1205
|
function samePostingChunk(left, right) {
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
posting.rowIds.length === other.rowIds.length &&
|
|
1247
|
-
posting.rowIds.every((rowId, index) => rowId === other.rowIds[index]) &&
|
|
1248
|
-
posting.tf.length === other.tf.length &&
|
|
1249
|
-
posting.tf.every((frequency, index) => frequency === other.tf[index]));
|
|
1250
|
-
}));
|
|
1206
|
+
return left.length === right.length && left.every((posting, postingIndex) => {
|
|
1207
|
+
const other = right[postingIndex];
|
|
1208
|
+
return other?.term === posting.term && posting.rowIds.length === other.rowIds.length && posting.rowIds.every((rowId, index) => rowId === other.rowIds[index]) && posting.tf.length === other.tf.length && posting.tf.every((frequency, index) => frequency === other.tf[index]);
|
|
1209
|
+
});
|
|
1251
1210
|
}
|
|
1252
1211
|
function safeSnapshotByteSum(total, byteLength) {
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1212
|
+
const result = total + byteLength;
|
|
1213
|
+
if (!Number.isSafeInteger(result) || result < 0) {
|
|
1214
|
+
throw new RangeError("Snapshot payload is too large");
|
|
1215
|
+
}
|
|
1216
|
+
return result;
|
|
1258
1217
|
}
|
|
1259
1218
|
function emptySnapshotObservedSummaries() {
|
|
1260
|
-
|
|
1219
|
+
return Object.fromEntries(SNAPSHOT_FRAME_KINDS.map((kind) => [kind, { frameCount: 0, itemCount: 0, storedBytes: 0 }]));
|
|
1261
1220
|
}
|
|
1262
1221
|
function snapshotMetadataFrameKind(item) {
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1222
|
+
if (item.kind === "table")
|
|
1223
|
+
return "catalog-page";
|
|
1224
|
+
if (item.kind === "segment")
|
|
1225
|
+
return "segment-page";
|
|
1226
|
+
if (item.kind === "transaction")
|
|
1227
|
+
return "transaction-page";
|
|
1228
|
+
if (item.kind === "unique-generation" || item.kind === "unique-chunk")
|
|
1229
|
+
return "unique-page";
|
|
1230
|
+
return "posting-page";
|
|
1272
1231
|
}
|
|
1273
1232
|
function copySnapshotFrame(frame) {
|
|
1274
|
-
|
|
1233
|
+
return { ...frame, payload: frame.payload.slice() };
|
|
1275
1234
|
}
|
|
1276
1235
|
function sameSnapshotFrameHeader(left, right) {
|
|
1277
|
-
|
|
1236
|
+
return sameSnapshotBytes(encodeSnapshotFrameStreamHeader(left), encodeSnapshotFrameStreamHeader(right));
|
|
1278
1237
|
}
|
|
1279
1238
|
function sameSnapshotFrame(left, right) {
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
left.kind === right.kind &&
|
|
1284
|
-
left.itemCount === right.itemCount &&
|
|
1285
|
-
left.key === right.key &&
|
|
1286
|
-
left.checksum === right.checksum &&
|
|
1287
|
-
sameSnapshotBytes(left.payload, right.payload));
|
|
1239
|
+
if (left?.sequence === void 0 || right?.sequence === void 0)
|
|
1240
|
+
return false;
|
|
1241
|
+
return left.sequence === right.sequence && left.kind === right.kind && left.itemCount === right.itemCount && left.key === right.key && left.checksum === right.checksum && sameSnapshotBytes(left.payload, right.payload);
|
|
1288
1242
|
}
|
|
1289
1243
|
function snapshotFrameCount(header) {
|
|
1290
|
-
|
|
1244
|
+
return SNAPSHOT_FRAME_KINDS.reduce((total, kind) => safeSnapshotByteSum(total, header.kinds[kind].frameCount), 0);
|
|
1291
1245
|
}
|
|
1292
1246
|
function sameSnapshotBytes(left, right) {
|
|
1293
|
-
|
|
1247
|
+
return left.byteLength === right.byteLength && left.every((byte, index) => right[index] === byte);
|
|
1294
1248
|
}
|
|
1249
|
+
export {
|
|
1250
|
+
MemoryBlockStore
|
|
1251
|
+
};
|