@gscdump/engine 7.3.2 → 7.5.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.
@@ -0,0 +1,318 @@
1
+ import { snappyUncompress } from "../node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/src/snappy.mjs";
2
+ import "../node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/src/node.mjs";
3
+ import { parquetDecodeBudget } from "./parquet-decode-budget.mjs";
4
+ import { BoundedHeaderTruncatedError, boundedPageHeader } from "./parquet-page-header.mjs";
5
+ import { hybridCursor, plainCursor } from "./parquet-value-cursor.mjs";
6
+ function boundedValuesPolicy(options) {
7
+ if (options === void 0) return void 0;
8
+ const policy = {
9
+ targetRows: 1e3,
10
+ maxBatchBytes: 1048576,
11
+ maxValueBytes: 262144,
12
+ maxActiveBytes: 25165824,
13
+ maxPages: 16384,
14
+ maxHeaderBytes: 65536,
15
+ maxDictionaryEntries: 1e5,
16
+ ...options
17
+ };
18
+ for (const [key, value] of Object.entries(policy)) if (!Number.isSafeInteger(value) || value < 1) throw new RangeError(`invalid boundedValues.${key}`);
19
+ return policy;
20
+ }
21
+ const supportedEncodings = /* @__PURE__ */ new Set([
22
+ 0,
23
+ 2,
24
+ 8
25
+ ]);
26
+ function natural(value, label) {
27
+ if (!Number.isSafeInteger(value) || value < 0) throw new Error(`bounded Parquet invalid ${label}`);
28
+ return value;
29
+ }
30
+ function scalar(element) {
31
+ if (element.num_children || !["REQUIRED", "OPTIONAL"].includes(element.repetition_type ?? "")) return false;
32
+ const converted = element.converted_type;
33
+ const logical = element.logical_type?.type;
34
+ if (element.type === "BYTE_ARRAY") return (converted === "UTF8" || logical === "STRING") && (!converted || converted === "UTF8") && (!logical || logical === "STRING");
35
+ return [
36
+ "BOOLEAN",
37
+ "INT32",
38
+ "INT64",
39
+ "INT96",
40
+ "FLOAT",
41
+ "DOUBLE"
42
+ ].includes(element.type ?? "") && (!converted || [
43
+ "DATE",
44
+ "TIME_MILLIS",
45
+ "TIME_MICROS",
46
+ "TIMESTAMP_MILLIS",
47
+ "TIMESTAMP_MICROS",
48
+ "UINT_8",
49
+ "UINT_16",
50
+ "UINT_32",
51
+ "UINT_64",
52
+ "INT_8",
53
+ "INT_16",
54
+ "INT_32",
55
+ "INT_64"
56
+ ].includes(converted)) && (!logical || [
57
+ "DATE",
58
+ "TIME",
59
+ "TIMESTAMP",
60
+ "INTEGER"
61
+ ].includes(logical));
62
+ }
63
+ async function readHeader(file, start, end, maxBytes) {
64
+ const limit = Math.min(end - start, maxBytes);
65
+ for (let length = Math.min(512, limit);;) try {
66
+ return boundedPageHeader(await file.slice(start, start + length));
67
+ } catch (error) {
68
+ if (!(error instanceof BoundedHeaderTruncatedError) || length === limit) throw error;
69
+ length = Math.min(limit, length * 2);
70
+ }
71
+ }
72
+ async function planInput(file, metadata, policy, maxReadBytes, columns, rowStart = 0, rowEnd = Infinity, maxReadAndDecompressBytes) {
73
+ const schema = metadata.schema.slice(1);
74
+ if (metadata.schema[0]?.num_children !== schema.length || schema.some((element) => !scalar(element)) || new Set(schema.map((element) => element.name)).size !== schema.length) throw new Error("bounded Parquet requires a flat scalar REQUIRED/OPTIONAL schema");
75
+ const selected = columns ? schema.filter((element) => columns.includes(element.name)) : schema;
76
+ const output = [];
77
+ let start = 0;
78
+ let pageCount = 0;
79
+ try {
80
+ for (const group of metadata.row_groups) {
81
+ const rows = Number(group.num_rows);
82
+ const plan = {
83
+ start,
84
+ rows,
85
+ columns: []
86
+ };
87
+ start += rows;
88
+ if (start <= rowStart || plan.start >= rowEnd) continue;
89
+ plan.budget = maxReadAndDecompressBytes === void 0 ? void 0 : parquetDecodeBudget(file, maxReadAndDecompressBytes);
90
+ output.push(plan);
91
+ const groupFile = plan.budget?.file ?? file;
92
+ let active = Math.min(policy.maxHeaderBytes, maxReadBytes);
93
+ for (const element of selected) {
94
+ const matches = group.columns.filter((col) => col.meta_data?.path_in_schema.length === 1 && col.meta_data.path_in_schema[0] === element.name);
95
+ if (matches.length !== 1) throw new Error("bounded Parquet missing or duplicate column");
96
+ const chunk = matches[0];
97
+ const meta = chunk.meta_data;
98
+ if (chunk.file_path || meta.type !== element.type || Number(meta.num_values) !== rows || !["SNAPPY", "UNCOMPRESSED"].includes(meta.codec)) throw new Error("bounded Parquet unsupported column or codec");
99
+ const first = Number(meta.dictionary_page_offset ?? meta.data_page_offset);
100
+ const end = first + Number(meta.total_compressed_size);
101
+ if (!Number.isSafeInteger(first) || first < 4 || !Number.isSafeInteger(end) || end > file.byteLength || end < first) throw new Error("bounded Parquet invalid column range");
102
+ const column = {
103
+ element,
104
+ meta,
105
+ pages: []
106
+ };
107
+ let pageRows = 0;
108
+ let maxData = 0;
109
+ for (let offset = first; offset < end;) {
110
+ if (++pageCount > policy.maxPages) throw new RangeError("bounded Parquet exceeds maxPages");
111
+ const header = await readHeader(groupFile, offset, end, Math.min(policy.maxHeaderBytes, maxReadBytes));
112
+ natural(header.compressed, "compressed page bytes");
113
+ natural(header.uncompressed, "uncompressed page bytes");
114
+ natural(header.count, "page values");
115
+ const page = {
116
+ ...header,
117
+ offset: offset + header.bytes
118
+ };
119
+ offset = page.offset + page.compressed;
120
+ if (offset > end || page.compressed > maxReadBytes) throw new RangeError("bounded Parquet page exceeds range bounds");
121
+ if (page.kind === 2) {
122
+ if (column.dictionary || column.pages.length || page.encoding !== 0 || page.count > policy.maxDictionaryEntries) throw new Error("bounded Parquet unsupported dictionary");
123
+ column.dictionary = page;
124
+ active += page.compressed + page.uncompressed + (page.count + 1) * 4;
125
+ } else {
126
+ if (!page.count || !supportedEncodings.has(page.encoding) || page.encoding !== 0 && !column.dictionary) throw new Error("bounded Parquet unsupported data encoding");
127
+ if (page.kind === 0 && (page.definitionEncoding !== 3 || page.repetitionEncoding !== 3)) throw new Error("bounded Parquet unsupported level encoding");
128
+ if (page.kind === 3) {
129
+ natural(page.nulls, "null count");
130
+ natural(page.rows, "page rows");
131
+ natural(page.definitions, "definition bytes");
132
+ natural(page.repetitions, "repetition bytes");
133
+ if (page.rows !== page.count || page.nulls > page.count || page.repetitions !== 0 || page.definitions > page.compressed || page.definitions > page.uncompressed || element.repetition_type === "REQUIRED" && (page.definitions !== 0 || page.nulls !== 0)) throw new Error("bounded Parquet invalid flat V2 page");
134
+ }
135
+ pageRows += page.count;
136
+ if (pageRows > rows) throw new Error("bounded Parquet excess physical rows");
137
+ maxData = Math.max(maxData, page.compressed + page.uncompressed);
138
+ column.pages.push(page);
139
+ }
140
+ }
141
+ if (pageRows !== rows) throw new Error("bounded Parquet incomplete physical rows");
142
+ if (column.dictionary && element.type === "BOOLEAN") throw new Error("bounded Parquet BOOLEAN dictionary is unsupported");
143
+ active += maxData;
144
+ if (!Number.isSafeInteger(active) || active > policy.maxActiveBytes) throw new RangeError("bounded Parquet exceeds maxActiveBytes");
145
+ plan.columns.push(column);
146
+ }
147
+ }
148
+ return output;
149
+ } catch (error) {
150
+ for (const plan of output) plan.budget?.close();
151
+ throw error;
152
+ }
153
+ }
154
+ function inflate(input, size, compressed, decompress) {
155
+ if (!compressed) {
156
+ if (input.length !== size) throw new Error("bounded Parquet uncompressed page size mismatch");
157
+ return input;
158
+ }
159
+ if (decompress) return decompress(input, size);
160
+ const output = new Uint8Array(size);
161
+ snappyUncompress(input, output);
162
+ return output;
163
+ }
164
+ function dataCursor(page, input, plan, policy, dictionary, decompress) {
165
+ let definitions;
166
+ let levels;
167
+ let values;
168
+ if (page.kind === 0) {
169
+ const decoded = inflate(input, page.uncompressed, plan.meta.codec === "SNAPPY", decompress);
170
+ let offset = 0;
171
+ if (plan.element.repetition_type === "OPTIONAL") {
172
+ if (decoded.length < 4) throw new Error("bounded Parquet missing definition levels");
173
+ offset = 4 + new DataView(decoded.buffer, decoded.byteOffset, decoded.byteLength).getUint32(0, true);
174
+ if (offset > decoded.length) throw new Error("bounded Parquet truncated definition levels");
175
+ levels = decoded.subarray(4, offset);
176
+ }
177
+ values = decoded.subarray(offset);
178
+ } else {
179
+ const length = page.definitions;
180
+ if (plan.element.repetition_type === "OPTIONAL") levels = input.subarray(0, length);
181
+ values = inflate(input.subarray(length), page.uncompressed - length, page.isCompressed !== false && plan.meta.codec === "SNAPPY", decompress);
182
+ }
183
+ let present = page.count;
184
+ if (levels) {
185
+ definitions = hybridCursor(levels, 1, page.count);
186
+ present = 0;
187
+ for (let i = 0; i < page.count; i++) present += definitions.next();
188
+ definitions.finish();
189
+ definitions = hybridCursor(levels, 1, page.count);
190
+ }
191
+ if (page.kind === 3 && page.count - present !== page.nulls) throw new Error("bounded Parquet null count mismatch");
192
+ let plain;
193
+ let ids;
194
+ if (page.encoding === 0) plain = plainCursor(values, plan.element, present, policy.maxValueBytes);
195
+ else {
196
+ if (!dictionary || !values.length) throw new Error("bounded Parquet missing dictionary IDs");
197
+ ids = hybridCursor(values.subarray(1), values[0], present);
198
+ }
199
+ return {
200
+ next() {
201
+ if (definitions && definitions.next() === 0) return {
202
+ bytes: 64,
203
+ value: () => null
204
+ };
205
+ if (plain) return plain.next();
206
+ const id = ids.next();
207
+ if (id >= dictionary.offsets.length - 1) throw new Error("bounded Parquet dictionary ID out of range");
208
+ const start = dictionary.offsets[id];
209
+ const end = dictionary.offsets[id + 1];
210
+ return plainCursor(dictionary.bytes.subarray(start, end), plan.element, 1, policy.maxValueBytes).next();
211
+ },
212
+ finish() {
213
+ definitions?.finish();
214
+ plain?.finish();
215
+ ids?.finish();
216
+ }
217
+ };
218
+ }
219
+ var ColumnCursor = class {
220
+ file;
221
+ plan;
222
+ policy;
223
+ decompress;
224
+ dictionary;
225
+ current;
226
+ pageIndex = 0;
227
+ remaining = 0;
228
+ constructor(file, plan, policy, decompress) {
229
+ this.file = file;
230
+ this.plan = plan;
231
+ this.policy = policy;
232
+ this.decompress = decompress;
233
+ }
234
+ needsPage() {
235
+ return this.remaining === 0;
236
+ }
237
+ async load() {
238
+ if (this.remaining) return;
239
+ this.current?.finish();
240
+ this.current = void 0;
241
+ if (!this.dictionary && this.plan.dictionary) {
242
+ const page = this.plan.dictionary;
243
+ const bytes = inflate(new Uint8Array(await this.file.slice(page.offset, page.offset + page.compressed)), page.uncompressed, this.plan.meta.codec === "SNAPPY", this.decompress);
244
+ const offsets = new Uint32Array(page.count + 1);
245
+ const cursor = plainCursor(bytes, this.plan.element, page.count, this.policy.maxValueBytes);
246
+ for (let index = 0; index < page.count; index++) {
247
+ offsets[index] = cursor.position();
248
+ cursor.skip();
249
+ }
250
+ offsets[page.count] = cursor.position();
251
+ cursor.finish();
252
+ this.dictionary = {
253
+ bytes,
254
+ offsets
255
+ };
256
+ }
257
+ const page = this.plan.pages[this.pageIndex++];
258
+ if (!page) throw new Error("bounded Parquet exhausted column");
259
+ const input = new Uint8Array(await this.file.slice(page.offset, page.offset + page.compressed));
260
+ this.current = dataCursor(page, input, this.plan, this.policy, this.dictionary, this.decompress);
261
+ this.remaining = page.count;
262
+ }
263
+ next() {
264
+ if (!this.current || this.remaining-- < 1) throw new Error("bounded Parquet column is not ready");
265
+ return this.current.next();
266
+ }
267
+ close() {
268
+ this.current = void 0;
269
+ this.dictionary = void 0;
270
+ }
271
+ finish() {
272
+ this.current?.finish();
273
+ if (this.remaining || this.pageIndex !== this.plan.pages.length) throw new Error("bounded Parquet incomplete column");
274
+ }
275
+ };
276
+ async function* decodeBoundedValues(file, metadata, policy, maxReadBytes, options, maxReadAndDecompressBytes) {
277
+ const plans = await planInput(file, metadata, policy, maxReadBytes, options.columns, options.rowStart, options.rowEnd, maxReadAndDecompressBytes);
278
+ try {
279
+ for (const plan of plans) {
280
+ const budget = plan.budget;
281
+ const cursors = plan.columns.map((column) => new ColumnCursor(budget?.file ?? file, column, policy, budget?.compressors.SNAPPY));
282
+ let batch = [];
283
+ let batchBytes = 0;
284
+ try {
285
+ for (let index = 0; index < plan.rows; index++) {
286
+ for (const cursor of cursors) if (cursor.needsPage()) await cursor.load();
287
+ const tokens = cursors.map((cursor) => cursor.next());
288
+ const position = plan.start + index;
289
+ if (position < (options.rowStart ?? 0) || position >= (options.rowEnd ?? Infinity)) continue;
290
+ const bytes = 128 + tokens.reduce((sum, token) => sum + 16 + token.bytes, 0);
291
+ if (bytes > policy.maxBatchBytes) throw new RangeError("bounded Parquet row exceeds maxBatchBytes");
292
+ if (batch.length && (batch.length >= policy.targetRows || batchBytes + bytes > policy.maxBatchBytes)) {
293
+ yield batch;
294
+ batch = [];
295
+ batchBytes = 0;
296
+ }
297
+ const row = {};
298
+ for (let col = 0; col < tokens.length; col++) Object.defineProperty(row, plan.columns[col].element.name, {
299
+ value: tokens[col].value(),
300
+ enumerable: true,
301
+ configurable: true,
302
+ writable: true
303
+ });
304
+ batch.push(row);
305
+ batchBytes += bytes;
306
+ }
307
+ for (const cursor of cursors) cursor.finish();
308
+ if (batch.length) yield batch;
309
+ } finally {
310
+ for (const cursor of cursors) cursor.close();
311
+ budget?.close();
312
+ }
313
+ }
314
+ } finally {
315
+ for (const plan of plans) plan.budget?.close();
316
+ }
317
+ }
318
+ export { boundedValuesPolicy, decodeBoundedValues };
@@ -0,0 +1,61 @@
1
+ import { snappyUncompress } from "../node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/src/snappy.mjs";
2
+ import "../node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/src/node.mjs";
3
+ function parquetDecodeBudget(source, maxBytes) {
4
+ let remaining = maxBytes;
5
+ let closed = false;
6
+ let failure;
7
+ function fail(error) {
8
+ if (!closed) {
9
+ closed = true;
10
+ failure = error;
11
+ }
12
+ throw failure;
13
+ }
14
+ function assertOpen() {
15
+ if (closed) throw failure;
16
+ }
17
+ function charge(bytes) {
18
+ assertOpen();
19
+ if (!Number.isSafeInteger(bytes) || bytes < 0 || bytes > remaining) fail(/* @__PURE__ */ new RangeError("parquet stream exceeds maxReadAndDecompressBytes"));
20
+ remaining -= bytes;
21
+ }
22
+ return {
23
+ file: {
24
+ byteLength: source.byteLength,
25
+ async slice(start, end = source.byteLength) {
26
+ try {
27
+ if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start || end > source.byteLength) fail(/* @__PURE__ */ new RangeError("parquet stream invalid decode-buffer range"));
28
+ charge(end - start);
29
+ const bytes = await source.slice(start, end);
30
+ assertOpen();
31
+ if (bytes.byteLength !== end - start) fail(/* @__PURE__ */ new Error("parquet stream returned an incomplete decode-buffer range"));
32
+ return bytes;
33
+ } catch (error) {
34
+ fail(error);
35
+ }
36
+ }
37
+ },
38
+ compressors: {
39
+ SNAPPY(input, size) {
40
+ charge(size);
41
+ try {
42
+ const output = new Uint8Array(size);
43
+ snappyUncompress(input, output);
44
+ return output;
45
+ } catch (error) {
46
+ fail(error);
47
+ }
48
+ },
49
+ ZSTD() {
50
+ fail(/* @__PURE__ */ new Error("parquet stream maxReadAndDecompressBytes does not support ZSTD"));
51
+ }
52
+ },
53
+ close() {
54
+ if (!closed) {
55
+ closed = true;
56
+ failure = /* @__PURE__ */ new Error("parquet stream decode-buffer budget is closed");
57
+ }
58
+ }
59
+ };
60
+ }
61
+ export { parquetDecodeBudget };
@@ -0,0 +1,136 @@
1
+ var BoundedHeaderTruncatedError = class extends Error {
2
+ constructor() {
3
+ super("bounded Parquet truncated page header");
4
+ }
5
+ };
6
+ function boundedPageHeader(bytes) {
7
+ const view = new DataView(bytes);
8
+ let offset = 0;
9
+ let fields = 0;
10
+ const take = (length) => {
11
+ if (!Number.isSafeInteger(length) || length < 0) throw new Error("bounded Parquet invalid page-header size");
12
+ if (offset + length > view.byteLength) throw new BoundedHeaderTruncatedError();
13
+ const start = offset;
14
+ offset += length;
15
+ return start;
16
+ };
17
+ const byte = () => view.getUint8(take(1));
18
+ const varint = () => {
19
+ let value = 0;
20
+ for (let shift = 0; shift < 49; shift += 7) {
21
+ const b = byte();
22
+ value += (b & 127) * 2 ** shift;
23
+ if (!(b & 128) && Number.isSafeInteger(value)) return value;
24
+ }
25
+ throw new Error("bounded Parquet invalid header integer");
26
+ };
27
+ const integer = () => {
28
+ const value = varint();
29
+ return value % 2 ? -(value + 1) / 2 : value / 2;
30
+ };
31
+ const skip = (type, depth) => {
32
+ if (depth > 8 || ++fields > 512) throw new Error("bounded Parquet excessive page-header structure");
33
+ if (type === 1 || type === 2) return;
34
+ if (type === 3) {
35
+ take(1);
36
+ return;
37
+ }
38
+ if (type === 4 || type === 5) {
39
+ varint();
40
+ return;
41
+ }
42
+ if (type === 6) {
43
+ for (let i = 0; i < 10; i++) if (!(byte() & 128)) return;
44
+ throw new Error("bounded Parquet invalid header int64");
45
+ }
46
+ if (type === 7) {
47
+ take(8);
48
+ return;
49
+ }
50
+ if (type === 8) {
51
+ take(varint());
52
+ return;
53
+ }
54
+ if (type === 12) {
55
+ struct(depth + 1);
56
+ return;
57
+ }
58
+ throw new Error("bounded Parquet unsupported page-header field");
59
+ };
60
+ function struct(depth, select) {
61
+ let id = 0;
62
+ const seen = /* @__PURE__ */ new Set();
63
+ while (true) {
64
+ const field = byte();
65
+ if (field === 0) return;
66
+ const type = field & 15;
67
+ id = field >> 4 ? id + (field >> 4) : integer();
68
+ if (id < 1 || seen.has(id)) throw new Error("bounded Parquet invalid page-header field");
69
+ seen.add(id);
70
+ if (!select?.(id, type)) skip(type, depth);
71
+ if (++fields > 512) throw new Error("bounded Parquet excessive page-header fields");
72
+ }
73
+ }
74
+ const root = {};
75
+ const children = {};
76
+ struct(0, (id, type) => {
77
+ if ([
78
+ 1,
79
+ 2,
80
+ 3
81
+ ].includes(id)) {
82
+ if (type !== 5) throw new Error("bounded Parquet invalid page-header number");
83
+ root[id] = integer();
84
+ return true;
85
+ }
86
+ if ([
87
+ 5,
88
+ 7,
89
+ 8
90
+ ].includes(id)) {
91
+ if (type !== 12) throw new Error("bounded Parquet invalid page-header body");
92
+ const body = {};
93
+ children[id] = body;
94
+ struct(1, (field, fieldType) => {
95
+ if (field > (id === 5 ? 4 : id === 7 ? 2 : 7)) return false;
96
+ if (id === 8 && field === 7) {
97
+ if (fieldType !== 1 && fieldType !== 2) throw new Error("bounded Parquet invalid compression boolean");
98
+ body[field] = fieldType === 1;
99
+ } else {
100
+ if (fieldType !== 5) throw new Error("bounded Parquet invalid data-header number");
101
+ body[field] = integer();
102
+ }
103
+ return true;
104
+ });
105
+ return true;
106
+ }
107
+ return false;
108
+ });
109
+ const kind = root[1];
110
+ const body = children[kind === 0 ? 5 : kind === 2 ? 7 : 8];
111
+ if (![
112
+ 0,
113
+ 2,
114
+ 3
115
+ ].includes(kind) || !body) throw new Error("bounded Parquet unsupported page type");
116
+ return {
117
+ bytes: offset,
118
+ kind,
119
+ compressed: root[3],
120
+ uncompressed: root[2],
121
+ count: body[1],
122
+ encoding: body[kind === 3 ? 4 : 2],
123
+ ...kind === 0 ? {
124
+ definitionEncoding: body[3],
125
+ repetitionEncoding: body[4]
126
+ } : {},
127
+ ...kind === 3 ? {
128
+ nulls: body[2],
129
+ rows: body[3],
130
+ definitions: body[5],
131
+ repetitions: body[6],
132
+ isCompressed: body[7] === void 0 ? true : body[7]
133
+ } : {}
134
+ };
135
+ }
136
+ export { BoundedHeaderTruncatedError, boundedPageHeader };
@@ -0,0 +1,41 @@
1
+ function parquetRangeCache(source, maxBytes) {
2
+ const entries = /* @__PURE__ */ new Map();
3
+ let retainedBytes = 0;
4
+ let closed = false;
5
+ return {
6
+ file: {
7
+ byteLength: source.byteLength,
8
+ async slice(start, end = source.byteLength) {
9
+ const key = `${start}:${end}`;
10
+ const cached = entries.get(key);
11
+ if (cached) {
12
+ entries.delete(key);
13
+ entries.set(key, cached);
14
+ return cached;
15
+ }
16
+ const bytes = await source.slice(start, end);
17
+ if (!closed && bytes.byteLength > 0 && bytes.byteLength <= maxBytes) {
18
+ const previous = entries.get(key);
19
+ if (previous) {
20
+ retainedBytes -= previous.byteLength;
21
+ entries.delete(key);
22
+ }
23
+ while (entries.size >= 128 || retainedBytes + bytes.byteLength > maxBytes) {
24
+ const oldest = entries.keys().next().value;
25
+ retainedBytes -= entries.get(oldest).byteLength;
26
+ entries.delete(oldest);
27
+ }
28
+ entries.set(key, bytes);
29
+ retainedBytes += bytes.byteLength;
30
+ }
31
+ return bytes;
32
+ }
33
+ },
34
+ close() {
35
+ closed = true;
36
+ entries.clear();
37
+ retainedBytes = 0;
38
+ }
39
+ };
40
+ }
41
+ export { parquetRangeCache };
@@ -0,0 +1,108 @@
1
+ import { DEFAULT_PARSERS, convert } from "../node_modules/.pnpm/hyparquet@1.31.1/node_modules/hyparquet/src/convert.mjs";
2
+ const utf8 = new TextDecoder();
3
+ function hybridCursor(bytes, width, count) {
4
+ if (!Number.isInteger(width) || width < 0 || width > 32) throw new Error("bounded Parquet invalid bit width");
5
+ let offset = 0;
6
+ let remaining = count;
7
+ let run = 0;
8
+ let repeat = 0;
9
+ let packed = false;
10
+ let bit = 0;
11
+ let packedStart = 0;
12
+ const readByte = () => {
13
+ if (offset >= bytes.length) throw new Error("bounded Parquet truncated RLE stream");
14
+ return bytes[offset++];
15
+ };
16
+ const next = () => {
17
+ if (remaining-- <= 0) throw new Error("bounded Parquet too many encoded values");
18
+ if (width === 0 && bytes.length === 0) return 0;
19
+ if (!run) {
20
+ let header = 0;
21
+ let shift = 0;
22
+ while (true) {
23
+ const b = readByte();
24
+ header += (b & 127) * 2 ** shift;
25
+ if (!(b & 128)) break;
26
+ shift += 7;
27
+ if (shift > 28) throw new Error("bounded Parquet invalid RLE run");
28
+ }
29
+ packed = !!(header & 1);
30
+ run = packed ? Math.floor(header / 2) * 8 : header / 2;
31
+ if (!Number.isSafeInteger(run) || run < 1 || run > 2147483647 || !packed && run > remaining + 1) throw new Error(`bounded Parquet invalid RLE run length: run=${run}, remaining=${remaining + 1}, packed=${packed}, width=${width}`);
32
+ if (packed) {
33
+ packedStart = offset;
34
+ bit = 0;
35
+ offset += run * width / 8;
36
+ if (offset > bytes.length) throw new Error("bounded Parquet truncated bit-packed stream");
37
+ } else {
38
+ repeat = 0;
39
+ for (let b = 0; b < Math.ceil(width / 8); b++) repeat += readByte() * 2 ** (8 * b);
40
+ if (repeat >= 2 ** width) throw new Error("bounded Parquet RLE value exceeds bit width");
41
+ }
42
+ }
43
+ run--;
44
+ if (!packed) return repeat;
45
+ let value = 0;
46
+ for (let b = 0; b < width; b++, bit++) value += (bytes[packedStart + Math.floor(bit / 8)] >> bit % 8 & 1) * 2 ** b;
47
+ return value;
48
+ };
49
+ return {
50
+ next,
51
+ finish() {
52
+ if (remaining !== 0 || offset !== bytes.length || run && !packed) throw new Error(`bounded Parquet trailing or incomplete RLE data: remaining=${remaining}, offset=${offset}, bytes=${bytes.length}, run=${run}, packed=${packed}, width=${width}`);
53
+ }
54
+ };
55
+ }
56
+ function plainCursor(bytes, element, count, maxValueBytes) {
57
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
58
+ let offset = 0;
59
+ let index = 0;
60
+ const width = element.type === "BOOLEAN" ? 0 : element.type === "INT32" || element.type === "FLOAT" ? 4 : element.type === "INT96" ? 12 : 8;
61
+ const decoder = {
62
+ element,
63
+ type: element.type,
64
+ parsers: DEFAULT_PARSERS,
65
+ schemaPath: []
66
+ };
67
+ const take = () => {
68
+ if (index >= count) throw new Error("bounded Parquet too many plain values");
69
+ let size = width;
70
+ if (element.type === "BYTE_ARRAY") {
71
+ if (offset + 4 > bytes.length) throw new Error("bounded Parquet truncated string length");
72
+ size = view.getUint32(offset, true);
73
+ offset += 4;
74
+ if (size > maxValueBytes) throw new RangeError("bounded Parquet exceeds maxValueBytes");
75
+ }
76
+ const start = element.type === "BOOLEAN" ? Math.floor(index / 8) : offset;
77
+ if (start + (element.type === "BOOLEAN" ? 1 : size) > bytes.length) throw new Error("bounded Parquet truncated plain value");
78
+ offset = element.type === "BOOLEAN" ? Math.ceil((index + 1) / 8) : offset + size;
79
+ return {
80
+ start,
81
+ size,
82
+ index: index++
83
+ };
84
+ };
85
+ return {
86
+ next() {
87
+ const { start, size, index: bitIndex } = take();
88
+ return {
89
+ bytes: 64 + (element.type === "BYTE_ARRAY" ? size * 2 : 32),
90
+ value() {
91
+ if (element.type === "BYTE_ARRAY") return utf8.decode(bytes.subarray(start, start + size));
92
+ let raw = element.type === "BOOLEAN" ? !!(view.getUint8(start) & 1 << bitIndex % 8) : element.type === "INT32" ? view.getInt32(start, true) : element.type === "INT64" ? view.getBigInt64(start, true) : element.type === "INT96" ? BigInt(view.getInt32(start + 8, true)) << 64n | view.getBigInt64(start, true) : element.type === "FLOAT" ? view.getFloat32(start, true) : view.getFloat64(start, true);
93
+ if (element.type === "INT64" && (element.converted_type === "UINT_64" || element.logical_type?.type === "INTEGER" && !element.logical_type.isSigned)) raw = BigInt.asUintN(64, raw);
94
+ const result = convert([raw], decoder)[0];
95
+ return result instanceof Date ? `${result.getUTCFullYear()}-${String(result.getUTCMonth() + 1).padStart(2, "0")}-${String(result.getUTCDate()).padStart(2, "0")}` : result;
96
+ }
97
+ };
98
+ },
99
+ skip() {
100
+ take();
101
+ },
102
+ position: () => offset,
103
+ finish() {
104
+ if (index !== count || offset !== bytes.length) throw new Error("bounded Parquet trailing or incomplete plain data");
105
+ }
106
+ };
107
+ }
108
+ export { hybridCursor, plainCursor };
@@ -1,7 +1,7 @@
1
1
  import { ByteWriter } from "./bytewriter.mjs";
2
2
  import "./schema.mjs";
3
3
  import "./wkb.mjs";
4
- import "./parquet-writer.mjs";
4
+ import { ParquetWriter } from "./parquet-writer.mjs";
5
5
  import "./write.mjs";
6
6
  import { parquetWriteRows } from "./write-rows.mjs";
7
- export { ByteWriter, parquetWriteRows };
7
+ export { ByteWriter, ParquetWriter, parquetWriteRows };
@@ -1,8 +1,8 @@
1
1
  import { ByteWriter } from "./bytewriter.mjs";
2
2
  import "./schema.mjs";
3
3
  import "./wkb.mjs";
4
- import "./parquet-writer.mjs";
4
+ import { ParquetWriter } from "./parquet-writer.mjs";
5
5
  import "./write.mjs";
6
6
  import { parquetWriteRows } from "./write-rows.mjs";
7
7
  import "./index.mjs";
8
- export { ByteWriter, parquetWriteRows };
8
+ export { ByteWriter, ParquetWriter, parquetWriteRows };