@minnowdb/core 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/block-format/block.js +254 -325
- package/dist/block-format/checksum.js +30 -52
- package/dist/block-format/codecs.js +112 -125
- package/dist/block-format/column.js +187 -205
- package/dist/block-format/index.js +5 -1
- package/dist/block-format/physical.js +371 -387
- package/dist/block-format/types.js +4 -1
- package/dist/block-format/unicode.js +30 -39
- package/dist/date-value.js +67 -78
- package/dist/engine/artifact-cache.js +106 -116
- package/dist/engine/batch.js +36 -44
- package/dist/engine/buffered-writer.js +156 -158
- package/dist/engine/byte-estimates.js +20 -22
- package/dist/engine/cache-limits.js +24 -25
- package/dist/engine/cancellation.js +5 -3
- package/dist/engine/catalog.js +64 -73
- package/dist/engine/client.js +715 -780
- package/dist/engine/database.js +15941 -19313
- package/dist/engine/defaults.js +104 -111
- package/dist/engine/errors.js +113 -128
- package/dist/engine/fts.js +233 -280
- package/dist/engine/group-index.js +287 -321
- package/dist/engine/index.js +23 -12
- package/dist/engine/join-index.js +177 -186
- package/dist/engine/keyed-live.js +168 -173
- package/dist/engine/live-api.js +0 -1
- package/dist/engine/live-equal.js +32 -37
- package/dist/engine/live.js +603 -647
- package/dist/engine/memory.js +109 -111
- package/dist/engine/optimizer.d.ts +19 -1
- package/dist/engine/optimizer.js +2633 -2934
- package/dist/engine/point-read.js +122 -182
- package/dist/engine/query-api.js +12 -3
- package/dist/engine/query-cache.js +61 -69
- package/dist/engine/query.d.ts +6 -0
- package/dist/engine/query.js +8503 -9980
- package/dist/engine/result-wire.js +221 -252
- package/dist/engine/schema-wire.js +97 -112
- package/dist/engine/schema.js +1099 -1254
- package/dist/engine/sort-keys.js +315 -389
- package/dist/engine/sql-domains.js +646 -769
- package/dist/engine/sql-driver.js +0 -1
- package/dist/engine/sql-functions.js +1031 -1125
- package/dist/engine/sql-json.js +199 -218
- package/dist/engine/sql-semantics.js +572 -639
- package/dist/engine/typed-live.js +271 -293
- package/dist/engine/vector.js +4484 -5145
- package/dist/engine/worker-host.d.ts +8 -63
- package/dist/engine/worker-host.js +28 -1130
- package/dist/engine/worker-indexeddb.d.ts +1 -0
- package/dist/engine/worker-indexeddb.js +3 -0
- package/dist/engine/worker-memory.d.ts +1 -0
- package/dist/engine/worker-memory.js +3 -0
- package/dist/engine/worker-opfs.d.ts +1 -0
- package/dist/engine/worker-opfs.js +3 -0
- package/dist/engine/worker-server.d.ts +93 -0
- package/dist/engine/worker-server.js +999 -0
- package/dist/engine/worker-store-indexeddb.d.ts +2 -0
- package/dist/engine/worker-store-indexeddb.js +11 -0
- package/dist/engine/worker-store-memory.d.ts +2 -0
- package/dist/engine/worker-store-memory.js +6 -0
- package/dist/engine/worker-store-opfs.d.ts +2 -0
- package/dist/engine/worker-store-opfs.js +9 -0
- package/dist/engine/worker.js +0 -12
- package/dist/engine/write-block-planner.js +93 -100
- package/dist/plan/index.js +15 -16
- package/dist/plan/model.d.ts +6 -0
- package/dist/plan/model.js +20 -27
- package/dist/storage/index.js +0 -12
- package/dist/storage/indexeddb.js +11776 -13171
- package/dist/storage/memory.js +1198 -1241
- package/dist/storage/opfs/files.js +238 -295
- package/dist/storage/opfs/index.js +9 -3
- package/dist/storage/opfs/leader.js +4386 -4856
- package/dist/storage/opfs/rpc.js +223 -259
- package/dist/storage/opfs/snapshot-ledger.js +219 -260
- package/dist/storage/opfs/store.js +884 -1003
- package/dist/storage/persistence.js +59 -74
- package/dist/storage/snapshot-stream.js +788 -826
- package/dist/storage/snapshot.js +36 -37
- package/dist/storage/toolkit/extents.js +444 -520
- package/dist/storage/toolkit/index.js +28 -29
- package/dist/storage/toolkit/record-core.js +5792 -6377
- package/dist/storage/toolkit/sync-file.js +34 -42
- package/dist/storage/toolkit/wal.js +92 -127
- package/dist/storage/toolkit/wire.js +232 -256
- package/dist/storage/types.js +2948 -3250
- package/dist/testing/block-store-conformance.js +1556 -1629
- package/dist/testing/index.js +310 -291
- package/dist/testing/opfs-shim.js +280 -312
- package/dist/testing/simulator.js +496 -534
- package/dist/testing/sqllogictest.js +425 -365
- package/dist/transactions/index.js +1464 -1712
- package/dist/worker-protocol/index.js +70 -69
- package/package.json +20 -2
|
@@ -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
|
|
5
|
-
const MAGIC = Uint8Array.of(
|
|
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
|
-
|
|
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
|
-
|
|
12
|
+
const MAX_BLOCK_METADATA_BYTE_LENGTH = 1024;
|
|
40
13
|
const MAX_METADATA_LENGTH = MAX_BLOCK_METADATA_BYTE_LENGTH;
|
|
41
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
|
|
313
|
-
|
|
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
|
-
|
|
318
|
-
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
+
};
|