@minnowdb/core 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) 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.js +67 -78
  10. package/dist/engine/artifact-cache.js +106 -116
  11. package/dist/engine/batch.js +36 -44
  12. package/dist/engine/buffered-writer.js +156 -158
  13. package/dist/engine/byte-estimates.js +20 -22
  14. package/dist/engine/cache-limits.js +24 -25
  15. package/dist/engine/cancellation.js +5 -3
  16. package/dist/engine/catalog.js +64 -73
  17. package/dist/engine/client.js +715 -780
  18. package/dist/engine/database.js +15941 -19313
  19. package/dist/engine/defaults.js +104 -111
  20. package/dist/engine/errors.js +113 -128
  21. package/dist/engine/fts.js +233 -280
  22. package/dist/engine/group-index.js +287 -321
  23. package/dist/engine/index.js +23 -12
  24. package/dist/engine/join-index.js +177 -186
  25. package/dist/engine/keyed-live.js +168 -173
  26. package/dist/engine/live-api.js +0 -1
  27. package/dist/engine/live-equal.js +32 -37
  28. package/dist/engine/live.js +603 -647
  29. package/dist/engine/memory.js +109 -111
  30. package/dist/engine/optimizer.d.ts +19 -1
  31. package/dist/engine/optimizer.js +2633 -2934
  32. package/dist/engine/point-read.js +122 -182
  33. package/dist/engine/query-api.js +12 -3
  34. package/dist/engine/query-cache.js +61 -69
  35. package/dist/engine/query.d.ts +6 -0
  36. package/dist/engine/query.js +8503 -9980
  37. package/dist/engine/result-wire.js +221 -252
  38. package/dist/engine/schema-wire.js +97 -112
  39. package/dist/engine/schema.js +1099 -1254
  40. package/dist/engine/sort-keys.js +315 -389
  41. package/dist/engine/sql-domains.js +646 -769
  42. package/dist/engine/sql-driver.js +0 -1
  43. package/dist/engine/sql-functions.js +1031 -1125
  44. package/dist/engine/sql-json.js +199 -218
  45. package/dist/engine/sql-semantics.js +572 -639
  46. package/dist/engine/typed-live.js +271 -293
  47. package/dist/engine/vector.js +4484 -5145
  48. package/dist/engine/worker-host.d.ts +8 -63
  49. package/dist/engine/worker-host.js +28 -1130
  50. package/dist/engine/worker-indexeddb.d.ts +1 -0
  51. package/dist/engine/worker-indexeddb.js +3 -0
  52. package/dist/engine/worker-memory.d.ts +1 -0
  53. package/dist/engine/worker-memory.js +3 -0
  54. package/dist/engine/worker-opfs.d.ts +1 -0
  55. package/dist/engine/worker-opfs.js +3 -0
  56. package/dist/engine/worker-server.d.ts +93 -0
  57. package/dist/engine/worker-server.js +999 -0
  58. package/dist/engine/worker-store-indexeddb.d.ts +2 -0
  59. package/dist/engine/worker-store-indexeddb.js +11 -0
  60. package/dist/engine/worker-store-memory.d.ts +2 -0
  61. package/dist/engine/worker-store-memory.js +6 -0
  62. package/dist/engine/worker-store-opfs.d.ts +2 -0
  63. package/dist/engine/worker-store-opfs.js +9 -0
  64. package/dist/engine/worker.js +0 -12
  65. package/dist/engine/write-block-planner.js +93 -100
  66. package/dist/plan/index.js +15 -16
  67. package/dist/plan/model.d.ts +6 -0
  68. package/dist/plan/model.js +20 -27
  69. package/dist/storage/index.js +0 -12
  70. package/dist/storage/indexeddb.js +11776 -13171
  71. package/dist/storage/memory.js +1198 -1241
  72. package/dist/storage/opfs/files.js +238 -295
  73. package/dist/storage/opfs/index.js +9 -3
  74. package/dist/storage/opfs/leader.js +4386 -4856
  75. package/dist/storage/opfs/rpc.js +223 -259
  76. package/dist/storage/opfs/snapshot-ledger.js +219 -260
  77. package/dist/storage/opfs/store.js +884 -1003
  78. package/dist/storage/persistence.js +59 -74
  79. package/dist/storage/snapshot-stream.js +788 -826
  80. package/dist/storage/snapshot.js +36 -37
  81. package/dist/storage/toolkit/extents.js +444 -520
  82. package/dist/storage/toolkit/index.js +28 -29
  83. package/dist/storage/toolkit/record-core.js +5792 -6377
  84. package/dist/storage/toolkit/sync-file.js +34 -42
  85. package/dist/storage/toolkit/wal.js +92 -127
  86. package/dist/storage/toolkit/wire.js +232 -256
  87. package/dist/storage/types.js +2948 -3250
  88. package/dist/testing/block-store-conformance.js +1556 -1629
  89. package/dist/testing/index.js +310 -291
  90. package/dist/testing/opfs-shim.js +280 -312
  91. package/dist/testing/simulator.js +496 -534
  92. package/dist/testing/sqllogictest.js +425 -365
  93. package/dist/transactions/index.js +1464 -1712
  94. package/dist/worker-protocol/index.js +70 -69
  95. package/package.json +20 -2
@@ -1,897 +1,859 @@
1
- import { assertWellFormedString, crc32, crc32Continue, MAX_STORED_BLOCK_BYTE_LENGTH, } from "../block-format/index.js";
1
+ import { assertWellFormedString, crc32, crc32Continue, MAX_STORED_BLOCK_BYTE_LENGTH } from "../block-format/index.js";
2
2
  import { dateIsoString, dateMilliseconds } from "../date-value.js";
3
- import { MAX_SNAPSHOT_FRAME_ITEMS, MAX_SNAPSHOT_METADATA_FRAME_BYTES, MAX_STORAGE_ID_CHARACTERS, MAX_ROW_ID_EXCLUSIVE_END, MAX_AUTO_INCREMENT_EXCLUSIVE_END, MAX_UNIQUE_KEY_BUILD_CHUNK_BYTES, MAX_UNIQUE_KEY_BUILD_TOKENS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_BASE_CHUNKS, MAX_FTS_TOKENS_PER_DOCUMENT, SNAPSHOT_FRAME_KINDS, normalizeSegmentRecord, uniqueKeyBuildChunkRetainedBytes, validateTableRecordBounds, validateStorageId, } from "./types.js";
4
- const MAGIC = Uint8Array.of(0x4d, 0x49, 0x4e, 0x53, 0x4e, 0x41, 0x50, 0x31); // MINSNAP1
3
+ import { MAX_SNAPSHOT_FRAME_ITEMS, MAX_SNAPSHOT_METADATA_FRAME_BYTES, MAX_STORAGE_ID_CHARACTERS, MAX_ROW_ID_EXCLUSIVE_END, MAX_AUTO_INCREMENT_EXCLUSIVE_END, MAX_UNIQUE_KEY_BUILD_CHUNK_BYTES, MAX_UNIQUE_KEY_BUILD_TOKENS_PER_CHUNK, MAX_FTS_POSTING_ROW_IDS_PER_CHUNK, MAX_FTS_POSTINGS_PER_CHUNK, MAX_FTS_POSTING_TERM_CHARACTERS, MAX_FTS_BASE_CHUNKS, MAX_FTS_TOKENS_PER_DOCUMENT, SNAPSHOT_FRAME_KINDS, normalizeSegmentRecord, uniqueKeyBuildChunkRetainedBytes, validateTableRecordBounds, validateStorageId } from "./types.js";
4
+ const MAGIC = Uint8Array.of(77, 73, 78, 83, 78, 65, 80, 49);
5
5
  const HEADER_PREFIX_BYTES = 20;
6
- const FRAME_MARKER = 0x46524d31;
6
+ const FRAME_MARKER = 1179798833;
7
7
  const FRAME_PREFIX_BYTES = 40;
8
- const FOOTER_MARKER = 0x46545231;
8
+ const FOOTER_MARKER = 1179931185;
9
9
  const FOOTER_BYTES = 40;
10
10
  const MAX_HEADER_BYTES = 64 * 1024;
11
- const MAX_WIRE_ENTRIES = 262_144;
11
+ const MAX_WIRE_ENTRIES = 262144;
12
12
  const MAX_WIRE_DEPTH = 128;
13
13
  const MAX_UINT64_DECIMAL_BYTES = 20;
14
14
  const textEncoder = new TextEncoder();
15
15
  const textDecoder = new TextDecoder("utf-8", { fatal: true });
16
16
  function safeWhole(value, label) {
17
- if (!Number.isSafeInteger(value) || value < 0) {
18
- throw new RangeError(`${label} must be a non-negative safe integer`);
19
- }
20
- return value;
17
+ if (!Number.isSafeInteger(value) || value < 0) {
18
+ throw new RangeError(`${label} must be a non-negative safe integer`);
19
+ }
20
+ return value;
21
21
  }
22
22
  function canonicalTimestamp(value, label) {
23
- if (typeof value !== "string")
24
- throw new TypeError(`${label} must be a string`);
25
- const date = new Date(value);
26
- if (!Number.isFinite(dateMilliseconds(date)) || dateIsoString(date) !== value) {
27
- throw new TypeError(`${label} must be a canonical UTC timestamp`);
28
- }
29
- return value;
23
+ if (typeof value !== "string")
24
+ throw new TypeError(`${label} must be a string`);
25
+ const date = new Date(value);
26
+ if (!Number.isFinite(dateMilliseconds(date)) || dateIsoString(date) !== value) {
27
+ throw new TypeError(`${label} must be a canonical UTC timestamp`);
28
+ }
29
+ return value;
30
30
  }
31
31
  function sameKeys(value, expected, label) {
32
- const keys = Object.keys(value).sort();
33
- const canonical = [...expected].sort();
34
- if (keys.length !== canonical.length || keys.some((key, index) => key !== canonical[index])) {
35
- throw new TypeError(`${label} has unknown or missing fields`);
36
- }
32
+ const keys = Object.keys(value).sort();
33
+ const canonical = [...expected].sort();
34
+ if (keys.length !== canonical.length || keys.some((key, index) => key !== canonical[index])) {
35
+ throw new TypeError(`${label} has unknown or missing fields`);
36
+ }
37
37
  }
38
- export function prepareSnapshotFrameStreamHeader(input) {
39
- const runtime = input;
40
- if (typeof runtime !== "object" || runtime === null) {
41
- throw new TypeError("Snapshot stream header must be an object");
42
- }
43
- sameKeys(runtime, ["formatVersion", "databaseVersion", "createdAt", "kinds"], "Snapshot header");
44
- const fields = runtime;
45
- if (fields.formatVersion !== 1)
46
- throw new TypeError("Unsupported snapshot format version");
47
- const databaseVersion = safeWhole(fields.databaseVersion, "Snapshot database version");
48
- const createdAt = canonicalTimestamp(fields.createdAt, "Snapshot creation time");
49
- if (typeof fields.kinds !== "object" || fields.kinds === null) {
50
- throw new TypeError("Snapshot kind summaries must be an object");
51
- }
52
- sameKeys(fields.kinds, SNAPSHOT_FRAME_KINDS, "Snapshot kind summaries");
53
- const rawKinds = fields.kinds;
54
- const kinds = Object.create(null);
55
- for (const kind of SNAPSHOT_FRAME_KINDS) {
56
- const summary = rawKinds[kind];
57
- if (typeof summary !== "object" || summary === null) {
58
- throw new TypeError(`Snapshot ${kind} summary must be an object`);
59
- }
60
- sameKeys(summary, ["frameCount", "itemCount", "storedBytes"], `Snapshot ${kind} summary`);
61
- const rawSummary = summary;
62
- const frameCount = safeWhole(rawSummary.frameCount, `Snapshot ${kind} frame count`);
63
- const itemCount = safeWhole(rawSummary.itemCount, `Snapshot ${kind} item count`);
64
- const storedBytes = safeWhole(rawSummary.storedBytes, `Snapshot ${kind} stored bytes`);
65
- if ((frameCount === 0) !== (itemCount === 0 && storedBytes === 0)) {
66
- throw new TypeError(`Snapshot ${kind} empty summary is inconsistent`);
67
- }
68
- kinds[kind] = { frameCount, itemCount, storedBytes };
69
- }
70
- return { formatVersion: 1, databaseVersion, createdAt, kinds };
38
+ function prepareSnapshotFrameStreamHeader(input) {
39
+ const runtime = input;
40
+ if (typeof runtime !== "object" || runtime === null) {
41
+ throw new TypeError("Snapshot stream header must be an object");
42
+ }
43
+ sameKeys(runtime, ["formatVersion", "databaseVersion", "createdAt", "kinds"], "Snapshot header");
44
+ const fields = runtime;
45
+ if (fields.formatVersion !== 1)
46
+ throw new TypeError("Unsupported snapshot format version");
47
+ const databaseVersion = safeWhole(fields.databaseVersion, "Snapshot database version");
48
+ const createdAt = canonicalTimestamp(fields.createdAt, "Snapshot creation time");
49
+ if (typeof fields.kinds !== "object" || fields.kinds === null) {
50
+ throw new TypeError("Snapshot kind summaries must be an object");
51
+ }
52
+ sameKeys(fields.kinds, SNAPSHOT_FRAME_KINDS, "Snapshot kind summaries");
53
+ const rawKinds = fields.kinds;
54
+ const kinds = /* @__PURE__ */ Object.create(null);
55
+ for (const kind of SNAPSHOT_FRAME_KINDS) {
56
+ const summary = rawKinds[kind];
57
+ if (typeof summary !== "object" || summary === null) {
58
+ throw new TypeError(`Snapshot ${kind} summary must be an object`);
59
+ }
60
+ sameKeys(summary, ["frameCount", "itemCount", "storedBytes"], `Snapshot ${kind} summary`);
61
+ const rawSummary = summary;
62
+ const frameCount = safeWhole(rawSummary.frameCount, `Snapshot ${kind} frame count`);
63
+ const itemCount = safeWhole(rawSummary.itemCount, `Snapshot ${kind} item count`);
64
+ const storedBytes = safeWhole(rawSummary.storedBytes, `Snapshot ${kind} stored bytes`);
65
+ if (frameCount === 0 !== (itemCount === 0 && storedBytes === 0)) {
66
+ throw new TypeError(`Snapshot ${kind} empty summary is inconsistent`);
67
+ }
68
+ kinds[kind] = { frameCount, itemCount, storedBytes };
69
+ }
70
+ return { formatVersion: 1, databaseVersion, createdAt, kinds };
71
71
  }
72
72
  function canonicalHeaderJson(header) {
73
- const prepared = prepareSnapshotFrameStreamHeader(header);
74
- return JSON.stringify({
75
- formatVersion: 1,
76
- databaseVersion: prepared.databaseVersion,
77
- createdAt: prepared.createdAt,
78
- kinds: Object.fromEntries(SNAPSHOT_FRAME_KINDS.map((kind) => [kind, prepared.kinds[kind]])),
79
- });
73
+ const prepared = prepareSnapshotFrameStreamHeader(header);
74
+ return JSON.stringify({
75
+ formatVersion: 1,
76
+ databaseVersion: prepared.databaseVersion,
77
+ createdAt: prepared.createdAt,
78
+ kinds: Object.fromEntries(SNAPSHOT_FRAME_KINDS.map((kind) => [kind, prepared.kinds[kind]]))
79
+ });
80
80
  }
81
- export function encodeSnapshotFrameStreamHeader(header) {
82
- const bytes = textEncoder.encode(canonicalHeaderJson(header));
83
- if (bytes.byteLength > MAX_HEADER_BYTES)
84
- throw new RangeError("Snapshot header is too large");
85
- const encoded = new Uint8Array(HEADER_PREFIX_BYTES + bytes.byteLength);
86
- encoded.set(MAGIC, 0);
87
- const view = new DataView(encoded.buffer);
88
- view.setUint32(8, 1, true);
89
- view.setUint32(12, bytes.byteLength, true);
90
- view.setUint32(16, crc32(bytes), true);
91
- encoded.set(bytes, HEADER_PREFIX_BYTES);
92
- return encoded;
81
+ function encodeSnapshotFrameStreamHeader(header) {
82
+ const bytes = textEncoder.encode(canonicalHeaderJson(header));
83
+ if (bytes.byteLength > MAX_HEADER_BYTES)
84
+ throw new RangeError("Snapshot header is too large");
85
+ const encoded = new Uint8Array(HEADER_PREFIX_BYTES + bytes.byteLength);
86
+ encoded.set(MAGIC, 0);
87
+ const view = new DataView(encoded.buffer);
88
+ view.setUint32(8, 1, true);
89
+ view.setUint32(12, bytes.byteLength, true);
90
+ view.setUint32(16, crc32(bytes), true);
91
+ encoded.set(bytes, HEADER_PREFIX_BYTES);
92
+ return encoded;
93
93
  }
94
- export function snapshotFrameStreamHeaderIdentity(header) {
95
- const bytes = encodeSnapshotFrameStreamHeader(header);
96
- return `snapshot-v1/${String(bytes.byteLength)}/${crc32(bytes).toString(16).padStart(8, "0")}`;
94
+ function snapshotFrameStreamHeaderIdentity(header) {
95
+ const bytes = encodeSnapshotFrameStreamHeader(header);
96
+ return `snapshot-v1/${String(bytes.byteLength)}/${crc32(bytes).toString(16).padStart(8, "0")}`;
97
97
  }
98
98
  function utf8Bytes(value, label) {
99
- assertWellFormedString(value, label);
100
- return textEncoder.encode(value);
99
+ assertWellFormedString(value, label);
100
+ return textEncoder.encode(value);
101
101
  }
102
102
  function stringFromUtf8(bytes) {
103
- try {
104
- return textDecoder.decode(bytes);
105
- }
106
- catch {
107
- throw new TypeError("Snapshot string is not valid UTF-8");
108
- }
103
+ try {
104
+ return textDecoder.decode(bytes);
105
+ } catch {
106
+ throw new TypeError("Snapshot string is not valid UTF-8");
107
+ }
109
108
  }
110
109
  function frameKindIndex(kind) {
111
- const index = SNAPSHOT_FRAME_KINDS.indexOf(kind);
112
- if (index < 0)
113
- throw new TypeError(`Unknown snapshot frame kind: ${kind}`);
114
- return index;
110
+ const index = SNAPSHOT_FRAME_KINDS.indexOf(kind);
111
+ if (index < 0)
112
+ throw new TypeError(`Unknown snapshot frame kind: ${kind}`);
113
+ return index;
115
114
  }
116
- export function snapshotFrameEnvelopeParts(frame) {
117
- const sequence = safeWhole(frame.sequence, "Snapshot frame sequence");
118
- const itemCount = safeWhole(frame.itemCount, "Snapshot frame item count");
119
- if (itemCount < 1 || itemCount > MAX_SNAPSHOT_FRAME_ITEMS) {
120
- throw new RangeError("Snapshot frame item count is out of bounds");
121
- }
122
- const isBlock = frame.kind === "block";
123
- if (isBlock !== (frame.key !== null) || (isBlock && itemCount !== 1)) {
124
- throw new TypeError("Snapshot block frame identity is inconsistent");
125
- }
126
- const keyBytes = frame.key === null
127
- ? new Uint8Array()
128
- : utf8Bytes(validateStorageId(frame.key), "Snapshot frame key");
129
- const payloadLimit = isBlock ? MAX_STORED_BLOCK_BYTE_LENGTH : MAX_SNAPSHOT_METADATA_FRAME_BYTES;
130
- if (frame.payload.byteLength < 1 || frame.payload.byteLength > payloadLimit) {
131
- throw new RangeError("Snapshot frame payload length is out of bounds");
132
- }
133
- if (!Number.isInteger(frame.checksum) || frame.checksum < 0 || frame.checksum > 0xffffffff) {
134
- throw new RangeError("Snapshot frame checksum is invalid");
135
- }
136
- const prefix = new Uint8Array(FRAME_PREFIX_BYTES);
137
- const view = new DataView(prefix.buffer);
138
- view.setUint32(0, FRAME_MARKER, true);
139
- view.setUint32(4, frameKindIndex(frame.kind), true);
140
- view.setBigUint64(8, BigInt(sequence), true);
141
- view.setUint32(16, itemCount, true);
142
- view.setUint32(20, keyBytes.byteLength, true);
143
- view.setBigUint64(24, BigInt(frame.payload.byteLength), true);
144
- view.setUint32(32, frame.checksum, true);
145
- view.setUint32(36, 0, true);
146
- return keyBytes.byteLength === 0 ? [prefix, frame.payload] : [prefix, keyBytes, frame.payload];
115
+ function snapshotFrameEnvelopeParts(frame) {
116
+ const sequence = safeWhole(frame.sequence, "Snapshot frame sequence");
117
+ const itemCount = safeWhole(frame.itemCount, "Snapshot frame item count");
118
+ if (itemCount < 1 || itemCount > MAX_SNAPSHOT_FRAME_ITEMS) {
119
+ throw new RangeError("Snapshot frame item count is out of bounds");
120
+ }
121
+ const isBlock = frame.kind === "block";
122
+ if (isBlock !== (frame.key !== null) || isBlock && itemCount !== 1) {
123
+ throw new TypeError("Snapshot block frame identity is inconsistent");
124
+ }
125
+ const keyBytes = frame.key === null ? new Uint8Array() : utf8Bytes(validateStorageId(frame.key), "Snapshot frame key");
126
+ const payloadLimit = isBlock ? MAX_STORED_BLOCK_BYTE_LENGTH : MAX_SNAPSHOT_METADATA_FRAME_BYTES;
127
+ if (frame.payload.byteLength < 1 || frame.payload.byteLength > payloadLimit) {
128
+ throw new RangeError("Snapshot frame payload length is out of bounds");
129
+ }
130
+ if (!Number.isInteger(frame.checksum) || frame.checksum < 0 || frame.checksum > 4294967295) {
131
+ throw new RangeError("Snapshot frame checksum is invalid");
132
+ }
133
+ const prefix = new Uint8Array(FRAME_PREFIX_BYTES);
134
+ const view = new DataView(prefix.buffer);
135
+ view.setUint32(0, FRAME_MARKER, true);
136
+ view.setUint32(4, frameKindIndex(frame.kind), true);
137
+ view.setBigUint64(8, BigInt(sequence), true);
138
+ view.setUint32(16, itemCount, true);
139
+ view.setUint32(20, keyBytes.byteLength, true);
140
+ view.setBigUint64(24, BigInt(frame.payload.byteLength), true);
141
+ view.setUint32(32, frame.checksum, true);
142
+ view.setUint32(36, 0, true);
143
+ return keyBytes.byteLength === 0 ? [prefix, frame.payload] : [prefix, keyBytes, frame.payload];
147
144
  }
148
- export function extendSnapshotFrameStreamChecksum(checksum, parts) {
149
- let next = checksum;
150
- for (const part of parts)
151
- next = crc32Continue(next, part);
152
- return next;
145
+ function extendSnapshotFrameStreamChecksum(checksum, parts) {
146
+ let next = checksum;
147
+ for (const part of parts)
148
+ next = crc32Continue(next, part);
149
+ return next;
153
150
  }
154
- export function encodeSnapshotFrameStreamFooter(footer) {
155
- const frameCount = safeWhole(footer.frameCount, "Snapshot footer frame count");
156
- const itemCount = safeWhole(footer.itemCount, "Snapshot footer item count");
157
- const storedBytes = safeWhole(footer.storedBytes, "Snapshot footer stored bytes");
158
- if (!Number.isInteger(footer.checksum) || footer.checksum < 0 || footer.checksum > 0xffffffff) {
159
- throw new RangeError("Snapshot footer checksum is invalid");
160
- }
161
- const bytes = new Uint8Array(FOOTER_BYTES);
162
- const view = new DataView(bytes.buffer);
163
- view.setUint32(0, FOOTER_MARKER, true);
164
- view.setBigUint64(4, BigInt(frameCount), true);
165
- view.setBigUint64(12, BigInt(itemCount), true);
166
- view.setBigUint64(20, BigInt(storedBytes), true);
167
- view.setUint32(28, footer.checksum, true);
168
- return bytes;
151
+ function encodeSnapshotFrameStreamFooter(footer) {
152
+ const frameCount = safeWhole(footer.frameCount, "Snapshot footer frame count");
153
+ const itemCount = safeWhole(footer.itemCount, "Snapshot footer item count");
154
+ const storedBytes = safeWhole(footer.storedBytes, "Snapshot footer stored bytes");
155
+ if (!Number.isInteger(footer.checksum) || footer.checksum < 0 || footer.checksum > 4294967295) {
156
+ throw new RangeError("Snapshot footer checksum is invalid");
157
+ }
158
+ const bytes = new Uint8Array(FOOTER_BYTES);
159
+ const view = new DataView(bytes.buffer);
160
+ view.setUint32(0, FOOTER_MARKER, true);
161
+ view.setBigUint64(4, BigInt(frameCount), true);
162
+ view.setBigUint64(12, BigInt(itemCount), true);
163
+ view.setBigUint64(20, BigInt(storedBytes), true);
164
+ view.setUint32(28, footer.checksum, true);
165
+ return bytes;
169
166
  }
170
167
  class WireWriter {
171
- #bytes = new Uint8Array(256);
172
- #length = 0;
173
- #reserve(count) {
174
- const next = this.#length + count;
175
- if (next > MAX_SNAPSHOT_METADATA_FRAME_BYTES) {
176
- throw new RangeError("Snapshot metadata frame exceeds its byte limit");
177
- }
178
- if (next > this.#bytes.byteLength) {
179
- let capacity = this.#bytes.byteLength;
180
- while (capacity < next)
181
- capacity = Math.min(MAX_SNAPSHOT_METADATA_FRAME_BYTES, capacity * 2);
182
- const grown = new Uint8Array(capacity);
183
- grown.set(this.#bytes.subarray(0, this.#length));
184
- this.#bytes = grown;
185
- }
186
- const offset = this.#length;
187
- this.#length = next;
188
- return offset;
189
- }
190
- byte(value) {
191
- const offset = this.#reserve(1);
192
- this.#bytes[offset] = value;
193
- }
194
- uint32(value) {
195
- const offset = this.#reserve(4);
196
- new DataView(this.#bytes.buffer).setUint32(offset, value, true);
197
- }
198
- float64(value) {
199
- const offset = this.#reserve(8);
200
- new DataView(this.#bytes.buffer).setFloat64(offset, value, true);
201
- }
202
- bytes(value) {
203
- const offset = this.#reserve(value.byteLength);
204
- this.#bytes.set(value, offset);
205
- }
206
- finish() {
207
- return this.#bytes.slice(0, this.#length);
208
- }
209
- get remaining() {
210
- return MAX_SNAPSHOT_METADATA_FRAME_BYTES - this.#length;
211
- }
168
+ #bytes = new Uint8Array(256);
169
+ #length = 0;
170
+ #reserve(count) {
171
+ const next = this.#length + count;
172
+ if (next > MAX_SNAPSHOT_METADATA_FRAME_BYTES) {
173
+ throw new RangeError("Snapshot metadata frame exceeds its byte limit");
174
+ }
175
+ if (next > this.#bytes.byteLength) {
176
+ let capacity = this.#bytes.byteLength;
177
+ while (capacity < next)
178
+ capacity = Math.min(MAX_SNAPSHOT_METADATA_FRAME_BYTES, capacity * 2);
179
+ const grown = new Uint8Array(capacity);
180
+ grown.set(this.#bytes.subarray(0, this.#length));
181
+ this.#bytes = grown;
182
+ }
183
+ const offset = this.#length;
184
+ this.#length = next;
185
+ return offset;
186
+ }
187
+ byte(value) {
188
+ const offset = this.#reserve(1);
189
+ this.#bytes[offset] = value;
190
+ }
191
+ uint32(value) {
192
+ const offset = this.#reserve(4);
193
+ new DataView(this.#bytes.buffer).setUint32(offset, value, true);
194
+ }
195
+ float64(value) {
196
+ const offset = this.#reserve(8);
197
+ new DataView(this.#bytes.buffer).setFloat64(offset, value, true);
198
+ }
199
+ bytes(value) {
200
+ const offset = this.#reserve(value.byteLength);
201
+ this.#bytes.set(value, offset);
202
+ }
203
+ finish() {
204
+ return this.#bytes.slice(0, this.#length);
205
+ }
206
+ get remaining() {
207
+ return MAX_SNAPSHOT_METADATA_FRAME_BYTES - this.#length;
208
+ }
212
209
  }
213
210
  function writeWireValue(writer, value, seen, state, depth) {
214
- if (depth > MAX_WIRE_DEPTH || ++state.entries > MAX_WIRE_ENTRIES) {
215
- throw new RangeError("Snapshot metadata value is too complex");
216
- }
217
- if (value === null)
218
- return writer.byte(0);
219
- if (value === false)
220
- return writer.byte(1);
221
- if (value === true)
222
- return writer.byte(2);
223
- if (typeof value === "number") {
224
- if (!Number.isFinite(value))
225
- throw new TypeError("Snapshot metadata numbers must be finite");
226
- writer.byte(3);
227
- writer.float64(value);
228
- return;
229
- }
230
- if (typeof value === "string") {
231
- if (value.length > writer.remaining)
232
- throw new RangeError("Snapshot metadata string is too large");
233
- const bytes = utf8Bytes(value, "Snapshot metadata string");
234
- writer.byte(4);
235
- writer.uint32(bytes.byteLength);
236
- writer.bytes(bytes);
237
- return;
238
- }
239
- if (typeof value === "bigint") {
240
- if (value < 0n || value >= MAX_ROW_ID_EXCLUSIVE_END) {
241
- throw new RangeError("Snapshot metadata bigint is outside the unsigned 64-bit range");
242
- }
243
- const bytes = utf8Bytes(value.toString(), "Snapshot metadata bigint");
244
- writer.byte(5);
245
- writer.uint32(bytes.byteLength);
246
- writer.bytes(bytes);
247
- return;
248
- }
249
- if (typeof value !== "object") {
250
- throw new TypeError(`Unsupported snapshot metadata value: ${typeof value}`);
251
- }
252
- if (seen.has(value))
253
- throw new TypeError("Snapshot metadata contains a cycle or alias");
254
- seen.add(value);
255
- if (value instanceof Date) {
256
- const milliseconds = dateMilliseconds(value);
257
- if (!Number.isSafeInteger(milliseconds)) {
258
- throw new TypeError("Snapshot metadata date is invalid");
259
- }
260
- writer.byte(8);
261
- writer.float64(milliseconds);
262
- }
263
- else if (value instanceof Uint8Array) {
264
- if (value.byteLength > writer.remaining - 5) {
265
- throw new RangeError("Snapshot metadata byte array is too large");
266
- }
267
- writer.byte(9);
268
- writer.uint32(value.byteLength);
269
- writer.bytes(value);
270
- }
271
- else if (Array.isArray(value)) {
272
- if (value.length > MAX_WIRE_ENTRIES) {
273
- throw new RangeError("Snapshot metadata array is too large");
274
- }
275
- writer.byte(6);
276
- writer.uint32(value.length);
277
- for (const item of value)
278
- writeWireValue(writer, item, seen, state, depth + 1);
279
- }
280
- else {
281
- const prototype = Object.getPrototypeOf(value);
282
- if (prototype !== Object.prototype && prototype !== null) {
283
- throw new TypeError("Snapshot metadata must contain plain objects");
284
- }
285
- const record = value;
286
- const keys = Object.keys(record).sort();
287
- if (keys.length > MAX_WIRE_ENTRIES) {
288
- throw new RangeError("Snapshot metadata object is too large");
289
- }
290
- writer.byte(7);
291
- writer.uint32(keys.length);
292
- for (const key of keys) {
293
- writeWireValue(writer, key, seen, state, depth + 1);
294
- writeWireValue(writer, record[key], seen, state, depth + 1);
295
- }
296
- }
211
+ if (depth > MAX_WIRE_DEPTH || ++state.entries > MAX_WIRE_ENTRIES) {
212
+ throw new RangeError("Snapshot metadata value is too complex");
213
+ }
214
+ if (value === null)
215
+ return writer.byte(0);
216
+ if (value === false)
217
+ return writer.byte(1);
218
+ if (value === true)
219
+ return writer.byte(2);
220
+ if (typeof value === "number") {
221
+ if (!Number.isFinite(value))
222
+ throw new TypeError("Snapshot metadata numbers must be finite");
223
+ writer.byte(3);
224
+ writer.float64(value);
225
+ return;
226
+ }
227
+ if (typeof value === "string") {
228
+ if (value.length > writer.remaining)
229
+ throw new RangeError("Snapshot metadata string is too large");
230
+ const bytes = utf8Bytes(value, "Snapshot metadata string");
231
+ writer.byte(4);
232
+ writer.uint32(bytes.byteLength);
233
+ writer.bytes(bytes);
234
+ return;
235
+ }
236
+ if (typeof value === "bigint") {
237
+ if (value < 0n || value >= MAX_ROW_ID_EXCLUSIVE_END) {
238
+ throw new RangeError("Snapshot metadata bigint is outside the unsigned 64-bit range");
239
+ }
240
+ const bytes = utf8Bytes(value.toString(), "Snapshot metadata bigint");
241
+ writer.byte(5);
242
+ writer.uint32(bytes.byteLength);
243
+ writer.bytes(bytes);
244
+ return;
245
+ }
246
+ if (typeof value !== "object") {
247
+ throw new TypeError(`Unsupported snapshot metadata value: ${typeof value}`);
248
+ }
249
+ if (seen.has(value))
250
+ throw new TypeError("Snapshot metadata contains a cycle or alias");
251
+ seen.add(value);
252
+ if (value instanceof Date) {
253
+ const milliseconds = dateMilliseconds(value);
254
+ if (!Number.isSafeInteger(milliseconds)) {
255
+ throw new TypeError("Snapshot metadata date is invalid");
256
+ }
257
+ writer.byte(8);
258
+ writer.float64(milliseconds);
259
+ } else if (value instanceof Uint8Array) {
260
+ if (value.byteLength > writer.remaining - 5) {
261
+ throw new RangeError("Snapshot metadata byte array is too large");
262
+ }
263
+ writer.byte(9);
264
+ writer.uint32(value.byteLength);
265
+ writer.bytes(value);
266
+ } else if (Array.isArray(value)) {
267
+ if (value.length > MAX_WIRE_ENTRIES) {
268
+ throw new RangeError("Snapshot metadata array is too large");
269
+ }
270
+ writer.byte(6);
271
+ writer.uint32(value.length);
272
+ for (const item of value)
273
+ writeWireValue(writer, item, seen, state, depth + 1);
274
+ } else {
275
+ const prototype = Object.getPrototypeOf(value);
276
+ if (prototype !== Object.prototype && prototype !== null) {
277
+ throw new TypeError("Snapshot metadata must contain plain objects");
278
+ }
279
+ const record = value;
280
+ const keys = Object.keys(record).sort();
281
+ if (keys.length > MAX_WIRE_ENTRIES) {
282
+ throw new RangeError("Snapshot metadata object is too large");
283
+ }
284
+ writer.byte(7);
285
+ writer.uint32(keys.length);
286
+ for (const key of keys) {
287
+ writeWireValue(writer, key, seen, state, depth + 1);
288
+ writeWireValue(writer, record[key], seen, state, depth + 1);
289
+ }
290
+ }
297
291
  }
298
- /** Canonical substrate-independent encoding for one bounded metadata page. */
299
- export function encodeSnapshotMetadataPage(items) {
300
- if (items.length < 1 || items.length > MAX_SNAPSHOT_FRAME_ITEMS) {
301
- throw new RangeError("Snapshot metadata page item count is out of bounds");
302
- }
303
- const writer = new WireWriter();
304
- writeWireValue(writer, items, new WeakSet(), { entries: 0 }, 0);
305
- return writer.finish();
292
+ function encodeSnapshotMetadataPage(items) {
293
+ if (items.length < 1 || items.length > MAX_SNAPSHOT_FRAME_ITEMS) {
294
+ throw new RangeError("Snapshot metadata page item count is out of bounds");
295
+ }
296
+ const writer = new WireWriter();
297
+ writeWireValue(writer, items, /* @__PURE__ */ new WeakSet(), { entries: 0 }, 0);
298
+ return writer.finish();
306
299
  }
307
300
  class WireReader {
308
- bytes;
309
- #offset = 0;
310
- #entries = 0;
311
- constructor(bytes) {
312
- this.bytes = bytes;
313
- }
314
- get done() {
315
- return this.#offset === this.bytes.byteLength;
316
- }
317
- get remaining() {
318
- return this.bytes.byteLength - this.#offset;
319
- }
320
- byte() {
321
- if (this.#offset >= this.bytes.byteLength)
322
- throw new Error("Snapshot metadata is truncated");
323
- return this.bytes[this.#offset++] ?? 0;
324
- }
325
- uint32() {
326
- if (this.#offset + 4 > this.bytes.byteLength)
327
- throw new Error("Snapshot metadata is truncated");
328
- const value = new DataView(this.bytes.buffer, this.bytes.byteOffset).getUint32(this.#offset, true);
329
- this.#offset += 4;
330
- return value;
331
- }
332
- float64() {
333
- if (this.#offset + 8 > this.bytes.byteLength)
334
- throw new Error("Snapshot metadata is truncated");
335
- const value = new DataView(this.bytes.buffer, this.bytes.byteOffset).getFloat64(this.#offset, true);
336
- this.#offset += 8;
337
- return value;
338
- }
339
- take(length) {
340
- if (length > this.bytes.byteLength - this.#offset)
341
- throw new Error("Snapshot metadata is truncated");
342
- const value = this.bytes.slice(this.#offset, this.#offset + length);
343
- this.#offset += length;
344
- return value;
345
- }
346
- entry(depth) {
347
- if (depth > MAX_WIRE_DEPTH || ++this.#entries > MAX_WIRE_ENTRIES) {
348
- throw new RangeError("Snapshot metadata value is too complex");
349
- }
301
+ bytes;
302
+ #offset = 0;
303
+ #entries = 0;
304
+ constructor(bytes) {
305
+ this.bytes = bytes;
306
+ }
307
+ get done() {
308
+ return this.#offset === this.bytes.byteLength;
309
+ }
310
+ get remaining() {
311
+ return this.bytes.byteLength - this.#offset;
312
+ }
313
+ byte() {
314
+ if (this.#offset >= this.bytes.byteLength)
315
+ throw new Error("Snapshot metadata is truncated");
316
+ return this.bytes[this.#offset++] ?? 0;
317
+ }
318
+ uint32() {
319
+ if (this.#offset + 4 > this.bytes.byteLength)
320
+ throw new Error("Snapshot metadata is truncated");
321
+ const value = new DataView(this.bytes.buffer, this.bytes.byteOffset).getUint32(this.#offset, true);
322
+ this.#offset += 4;
323
+ return value;
324
+ }
325
+ float64() {
326
+ if (this.#offset + 8 > this.bytes.byteLength)
327
+ throw new Error("Snapshot metadata is truncated");
328
+ const value = new DataView(this.bytes.buffer, this.bytes.byteOffset).getFloat64(this.#offset, true);
329
+ this.#offset += 8;
330
+ return value;
331
+ }
332
+ take(length) {
333
+ if (length > this.bytes.byteLength - this.#offset)
334
+ throw new Error("Snapshot metadata is truncated");
335
+ const value = this.bytes.slice(this.#offset, this.#offset + length);
336
+ this.#offset += length;
337
+ return value;
338
+ }
339
+ entry(depth) {
340
+ if (depth > MAX_WIRE_DEPTH || ++this.#entries > MAX_WIRE_ENTRIES) {
341
+ throw new RangeError("Snapshot metadata value is too complex");
350
342
  }
343
+ }
351
344
  }
352
345
  function readWireValue(reader, depth) {
353
- reader.entry(depth);
354
- const tag = reader.byte();
355
- if (tag === 0)
356
- return null;
357
- if (tag === 1)
358
- return false;
359
- if (tag === 2)
360
- return true;
361
- if (tag === 3) {
362
- const value = reader.float64();
363
- if (!Number.isFinite(value))
364
- throw new TypeError("Snapshot metadata number is not finite");
365
- return value;
366
- }
367
- if (tag === 4)
368
- return stringFromUtf8(reader.take(reader.uint32()));
369
- if (tag === 5) {
370
- const length = reader.uint32();
371
- if (length < 1 || length > MAX_UINT64_DECIMAL_BYTES || length > reader.remaining) {
372
- throw new RangeError("Snapshot bigint is outside the unsigned 64-bit range");
373
- }
374
- const text = stringFromUtf8(reader.take(length));
375
- if (!/^(?:0|[1-9][0-9]*)$/.test(text)) {
376
- throw new TypeError("Snapshot bigint is not canonical");
377
- }
378
- const value = BigInt(text);
379
- if (value >= MAX_ROW_ID_EXCLUSIVE_END) {
380
- throw new RangeError("Snapshot bigint is outside the unsigned 64-bit range");
381
- }
382
- return value;
383
- }
384
- if (tag === 8) {
385
- const milliseconds = reader.float64();
386
- if (!Number.isSafeInteger(milliseconds))
387
- throw new TypeError("Snapshot date is invalid");
388
- const value = new Date(milliseconds);
389
- if (dateMilliseconds(value) !== milliseconds)
390
- throw new TypeError("Snapshot date is invalid");
391
- return value;
392
- }
393
- if (tag === 9) {
394
- const length = reader.uint32();
395
- if (length > MAX_SNAPSHOT_METADATA_FRAME_BYTES || length > reader.remaining) {
396
- throw new RangeError("Snapshot metadata byte array is too large");
397
- }
398
- return reader.take(length);
399
- }
400
- if (tag === 6) {
401
- const length = reader.uint32();
402
- if (length > MAX_WIRE_ENTRIES || length > reader.bytes.byteLength) {
403
- throw new RangeError("Snapshot array is too large");
404
- }
405
- return Array.from({ length }, () => readWireValue(reader, depth + 1));
406
- }
407
- if (tag === 7) {
408
- const length = reader.uint32();
409
- if (length > MAX_WIRE_ENTRIES)
410
- throw new RangeError("Snapshot object is too large");
411
- const value = {};
412
- let previous = "";
413
- for (let index = 0; index < length; index += 1) {
414
- const key = readWireValue(reader, depth + 1);
415
- if (typeof key !== "string" || (index > 0 && key <= previous)) {
416
- throw new TypeError("Snapshot object keys are not canonical");
417
- }
418
- previous = key;
419
- const item = readWireValue(reader, depth + 1);
420
- if (key === "__proto__") {
421
- Object.defineProperty(value, key, { value: item, enumerable: true, writable: true });
422
- }
423
- else
424
- value[key] = item;
425
- }
426
- return value;
427
- }
428
- throw new TypeError(`Unknown snapshot metadata tag: ${String(tag)}`);
429
- }
430
- export function decodeSnapshotMetadataPage(payload) {
431
- if (payload.byteLength < 1 || payload.byteLength > MAX_SNAPSHOT_METADATA_FRAME_BYTES) {
432
- throw new RangeError("Snapshot metadata page length is out of bounds");
433
- }
434
- const reader = new WireReader(payload);
435
- const value = readWireValue(reader, 0);
436
- if (!reader.done || !Array.isArray(value)) {
437
- throw new TypeError("Snapshot metadata page is not canonical");
346
+ reader.entry(depth);
347
+ const tag = reader.byte();
348
+ if (tag === 0)
349
+ return null;
350
+ if (tag === 1)
351
+ return false;
352
+ if (tag === 2)
353
+ return true;
354
+ if (tag === 3) {
355
+ const value = reader.float64();
356
+ if (!Number.isFinite(value))
357
+ throw new TypeError("Snapshot metadata number is not finite");
358
+ return value;
359
+ }
360
+ if (tag === 4)
361
+ return stringFromUtf8(reader.take(reader.uint32()));
362
+ if (tag === 5) {
363
+ const length = reader.uint32();
364
+ if (length < 1 || length > MAX_UINT64_DECIMAL_BYTES || length > reader.remaining) {
365
+ throw new RangeError("Snapshot bigint is outside the unsigned 64-bit range");
366
+ }
367
+ const text = stringFromUtf8(reader.take(length));
368
+ if (!/^(?:0|[1-9][0-9]*)$/.test(text)) {
369
+ throw new TypeError("Snapshot bigint is not canonical");
370
+ }
371
+ const value = BigInt(text);
372
+ if (value >= MAX_ROW_ID_EXCLUSIVE_END) {
373
+ throw new RangeError("Snapshot bigint is outside the unsigned 64-bit range");
438
374
  }
439
- if (value.length < 1 || value.length > MAX_SNAPSHOT_FRAME_ITEMS) {
440
- throw new RangeError("Snapshot metadata page item count is out of bounds");
375
+ return value;
376
+ }
377
+ if (tag === 8) {
378
+ const milliseconds = reader.float64();
379
+ if (!Number.isSafeInteger(milliseconds))
380
+ throw new TypeError("Snapshot date is invalid");
381
+ const value = new Date(milliseconds);
382
+ if (dateMilliseconds(value) !== milliseconds)
383
+ throw new TypeError("Snapshot date is invalid");
384
+ return value;
385
+ }
386
+ if (tag === 9) {
387
+ const length = reader.uint32();
388
+ if (length > MAX_SNAPSHOT_METADATA_FRAME_BYTES || length > reader.remaining) {
389
+ throw new RangeError("Snapshot metadata byte array is too large");
390
+ }
391
+ return reader.take(length);
392
+ }
393
+ if (tag === 6) {
394
+ const length = reader.uint32();
395
+ if (length > MAX_WIRE_ENTRIES || length > reader.bytes.byteLength) {
396
+ throw new RangeError("Snapshot array is too large");
397
+ }
398
+ return Array.from({ length }, () => readWireValue(reader, depth + 1));
399
+ }
400
+ if (tag === 7) {
401
+ const length = reader.uint32();
402
+ if (length > MAX_WIRE_ENTRIES)
403
+ throw new RangeError("Snapshot object is too large");
404
+ const value = {};
405
+ let previous = "";
406
+ for (let index = 0; index < length; index += 1) {
407
+ const key = readWireValue(reader, depth + 1);
408
+ if (typeof key !== "string" || index > 0 && key <= previous) {
409
+ throw new TypeError("Snapshot object keys are not canonical");
410
+ }
411
+ previous = key;
412
+ const item = readWireValue(reader, depth + 1);
413
+ if (key === "__proto__") {
414
+ Object.defineProperty(value, key, { value: item, enumerable: true, writable: true });
415
+ } else
416
+ value[key] = item;
441
417
  }
442
418
  return value;
419
+ }
420
+ throw new TypeError(`Unknown snapshot metadata tag: ${String(tag)}`);
421
+ }
422
+ function decodeSnapshotMetadataPage(payload) {
423
+ if (payload.byteLength < 1 || payload.byteLength > MAX_SNAPSHOT_METADATA_FRAME_BYTES) {
424
+ throw new RangeError("Snapshot metadata page length is out of bounds");
425
+ }
426
+ const reader = new WireReader(payload);
427
+ const value = readWireValue(reader, 0);
428
+ if (!reader.done || !Array.isArray(value)) {
429
+ throw new TypeError("Snapshot metadata page is not canonical");
430
+ }
431
+ if (value.length < 1 || value.length > MAX_SNAPSHOT_FRAME_ITEMS) {
432
+ throw new RangeError("Snapshot metadata page item count is out of bounds");
433
+ }
434
+ return value;
443
435
  }
444
436
  function recordValue(value, label) {
445
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
446
- throw new TypeError(`${label} must be an object`);
447
- }
448
- return value;
437
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
438
+ throw new TypeError(`${label} must be an object`);
439
+ }
440
+ return value;
449
441
  }
450
442
  function exactRecord(value, fields, label) {
451
- const record = recordValue(value, label);
452
- sameKeys(record, fields, label);
453
- return record;
443
+ const record = recordValue(value, label);
444
+ sameKeys(record, fields, label);
445
+ return record;
454
446
  }
455
447
  function canonicalOrdinal(value, label) {
456
- return safeWhole(value, label);
448
+ return safeWhole(value, label);
457
449
  }
458
450
  function canonicalCounter(value, label) {
459
- if (typeof value !== "bigint" || value < 1n || value > MAX_ROW_ID_EXCLUSIVE_END) {
460
- throw new RangeError(`${label} must be a positive bounded bigint`);
461
- }
462
- return value;
451
+ if (typeof value !== "bigint" || value < 1n || value > MAX_ROW_ID_EXCLUSIVE_END) {
452
+ throw new RangeError(`${label} must be a positive bounded bigint`);
453
+ }
454
+ return value;
463
455
  }
464
456
  function prepareCatalogItem(value) {
465
- const item = exactRecord(value, ["kind", "record", "nextRowId", "autoIncrement"], "Snapshot catalog item");
466
- if (item.kind !== "table")
467
- throw new TypeError("Snapshot catalog item kind is invalid");
468
- const table = item.record;
469
- validateTableRecordBounds(table);
470
- const nextRowId = canonicalCounter(item.nextRowId, "Snapshot next row ID");
471
- if (!Array.isArray(item.autoIncrement) || item.autoIncrement.length > table.columns.length) {
472
- throw new RangeError("Snapshot auto-increment counters are invalid");
473
- }
474
- const autoIncrement = item.autoIncrement.map((raw) => {
475
- const counter = exactRecord(raw, ["columnId", "next"], "Snapshot auto-increment counter");
476
- const columnId = validateStorageId(counter.columnId, "Snapshot auto-increment column ID");
477
- const column = table.columns.find((candidate) => candidate.id === columnId);
478
- if (column?.defaultValue?.kind !== "autoincrement") {
479
- throw new TypeError("Snapshot auto-increment counter has no matching catalog column");
480
- }
481
- const next = canonicalCounter(counter.next, "Snapshot auto-increment value");
482
- if (next > MAX_AUTO_INCREMENT_EXCLUSIVE_END) {
483
- throw new RangeError("Snapshot auto-increment value exceeds its safe integer range");
484
- }
485
- return { columnId, next };
486
- });
487
- const canonicalIds = autoIncrement.map((entry) => entry.columnId);
488
- if (canonicalIds.some((id, index) => index > 0 && id <= (canonicalIds[index - 1] ?? ""))) {
489
- throw new TypeError("Snapshot auto-increment counters are not canonical");
490
- }
491
- return { kind: "table", record: table, nextRowId, autoIncrement };
457
+ const item = exactRecord(value, ["kind", "record", "nextRowId", "autoIncrement"], "Snapshot catalog item");
458
+ if (item.kind !== "table")
459
+ throw new TypeError("Snapshot catalog item kind is invalid");
460
+ const table = item.record;
461
+ validateTableRecordBounds(table);
462
+ const nextRowId = canonicalCounter(item.nextRowId, "Snapshot next row ID");
463
+ if (!Array.isArray(item.autoIncrement) || item.autoIncrement.length > table.columns.length) {
464
+ throw new RangeError("Snapshot auto-increment counters are invalid");
465
+ }
466
+ const autoIncrement = item.autoIncrement.map((raw) => {
467
+ const counter = exactRecord(raw, ["columnId", "next"], "Snapshot auto-increment counter");
468
+ const columnId = validateStorageId(counter.columnId, "Snapshot auto-increment column ID");
469
+ const column = table.columns.find((candidate) => candidate.id === columnId);
470
+ if (column?.defaultValue?.kind !== "autoincrement") {
471
+ throw new TypeError("Snapshot auto-increment counter has no matching catalog column");
472
+ }
473
+ const next = canonicalCounter(counter.next, "Snapshot auto-increment value");
474
+ if (next > MAX_AUTO_INCREMENT_EXCLUSIVE_END) {
475
+ throw new RangeError("Snapshot auto-increment value exceeds its safe integer range");
476
+ }
477
+ return { columnId, next };
478
+ });
479
+ const canonicalIds = autoIncrement.map((entry) => entry.columnId);
480
+ if (canonicalIds.some((id, index) => index > 0 && id <= (canonicalIds[index - 1] ?? ""))) {
481
+ throw new TypeError("Snapshot auto-increment counters are not canonical");
482
+ }
483
+ return { kind: "table", record: table, nextRowId, autoIncrement };
492
484
  }
493
485
  function prepareSegmentItem(value) {
494
- const item = exactRecord(value, ["kind", "record"], "Snapshot segment item");
495
- if (item.kind !== "segment")
496
- throw new TypeError("Snapshot segment item kind is invalid");
497
- return { kind: "segment", record: normalizeSegmentRecord(item.record) };
486
+ const item = exactRecord(value, ["kind", "record"], "Snapshot segment item");
487
+ if (item.kind !== "segment")
488
+ throw new TypeError("Snapshot segment item kind is invalid");
489
+ return { kind: "segment", record: normalizeSegmentRecord(item.record) };
498
490
  }
499
491
  function prepareTransactionItem(value) {
500
- const item = exactRecord(value, ["kind", "record"], "Snapshot transaction item");
501
- if (item.kind !== "transaction") {
502
- throw new TypeError("Snapshot transaction item kind is invalid");
503
- }
504
- const transaction = exactRecord(item.record, [
505
- "id",
506
- "ownerId",
507
- "expiresAt",
508
- "snapshotVersion",
509
- "pendingBlockIds",
510
- "pendingSegmentIds",
511
- "status",
512
- "revision",
513
- "startedAt",
514
- "updatedAt",
515
- "committedVersion",
516
- ], "Snapshot transaction record");
517
- validateStorageId(transaction.id, "Snapshot transaction ID");
518
- validateStorageId(transaction.ownerId, "Snapshot transaction owner ID");
519
- canonicalTimestamp(transaction.expiresAt, "Snapshot transaction expiry");
520
- canonicalTimestamp(transaction.startedAt, "Snapshot transaction start time");
521
- canonicalTimestamp(transaction.updatedAt, "Snapshot transaction update time");
522
- if (transaction.status !== "committed" ||
523
- transaction.committedVersion === null ||
524
- !Number.isSafeInteger(transaction.committedVersion) ||
525
- transaction.committedVersion < 0 ||
526
- (transaction.snapshotVersion !== null &&
527
- (!Number.isSafeInteger(transaction.snapshotVersion) ||
528
- transaction.snapshotVersion < 0)) ||
529
- !Number.isSafeInteger(transaction.revision) ||
530
- transaction.revision < 0 ||
531
- !Array.isArray(transaction.pendingBlockIds) ||
532
- transaction.pendingBlockIds.length !== 0 ||
533
- !Array.isArray(transaction.pendingSegmentIds) ||
534
- transaction.pendingSegmentIds.length !== 0) {
535
- throw new TypeError("Snapshot transaction must be a canonical committed record");
536
- }
537
- return { kind: "transaction", record: transaction };
492
+ const item = exactRecord(value, ["kind", "record"], "Snapshot transaction item");
493
+ if (item.kind !== "transaction") {
494
+ throw new TypeError("Snapshot transaction item kind is invalid");
495
+ }
496
+ const transaction = exactRecord(item.record, [
497
+ "id",
498
+ "ownerId",
499
+ "expiresAt",
500
+ "snapshotVersion",
501
+ "pendingBlockIds",
502
+ "pendingSegmentIds",
503
+ "status",
504
+ "revision",
505
+ "startedAt",
506
+ "updatedAt",
507
+ "committedVersion"
508
+ ], "Snapshot transaction record");
509
+ validateStorageId(transaction.id, "Snapshot transaction ID");
510
+ validateStorageId(transaction.ownerId, "Snapshot transaction owner ID");
511
+ canonicalTimestamp(transaction.expiresAt, "Snapshot transaction expiry");
512
+ canonicalTimestamp(transaction.startedAt, "Snapshot transaction start time");
513
+ canonicalTimestamp(transaction.updatedAt, "Snapshot transaction update time");
514
+ if (transaction.status !== "committed" || transaction.committedVersion === null || !Number.isSafeInteger(transaction.committedVersion) || transaction.committedVersion < 0 || transaction.snapshotVersion !== null && (!Number.isSafeInteger(transaction.snapshotVersion) || transaction.snapshotVersion < 0) || !Number.isSafeInteger(transaction.revision) || transaction.revision < 0 || !Array.isArray(transaction.pendingBlockIds) || transaction.pendingBlockIds.length !== 0 || !Array.isArray(transaction.pendingSegmentIds) || transaction.pendingSegmentIds.length !== 0) {
515
+ throw new TypeError("Snapshot transaction must be a canonical committed record");
516
+ }
517
+ return { kind: "transaction", record: transaction };
538
518
  }
539
519
  function prepareUniqueItem(value) {
540
- const record = recordValue(value, "Snapshot UNIQUE item");
541
- if (record.kind === "unique-generation") {
542
- sameKeys(record, ["kind", "tableId", "indexId", "namespaceId", "generationId", "chunkCount", "tokenCount"], "Snapshot UNIQUE generation");
543
- const indexId = record.indexId === null
544
- ? null
545
- : validateStorageId(record.indexId, "Snapshot UNIQUE index ID");
546
- return {
547
- kind: "unique-generation",
548
- tableId: validateStorageId(record.tableId, "Snapshot UNIQUE table ID"),
549
- indexId,
550
- namespaceId: validateStorageId(record.namespaceId, "Snapshot UNIQUE namespace ID"),
551
- generationId: validateStorageId(record.generationId, "Snapshot UNIQUE generation ID"),
552
- chunkCount: canonicalOrdinal(record.chunkCount, "Snapshot UNIQUE chunk count"),
553
- tokenCount: canonicalOrdinal(record.tokenCount, "Snapshot UNIQUE token count"),
554
- };
555
- }
556
- sameKeys(record, ["kind", "namespaceId", "generationId", "ordinal", "keyTokens"], "Snapshot UNIQUE chunk");
557
- if (record.kind !== "unique-chunk" || !Array.isArray(record.keyTokens)) {
558
- throw new TypeError("Snapshot UNIQUE chunk is invalid");
559
- }
560
- if (record.keyTokens.length < 1 ||
561
- record.keyTokens.length > MAX_UNIQUE_KEY_BUILD_TOKENS_PER_CHUNK) {
562
- throw new RangeError("Snapshot UNIQUE chunk token count is out of bounds");
563
- }
564
- const keyTokens = record.keyTokens.map((token) => {
565
- if (typeof token !== "string")
566
- throw new TypeError("Snapshot UNIQUE token must be a string");
567
- assertWellFormedString(token, "Snapshot UNIQUE token");
568
- return token;
569
- });
570
- if (uniqueKeyBuildChunkRetainedBytes(keyTokens) > MAX_UNIQUE_KEY_BUILD_CHUNK_BYTES) {
571
- throw new RangeError("Snapshot UNIQUE chunk is too large");
572
- }
573
- if (keyTokens.some((token, index) => index > 0 && token <= (keyTokens[index - 1] ?? ""))) {
574
- throw new TypeError("Snapshot UNIQUE tokens are not strict lexical order");
575
- }
520
+ const record = recordValue(value, "Snapshot UNIQUE item");
521
+ if (record.kind === "unique-generation") {
522
+ sameKeys(record, ["kind", "tableId", "indexId", "namespaceId", "generationId", "chunkCount", "tokenCount"], "Snapshot UNIQUE generation");
523
+ const indexId = record.indexId === null ? null : validateStorageId(record.indexId, "Snapshot UNIQUE index ID");
576
524
  return {
577
- kind: "unique-chunk",
578
- namespaceId: validateStorageId(record.namespaceId, "Snapshot UNIQUE namespace ID"),
579
- generationId: validateStorageId(record.generationId, "Snapshot UNIQUE generation ID"),
580
- ordinal: canonicalOrdinal(record.ordinal, "Snapshot UNIQUE chunk ordinal"),
581
- keyTokens,
525
+ kind: "unique-generation",
526
+ tableId: validateStorageId(record.tableId, "Snapshot UNIQUE table ID"),
527
+ indexId,
528
+ namespaceId: validateStorageId(record.namespaceId, "Snapshot UNIQUE namespace ID"),
529
+ generationId: validateStorageId(record.generationId, "Snapshot UNIQUE generation ID"),
530
+ chunkCount: canonicalOrdinal(record.chunkCount, "Snapshot UNIQUE chunk count"),
531
+ tokenCount: canonicalOrdinal(record.tokenCount, "Snapshot UNIQUE token count")
582
532
  };
533
+ }
534
+ sameKeys(record, ["kind", "namespaceId", "generationId", "ordinal", "keyTokens"], "Snapshot UNIQUE chunk");
535
+ if (record.kind !== "unique-chunk" || !Array.isArray(record.keyTokens)) {
536
+ throw new TypeError("Snapshot UNIQUE chunk is invalid");
537
+ }
538
+ if (record.keyTokens.length < 1 || record.keyTokens.length > MAX_UNIQUE_KEY_BUILD_TOKENS_PER_CHUNK) {
539
+ throw new RangeError("Snapshot UNIQUE chunk token count is out of bounds");
540
+ }
541
+ const keyTokens = record.keyTokens.map((token) => {
542
+ if (typeof token !== "string")
543
+ throw new TypeError("Snapshot UNIQUE token must be a string");
544
+ assertWellFormedString(token, "Snapshot UNIQUE token");
545
+ return token;
546
+ });
547
+ if (uniqueKeyBuildChunkRetainedBytes(keyTokens) > MAX_UNIQUE_KEY_BUILD_CHUNK_BYTES) {
548
+ throw new RangeError("Snapshot UNIQUE chunk is too large");
549
+ }
550
+ if (keyTokens.some((token, index) => index > 0 && token <= (keyTokens[index - 1] ?? ""))) {
551
+ throw new TypeError("Snapshot UNIQUE tokens are not strict lexical order");
552
+ }
553
+ return {
554
+ kind: "unique-chunk",
555
+ namespaceId: validateStorageId(record.namespaceId, "Snapshot UNIQUE namespace ID"),
556
+ generationId: validateStorageId(record.generationId, "Snapshot UNIQUE generation ID"),
557
+ ordinal: canonicalOrdinal(record.ordinal, "Snapshot UNIQUE chunk ordinal"),
558
+ keyTokens
559
+ };
583
560
  }
584
561
  function preparePostingItem(value) {
585
- const record = recordValue(value, "Snapshot posting item");
586
- if (record.kind === "posting-generation") {
587
- sameKeys(record, [
588
- "kind",
589
- "tableId",
590
- "ownerKind",
591
- "ownerId",
592
- "storageColumnId",
593
- "generationId",
594
- "coversVersion",
595
- "chunkCount",
596
- "totalTokens",
597
- ], "Snapshot posting generation");
598
- if (record.ownerKind !== "fts-column" && record.ownerKind !== "secondary-index") {
599
- throw new TypeError("Snapshot posting owner kind is invalid");
600
- }
601
- const totalTokens = canonicalOrdinal(record.totalTokens, "Snapshot posting token total");
602
- const chunkCount = canonicalOrdinal(record.chunkCount, "Snapshot posting chunk count");
603
- if (chunkCount > MAX_FTS_BASE_CHUNKS) {
604
- throw new RangeError("Snapshot posting generation has too many chunks");
605
- }
606
- return {
607
- kind: "posting-generation",
608
- tableId: validateStorageId(record.tableId, "Snapshot posting table ID"),
609
- ownerKind: record.ownerKind,
610
- ownerId: validateStorageId(record.ownerId, "Snapshot posting owner ID"),
611
- storageColumnId: validateStorageId(record.storageColumnId, "Snapshot posting storage column ID"),
612
- generationId: validateStorageId(record.generationId, "Snapshot posting generation ID"),
613
- coversVersion: canonicalOrdinal(record.coversVersion, "Snapshot posting covered version"),
614
- chunkCount,
615
- totalTokens,
616
- };
617
- }
618
- sameKeys(record, ["kind", "storageColumnId", "generationId", "ordinal", "postings"], "Snapshot posting chunk");
619
- if (record.kind !== "posting-chunk" || !Array.isArray(record.postings)) {
620
- throw new TypeError("Snapshot posting chunk is invalid");
621
- }
622
- if (record.postings.length < 1 || record.postings.length > MAX_FTS_POSTINGS_PER_CHUNK) {
623
- throw new RangeError("Snapshot posting chunk count is out of bounds");
624
- }
625
- let rowIds = 0;
626
- let previousTerm = "";
627
- const postings = record.postings.map((raw, postingIndex) => {
628
- const posting = exactRecord(raw, ["term", "rowIds", "tf"], "Snapshot posting");
629
- if (typeof posting.term !== "string" ||
630
- posting.term.length > MAX_FTS_POSTING_TERM_CHARACTERS ||
631
- !Array.isArray(posting.rowIds) ||
632
- !Array.isArray(posting.tf) ||
633
- posting.rowIds.length === 0 ||
634
- posting.rowIds.length !== posting.tf.length ||
635
- (postingIndex > 0 && posting.term <= previousTerm)) {
636
- throw new TypeError("Snapshot posting is not canonical");
637
- }
638
- assertWellFormedString(posting.term, "Snapshot posting term");
639
- previousTerm = posting.term;
640
- rowIds += posting.rowIds.length;
641
- if (rowIds > MAX_FTS_POSTING_ROW_IDS_PER_CHUNK) {
642
- throw new RangeError("Snapshot posting chunk has too many row IDs");
643
- }
644
- let previousRowId = 0n;
645
- const normalizedRowIds = posting.rowIds.map((rowId, index) => {
646
- if (typeof rowId !== "bigint" ||
647
- rowId < 1n ||
648
- rowId >= MAX_ROW_ID_EXCLUSIVE_END ||
649
- (index > 0 && rowId <= previousRowId)) {
650
- throw new TypeError("Snapshot posting row IDs are not canonical");
651
- }
652
- previousRowId = rowId;
653
- return rowId;
654
- });
655
- const tf = posting.tf.map((count) => {
656
- if (!Number.isSafeInteger(count) ||
657
- count < 1 ||
658
- count > MAX_FTS_TOKENS_PER_DOCUMENT) {
659
- throw new TypeError("Snapshot posting term frequency is invalid");
660
- }
661
- return count;
662
- });
663
- return { term: posting.term, rowIds: normalizedRowIds, tf };
664
- });
562
+ const record = recordValue(value, "Snapshot posting item");
563
+ if (record.kind === "posting-generation") {
564
+ sameKeys(record, [
565
+ "kind",
566
+ "tableId",
567
+ "ownerKind",
568
+ "ownerId",
569
+ "storageColumnId",
570
+ "generationId",
571
+ "coversVersion",
572
+ "chunkCount",
573
+ "totalTokens"
574
+ ], "Snapshot posting generation");
575
+ if (record.ownerKind !== "fts-column" && record.ownerKind !== "secondary-index") {
576
+ throw new TypeError("Snapshot posting owner kind is invalid");
577
+ }
578
+ const totalTokens = canonicalOrdinal(record.totalTokens, "Snapshot posting token total");
579
+ const chunkCount = canonicalOrdinal(record.chunkCount, "Snapshot posting chunk count");
580
+ if (chunkCount > MAX_FTS_BASE_CHUNKS) {
581
+ throw new RangeError("Snapshot posting generation has too many chunks");
582
+ }
665
583
  return {
666
- kind: "posting-chunk",
667
- storageColumnId: validateStorageId(record.storageColumnId, "Snapshot posting storage column ID"),
668
- generationId: validateStorageId(record.generationId, "Snapshot posting generation ID"),
669
- ordinal: canonicalOrdinal(record.ordinal, "Snapshot posting chunk ordinal"),
670
- postings,
584
+ kind: "posting-generation",
585
+ tableId: validateStorageId(record.tableId, "Snapshot posting table ID"),
586
+ ownerKind: record.ownerKind,
587
+ ownerId: validateStorageId(record.ownerId, "Snapshot posting owner ID"),
588
+ storageColumnId: validateStorageId(record.storageColumnId, "Snapshot posting storage column ID"),
589
+ generationId: validateStorageId(record.generationId, "Snapshot posting generation ID"),
590
+ coversVersion: canonicalOrdinal(record.coversVersion, "Snapshot posting covered version"),
591
+ chunkCount,
592
+ totalTokens
671
593
  };
672
- }
673
- /** Decodes and strictly validates one canonical metadata frame before durable staging. */
674
- export function decodeSnapshotMetadataItems(kind, payload) {
675
- return decodeSnapshotMetadataPage(payload).map((item) => {
676
- if (kind === "catalog-page")
677
- return prepareCatalogItem(item);
678
- if (kind === "segment-page")
679
- return prepareSegmentItem(item);
680
- if (kind === "transaction-page")
681
- return prepareTransactionItem(item);
682
- if (kind === "unique-page")
683
- return prepareUniqueItem(item);
684
- return preparePostingItem(item);
594
+ }
595
+ sameKeys(record, ["kind", "storageColumnId", "generationId", "ordinal", "postings"], "Snapshot posting chunk");
596
+ if (record.kind !== "posting-chunk" || !Array.isArray(record.postings)) {
597
+ throw new TypeError("Snapshot posting chunk is invalid");
598
+ }
599
+ if (record.postings.length < 1 || record.postings.length > MAX_FTS_POSTINGS_PER_CHUNK) {
600
+ throw new RangeError("Snapshot posting chunk count is out of bounds");
601
+ }
602
+ let rowIds = 0;
603
+ let previousTerm = "";
604
+ const postings = record.postings.map((raw, postingIndex) => {
605
+ const posting = exactRecord(raw, ["term", "rowIds", "tf"], "Snapshot posting");
606
+ if (typeof posting.term !== "string" || posting.term.length > MAX_FTS_POSTING_TERM_CHARACTERS || !Array.isArray(posting.rowIds) || !Array.isArray(posting.tf) || posting.rowIds.length === 0 || posting.rowIds.length !== posting.tf.length || postingIndex > 0 && posting.term <= previousTerm) {
607
+ throw new TypeError("Snapshot posting is not canonical");
608
+ }
609
+ assertWellFormedString(posting.term, "Snapshot posting term");
610
+ previousTerm = posting.term;
611
+ rowIds += posting.rowIds.length;
612
+ if (rowIds > MAX_FTS_POSTING_ROW_IDS_PER_CHUNK) {
613
+ throw new RangeError("Snapshot posting chunk has too many row IDs");
614
+ }
615
+ let previousRowId = 0n;
616
+ const normalizedRowIds = posting.rowIds.map((rowId, index) => {
617
+ if (typeof rowId !== "bigint" || rowId < 1n || rowId >= MAX_ROW_ID_EXCLUSIVE_END || index > 0 && rowId <= previousRowId) {
618
+ throw new TypeError("Snapshot posting row IDs are not canonical");
619
+ }
620
+ previousRowId = rowId;
621
+ return rowId;
622
+ });
623
+ const tf = posting.tf.map((count) => {
624
+ if (!Number.isSafeInteger(count) || count < 1 || count > MAX_FTS_TOKENS_PER_DOCUMENT) {
625
+ throw new TypeError("Snapshot posting term frequency is invalid");
626
+ }
627
+ return count;
685
628
  });
629
+ return { term: posting.term, rowIds: normalizedRowIds, tf };
630
+ });
631
+ return {
632
+ kind: "posting-chunk",
633
+ storageColumnId: validateStorageId(record.storageColumnId, "Snapshot posting storage column ID"),
634
+ generationId: validateStorageId(record.generationId, "Snapshot posting generation ID"),
635
+ ordinal: canonicalOrdinal(record.ordinal, "Snapshot posting chunk ordinal"),
636
+ postings
637
+ };
638
+ }
639
+ function decodeSnapshotMetadataItems(kind, payload) {
640
+ return decodeSnapshotMetadataPage(payload).map((item) => {
641
+ if (kind === "catalog-page")
642
+ return prepareCatalogItem(item);
643
+ if (kind === "segment-page")
644
+ return prepareSegmentItem(item);
645
+ if (kind === "transaction-page")
646
+ return prepareTransactionItem(item);
647
+ if (kind === "unique-page")
648
+ return prepareUniqueItem(item);
649
+ return preparePostingItem(item);
650
+ });
686
651
  }
687
652
  class ChunkReader {
688
- #chunk = new Uint8Array();
689
- #offset = 0;
690
- #iterator;
691
- constructor(source) {
692
- this.#iterator = source[Symbol.asyncIterator]();
693
- }
694
- async #nextChunk() {
695
- const next = await this.#iterator.next();
696
- if (next.done)
697
- return false;
698
- if (!(next.value instanceof Uint8Array) || next.value.byteLength === 0) {
699
- throw new RangeError("Snapshot source chunks must be non-empty Uint8Array values");
700
- }
701
- this.#chunk = next.value;
702
- this.#offset = 0;
703
- return true;
704
- }
705
- async read(length) {
706
- const result = new Uint8Array(length);
707
- let written = 0;
708
- while (written < length) {
709
- if (this.#offset === this.#chunk.byteLength) {
710
- if (!(await this.#nextChunk()))
711
- throw new Error("Snapshot stream is truncated");
712
- }
713
- const count = Math.min(length - written, this.#chunk.byteLength - this.#offset);
714
- result.set(this.#chunk.subarray(this.#offset, this.#offset + count), written);
715
- this.#offset += count;
716
- written += count;
717
- }
718
- return result;
719
- }
720
- async done() {
721
- if (this.#offset < this.#chunk.byteLength)
722
- return false;
723
- return !(await this.#nextChunk());
724
- }
725
- async close() {
726
- await this.#iterator.return?.();
727
- }
653
+ #chunk = new Uint8Array();
654
+ #offset = 0;
655
+ #iterator;
656
+ constructor(source) {
657
+ this.#iterator = source[Symbol.asyncIterator]();
658
+ }
659
+ async #nextChunk() {
660
+ const next = await this.#iterator.next();
661
+ if (next.done)
662
+ return false;
663
+ if (!(next.value instanceof Uint8Array) || next.value.byteLength === 0) {
664
+ throw new RangeError("Snapshot source chunks must be non-empty Uint8Array values");
665
+ }
666
+ this.#chunk = next.value;
667
+ this.#offset = 0;
668
+ return true;
669
+ }
670
+ async read(length) {
671
+ const result = new Uint8Array(length);
672
+ let written = 0;
673
+ while (written < length) {
674
+ if (this.#offset === this.#chunk.byteLength) {
675
+ if (!await this.#nextChunk())
676
+ throw new Error("Snapshot stream is truncated");
677
+ }
678
+ const count = Math.min(length - written, this.#chunk.byteLength - this.#offset);
679
+ result.set(this.#chunk.subarray(this.#offset, this.#offset + count), written);
680
+ this.#offset += count;
681
+ written += count;
682
+ }
683
+ return result;
684
+ }
685
+ async done() {
686
+ if (this.#offset < this.#chunk.byteLength)
687
+ return false;
688
+ return !await this.#nextChunk();
689
+ }
690
+ async close() {
691
+ await this.#iterator.return?.();
692
+ }
728
693
  }
729
- /** Parses and verifies the bounded framed snapshot container without retaining prior frames. */
730
- export async function* decodeSnapshotFrameStream(source) {
731
- const reader = new ChunkReader(source);
732
- let failure;
694
+ async function* decodeSnapshotFrameStream(source) {
695
+ const reader = new ChunkReader(source);
696
+ let failure;
697
+ try {
698
+ const prefix = await reader.read(HEADER_PREFIX_BYTES);
699
+ if (!MAGIC.every((byte, index) => prefix[index] === byte))
700
+ throw new Error("Not a Minnow snapshot");
701
+ const prefixView = new DataView(prefix.buffer);
702
+ if (prefixView.getUint32(8, true) !== 1)
703
+ throw new Error("Unsupported snapshot format version");
704
+ const headerLength = prefixView.getUint32(12, true);
705
+ if (headerLength < 1 || headerLength > MAX_HEADER_BYTES)
706
+ throw new RangeError("Snapshot header is too large");
707
+ const headerBytes = await reader.read(headerLength);
708
+ if (crc32(headerBytes) !== prefixView.getUint32(16, true))
709
+ throw new Error("Snapshot header checksum mismatch");
710
+ let parsed;
733
711
  try {
734
- const prefix = await reader.read(HEADER_PREFIX_BYTES);
735
- if (!MAGIC.every((byte, index) => prefix[index] === byte))
736
- throw new Error("Not a Minnow snapshot");
737
- const prefixView = new DataView(prefix.buffer);
738
- if (prefixView.getUint32(8, true) !== 1)
739
- throw new Error("Unsupported snapshot format version");
740
- const headerLength = prefixView.getUint32(12, true);
741
- if (headerLength < 1 || headerLength > MAX_HEADER_BYTES)
742
- throw new RangeError("Snapshot header is too large");
743
- const headerBytes = await reader.read(headerLength);
744
- if (crc32(headerBytes) !== prefixView.getUint32(16, true))
745
- throw new Error("Snapshot header checksum mismatch");
746
- let parsed;
747
- try {
748
- parsed = JSON.parse(textDecoder.decode(headerBytes));
749
- }
750
- catch {
751
- throw new TypeError("Snapshot header is not canonical JSON");
752
- }
753
- const header = prepareSnapshotFrameStreamHeader(parsed);
754
- if (canonicalHeaderJson(header) !== textDecoder.decode(headerBytes)) {
755
- throw new TypeError("Snapshot header is not canonically encoded");
756
- }
757
- yield { type: "header", header };
758
- const expectedFrames = SNAPSHOT_FRAME_KINDS.reduce((total, kind) => {
759
- const next = total + header.kinds[kind].frameCount;
760
- if (!Number.isSafeInteger(next))
761
- throw new RangeError("Snapshot frame count is too large");
762
- return next;
763
- }, 0);
764
- const observed = Object.fromEntries(SNAPSHOT_FRAME_KINDS.map((kind) => [kind, { frameCount: 0, itemCount: 0, storedBytes: 0 }]));
765
- let sequence = 0;
766
- let itemCount = 0;
767
- let storedBytes = 0;
768
- let checksum = 0;
769
- let previousKind = 0;
770
- while (sequence < expectedFrames) {
771
- const framePrefix = await reader.read(FRAME_PREFIX_BYTES);
772
- const view = new DataView(framePrefix.buffer);
773
- if (view.getUint32(0, true) !== FRAME_MARKER)
774
- throw new Error("Snapshot frame marker mismatch");
775
- const kindIndex = view.getUint32(4, true);
776
- const kind = SNAPSHOT_FRAME_KINDS[kindIndex];
777
- if (kind === undefined || kindIndex < previousKind)
778
- throw new Error("Snapshot frame order is invalid");
779
- previousKind = kindIndex;
780
- const actualSequence = Number(view.getBigUint64(8, true));
781
- if (!Number.isSafeInteger(actualSequence) || actualSequence !== sequence)
782
- throw new Error("Snapshot frame sequence is invalid");
783
- const frameItems = view.getUint32(16, true);
784
- if (frameItems < 1 || frameItems > MAX_SNAPSHOT_FRAME_ITEMS) {
785
- throw new RangeError("Snapshot frame item count is out of bounds");
786
- }
787
- const keyLength = view.getUint32(20, true);
788
- if (keyLength > MAX_STORAGE_ID_CHARACTERS * 3 ||
789
- (kind === "block" ? keyLength === 0 || frameItems !== 1 : keyLength !== 0)) {
790
- throw new RangeError("Snapshot frame key is too large");
791
- }
792
- const payloadLength = Number(view.getBigUint64(24, true));
793
- if (!Number.isSafeInteger(payloadLength))
794
- throw new RangeError("Snapshot frame is too large");
795
- const payloadLimit = kind === "block" ? MAX_STORED_BLOCK_BYTE_LENGTH : MAX_SNAPSHOT_METADATA_FRAME_BYTES;
796
- if (payloadLength < 1 || payloadLength > payloadLimit) {
797
- throw new RangeError("Snapshot frame payload length is out of bounds");
798
- }
799
- const expectedChecksum = view.getUint32(32, true);
800
- if (view.getUint32(36, true) !== 0)
801
- throw new TypeError("Snapshot frame reserved bits are set");
802
- const keyBytes = await reader.read(keyLength);
803
- const key = keyLength === 0 ? null : validateStorageId(stringFromUtf8(keyBytes));
804
- const payload = await reader.read(payloadLength);
805
- const frame = {
806
- sequence,
807
- kind,
808
- itemCount: frameItems,
809
- key,
810
- payload,
811
- checksum: expectedChecksum,
812
- };
813
- snapshotFrameEnvelopeParts(frame);
814
- if (crc32(payload) !== expectedChecksum)
815
- throw new Error("Snapshot frame checksum mismatch");
816
- if (kind !== "block") {
817
- if (payload.byteLength < 5 || payload[0] !== 6) {
818
- throw new TypeError("Snapshot metadata frame must contain one canonical page array");
819
- }
820
- const encodedItems = new DataView(payload.buffer, payload.byteOffset + 1, 4).getUint32(0, true);
821
- if (encodedItems !== frameItems) {
822
- throw new Error("Snapshot metadata frame item count does not match its payload");
823
- }
824
- if (decodeSnapshotMetadataItems(kind, payload).length !== frameItems) {
825
- throw new Error("Snapshot metadata frame item count is invalid");
826
- }
827
- }
828
- checksum = extendSnapshotFrameStreamChecksum(checksum, [framePrefix, keyBytes, payload]);
829
- itemCount = safeWhole(itemCount + frameItems, "Snapshot observed item count");
830
- storedBytes = safeWhole(storedBytes + payloadLength, "Snapshot observed stored bytes");
831
- const kindObserved = observed[kind];
832
- kindObserved.frameCount = safeWhole(kindObserved.frameCount + 1, `Snapshot ${kind} observed frame count`);
833
- kindObserved.itemCount = safeWhole(kindObserved.itemCount + frameItems, `Snapshot ${kind} observed item count`);
834
- kindObserved.storedBytes = safeWhole(kindObserved.storedBytes + payloadLength, `Snapshot ${kind} observed stored bytes`);
835
- const kindExpected = header.kinds[kind];
836
- if (kindObserved.frameCount > kindExpected.frameCount ||
837
- kindObserved.itemCount > kindExpected.itemCount ||
838
- kindObserved.storedBytes > kindExpected.storedBytes) {
839
- throw new Error(`Snapshot ${kind} frames exceed the declared summary`);
840
- }
841
- yield { type: "frame", frame };
842
- sequence = safeWhole(sequence + 1, "Snapshot frame sequence");
843
- }
844
- const footerBytes = await reader.read(FOOTER_BYTES);
845
- const footerView = new DataView(footerBytes.buffer);
846
- if (footerView.getUint32(0, true) !== FOOTER_MARKER)
847
- throw new Error("Snapshot footer marker mismatch");
848
- const footerFrameCount = Number(footerView.getBigUint64(4, true));
849
- const footerItemCount = Number(footerView.getBigUint64(12, true));
850
- const footerStoredBytes = Number(footerView.getBigUint64(20, true));
851
- if (!Number.isSafeInteger(footerFrameCount) ||
852
- !Number.isSafeInteger(footerItemCount) ||
853
- !Number.isSafeInteger(footerStoredBytes)) {
854
- throw new RangeError("Snapshot footer totals are too large");
855
- }
856
- const footer = {
857
- frameCount: footerFrameCount,
858
- itemCount: footerItemCount,
859
- storedBytes: footerStoredBytes,
860
- checksum: footerView.getUint32(28, true),
861
- };
862
- if (footerBytes.subarray(32).some((byte) => byte !== 0))
863
- throw new TypeError("Snapshot footer reserved bits are set");
864
- if (footer.frameCount !== sequence ||
865
- footer.itemCount !== itemCount ||
866
- footer.storedBytes !== storedBytes ||
867
- footer.checksum !== checksum) {
868
- throw new Error("Snapshot footer does not match its body");
869
- }
870
- for (const kind of SNAPSHOT_FRAME_KINDS) {
871
- const actual = observed[kind];
872
- const expected = header.kinds[kind];
873
- if (actual.frameCount !== expected.frameCount ||
874
- actual.itemCount !== expected.itemCount ||
875
- actual.storedBytes !== expected.storedBytes) {
876
- throw new Error(`Snapshot ${kind} summary does not match its frames`);
877
- }
878
- }
879
- if (!(await reader.done()))
880
- throw new Error("Snapshot has trailing bytes");
881
- yield { type: "footer", footer };
882
- }
883
- catch (error) {
884
- failure = error;
885
- throw error;
712
+ parsed = JSON.parse(textDecoder.decode(headerBytes));
713
+ } catch {
714
+ throw new TypeError("Snapshot header is not canonical JSON");
715
+ }
716
+ const header = prepareSnapshotFrameStreamHeader(parsed);
717
+ if (canonicalHeaderJson(header) !== textDecoder.decode(headerBytes)) {
718
+ throw new TypeError("Snapshot header is not canonically encoded");
719
+ }
720
+ yield { type: "header", header };
721
+ const expectedFrames = SNAPSHOT_FRAME_KINDS.reduce((total, kind) => {
722
+ const next = total + header.kinds[kind].frameCount;
723
+ if (!Number.isSafeInteger(next))
724
+ throw new RangeError("Snapshot frame count is too large");
725
+ return next;
726
+ }, 0);
727
+ const observed = Object.fromEntries(SNAPSHOT_FRAME_KINDS.map((kind) => [kind, { frameCount: 0, itemCount: 0, storedBytes: 0 }]));
728
+ let sequence = 0;
729
+ let itemCount = 0;
730
+ let storedBytes = 0;
731
+ let checksum = 0;
732
+ let previousKind = 0;
733
+ while (sequence < expectedFrames) {
734
+ const framePrefix = await reader.read(FRAME_PREFIX_BYTES);
735
+ const view = new DataView(framePrefix.buffer);
736
+ if (view.getUint32(0, true) !== FRAME_MARKER)
737
+ throw new Error("Snapshot frame marker mismatch");
738
+ const kindIndex = view.getUint32(4, true);
739
+ const kind = SNAPSHOT_FRAME_KINDS[kindIndex];
740
+ if (kind === void 0 || kindIndex < previousKind)
741
+ throw new Error("Snapshot frame order is invalid");
742
+ previousKind = kindIndex;
743
+ const actualSequence = Number(view.getBigUint64(8, true));
744
+ if (!Number.isSafeInteger(actualSequence) || actualSequence !== sequence)
745
+ throw new Error("Snapshot frame sequence is invalid");
746
+ const frameItems = view.getUint32(16, true);
747
+ if (frameItems < 1 || frameItems > MAX_SNAPSHOT_FRAME_ITEMS) {
748
+ throw new RangeError("Snapshot frame item count is out of bounds");
749
+ }
750
+ const keyLength = view.getUint32(20, true);
751
+ if (keyLength > MAX_STORAGE_ID_CHARACTERS * 3 || (kind === "block" ? keyLength === 0 || frameItems !== 1 : keyLength !== 0)) {
752
+ throw new RangeError("Snapshot frame key is too large");
753
+ }
754
+ const payloadLength = Number(view.getBigUint64(24, true));
755
+ if (!Number.isSafeInteger(payloadLength))
756
+ throw new RangeError("Snapshot frame is too large");
757
+ const payloadLimit = kind === "block" ? MAX_STORED_BLOCK_BYTE_LENGTH : MAX_SNAPSHOT_METADATA_FRAME_BYTES;
758
+ if (payloadLength < 1 || payloadLength > payloadLimit) {
759
+ throw new RangeError("Snapshot frame payload length is out of bounds");
760
+ }
761
+ const expectedChecksum = view.getUint32(32, true);
762
+ if (view.getUint32(36, true) !== 0)
763
+ throw new TypeError("Snapshot frame reserved bits are set");
764
+ const keyBytes = await reader.read(keyLength);
765
+ const key = keyLength === 0 ? null : validateStorageId(stringFromUtf8(keyBytes));
766
+ const payload = await reader.read(payloadLength);
767
+ const frame = {
768
+ sequence,
769
+ kind,
770
+ itemCount: frameItems,
771
+ key,
772
+ payload,
773
+ checksum: expectedChecksum
774
+ };
775
+ snapshotFrameEnvelopeParts(frame);
776
+ if (crc32(payload) !== expectedChecksum)
777
+ throw new Error("Snapshot frame checksum mismatch");
778
+ if (kind !== "block") {
779
+ if (payload.byteLength < 5 || payload[0] !== 6) {
780
+ throw new TypeError("Snapshot metadata frame must contain one canonical page array");
781
+ }
782
+ const encodedItems = new DataView(payload.buffer, payload.byteOffset + 1, 4).getUint32(0, true);
783
+ if (encodedItems !== frameItems) {
784
+ throw new Error("Snapshot metadata frame item count does not match its payload");
785
+ }
786
+ if (decodeSnapshotMetadataItems(kind, payload).length !== frameItems) {
787
+ throw new Error("Snapshot metadata frame item count is invalid");
788
+ }
789
+ }
790
+ checksum = extendSnapshotFrameStreamChecksum(checksum, [framePrefix, keyBytes, payload]);
791
+ itemCount = safeWhole(itemCount + frameItems, "Snapshot observed item count");
792
+ storedBytes = safeWhole(storedBytes + payloadLength, "Snapshot observed stored bytes");
793
+ const kindObserved = observed[kind];
794
+ kindObserved.frameCount = safeWhole(kindObserved.frameCount + 1, `Snapshot ${kind} observed frame count`);
795
+ kindObserved.itemCount = safeWhole(kindObserved.itemCount + frameItems, `Snapshot ${kind} observed item count`);
796
+ kindObserved.storedBytes = safeWhole(kindObserved.storedBytes + payloadLength, `Snapshot ${kind} observed stored bytes`);
797
+ const kindExpected = header.kinds[kind];
798
+ if (kindObserved.frameCount > kindExpected.frameCount || kindObserved.itemCount > kindExpected.itemCount || kindObserved.storedBytes > kindExpected.storedBytes) {
799
+ throw new Error(`Snapshot ${kind} frames exceed the declared summary`);
800
+ }
801
+ yield { type: "frame", frame };
802
+ sequence = safeWhole(sequence + 1, "Snapshot frame sequence");
803
+ }
804
+ const footerBytes = await reader.read(FOOTER_BYTES);
805
+ const footerView = new DataView(footerBytes.buffer);
806
+ if (footerView.getUint32(0, true) !== FOOTER_MARKER)
807
+ throw new Error("Snapshot footer marker mismatch");
808
+ const footerFrameCount = Number(footerView.getBigUint64(4, true));
809
+ const footerItemCount = Number(footerView.getBigUint64(12, true));
810
+ const footerStoredBytes = Number(footerView.getBigUint64(20, true));
811
+ if (!Number.isSafeInteger(footerFrameCount) || !Number.isSafeInteger(footerItemCount) || !Number.isSafeInteger(footerStoredBytes)) {
812
+ throw new RangeError("Snapshot footer totals are too large");
813
+ }
814
+ const footer = {
815
+ frameCount: footerFrameCount,
816
+ itemCount: footerItemCount,
817
+ storedBytes: footerStoredBytes,
818
+ checksum: footerView.getUint32(28, true)
819
+ };
820
+ if (footerBytes.subarray(32).some((byte) => byte !== 0))
821
+ throw new TypeError("Snapshot footer reserved bits are set");
822
+ if (footer.frameCount !== sequence || footer.itemCount !== itemCount || footer.storedBytes !== storedBytes || footer.checksum !== checksum) {
823
+ throw new Error("Snapshot footer does not match its body");
886
824
  }
887
- finally {
888
- try {
889
- await reader.close();
890
- }
891
- catch (closeError) {
892
- if (failure === undefined) {
893
- await Promise.reject(closeError instanceof Error ? closeError : new Error(String(closeError)));
894
- }
895
- }
825
+ for (const kind of SNAPSHOT_FRAME_KINDS) {
826
+ const actual = observed[kind];
827
+ const expected = header.kinds[kind];
828
+ if (actual.frameCount !== expected.frameCount || actual.itemCount !== expected.itemCount || actual.storedBytes !== expected.storedBytes) {
829
+ throw new Error(`Snapshot ${kind} summary does not match its frames`);
830
+ }
831
+ }
832
+ if (!await reader.done())
833
+ throw new Error("Snapshot has trailing bytes");
834
+ yield { type: "footer", footer };
835
+ } catch (error) {
836
+ failure = error;
837
+ throw error;
838
+ } finally {
839
+ try {
840
+ await reader.close();
841
+ } catch (closeError) {
842
+ if (failure === void 0) {
843
+ await Promise.reject(closeError instanceof Error ? closeError : new Error(String(closeError)));
844
+ }
896
845
  }
846
+ }
897
847
  }
848
+ export {
849
+ decodeSnapshotFrameStream,
850
+ decodeSnapshotMetadataItems,
851
+ decodeSnapshotMetadataPage,
852
+ encodeSnapshotFrameStreamFooter,
853
+ encodeSnapshotFrameStreamHeader,
854
+ encodeSnapshotMetadataPage,
855
+ extendSnapshotFrameStreamChecksum,
856
+ prepareSnapshotFrameStreamHeader,
857
+ snapshotFrameEnvelopeParts,
858
+ snapshotFrameStreamHeaderIdentity
859
+ };