@minnowdb/core 0.6.9 → 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.
Files changed (103) hide show
  1. package/dist/block-format/block.js +254 -325
  2. package/dist/block-format/checksum.js +30 -52
  3. package/dist/block-format/codecs.js +112 -125
  4. package/dist/block-format/column.js +187 -205
  5. package/dist/block-format/index.js +5 -1
  6. package/dist/block-format/physical.js +371 -387
  7. package/dist/block-format/types.js +4 -1
  8. package/dist/block-format/unicode.js +30 -39
  9. package/dist/date-value.d.ts +5 -0
  10. package/dist/date-value.js +71 -41
  11. package/dist/engine/artifact-cache.js +106 -116
  12. package/dist/engine/batch.js +36 -44
  13. package/dist/engine/buffered-writer.js +156 -158
  14. package/dist/engine/byte-estimates.js +20 -22
  15. package/dist/engine/cache-limits.js +24 -25
  16. package/dist/engine/cancellation.js +5 -3
  17. package/dist/engine/catalog.js +64 -73
  18. package/dist/engine/client.js +715 -780
  19. package/dist/engine/database.js +15952 -18465
  20. package/dist/engine/defaults.js +104 -111
  21. package/dist/engine/errors.js +113 -128
  22. package/dist/engine/fts.js +233 -280
  23. package/dist/engine/group-index.js +287 -321
  24. package/dist/engine/index.js +23 -12
  25. package/dist/engine/join-index.js +177 -186
  26. package/dist/engine/keyed-live.js +168 -173
  27. package/dist/engine/live-api.js +0 -1
  28. package/dist/engine/live-equal.js +32 -37
  29. package/dist/engine/live.js +603 -647
  30. package/dist/engine/memory.js +109 -111
  31. package/dist/engine/optimizer.d.ts +19 -1
  32. package/dist/engine/optimizer.js +2642 -2515
  33. package/dist/engine/point-read.d.ts +4 -2
  34. package/dist/engine/point-read.js +121 -171
  35. package/dist/engine/query-api.js +12 -3
  36. package/dist/engine/query-cache.js +61 -69
  37. package/dist/engine/query.d.ts +45 -0
  38. package/dist/engine/query.js +8506 -9375
  39. package/dist/engine/result-wire.js +221 -252
  40. package/dist/engine/schema-wire.js +97 -112
  41. package/dist/engine/schema.js +1099 -1254
  42. package/dist/engine/sort-keys.js +315 -389
  43. package/dist/engine/sql-domains.d.ts +7 -1
  44. package/dist/engine/sql-domains.js +648 -734
  45. package/dist/engine/sql-driver.js +0 -1
  46. package/dist/engine/sql-functions.d.ts +11 -0
  47. package/dist/engine/sql-functions.js +1092 -0
  48. package/dist/engine/sql-json.js +199 -218
  49. package/dist/engine/sql-semantics.d.ts +25 -4
  50. package/dist/engine/sql-semantics.js +579 -513
  51. package/dist/engine/typed-live.js +271 -293
  52. package/dist/engine/vector.d.ts +7 -1
  53. package/dist/engine/vector.js +4513 -4580
  54. package/dist/engine/worker-host.d.ts +8 -63
  55. package/dist/engine/worker-host.js +28 -1130
  56. package/dist/engine/worker-indexeddb.d.ts +1 -0
  57. package/dist/engine/worker-indexeddb.js +3 -0
  58. package/dist/engine/worker-memory.d.ts +1 -0
  59. package/dist/engine/worker-memory.js +3 -0
  60. package/dist/engine/worker-opfs.d.ts +1 -0
  61. package/dist/engine/worker-opfs.js +3 -0
  62. package/dist/engine/worker-server.d.ts +93 -0
  63. package/dist/engine/worker-server.js +999 -0
  64. package/dist/engine/worker-store-indexeddb.d.ts +2 -0
  65. package/dist/engine/worker-store-indexeddb.js +11 -0
  66. package/dist/engine/worker-store-memory.d.ts +2 -0
  67. package/dist/engine/worker-store-memory.js +6 -0
  68. package/dist/engine/worker-store-opfs.d.ts +2 -0
  69. package/dist/engine/worker-store-opfs.js +9 -0
  70. package/dist/engine/worker.js +0 -12
  71. package/dist/engine/write-block-planner.js +93 -100
  72. package/dist/plan/index.js +15 -16
  73. package/dist/plan/model.d.ts +9 -1
  74. package/dist/plan/model.js +20 -27
  75. package/dist/storage/index.js +0 -12
  76. package/dist/storage/indexeddb.js +11776 -13171
  77. package/dist/storage/memory.js +1198 -1241
  78. package/dist/storage/opfs/files.js +238 -295
  79. package/dist/storage/opfs/index.js +9 -3
  80. package/dist/storage/opfs/leader.js +4386 -4856
  81. package/dist/storage/opfs/rpc.js +223 -259
  82. package/dist/storage/opfs/snapshot-ledger.js +219 -260
  83. package/dist/storage/opfs/store.js +884 -1003
  84. package/dist/storage/persistence.js +59 -74
  85. package/dist/storage/snapshot-stream.js +788 -826
  86. package/dist/storage/snapshot.js +36 -37
  87. package/dist/storage/toolkit/extents.js +444 -520
  88. package/dist/storage/toolkit/index.js +28 -29
  89. package/dist/storage/toolkit/record-core.js +5792 -6377
  90. package/dist/storage/toolkit/sync-file.js +34 -42
  91. package/dist/storage/toolkit/wal.js +92 -127
  92. package/dist/storage/toolkit/wire.js +232 -256
  93. package/dist/storage/types.js +2952 -3228
  94. package/dist/testing/block-store-conformance.js +1556 -1629
  95. package/dist/testing/index.js +310 -291
  96. package/dist/testing/opfs-shim.js +280 -312
  97. package/dist/testing/simulator.js +496 -534
  98. package/dist/testing/sqllogictest.js +425 -365
  99. package/dist/transactions/index.js +1464 -1712
  100. package/dist/worker-protocol/index.js +70 -69
  101. package/package.json +20 -2
  102. package/postgres-feature-profile.json +15 -5
  103. package/sql-feature-matrix.json +252 -2
@@ -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, } from "./types.js";
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, } from "./snapshot-stream.js";
5
- import { RecordCore, validateFtsPostingChunks, validateId, validateTempRunPage, validateTempRunPageIdentity, } from "./toolkit/record-core.js";
6
- /**
7
- * The in-process store: record semantics live in `RecordCore` (shared with the OPFS store),
8
- * block and temp-page bytes live in Maps here, and atomicity comes from running every mutating
9
- * record operation on a promise-chain queue — each queued body is synchronous, so no operation
10
- * ever observes another mid-mutation.
11
- */
12
- export class MemoryBlockStore {
13
- #blocks = new Map();
14
- #blockChecksums = new Map();
15
- #physicalBlockBytes = 0;
16
- #tempRunPages = new Map();
17
- #tempUsageByOwner = new Map();
18
- #tempPageKeysByRun = new Map();
19
- #tempBytesTotal = 0;
20
- #tempPagesTotal = 0;
21
- #tempRunsTotal = 0;
22
- #ftsBaseBuilds = new Map();
23
- #snapshotFrameExport;
24
- #snapshotFrameImport;
25
- #completedSnapshotFrameImport;
26
- #core = new RecordCore({
27
- hasBlock: (id) => this.#blocks.has(id),
28
- blockByteLength: (id) => this.#blocks.get(id)?.byteLength,
29
- blockChecksum: (id) => this.#blockChecksums.get(id),
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
- #commitQueue = Promise.resolve();
32
- async getBlock(id) {
33
- validateStorageId(id, "Block ID");
34
- const bytes = this.#blocks.get(id);
35
- return bytes === undefined ? undefined : new Uint8Array(bytes);
36
- }
37
- async getBlocks(ids) {
38
- assertStorageBulkReadItems(ids, "Block read batch");
39
- for (const id of ids)
40
- validateStorageId(id, "Block ID");
41
- let bytes = 0;
42
- for (const id of ids) {
43
- bytes += this.#blocks.get(id)?.byteLength ?? 0;
44
- if (!Number.isSafeInteger(bytes) || bytes > MAX_BLOCK_READ_BATCH_BYTES) {
45
- throw new BlockReadBatchTooLargeError(bytes);
46
- }
47
- }
48
- return ids.map((id) => {
49
- const bytes = this.#blocks.get(id);
50
- return bytes === undefined ? undefined : new Uint8Array(bytes);
51
- });
52
- }
53
- async readManifestBlock(version, id) {
54
- validateStorageId(id, "Block ID");
55
- return this.#runAtomic(() => {
56
- if (!this.#core.hasManifestBlocks(version, [id])[0])
57
- return undefined;
58
- const bytes = this.#blocks.get(id);
59
- if (bytes === undefined) {
60
- throw new StorageCorruptionError("memory", id, "manifest member payload is missing");
61
- }
62
- return new Uint8Array(bytes);
63
- });
64
- }
65
- async hasManifestBlocks(version, ids) {
66
- assertStorageBulkReadItems(ids, "Manifest membership batch");
67
- return this.#runAtomic(() => this.#core.hasManifestBlocks(version, ids));
68
- }
69
- async putTempRunPage(page) {
70
- assertTempRunPageBatchLimits([page]);
71
- validateTempRunPage(page);
72
- return this.#runAtomic(() => this.#putTempRunPages([page]));
73
- }
74
- async putTempRunPages(pages) {
75
- assertTempRunPageBatchLimits(pages);
76
- for (const page of pages)
77
- validateTempRunPage(page);
78
- return this.#runAtomic(() => this.#putTempRunPages(pages));
79
- }
80
- async getTempRunPage(ownerId, runId, pageIndex) {
81
- validateTempRunPageIdentity(ownerId, runId, pageIndex);
82
- return this.#tempRunPages.get(tempRunPageKey(ownerId, runId, pageIndex))?.slice();
83
- }
84
- async removeTempRun(ownerId, runId) {
85
- validateTempRunPageIdentity(ownerId, runId, 0);
86
- return this.#runAtomic(() => this.#removeTempRunPages(ownerId, runId));
87
- }
88
- async removeTempOwner(ownerId) {
89
- validateId(ownerId);
90
- return this.#runAtomic(() => {
91
- this.#removeTempOwnerPages(ownerId);
92
- this.#core.removeTempOwner(ownerId);
93
- });
94
- }
95
- async createTempOwner(record) {
96
- return this.#runAtomic(() => {
97
- this.#core.createTempOwner(record);
98
- });
99
- }
100
- async getTempOwner(ownerId) {
101
- return this.#core.getTempOwner(ownerId);
102
- }
103
- async renewTempOwner(input) {
104
- return this.#runAtomic(() => this.#core.renewTempOwner(input));
105
- }
106
- async removeTempOwnerIfExpired(ownerId, expiresAtCutoff) {
107
- return this.#runAtomic(() => {
108
- const removed = this.#core.removeTempOwnerIfExpired(ownerId, expiresAtCutoff);
109
- if (removed)
110
- this.#removeTempOwnerPages(ownerId);
111
- return removed;
112
- });
113
- }
114
- async listTempOwnerIdsPage(afterOwnerId, limit) {
115
- const pageOwnerIds = [];
116
- for (const key of this.#tempRunPages.keys()) {
117
- const separator = key.indexOf(":");
118
- const length = Number(key.slice(0, separator));
119
- pageOwnerIds.push(key.slice(separator + 1, separator + 1 + length));
120
- }
121
- return this.#core.listTempOwnerIdsPage(afterOwnerId, limit, pageOwnerIds);
122
- }
123
- async listExpiredTempOwnerPage(expiresAtCutoff, afterCursor, limit) {
124
- return this.#core.listExpiredTempOwnerPage(expiresAtCutoff, afterCursor, limit);
125
- }
126
- #putTempRunPages(pages) {
127
- const seenKeys = new Set();
128
- const deltas = new Map();
129
- let totalBytesDelta = 0;
130
- let totalPagesDelta = 0;
131
- let totalRunsDelta = 0;
132
- for (const page of pages) {
133
- if (this.#core.getTempOwner(page.ownerId) === undefined) {
134
- throw new Error(`Temp owner does not exist: ${page.ownerId}`);
135
- }
136
- const key = tempRunPageKey(page.ownerId, page.runId, page.pageIndex);
137
- if (seenKeys.has(key))
138
- throw new TypeError(`Temp run page batch repeats ${key}`);
139
- seenKeys.add(key);
140
- const previousBytes = this.#tempRunPages.get(key)?.byteLength ?? 0;
141
- const isNewPage = !this.#tempRunPages.has(key);
142
- const delta = deltas.get(page.ownerId) ?? { bytes: 0, pages: 0, newRuns: new Set() };
143
- delta.bytes += page.bytes.byteLength - previousBytes;
144
- if (isNewPage)
145
- delta.pages += 1;
146
- const usage = this.#tempUsageByOwner.get(page.ownerId);
147
- if (usage?.runs.has(page.runId) !== true && !delta.newRuns.has(page.runId)) {
148
- delta.newRuns.add(page.runId);
149
- }
150
- deltas.set(page.ownerId, delta);
151
- totalBytesDelta += page.bytes.byteLength - previousBytes;
152
- if (isNewPage)
153
- totalPagesDelta += 1;
154
- }
155
- for (const [ownerId, delta] of deltas) {
156
- const usage = this.#tempUsageByOwner.get(ownerId);
157
- const nextBytes = (usage?.bytes ?? 0) + delta.bytes;
158
- const nextPages = (usage?.pages ?? 0) + delta.pages;
159
- const nextRuns = (usage?.runs.size ?? 0) + delta.newRuns.size;
160
- assertTempResourceLimit("temp owner byte", nextBytes, MAX_TEMP_BYTES_PER_OWNER);
161
- assertTempResourceLimit("temp page", nextPages, MAX_TEMP_PAGES_PER_OWNER);
162
- assertTempResourceLimit("temp run", nextRuns, MAX_TEMP_RUNS_PER_OWNER);
163
- totalRunsDelta += delta.newRuns.size;
164
- }
165
- assertTempResourceLimit("temporary byte", this.#tempBytesTotal + totalBytesDelta, MAX_TEMP_BYTES_TOTAL);
166
- assertTempResourceLimit("temporary page total", this.#tempPagesTotal + totalPagesDelta, MAX_TEMP_PAGES_TOTAL);
167
- assertTempResourceLimit("temporary run total", this.#tempRunsTotal + totalRunsDelta, MAX_TEMP_RUNS_TOTAL);
168
- for (const page of pages) {
169
- const key = tempRunPageKey(page.ownerId, page.runId, page.pageIndex);
170
- const previousBytes = this.#tempRunPages.get(key)?.byteLength ?? 0;
171
- const isNewPage = !this.#tempRunPages.has(key);
172
- const usage = this.#tempUsageByOwner.get(page.ownerId) ?? {
173
- bytes: 0,
174
- pages: 0,
175
- runs: new Map(),
176
- };
177
- const previousRunPages = usage.runs.get(page.runId) ?? 0;
178
- this.#tempRunPages.set(key, page.bytes.slice());
179
- const runKey = tempRunPagePrefix(page.ownerId, page.runId);
180
- const runKeys = this.#tempPageKeysByRun.get(runKey) ?? new Set();
181
- runKeys.add(key);
182
- this.#tempPageKeysByRun.set(runKey, runKeys);
183
- usage.bytes += page.bytes.byteLength - previousBytes;
184
- this.#tempBytesTotal += page.bytes.byteLength - previousBytes;
185
- if (isNewPage) {
186
- usage.pages += 1;
187
- this.#tempPagesTotal += 1;
188
- usage.runs.set(page.runId, previousRunPages + 1);
189
- if (previousRunPages === 0)
190
- this.#tempRunsTotal += 1;
191
- }
192
- this.#tempUsageByOwner.set(page.ownerId, usage);
193
- }
194
- }
195
- #removeTempRunPages(ownerId, runId) {
196
- const runKey = tempRunPagePrefix(ownerId, runId);
197
- const keys = this.#tempPageKeysByRun.get(runKey);
198
- if (keys === undefined)
199
- return;
200
- const usage = this.#tempUsageByOwner.get(ownerId);
201
- let removedBytes = 0;
202
- for (const key of keys) {
203
- removedBytes += this.#tempRunPages.get(key)?.byteLength ?? 0;
204
- this.#tempRunPages.delete(key);
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
- this.#tempPageKeysByRun.delete(runKey);
207
- this.#tempBytesTotal -= removedBytes;
208
- this.#tempPagesTotal -= keys.size;
209
- this.#tempRunsTotal -= 1;
210
- if (usage !== undefined) {
211
- usage.bytes -= removedBytes;
212
- usage.pages -= keys.size;
213
- usage.runs.delete(runId);
214
- if (usage.pages === 0)
215
- this.#tempUsageByOwner.delete(ownerId);
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
- #removeTempOwnerPages(ownerId) {
219
- const usage = this.#tempUsageByOwner.get(ownerId);
220
- if (usage === undefined)
221
- return;
222
- for (const runId of [...usage.runs.keys()])
223
- this.#removeTempRunPages(ownerId, runId);
224
- }
225
- async addTable(record, options = {}) {
226
- this.#core.addTable(record, options);
227
- }
228
- async getTable(id) {
229
- return this.#core.getTable(id);
230
- }
231
- async updateTable(id, expectedRevision, update) {
232
- return this.#runAtomic(() => {
233
- const before = this.#core.getTable(id);
234
- const updated = this.#core.updateTable(id, expectedRevision, update);
235
- if (before !== undefined)
236
- this.#removeRetiredPostingBuilds(before, updated);
237
- return updated;
238
- });
239
- }
240
- async removeTable(id, expectedRevision, options = {}) {
241
- return this.#runAtomic(() => {
242
- this.#core.removeTable(id, expectedRevision, options);
243
- this.#removeTablePostingBuilds(id);
244
- });
245
- }
246
- async dropTable(input) {
247
- return this.#runAtomic(() => {
248
- const summary = this.#core.dropTable(input);
249
- this.#removeTablePostingBuilds(input.tableId);
250
- return summary;
251
- });
252
- }
253
- async dropTableColumn(input) {
254
- return this.#runAtomic(() => {
255
- const summary = this.#core.dropTableColumn(input);
256
- this.#ftsBaseBuilds.delete(`${input.tableId}/${input.columnId}`);
257
- return summary;
258
- });
259
- }
260
- #removeRetiredPostingBuilds(before, after) {
261
- const retained = new Set([
262
- ...Object.keys(after.ftsColumns ?? {}),
263
- ...Object.values(after.secondaryIndexes ?? {}).map((index) => index.storageColumnId),
264
- ]);
265
- for (const columnId of [
266
- ...Object.keys(before.ftsColumns ?? {}),
267
- ...Object.values(before.secondaryIndexes ?? {}).map((index) => index.storageColumnId),
268
- ]) {
269
- if (!retained.has(columnId))
270
- this.#ftsBaseBuilds.delete(`${before.id}/${columnId}`);
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
- #removeTablePostingBuilds(tableId) {
274
- const prefix = `${tableId}/`;
275
- for (const key of [...this.#ftsBaseBuilds.keys()]) {
276
- if (key.startsWith(prefix))
277
- this.#ftsBaseBuilds.delete(key);
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
- async writeFtsBase(tableId, columnId, input) {
281
- return this.#runAtomic(() => {
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
- async writeFtsBaseBuildChunk(input) {
326
- validatePostingBuildLifetime(input.expiresAtCutoff, input.expiresAt);
327
- return this.#runAtomic(() => {
328
- const build = this.#requirePostingBuild(input);
329
- if (!Number.isSafeInteger(input.ordinal) || input.ordinal < 0) {
330
- throw new RangeError(`Full-text base chunk ordinal is invalid: ${String(input.ordinal)}`);
331
- }
332
- validateFtsPostingChunks([input.chunk], "Full-text base build");
333
- const replay = build.chunks.get(input.ordinal);
334
- if (replay !== undefined) {
335
- if (!samePostingChunk(replay, input.chunk)) {
336
- throw new PostingBuildConflictError(input.buildId, input.ownerId, "chunk replay changed");
337
- }
338
- build.expiresAt = input.expiresAt;
339
- return;
340
- }
341
- if (input.ordinal !== build.chunks.size) {
342
- throw new Error(`Full-text base chunk is out of order: ${String(input.ordinal)}`);
343
- }
344
- // Chunks are row windows, not term partitions. Terms are sorted inside each chunk, but
345
- // adjacent chunks can legitimately overlap or restart at an earlier term.
346
- build.chunks.set(input.ordinal, structuredClone(input.chunk));
347
- build.expiresAt = input.expiresAt;
348
- });
349
- }
350
- async finishFtsBaseBuild(input) {
351
- return this.#runAtomic(() => {
352
- const key = `${input.tableId}/${input.columnId}`;
353
- const build = this.#requirePostingBuild(input);
354
- if (!Number.isSafeInteger(input.coversVersion) ||
355
- input.coversVersion < -1 ||
356
- !Number.isSafeInteger(input.chunkCount) ||
357
- input.chunkCount < 0 ||
358
- !Number.isSafeInteger(input.totalTokens) ||
359
- input.totalTokens < 0) {
360
- throw new TypeError("Full-text base build metadata is invalid");
361
- }
362
- if (input.chunkCount !== build.chunks.size) {
363
- throw new Error(`Full-text base build is incomplete: ${input.buildId}`);
364
- }
365
- const table = this.#core.getTable(input.tableId);
366
- if (table === undefined || !activePostingStorageColumnIds(table).has(input.columnId)) {
367
- this.#ftsBaseBuilds.delete(key);
368
- return;
369
- }
370
- const chunks = Array.from({ length: input.chunkCount }, (_, ordinal) => {
371
- const chunk = build.chunks.get(ordinal);
372
- if (chunk === undefined) {
373
- throw new Error(`Full-text base chunk is missing: ${String(ordinal)}`);
374
- }
375
- return chunk;
376
- });
377
- this.#core.writeFtsBase(input.tableId, input.columnId, {
378
- coversVersion: input.coversVersion,
379
- chunks,
380
- totalTokens: input.totalTokens,
381
- });
382
- this.#ftsBaseBuilds.delete(key);
383
- });
384
- }
385
- async abortFtsBaseBuild(input) {
386
- return this.#runAtomic(() => {
387
- const key = `${input.tableId}/${input.columnId}`;
388
- const build = this.#ftsBaseBuilds.get(key);
389
- if (build === undefined)
390
- return;
391
- if (build.buildId !== input.buildId ||
392
- (build.ownerId !== input.ownerId &&
393
- Date.parse(build.expiresAt) > Date.parse(input.expiresAtCutoff))) {
394
- throw new PostingBuildConflictError(build.buildId, build.ownerId, "abort ownership changed");
395
- }
396
- this.#ftsBaseBuilds.delete(key);
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
- #requirePostingBuild(input) {
400
- const build = this.#ftsBaseBuilds.get(`${input.tableId}/${input.columnId}`);
401
- if (build?.buildId !== input.buildId || build.ownerId !== input.ownerId) {
402
- throw new PostingBuildConflictError(build?.buildId ?? input.buildId, build?.ownerId ?? input.ownerId, "session is missing or owned by another caller");
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
- if (Date.parse(build.expiresAt) <= Date.parse(input.expiresAtCutoff)) {
405
- throw new PostingBuildConflictError(build.buildId, build.ownerId, "session is expired");
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
- return build;
408
- }
409
- async readFtsCandidates(tableId, columnId, terms, upToVersion, maxRowIds) {
410
- return this.#core.readFtsCandidates(tableId, columnId, terms, upToVersion, maxRowIds);
411
- }
412
- async readFtsPostings(tableId, columnId, upToVersion, maxRowIds, maxRetainedBytes) {
413
- return this.#core.readFtsPostings(tableId, columnId, upToVersion, maxRowIds, maxRetainedBytes);
414
- }
415
- async getTableByName(name) {
416
- const id = this.#core.getTableIdByName(name);
417
- return id === undefined ? undefined : this.getTable(id);
418
- }
419
- async listTables() {
420
- return this.#core.listTables();
421
- }
422
- async getSegment(id) {
423
- return this.#core.getSegment(id);
424
- }
425
- async listSegmentPage(afterId, limit) {
426
- return this.#core.listSegmentPage(afterId, limit);
427
- }
428
- async listTableSegmentPage(tableId, afterId, limit) {
429
- return this.#core.listTableSegmentPage(tableId, afterId, limit);
430
- }
431
- async removeAbortedSegment(id, expectedTransactionId) {
432
- return this.#runAtomic(() => this.#core.removeAbortedSegment(id, expectedTransactionId));
433
- }
434
- async adoptAbortedSegment(input) {
435
- return this.#runAtomic(() => this.#core.adoptAbortedSegment(input));
436
- }
437
- async reserveRowIds(tableId, count) {
438
- return this.#core.reserveRowIds(tableId, count);
439
- }
440
- async reserveAutoIncrement(tableId, columnId, count, atLeast) {
441
- return this.#runAtomic(() => this.#core.reserveAutoIncrement(tableId, columnId, count, atLeast));
442
- }
443
- async getExistingUniqueKeys(tableId, keyTokens) {
444
- assertStorageBulkReadItems(keyTokens, "Unique-key lookup batch");
445
- return this.#core.getExistingUniqueKeys(tableId, keyTokens);
446
- }
447
- async beginUniqueKeyBuild(input) {
448
- return this.#runAtomic(() => this.#core.beginUniqueKeyBuild(input));
449
- }
450
- async getUniqueKeyBuild(buildId) {
451
- return this.#core.getUniqueKeyBuild(buildId);
452
- }
453
- async renewUniqueKeyBuild(input) {
454
- return this.#runAtomic(() => this.#core.renewUniqueKeyBuild(input));
455
- }
456
- async appendUniqueKeyBuildChunk(input) {
457
- return this.#runAtomic(() => this.#core.appendUniqueKeyBuildChunk(input));
458
- }
459
- async finishUniqueKeyBuild(input) {
460
- return this.#runAtomic(() => this.#core.finishUniqueKeyBuild(input));
461
- }
462
- async abortUniqueKeyBuild(input) {
463
- return this.#runAtomic(() => this.#core.abortUniqueKeyBuild(input));
464
- }
465
- async getCurrentManifestVersion() {
466
- return this.#core.getCurrentManifestVersion();
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
- return this.#runAtomic(() => {
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
- async closeSnapshotFrameExport(input) {
788
- return this.#runAtomic(() => {
789
- const state = this.#snapshotFrameExport;
790
- if (state === undefined)
791
- return false;
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
- return this.#runAtomic(() => {
808
- const current = this.#snapshotFrameImport;
809
- if (current !== undefined) {
810
- const expired = Date.parse(current.session.expiresAt) <= Date.parse(input.createdAt);
811
- const sameIdentity = current.session.identity === input.identity;
812
- const sameHeader = sameSnapshotFrameHeader(current.header, header);
813
- if (expired && (!sameIdentity || !sameHeader)) {
814
- if (!current.completedReplay)
815
- this.#clearPhysicalBlocks();
816
- this.#snapshotFrameImport = undefined;
817
- }
818
- else {
819
- if (!expired && current.session.identity !== input.identity) {
820
- throw new SnapshotImportConflictError(current.session.identity, current.session.ownerId, "another snapshot import is still live");
821
- }
822
- if (!expired && current.session.ownerId !== input.ownerId) {
823
- throw new SnapshotImportConflictError(current.session.identity, current.session.ownerId, "snapshot import is owned by another caller");
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
- let batchBytes = 0;
886
- let metadataBatchBytes = 0;
887
- for (const frame of input.frames) {
888
- batchBytes = safeSnapshotByteSum(batchBytes, frame.payload.byteLength);
889
- if (batchBytes > MAX_SNAPSHOT_FRAME_BATCH_BYTES) {
890
- throw new RangeError("Snapshot import frame batch exceeds its byte limit");
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
- if (frame.kind !== "block") {
893
- metadataBatchBytes = safeSnapshotByteSum(metadataBatchBytes, frame.payload.byteLength);
894
- if (metadataBatchBytes > MAX_SNAPSHOT_METADATA_BATCH_BYTES) {
895
- throw new RangeError("Snapshot metadata frame batch exceeds its byte limit");
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
- return this.#runAtomic(() => {
900
- const state = this.#requireLiveSnapshotFrameImport(input);
901
- if (state.lastBatchFrames.length === input.frames.length &&
902
- input.frames[0]?.sequence === state.session.nextSequence - state.lastBatchFrames.length) {
903
- if (!state.lastBatchFrames.every((frame, index) => sameSnapshotFrame(frame, input.frames[index]))) {
904
- throw new SnapshotImportConflictError(input.identity, input.ownerId, "replayed snapshot frame bytes changed");
905
- }
906
- state.session = { ...state.session, expiresAt: input.expiresAt };
907
- return structuredClone(state.session);
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
- if (Date.parse(state.session.expiresAt) <= Date.parse(input.expiresAtCutoff)) {
1066
- throw new SnapshotImportConflictError(input.identity, input.ownerId, "session is expired");
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
- return state;
1069
- }
1070
- #captureSnapshotMetadataFrames() {
1071
- const frames = [];
1072
- const append = (item) => {
1073
- const payload = encodeSnapshotMetadataPage([item]);
1074
- frames.push({
1075
- sequence: frames.length,
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
- for (const item of this.#core.snapshotFrameMetadataItems())
1084
- append(item);
1085
- for (const item of this.#core.snapshotFramePostingItems())
1086
- append(item);
1087
- return frames;
1088
- }
1089
- async inspectInterruptedImport() {
1090
- const framed = this.#snapshotFrameImport;
1091
- if (framed !== undefined) {
1092
- return {
1093
- identity: framed.session.identity,
1094
- version: framed.session.version,
1095
- createdAt: framed.session.createdAt,
1096
- stagedBlockCount: framed.observed.block.itemCount,
1097
- stagedBytes: framed.session.stagedBytes,
1098
- };
1099
- }
1100
- return null;
1101
- }
1102
- async abortInterruptedImport(identity) {
1103
- return this.#runAtomic(() => {
1104
- const framed = this.#snapshotFrameImport;
1105
- if (framed?.session.identity === identity) {
1106
- const result = {
1107
- identity,
1108
- removedBlockCount: framed.completedReplay ? 0 : framed.observed.block.itemCount,
1109
- removedBytes: framed.completedReplay ? 0 : framed.observed.block.storedBytes,
1110
- };
1111
- if (!framed.completedReplay)
1112
- this.#clearPhysicalBlocks();
1113
- this.#snapshotFrameImport = undefined;
1114
- return result;
1115
- }
1116
- throw new Error(`Interrupted snapshot import not found: ${identity}`);
1117
- });
1118
- }
1119
- /** Block and spill bytes held; the record maps are negligible beside them. */
1120
- async getLogicalStorageBytes() {
1121
- return this.#physicalBlockBytes + this.#tempBytesTotal;
1122
- }
1123
- async getStorageStats() {
1124
- return this.#runAtomic(() => {
1125
- const current = this.#core.getCurrentManifest();
1126
- const temporaryBytes = this.#tempBytesTotal;
1127
- const manifestCount = countRecordPages((cursor) => this.#core.listManifestPage(cursor, 1_024));
1128
- const transactionCount = countRecordPages((cursor) => this.#core.listTransactionPage(cursor, 1_024));
1129
- const segmentCount = countRecordPages((cursor) => this.#core.listSegmentPage(cursor, 1_024));
1130
- const liveBlockCount = current?.liveBlockCount ?? 0;
1131
- const liveBlockBytes = current?.liveBlockBytes ?? 0;
1132
- if (liveBlockCount > this.#blocks.size || liveBlockBytes > this.#physicalBlockBytes) {
1133
- throw new StorageCorruptionError("memory", "manifest", "live block summary exceeds physical payloads");
1134
- }
1135
- return {
1136
- backend: "memory",
1137
- logicalBytes: this.#physicalBlockBytes + temporaryBytes,
1138
- physicalBytes: this.#physicalBlockBytes + temporaryBytes,
1139
- liveBlockCount,
1140
- obsoleteBlockCount: this.#blocks.size - liveBlockCount,
1141
- liveBlockBytes,
1142
- obsoleteBlockBytes: this.#physicalBlockBytes - liveBlockBytes,
1143
- temporaryBytes,
1144
- walBytes: null,
1145
- checkpointBytes: null,
1146
- orphanBytes: null,
1147
- manifestCount,
1148
- transactionCount,
1149
- segmentCount,
1150
- };
1151
- });
1152
- }
1153
- #putBlock(id, bytes) {
1154
- const stored = new Uint8Array(bytes);
1155
- const previous = this.#blocks.get(id);
1156
- this.#physicalBlockBytes += stored.byteLength - (previous?.byteLength ?? 0);
1157
- this.#blocks.set(id, stored);
1158
- this.#blockChecksums.set(id, crc32(stored));
1159
- }
1160
- #deleteBlock(id) {
1161
- this.#physicalBlockBytes -= this.#blocks.get(id)?.byteLength ?? 0;
1162
- this.#blocks.delete(id);
1163
- this.#blockChecksums.delete(id);
1164
- }
1165
- #clearPhysicalBlocks() {
1166
- this.#blocks.clear();
1167
- this.#blockChecksums.clear();
1168
- this.#physicalBlockBytes = 0;
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
- return `${String(ownerId.length)}:${ownerId}:${String(runId.length)}:${runId}:${String(pageIndex)}`;
1160
+ return `${String(ownerId.length)}:${ownerId}:${String(runId.length)}:${runId}:${String(pageIndex)}`;
1197
1161
  }
1198
1162
  function tempRunPagePrefix(ownerId, runId) {
1199
- return `${String(ownerId.length)}:${ownerId}:${String(runId.length)}:${runId}:`;
1163
+ return `${String(ownerId.length)}:${ownerId}:${String(runId.length)}:${runId}:`;
1200
1164
  }
1201
1165
  function countRecordPages(read) {
1202
- let count = 0;
1203
- let cursor = null;
1204
- do {
1205
- const page = read(cursor);
1206
- count += page.records.length;
1207
- if (!Number.isSafeInteger(count))
1208
- throw new RangeError("Storage record count overflow");
1209
- cursor = page.nextCursor;
1210
- } while (cursor !== null);
1211
- return count;
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
- if (!Number.isSafeInteger(actual) || actual < 0 || actual > limit) {
1215
- throw new StorageResourceLimitError(resource, actual, limit);
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
- const cutoffMs = Date.parse(cutoff);
1220
- const expiresAtMs = Date.parse(expiresAt);
1221
- if (!Number.isFinite(cutoffMs) || !Number.isFinite(expiresAtMs)) {
1222
- throw new TypeError("Snapshot session timestamps are invalid");
1223
- }
1224
- if (expiresAtMs <= cutoffMs) {
1225
- throw new RangeError("Snapshot session expiration must be after its cutoff");
1226
- }
1227
- if (expiresAtMs - cutoffMs > MAX_SNAPSHOT_SESSION_TTL_MS) {
1228
- throw new RangeError("Snapshot session exceeds the maximum lifetime");
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
- const cutoffMs = Date.parse(cutoff);
1233
- const expiresAtMs = Date.parse(expiresAt);
1234
- if (!Number.isFinite(cutoffMs) || !Number.isFinite(expiresAtMs)) {
1235
- throw new TypeError("Posting build timestamps are invalid");
1236
- }
1237
- if (expiresAtMs <= cutoffMs || expiresAtMs - cutoffMs > MAX_POSTING_BUILD_TTL_MS) {
1238
- throw new RangeError("Posting build expiration is outside its bounded lifetime");
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
- return (left.length === right.length &&
1243
- left.every((posting, postingIndex) => {
1244
- const other = right[postingIndex];
1245
- return (other?.term === posting.term &&
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
- const result = total + byteLength;
1254
- if (!Number.isSafeInteger(result) || result < 0) {
1255
- throw new RangeError("Snapshot payload is too large");
1256
- }
1257
- return result;
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
- return Object.fromEntries(SNAPSHOT_FRAME_KINDS.map((kind) => [kind, { frameCount: 0, itemCount: 0, storedBytes: 0 }]));
1219
+ return Object.fromEntries(SNAPSHOT_FRAME_KINDS.map((kind) => [kind, { frameCount: 0, itemCount: 0, storedBytes: 0 }]));
1261
1220
  }
1262
1221
  function snapshotMetadataFrameKind(item) {
1263
- if (item.kind === "table")
1264
- return "catalog-page";
1265
- if (item.kind === "segment")
1266
- return "segment-page";
1267
- if (item.kind === "transaction")
1268
- return "transaction-page";
1269
- if (item.kind === "unique-generation" || item.kind === "unique-chunk")
1270
- return "unique-page";
1271
- return "posting-page";
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
- return { ...frame, payload: frame.payload.slice() };
1233
+ return { ...frame, payload: frame.payload.slice() };
1275
1234
  }
1276
1235
  function sameSnapshotFrameHeader(left, right) {
1277
- return sameSnapshotBytes(encodeSnapshotFrameStreamHeader(left), encodeSnapshotFrameStreamHeader(right));
1236
+ return sameSnapshotBytes(encodeSnapshotFrameStreamHeader(left), encodeSnapshotFrameStreamHeader(right));
1278
1237
  }
1279
1238
  function sameSnapshotFrame(left, right) {
1280
- if (left?.sequence === undefined || right?.sequence === undefined)
1281
- return false;
1282
- return (left.sequence === right.sequence &&
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
- return SNAPSHOT_FRAME_KINDS.reduce((total, kind) => safeSnapshotByteSum(total, header.kinds[kind].frameCount), 0);
1244
+ return SNAPSHOT_FRAME_KINDS.reduce((total, kind) => safeSnapshotByteSum(total, header.kinds[kind].frameCount), 0);
1291
1245
  }
1292
1246
  function sameSnapshotBytes(left, right) {
1293
- return left.byteLength === right.byteLength && left.every((byte, index) => right[index] === byte);
1247
+ return left.byteLength === right.byteLength && left.every((byte, index) => right[index] === byte);
1294
1248
  }
1249
+ export {
1250
+ MemoryBlockStore
1251
+ };