@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,368 +1,297 @@
1
1
  import { crc32 } from "./checksum.js";
2
2
  import { decodeValidatedColumn, encodeColumn } from "./column.js";
3
3
  import { CompressionOutputLimitError, getCodec, getCompressionMemoryBound } from "./codecs.js";
4
- import { assertBlockRowCount, MAX_PHYSICAL_COLUMN_BYTE_LENGTH, validatePhysicalColumn, } from "./physical.js";
5
- const MAGIC = Uint8Array.of(0x42, 0x52, 0x44, 0x42);
6
- /**
7
- * Version 2 layout (all integers little-endian):
8
- *
9
- * 0 magic "BRDB"
10
- * 4 envelope checksum: crc32 over bytes [8, headerLength + metadataLength)
11
- * 8 format version (u16)
12
- * 10 header length (u16)
13
- * 12 logical type id (u8), 13 physical encoding id (u8), 14 codec id (u8), 15 flags (u8)
14
- * 16 row count (u32)
15
- * 20 null count (u32)
16
- * 24 metadata length (u32)
17
- * 28 encoded length (u32)
18
- * 32 stored length (u32)
19
- * 36 logical checksum: crc32 over the uncompressed encoded payload
20
- * 40 stored checksum: crc32 over the stored payload, before decompression
21
- *
22
- * The envelope checksum integrity-checks every header field and the metadata JSON, so header-only
23
- * reads (zone-map pruning, block inventories) can trust derived statistics without paying the
24
- * decompress-and-revalidate cost of the payload.
25
- */
26
- /**
27
- * The first frozen block envelope version. Every persisted block carries it, and readers dispatch
28
- * by this value, so the number is a compatibility contract rather than an internal detail.
29
- *
30
- * Never change v2 decoding in place. A future writer gets a new number and keeps the v2 read path;
31
- * format-vectors.test.ts permanently freezes Minnow-owned v2 bytes.
32
- */
33
- export const BLOCK_FORMAT_VERSION = 2;
4
+ import { assertBlockRowCount, MAX_PHYSICAL_COLUMN_BYTE_LENGTH, validatePhysicalColumn } from "./physical.js";
5
+ const MAGIC = Uint8Array.of(66, 82, 68, 66);
6
+ const BLOCK_FORMAT_VERSION = 2;
34
7
  const VERSION = BLOCK_FORMAT_VERSION;
35
- export const BLOCK_HEADER_LENGTH = 44;
8
+ const BLOCK_HEADER_LENGTH = 44;
36
9
  const HEADER_LENGTH = BLOCK_HEADER_LENGTH;
37
10
  const ENVELOPE_CHECKSUM_START = 8;
38
11
  const MAX_BLOCK_LENGTH = MAX_PHYSICAL_COLUMN_BYTE_LENGTH;
39
- export const MAX_BLOCK_METADATA_BYTE_LENGTH = 1_024;
12
+ const MAX_BLOCK_METADATA_BYTE_LENGTH = 1024;
40
13
  const MAX_METADATA_LENGTH = MAX_BLOCK_METADATA_BYTE_LENGTH;
41
- /** Absolute persisted value ceiling: envelope + metadata + maximum stored payload. */
42
- export const MAX_STORED_BLOCK_BYTE_LENGTH = BLOCK_HEADER_LENGTH + MAX_METADATA_LENGTH + MAX_PHYSICAL_COLUMN_BYTE_LENGTH;
14
+ const MAX_STORED_BLOCK_BYTE_LENGTH = BLOCK_HEADER_LENGTH + MAX_METADATA_LENGTH + MAX_PHYSICAL_COLUMN_BYTE_LENGTH;
43
15
  const textEncoder = new TextEncoder();
44
16
  const textDecoder = new TextDecoder("utf-8", { fatal: true });
45
17
  const typeIds = { boolean: 1, number: 2, string: 3, datetime: 4 };
46
18
  const typesById = new Map(Object.entries(typeIds).map(([type, id]) => [id, type]));
47
- // Codec 1 was a pre-freeze run-length prototype, removed because it was both slower and larger
48
- // than raw on every measured column shape. Keep the id permanently reserved so it can never be
49
- // confused with a different codec in diagnostic or development data.
50
19
  const codecIds = { raw: 0, gzip: 2 };
51
20
  const codecsById = new Map(Object.entries(codecIds).map(([codec, id]) => [id, codec]));
52
- /** A codec expanded an otherwise valid physical column beyond the stored block hard limit. */
53
- export class StoredBlockPayloadTooLargeError extends RangeError {
54
- byteLength;
55
- constructor(byteLength) {
56
- super("Stored block payload exceeds maximum byte length");
57
- this.byteLength = byteLength;
58
- this.name = "StoredBlockPayloadTooLargeError";
59
- }
21
+ class StoredBlockPayloadTooLargeError extends RangeError {
22
+ byteLength;
23
+ constructor(byteLength) {
24
+ super("Stored block payload exceeds maximum byte length");
25
+ this.byteLength = byteLength;
26
+ this.name = "StoredBlockPayloadTooLargeError";
27
+ }
60
28
  }
61
- /** Validates a codec result length without allocating the payload. */
62
- export function assertStoredBlockPayloadByteLength(byteLength) {
63
- if (!Number.isSafeInteger(byteLength) || byteLength < 0) {
64
- throw new RangeError("Invalid stored payload length");
65
- }
66
- if (byteLength > MAX_BLOCK_LENGTH)
67
- throw new StoredBlockPayloadTooLargeError(byteLength);
29
+ function assertStoredBlockPayloadByteLength(byteLength) {
30
+ if (!Number.isSafeInteger(byteLength) || byteLength < 0) {
31
+ throw new RangeError("Invalid stored payload length");
32
+ }
33
+ if (byteLength > MAX_BLOCK_LENGTH)
34
+ throw new StoredBlockPayloadTooLargeError(byteLength);
68
35
  }
69
- /**
70
- * Returns a conservative bound for the complete persisted block value: the fixed header, the
71
- * exact UTF-8 JSON metadata, and the selected codec's maximum stored payload.
72
- */
73
- export function maximumPhysicalBlockByteLength(encodedByteLength, metadata, compression) {
74
- assertLength(encodedByteLength, "encoded payload");
75
- const metadataByteLength = encodeCanonicalMetadata(metadata).byteLength;
76
- assertMetadataLength(metadataByteLength);
77
- const maximumStoredPayloadBytes = getCompressionMemoryBound(compression, encodedByteLength).maximumOutputBytes;
78
- const total = HEADER_LENGTH + metadataByteLength + maximumStoredPayloadBytes;
79
- if (!Number.isSafeInteger(total)) {
80
- throw new RangeError("Stored block byte bound exceeds the safe integer range");
81
- }
82
- return total;
36
+ function maximumPhysicalBlockByteLength(encodedByteLength, metadata, compression) {
37
+ assertLength(encodedByteLength, "encoded payload");
38
+ const metadataByteLength = encodeCanonicalMetadata(metadata).byteLength;
39
+ assertMetadataLength(metadataByteLength);
40
+ const maximumStoredPayloadBytes = getCompressionMemoryBound(compression, encodedByteLength).maximumOutputBytes;
41
+ const total = HEADER_LENGTH + metadataByteLength + maximumStoredPayloadBytes;
42
+ if (!Number.isSafeInteger(total)) {
43
+ throw new RangeError("Stored block byte bound exceeds the safe integer range");
44
+ }
45
+ return total;
83
46
  }
84
- export async function encodeBlock(input, compression = "raw") {
85
- assertBlockRowCount(input.values.length);
86
- const encoded = encodeColumn(input);
87
- return encodeValidatedPhysicalBlock({
88
- type: input.type,
89
- rowCount: input.values.length,
90
- nullCount: encoded.nullCount,
91
- bytes: encoded.bytes,
92
- metadata: encoded.metadata,
93
- }, compression);
47
+ async function encodeBlock(input, compression = "raw") {
48
+ assertBlockRowCount(input.values.length);
49
+ const encoded = encodeColumn(input);
50
+ return encodeValidatedPhysicalBlock({
51
+ type: input.type,
52
+ rowCount: input.values.length,
53
+ nullCount: encoded.nullCount,
54
+ bytes: encoded.bytes,
55
+ metadata: encoded.metadata
56
+ }, compression);
94
57
  }
95
- /** Encodes physical column bytes after validating their complete canonical v2 layout. */
96
- export async function encodePhysicalBlock(input, compression = "raw") {
97
- return encodeValidatedPhysicalBlock(validatePhysicalColumn(input), compression);
58
+ async function encodePhysicalBlock(input, compression = "raw") {
59
+ return encodeValidatedPhysicalBlock(validatePhysicalColumn(input), compression);
98
60
  }
99
61
  async function encodeValidatedPhysicalBlock(input, compression) {
100
- // validatePhysicalColumn constructs this exact closed metadata shape. Avoid serializing,
101
- // parsing, and serializing it again on every write; the public sizing helper validates its
102
- // independently supplied metadata because it has no such provenance.
103
- const metadata = textEncoder.encode(JSON.stringify(input.metadata));
104
- // Checksum before the first possible async yield. The gzip codec also takes its input copy
105
- // synchronously when called, so caller mutation after encodePhysicalBlock returns cannot make
106
- // the checksum and compressed bytes observe different physical payloads.
107
- const logicalChecksum = crc32(input.bytes);
108
- // Avoid an async yield on raw blocks: validation and the copy into the immutable envelope
109
- // complete before a caller can mutate a physical input buffer. The gzip codec takes its own
110
- // input copy before yielding to the native stream.
111
- let stored;
112
- try {
113
- stored =
114
- compression === "raw"
115
- ? input.bytes
116
- : await getCodec(compression).compress(input.bytes, MAX_BLOCK_LENGTH);
117
- }
118
- catch (error) {
119
- if (error instanceof CompressionOutputLimitError) {
120
- throw new StoredBlockPayloadTooLargeError(error.byteLength);
121
- }
122
- throw error;
62
+ const metadata = textEncoder.encode(JSON.stringify(input.metadata));
63
+ const logicalChecksum = crc32(input.bytes);
64
+ let stored;
65
+ try {
66
+ stored = compression === "raw" ? input.bytes : await getCodec(compression).compress(input.bytes, MAX_BLOCK_LENGTH);
67
+ } catch (error) {
68
+ if (error instanceof CompressionOutputLimitError) {
69
+ throw new StoredBlockPayloadTooLargeError(error.byteLength);
123
70
  }
124
- assertMetadataLength(metadata.byteLength);
125
- assertLength(input.bytes.byteLength, "encoded payload");
126
- assertStoredBlockPayloadByteLength(stored.byteLength);
127
- const storedChecksum = compression === "raw" ? logicalChecksum : crc32(stored);
128
- const output = new Uint8Array(HEADER_LENGTH + metadata.byteLength + stored.byteLength);
129
- output.set(MAGIC);
130
- const view = new DataView(output.buffer);
131
- view.setUint16(8, VERSION, true);
132
- view.setUint16(10, HEADER_LENGTH, true);
133
- view.setUint8(12, typeIds[input.type]);
134
- view.setUint8(13, typeIds[input.type]); // the physical encoding mirrors the logical type
135
- view.setUint8(14, codecIds[compression]);
136
- view.setUint8(15, 0);
137
- view.setUint32(16, input.rowCount, true);
138
- view.setUint32(20, input.nullCount, true);
139
- view.setUint32(24, metadata.byteLength, true);
140
- view.setUint32(28, input.bytes.byteLength, true);
141
- view.setUint32(32, stored.byteLength, true);
142
- view.setUint32(36, logicalChecksum, true);
143
- view.setUint32(40, storedChecksum, true);
144
- output.set(metadata, HEADER_LENGTH);
145
- output.set(stored, HEADER_LENGTH + metadata.byteLength);
146
- view.setUint32(4, crc32(output.subarray(ENVELOPE_CHECKSUM_START, HEADER_LENGTH + metadata.byteLength)), true);
147
- return output;
71
+ throw error;
72
+ }
73
+ assertMetadataLength(metadata.byteLength);
74
+ assertLength(input.bytes.byteLength, "encoded payload");
75
+ assertStoredBlockPayloadByteLength(stored.byteLength);
76
+ const storedChecksum = compression === "raw" ? logicalChecksum : crc32(stored);
77
+ const output = new Uint8Array(HEADER_LENGTH + metadata.byteLength + stored.byteLength);
78
+ output.set(MAGIC);
79
+ const view = new DataView(output.buffer);
80
+ view.setUint16(8, VERSION, true);
81
+ view.setUint16(10, HEADER_LENGTH, true);
82
+ view.setUint8(12, typeIds[input.type]);
83
+ view.setUint8(13, typeIds[input.type]);
84
+ view.setUint8(14, codecIds[compression]);
85
+ view.setUint8(15, 0);
86
+ view.setUint32(16, input.rowCount, true);
87
+ view.setUint32(20, input.nullCount, true);
88
+ view.setUint32(24, metadata.byteLength, true);
89
+ view.setUint32(28, input.bytes.byteLength, true);
90
+ view.setUint32(32, stored.byteLength, true);
91
+ view.setUint32(36, logicalChecksum, true);
92
+ view.setUint32(40, storedChecksum, true);
93
+ output.set(metadata, HEADER_LENGTH);
94
+ output.set(stored, HEADER_LENGTH + metadata.byteLength);
95
+ view.setUint32(4, crc32(output.subarray(ENVELOPE_CHECKSUM_START, HEADER_LENGTH + metadata.byteLength)), true);
96
+ return output;
148
97
  }
149
- export async function decodeBlock(bytes) {
150
- // Materialize raw values before the first async yield. Otherwise a caller could mutate the
151
- // input after its checksum passes but before decodeColumn reads it. The gzip codec takes an
152
- // input copy synchronously, so its awaited path has no equivalent gap.
153
- const description = verifyStoredBlock(bytes);
154
- const decoded = description.compression === "raw"
155
- ? validateDecodedPhysicalBlock(bytes.subarray(description.headerLength + description.metadataLength), description)
156
- : await decodeVerifiedPhysicalBlock(bytes, description);
157
- return {
158
- description: decoded.description,
159
- column: decodeValidatedColumn(decoded.column),
160
- };
98
+ async function decodeBlock(bytes) {
99
+ const description = verifyStoredBlock(bytes);
100
+ const decoded = description.compression === "raw" ? validateDecodedPhysicalBlock(bytes.subarray(description.headerLength + description.metadataLength), description) : await decodeVerifiedPhysicalBlock(bytes, description);
101
+ return {
102
+ description: decoded.description,
103
+ column: decodeValidatedColumn(decoded.column)
104
+ };
161
105
  }
162
- /**
163
- * Decompresses and checksum-verifies a block without materializing row values.
164
- *
165
- * For a raw block, `column.bytes` is a zero-copy view into `bytes`. Both views must be treated as
166
- * immutable for the lifetime of the result. Gzip results own a new decompressed allocation.
167
- */
168
- export async function decodePhysicalBlock(bytes) {
169
- const description = verifyStoredBlock(bytes);
170
- if (description.compression === "raw") {
171
- return validateDecodedPhysicalBlock(bytes.subarray(description.headerLength + description.metadataLength), description);
172
- }
173
- return decodeVerifiedPhysicalBlock(bytes, description);
106
+ async function decodePhysicalBlock(bytes) {
107
+ const description = verifyStoredBlock(bytes);
108
+ if (description.compression === "raw") {
109
+ return validateDecodedPhysicalBlock(bytes.subarray(description.headerLength + description.metadataLength), description);
110
+ }
111
+ return decodeVerifiedPhysicalBlock(bytes, description);
174
112
  }
175
113
  async function decodeVerifiedPhysicalBlock(bytes, description) {
176
- const stored = bytes.subarray(description.headerLength + description.metadataLength);
177
- const encoded = await getCodec(description.compression).decompress(stored, description.encodedLength);
178
- // For raw blocks the stored and logical bytes are identical, and inspectBlock requires both
179
- // declared checksums to agree. verifyStoredBlock therefore performed the logical check too,
180
- // avoiding a second full scan on the default codec.
181
- if (description.compression !== "raw" && crc32(encoded) !== description.checksum) {
182
- throw new Error("Block logical checksum mismatch");
183
- }
184
- return validateDecodedPhysicalBlock(encoded, description);
114
+ const stored = bytes.subarray(description.headerLength + description.metadataLength);
115
+ const encoded = await getCodec(description.compression).decompress(stored, description.encodedLength);
116
+ if (description.compression !== "raw" && crc32(encoded) !== description.checksum) {
117
+ throw new Error("Block logical checksum mismatch");
118
+ }
119
+ return validateDecodedPhysicalBlock(encoded, description);
185
120
  }
186
121
  function validateDecodedPhysicalBlock(encoded, description) {
187
- const column = validatePhysicalColumn({
188
- type: description.type,
189
- rowCount: description.rowCount,
190
- bytes: encoded,
191
- });
192
- if (column.nullCount !== description.nullCount)
193
- throw new Error("Block null count mismatch");
194
- if (!metadataEquals(column.metadata, description.metadata)) {
195
- throw new Error("Block metadata does not match physical payload");
196
- }
197
- return {
198
- description,
199
- column,
200
- };
122
+ const column = validatePhysicalColumn({
123
+ type: description.type,
124
+ rowCount: description.rowCount,
125
+ bytes: encoded
126
+ });
127
+ if (column.nullCount !== description.nullCount)
128
+ throw new Error("Block null count mismatch");
129
+ if (!metadataEquals(column.metadata, description.metadata)) {
130
+ throw new Error("Block metadata does not match physical payload");
131
+ }
132
+ return {
133
+ description,
134
+ column
135
+ };
201
136
  }
202
- /**
203
- * Verifies every stored byte without decompressing or copying the payload.
204
- *
205
- * Snapshot import and OPFS recovery use this stronger operation before accepting a block.
206
- * Planning and pruning should continue to use `inspectBlock`, which deliberately reads only the
207
- * fixed header and metadata envelope.
208
- */
209
- export function verifyStoredBlock(bytes) {
210
- const description = inspectBlock(bytes);
211
- const storedStart = bytes.byteLength - description.storedLength;
212
- if (crc32(bytes.subarray(storedStart)) !== description.storedChecksum) {
213
- throw new Error("Block stored payload checksum mismatch");
214
- }
215
- return description;
137
+ function verifyStoredBlock(bytes) {
138
+ const description = inspectBlock(bytes);
139
+ const storedStart = bytes.byteLength - description.storedLength;
140
+ if (crc32(bytes.subarray(storedStart)) !== description.storedChecksum) {
141
+ throw new Error("Block stored payload checksum mismatch");
142
+ }
143
+ return description;
216
144
  }
217
- export function inspectBlock(bytes) {
218
- // Only magic and version are common across versions. Dispatch before interpreting any later
219
- // offsets, so future envelopes can grow or replace the v2 header without weakening its reader.
220
- if (bytes.byteLength < 10)
221
- throw new Error("Truncated block header");
222
- if (!MAGIC.every((byte, index) => bytes[index] === byte))
223
- throw new Error("Invalid block magic");
224
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
225
- const formatVersion = view.getUint16(8, true);
226
- switch (formatVersion) {
227
- case 2:
228
- return inspectV2Block(bytes);
229
- default:
230
- throw new Error(`Unsupported block version ${String(formatVersion)}`);
231
- }
145
+ function inspectBlock(bytes) {
146
+ if (bytes.byteLength < 10)
147
+ throw new Error("Truncated block header");
148
+ if (!MAGIC.every((byte, index) => bytes[index] === byte))
149
+ throw new Error("Invalid block magic");
150
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
151
+ const formatVersion = view.getUint16(8, true);
152
+ switch (formatVersion) {
153
+ case 2:
154
+ return inspectV2Block(bytes);
155
+ default:
156
+ throw new Error(`Unsupported block version ${String(formatVersion)}`);
157
+ }
232
158
  }
233
- /** Permanent reader for the frozen v2 envelope. */
234
159
  function inspectV2Block(bytes) {
235
- if (bytes.byteLength < HEADER_LENGTH)
236
- throw new Error("Truncated block header");
237
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
238
- const formatVersion = view.getUint16(8, true);
239
- if (view.getUint16(10, true) !== HEADER_LENGTH)
240
- throw new Error("Invalid block header length");
241
- const metadataLength = view.getUint32(24, true);
242
- const encodedLength = view.getUint32(28, true);
243
- const storedLength = view.getUint32(32, true);
244
- assertMetadataLength(metadataLength);
245
- assertLength(encodedLength, "encoded payload");
246
- assertLength(storedLength, "stored payload");
247
- if (HEADER_LENGTH + metadataLength + storedLength !== bytes.byteLength) {
248
- throw new Error("Block length mismatch");
249
- }
250
- // Verified before any header field is trusted: the envelope checksum covers every field after
251
- // itself plus the metadata JSON, making header-only reads safe against silent corruption.
252
- const envelope = crc32(bytes.subarray(ENVELOPE_CHECKSUM_START, HEADER_LENGTH + metadataLength));
253
- if (envelope !== view.getUint32(4, true)) {
254
- throw new Error("Block envelope checksum mismatch");
255
- }
256
- const rowCount = view.getUint32(16, true);
257
- assertBlockRowCount(rowCount);
258
- const type = typesById.get(view.getUint8(12));
259
- if (type === undefined)
260
- throw new Error("Unknown logical type");
261
- if (view.getUint8(13) !== typeIds[type])
262
- throw new Error("Unsupported physical encoding");
263
- const codecId = view.getUint8(14);
264
- const compression = codecsById.get(codecId);
265
- if (compression === undefined) {
266
- throw new Error(codecId === 1 ? "Block uses reserved compression codec id 1" : "Unknown compression codec");
267
- }
268
- if (view.getUint8(15) !== 0)
269
- throw new Error("Unsupported mandatory block flags");
270
- const checksum = view.getUint32(36, true);
271
- const storedChecksum = view.getUint32(40, true);
272
- if (compression === "raw" && checksum !== storedChecksum) {
273
- throw new Error("Raw block checksums do not match");
274
- }
275
- if (compression === "raw" && encodedLength !== storedLength) {
276
- throw new Error("Raw block lengths do not match");
277
- }
278
- const nullCount = view.getUint32(20, true);
279
- if (nullCount > rowCount)
280
- throw new Error("Null count exceeds row count");
281
- let parsedMetadata;
282
- let metadataJson;
283
- try {
284
- metadataJson = textDecoder.decode(bytes.subarray(HEADER_LENGTH, HEADER_LENGTH + metadataLength));
285
- parsedMetadata = JSON.parse(metadataJson);
286
- }
287
- catch {
288
- throw new Error("Invalid block metadata");
289
- }
290
- const metadata = parseMetadata(parsedMetadata);
291
- // There is one canonical metadata representation. Rejecting alternate key order, duplicate
292
- // keys, unknown fields, and insignificant whitespace keeps byte fixtures meaningful and avoids
293
- // multiple envelopes describing the same block.
294
- if (JSON.stringify(metadata) !== metadataJson)
295
- throw new Error("Non-canonical block metadata");
296
- return {
297
- formatVersion,
298
- headerLength: HEADER_LENGTH,
299
- metadataLength,
300
- type,
301
- compression,
302
- rowCount,
303
- nullCount,
304
- encodedLength,
305
- storedLength,
306
- checksum,
307
- storedChecksum,
308
- metadata,
309
- };
160
+ if (bytes.byteLength < HEADER_LENGTH)
161
+ throw new Error("Truncated block header");
162
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
163
+ const formatVersion = view.getUint16(8, true);
164
+ if (view.getUint16(10, true) !== HEADER_LENGTH)
165
+ throw new Error("Invalid block header length");
166
+ const metadataLength = view.getUint32(24, true);
167
+ const encodedLength = view.getUint32(28, true);
168
+ const storedLength = view.getUint32(32, true);
169
+ assertMetadataLength(metadataLength);
170
+ assertLength(encodedLength, "encoded payload");
171
+ assertLength(storedLength, "stored payload");
172
+ if (HEADER_LENGTH + metadataLength + storedLength !== bytes.byteLength) {
173
+ throw new Error("Block length mismatch");
174
+ }
175
+ const envelope = crc32(bytes.subarray(ENVELOPE_CHECKSUM_START, HEADER_LENGTH + metadataLength));
176
+ if (envelope !== view.getUint32(4, true)) {
177
+ throw new Error("Block envelope checksum mismatch");
178
+ }
179
+ const rowCount = view.getUint32(16, true);
180
+ assertBlockRowCount(rowCount);
181
+ const type = typesById.get(view.getUint8(12));
182
+ if (type === void 0)
183
+ throw new Error("Unknown logical type");
184
+ if (view.getUint8(13) !== typeIds[type])
185
+ throw new Error("Unsupported physical encoding");
186
+ const codecId = view.getUint8(14);
187
+ const compression = codecsById.get(codecId);
188
+ if (compression === void 0) {
189
+ throw new Error(codecId === 1 ? "Block uses reserved compression codec id 1" : "Unknown compression codec");
190
+ }
191
+ if (view.getUint8(15) !== 0)
192
+ throw new Error("Unsupported mandatory block flags");
193
+ const checksum = view.getUint32(36, true);
194
+ const storedChecksum = view.getUint32(40, true);
195
+ if (compression === "raw" && checksum !== storedChecksum) {
196
+ throw new Error("Raw block checksums do not match");
197
+ }
198
+ if (compression === "raw" && encodedLength !== storedLength) {
199
+ throw new Error("Raw block lengths do not match");
200
+ }
201
+ const nullCount = view.getUint32(20, true);
202
+ if (nullCount > rowCount)
203
+ throw new Error("Null count exceeds row count");
204
+ let parsedMetadata;
205
+ let metadataJson;
206
+ try {
207
+ metadataJson = textDecoder.decode(bytes.subarray(HEADER_LENGTH, HEADER_LENGTH + metadataLength));
208
+ parsedMetadata = JSON.parse(metadataJson);
209
+ } catch {
210
+ throw new Error("Invalid block metadata");
211
+ }
212
+ const metadata = parseMetadata(parsedMetadata);
213
+ if (JSON.stringify(metadata) !== metadataJson)
214
+ throw new Error("Non-canonical block metadata");
215
+ return {
216
+ formatVersion,
217
+ headerLength: HEADER_LENGTH,
218
+ metadataLength,
219
+ type,
220
+ compression,
221
+ rowCount,
222
+ nullCount,
223
+ encodedLength,
224
+ storedLength,
225
+ checksum,
226
+ storedChecksum,
227
+ metadata
228
+ };
310
229
  }
311
230
  function assertMetadataLength(length) {
312
- if (!Number.isSafeInteger(length) || length < 0 || length > MAX_METADATA_LENGTH) {
313
- throw new RangeError("Invalid metadata length");
314
- }
231
+ if (!Number.isSafeInteger(length) || length < 0 || length > MAX_METADATA_LENGTH) {
232
+ throw new RangeError("Invalid metadata length");
233
+ }
315
234
  }
316
235
  function assertLength(length, name) {
317
- if (!Number.isSafeInteger(length) || length < 0 || length > MAX_BLOCK_LENGTH) {
318
- throw new RangeError(`Invalid ${name} length`);
319
- }
236
+ if (!Number.isSafeInteger(length) || length < 0 || length > MAX_BLOCK_LENGTH) {
237
+ throw new RangeError(`Invalid ${name} length`);
238
+ }
320
239
  }
321
240
  function encodeCanonicalMetadata(metadata) {
322
- let source;
323
- try {
324
- source = JSON.stringify(metadata);
325
- }
326
- catch {
327
- throw new TypeError("Block metadata must be canonical JSON");
328
- }
329
- if (typeof source !== "string")
330
- throw new TypeError("Block metadata must be canonical JSON");
331
- let canonical;
332
- try {
333
- canonical = parseMetadata(JSON.parse(source));
334
- }
335
- catch {
336
- throw new TypeError("Block metadata must match the supported canonical shape");
337
- }
338
- if (JSON.stringify(canonical) !== source) {
339
- throw new TypeError("Block metadata must match the supported canonical shape");
340
- }
341
- return textEncoder.encode(source);
241
+ let source;
242
+ try {
243
+ source = JSON.stringify(metadata);
244
+ } catch {
245
+ throw new TypeError("Block metadata must be canonical JSON");
246
+ }
247
+ if (typeof source !== "string")
248
+ throw new TypeError("Block metadata must be canonical JSON");
249
+ let canonical;
250
+ try {
251
+ canonical = parseMetadata(JSON.parse(source));
252
+ } catch {
253
+ throw new TypeError("Block metadata must match the supported canonical shape");
254
+ }
255
+ if (JSON.stringify(canonical) !== source) {
256
+ throw new TypeError("Block metadata must match the supported canonical shape");
257
+ }
258
+ return textEncoder.encode(source);
342
259
  }
343
260
  function parseMetadata(value) {
344
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
345
- throw new Error("Invalid block metadata shape");
346
- }
347
- const zoneMap = Reflect.get(value, "zoneMap");
348
- if (zoneMap !== undefined) {
349
- if (typeof zoneMap !== "object" ||
350
- zoneMap === null ||
351
- !Number.isFinite(Reflect.get(zoneMap, "min")) ||
352
- !Number.isFinite(Reflect.get(zoneMap, "max"))) {
353
- throw new Error("Invalid zone map");
354
- }
355
- const min = Reflect.get(zoneMap, "min");
356
- const max = Reflect.get(zoneMap, "max");
357
- if (min > max)
358
- throw new Error("Invalid zone map");
359
- return { zoneMap: { min, max } };
261
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
262
+ throw new Error("Invalid block metadata shape");
263
+ }
264
+ const zoneMap = Reflect.get(value, "zoneMap");
265
+ if (zoneMap !== void 0) {
266
+ if (typeof zoneMap !== "object" || zoneMap === null || !Number.isFinite(Reflect.get(zoneMap, "min")) || !Number.isFinite(Reflect.get(zoneMap, "max"))) {
267
+ throw new Error("Invalid zone map");
360
268
  }
361
- return {};
269
+ const min = Reflect.get(zoneMap, "min");
270
+ const max = Reflect.get(zoneMap, "max");
271
+ if (min > max)
272
+ throw new Error("Invalid zone map");
273
+ return { zoneMap: { min, max } };
274
+ }
275
+ return {};
362
276
  }
363
277
  function metadataEquals(left, right) {
364
- if (left.zoneMap === undefined || right.zoneMap === undefined) {
365
- return left.zoneMap === undefined && right.zoneMap === undefined;
366
- }
367
- return left.zoneMap.min === right.zoneMap.min && left.zoneMap.max === right.zoneMap.max;
278
+ if (left.zoneMap === void 0 || right.zoneMap === void 0) {
279
+ return left.zoneMap === void 0 && right.zoneMap === void 0;
280
+ }
281
+ return left.zoneMap.min === right.zoneMap.min && left.zoneMap.max === right.zoneMap.max;
368
282
  }
283
+ export {
284
+ BLOCK_FORMAT_VERSION,
285
+ BLOCK_HEADER_LENGTH,
286
+ MAX_BLOCK_METADATA_BYTE_LENGTH,
287
+ MAX_STORED_BLOCK_BYTE_LENGTH,
288
+ StoredBlockPayloadTooLargeError,
289
+ assertStoredBlockPayloadByteLength,
290
+ decodeBlock,
291
+ decodePhysicalBlock,
292
+ encodeBlock,
293
+ encodePhysicalBlock,
294
+ inspectBlock,
295
+ maximumPhysicalBlockByteLength,
296
+ verifyStoredBlock
297
+ };