@zakkster/lite-bake-stream 1.0.0
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/CHANGELOG.md +457 -0
- package/LICENSE +21 -0
- package/README.md +150 -0
- package/SPEC.md +364 -0
- package/llms.txt +81 -0
- package/package.json +117 -0
- package/src/FileIngest.js +104 -0
- package/src/MultiReader.js +160 -0
- package/src/PreserveReader.js +180 -0
- package/src/PreserveTokenizer.js +172 -0
- package/src/PreserveWriter.js +218 -0
- package/src/RangeReader.js +470 -0
- package/src/Reader.js +349 -0
- package/src/Split.js +359 -0
- package/src/StringTable.js +225 -0
- package/src/Tokenizer.js +691 -0
- package/src/Writer.js +713 -0
- package/src/index.js +193 -0
- package/types/FileIngest.d.ts +44 -0
- package/types/MultiReader.d.ts +36 -0
- package/types/PreserveReader.d.ts +44 -0
- package/types/PreserveTokenizer.d.ts +27 -0
- package/types/PreserveWriter.d.ts +35 -0
- package/types/RangeReader.d.ts +93 -0
- package/types/Reader.d.ts +85 -0
- package/types/Split.d.ts +66 -0
- package/types/StringTable.d.ts +23 -0
- package/types/Tokenizer.d.ts +42 -0
- package/types/Writer.d.ts +92 -0
- package/types/index.d.ts +58 -0
package/src/Reader.js
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
// @zakkster/lite-bake-stream / Reader
|
|
2
|
+
// Parses LBK1 container and exposes typed-array views over shard payloads.
|
|
3
|
+
// Copyright (c) 2026 Zahary Shinikchiev. MIT.
|
|
4
|
+
//
|
|
5
|
+
// v1 lane kinds: F64 (1) and U32 (3, string-table index).
|
|
6
|
+
// Per-shard string tables live immediately after each shard's payload; the
|
|
7
|
+
// shard directory carries their offset+length. Reader parses each on load.
|
|
8
|
+
//
|
|
9
|
+
// Error codes:
|
|
10
|
+
// R_BAD_MAGIC - header magic mismatch
|
|
11
|
+
// R_UNSUPPORTED_VERSION - format_version > 1
|
|
12
|
+
// R_UNSUPPORTED_ENDIAN - endian byte is neither LE nor BE
|
|
13
|
+
// R_BAD_FIELD_FLAGS - a FieldDescriptor.flags is non-zero (v2+ reserved)
|
|
14
|
+
// R_UNSUPPORTED_LANE - lane_kind is not F64 or U32
|
|
15
|
+
// R_SHARD_VERSION_TOO_NEW - a shard's min_reader_version > this reader
|
|
16
|
+
// R_TRUNCATED - container ends before an expected structure
|
|
17
|
+
// R_UNKNOWN_FIELD - get() called with an unknown field name
|
|
18
|
+
|
|
19
|
+
import { StringTable } from './StringTable.js';
|
|
20
|
+
|
|
21
|
+
export const VERSION = '1.0.0';
|
|
22
|
+
|
|
23
|
+
const CONTAINER_HEADER_BYTES = 48;
|
|
24
|
+
const SHARD_ENTRY_BYTES = 40;
|
|
25
|
+
const FIELD_DESCRIPTOR_BYTES = 24;
|
|
26
|
+
const FOOTER_BYTES = 16;
|
|
27
|
+
|
|
28
|
+
const LANE_F64 = 1;
|
|
29
|
+
const LANE_U32 = 3;
|
|
30
|
+
const READER_VERSION = 1;
|
|
31
|
+
|
|
32
|
+
export class ReaderError extends Error {
|
|
33
|
+
constructor(code, msg) { super(msg); this.code = code; this.name = 'ReaderError'; }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class Reader {
|
|
37
|
+
static fromBuffer(bufferOrArrayBuffer) {
|
|
38
|
+
const buffer = bufferOrArrayBuffer instanceof ArrayBuffer
|
|
39
|
+
? bufferOrArrayBuffer
|
|
40
|
+
: bufferOrArrayBuffer.buffer;
|
|
41
|
+
return new Reader(buffer);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
constructor(buffer) {
|
|
45
|
+
this._buffer = buffer;
|
|
46
|
+
this._dv = new DataView(buffer);
|
|
47
|
+
this._bytes = new Uint8Array(buffer);
|
|
48
|
+
this._parseHeader();
|
|
49
|
+
this._parseSchema();
|
|
50
|
+
this._parseShardDirectory();
|
|
51
|
+
this._parseZoneMaps(); // M7 — no-op if metadata_off is 0
|
|
52
|
+
this._buildFieldIndex();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
_parseHeader() {
|
|
56
|
+
if (this._buffer.byteLength < CONTAINER_HEADER_BYTES) {
|
|
57
|
+
throw new ReaderError('R_TRUNCATED', 'container smaller than header');
|
|
58
|
+
}
|
|
59
|
+
const b = this._bytes;
|
|
60
|
+
if (b[0] !== 0x4C || b[1] !== 0x42 || b[2] !== 0x4B || b[3] !== 0x31) {
|
|
61
|
+
throw new ReaderError('R_BAD_MAGIC', 'header magic is not LBK1');
|
|
62
|
+
}
|
|
63
|
+
const version = this._dv.getUint16(4, true);
|
|
64
|
+
if (version > 1) throw new ReaderError('R_UNSUPPORTED_VERSION', 'format_version=' + version);
|
|
65
|
+
const endian = b[6];
|
|
66
|
+
if (endian !== 1 && endian !== 2) throw new ReaderError('R_UNSUPPORTED_ENDIAN', 'endian=' + endian);
|
|
67
|
+
if (endian !== 1) throw new ReaderError('R_UNSUPPORTED_ENDIAN', 'BE payloads not implemented in v1 reader');
|
|
68
|
+
|
|
69
|
+
// Header flags byte (offset 7). Bit 0 = preserve mode. This Reader parses
|
|
70
|
+
// the schema-mode variant only; preserve containers have no schema block
|
|
71
|
+
// and would return garbage if we tried, so refuse loudly.
|
|
72
|
+
const flags = this._bytes[7];
|
|
73
|
+
if (flags & 0x01) {
|
|
74
|
+
throw new ReaderError('R_WRONG_MODE',
|
|
75
|
+
'container is preserve-mode; use PreserveReader (from @zakkster/lite-bake-stream/preserve-reader) or deserialize() which auto-dispatches');
|
|
76
|
+
}
|
|
77
|
+
if (flags & ~0x01) {
|
|
78
|
+
throw new ReaderError('R_BAD_FLAGS', 'unknown flag bits set in header byte 7: 0x' + flags.toString(16));
|
|
79
|
+
}
|
|
80
|
+
this._flags = flags;
|
|
81
|
+
|
|
82
|
+
this._schemaBlockOff = Number(this._dv.getBigUint64(8, true));
|
|
83
|
+
this._metadataOff = Number(this._dv.getBigUint64(16, true)); // 0 = no metadata block; M7+ zone maps
|
|
84
|
+
this._shardDirOff = Number(this._dv.getBigUint64(24, true));
|
|
85
|
+
this._shardCount = this._dv.getUint32(32, true);
|
|
86
|
+
// 4 bytes reserved at 36
|
|
87
|
+
this._totalRows = Number(this._dv.getBigUint64(40, true));
|
|
88
|
+
this._formatVersion = version;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
_parseSchema() {
|
|
92
|
+
const off = this._schemaBlockOff;
|
|
93
|
+
if (off + 8 > this._buffer.byteLength) throw new ReaderError('R_TRUNCATED', 'schema header truncated');
|
|
94
|
+
const fieldCount = this._dv.getUint32(off + 0, true);
|
|
95
|
+
const rowStride = this._dv.getUint32(off + 4, true);
|
|
96
|
+
const descriptorsOff = off + 8;
|
|
97
|
+
const descriptorsBytes = fieldCount * FIELD_DESCRIPTOR_BYTES;
|
|
98
|
+
if (descriptorsOff + descriptorsBytes + 4 > this._buffer.byteLength) {
|
|
99
|
+
throw new ReaderError('R_TRUNCATED', 'schema descriptors truncated');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const fields = new Array(fieldCount);
|
|
103
|
+
const nameBlobLenOff = descriptorsOff + descriptorsBytes;
|
|
104
|
+
const nameBlobLen = this._dv.getUint32(nameBlobLenOff, true);
|
|
105
|
+
const nameBlobOff = nameBlobLenOff + 4;
|
|
106
|
+
if (nameBlobOff + nameBlobLen > this._buffer.byteLength) {
|
|
107
|
+
throw new ReaderError('R_TRUNCATED', 'schema name blob truncated');
|
|
108
|
+
}
|
|
109
|
+
const decoder = new TextDecoder('utf-8');
|
|
110
|
+
|
|
111
|
+
for (let i = 0; i < fieldCount; i++) {
|
|
112
|
+
const descOff = descriptorsOff + i * FIELD_DESCRIPTOR_BYTES;
|
|
113
|
+
const nameLen = this._dv.getUint16(descOff + 0, true);
|
|
114
|
+
const offsetInRow = this._dv.getUint16(descOff + 2, true);
|
|
115
|
+
const laneKind = this._bytes[descOff + 4];
|
|
116
|
+
const flags = this._bytes[descOff + 5];
|
|
117
|
+
const nameStrOff = Number(this._dv.getBigUint64(descOff + 8, true));
|
|
118
|
+
|
|
119
|
+
if (flags !== 0) throw new ReaderError('R_BAD_FIELD_FLAGS', 'field ' + i + ' has non-zero flags (v2+ reserved)');
|
|
120
|
+
if (laneKind !== LANE_F64 && laneKind !== LANE_U32)
|
|
121
|
+
throw new ReaderError('R_UNSUPPORTED_LANE', 'field ' + i + ' lane_kind=' + laneKind);
|
|
122
|
+
|
|
123
|
+
const nameBytes = this._bytes.subarray(nameBlobOff + nameStrOff, nameBlobOff + nameStrOff + nameLen);
|
|
124
|
+
const name = decoder.decode(nameBytes);
|
|
125
|
+
fields[i] = { name, laneKind, offsetInRow };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this._schema = { fields, rowStride };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
_parseShardDirectory() {
|
|
132
|
+
const off = this._shardDirOff;
|
|
133
|
+
const need = this._shardCount * SHARD_ENTRY_BYTES;
|
|
134
|
+
if (off + need > this._buffer.byteLength) throw new ReaderError('R_TRUNCATED', 'shard directory truncated');
|
|
135
|
+
const shards = new Array(this._shardCount);
|
|
136
|
+
for (let i = 0; i < this._shardCount; i++) {
|
|
137
|
+
const entryOff = off + i * SHARD_ENTRY_BYTES;
|
|
138
|
+
const payloadOff = Number(this._dv.getBigUint64(entryOff + 0, true));
|
|
139
|
+
const payloadLen = this._dv.getUint32(entryOff + 8, true);
|
|
140
|
+
const rowCount = this._dv.getUint32(entryOff + 12, true);
|
|
141
|
+
const minReaderVer = this._dv.getUint16(entryOff + 16, true);
|
|
142
|
+
// 2 bytes flags at 18, 4 bytes reserved at 20
|
|
143
|
+
const localStrOff = Number(this._dv.getBigUint64(entryOff + 24, true));
|
|
144
|
+
const localStrLen = Number(this._dv.getBigUint64(entryOff + 32, true));
|
|
145
|
+
if (minReaderVer > READER_VERSION) {
|
|
146
|
+
throw new ReaderError('R_SHARD_VERSION_TOO_NEW',
|
|
147
|
+
'shard ' + i + ' requires reader version ' + minReaderVer);
|
|
148
|
+
}
|
|
149
|
+
if (payloadOff + payloadLen > this._buffer.byteLength) {
|
|
150
|
+
throw new ReaderError('R_TRUNCATED', 'shard ' + i + ' payload truncated');
|
|
151
|
+
}
|
|
152
|
+
// Byte view over payload (no F64 view; row stride can be non-multiple-of-8 with U32 fields)
|
|
153
|
+
const payloadBytes = new Uint8Array(this._buffer, payloadOff, payloadLen);
|
|
154
|
+
const payloadDv = new DataView(this._buffer, payloadOff, payloadLen);
|
|
155
|
+
// String table view (present iff localStrLen > 0)
|
|
156
|
+
let stringTable = null;
|
|
157
|
+
if (localStrLen > 0) {
|
|
158
|
+
if (localStrOff + localStrLen > this._buffer.byteLength) {
|
|
159
|
+
throw new ReaderError('R_TRUNCATED', 'shard ' + i + ' string table truncated');
|
|
160
|
+
}
|
|
161
|
+
stringTable = StringTable.parse(new Uint8Array(this._buffer, localStrOff, localStrLen), 0);
|
|
162
|
+
}
|
|
163
|
+
shards[i] = { payloadOff, payloadLen, rowCount, payloadBytes, payloadDv, stringTable };
|
|
164
|
+
}
|
|
165
|
+
this._shards = shards;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
_buildFieldIndex() {
|
|
169
|
+
const idx = new Map();
|
|
170
|
+
for (let i = 0; i < this._schema.fields.length; i++) idx.set(this._schema.fields[i].name, i);
|
|
171
|
+
this._fieldIndex = idx;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// M7: parse the zone maps segment if present. No-op if _metadataOff is 0
|
|
175
|
+
// or the segment magic doesn't match. Populates:
|
|
176
|
+
// _zoneMapsTrackedFields: number[] -- schema field indices with bounds
|
|
177
|
+
// _zoneMapsFieldToPos: Map<name, t> -- fast field-name -> tracking pos
|
|
178
|
+
// _zoneMapsMins/Maxes: Float64Array -- length shardCount * T, row-major
|
|
179
|
+
_parseZoneMaps() {
|
|
180
|
+
this._zoneMapsTrackedFields = null;
|
|
181
|
+
if (this._metadataOff === 0) return;
|
|
182
|
+
if (this._metadataOff + 16 > this._buffer.byteLength) return;
|
|
183
|
+
const off = this._metadataOff;
|
|
184
|
+
// Check 'ZM01' magic
|
|
185
|
+
if (this._bytes[off] !== 0x30 || this._bytes[off + 1] !== 0x5A ||
|
|
186
|
+
this._bytes[off + 2] !== 0x4D || this._bytes[off + 3] !== 0x31) return;
|
|
187
|
+
const shardCount = this._dv.getUint32(off + 4, true);
|
|
188
|
+
const T = this._dv.getUint32(off + 8, true);
|
|
189
|
+
if (shardCount !== this._shardCount) {
|
|
190
|
+
throw new ReaderError('R_TRUNCATED', 'zone maps shard_count mismatch');
|
|
191
|
+
}
|
|
192
|
+
if (T === 0) return;
|
|
193
|
+
const fieldTableOff = off + 16;
|
|
194
|
+
const fieldTablePad = (8 - ((T * 2) & 7)) & 7;
|
|
195
|
+
const minsOff = fieldTableOff + T * 2 + fieldTablePad;
|
|
196
|
+
const maxesOff = minsOff + shardCount * T * 8;
|
|
197
|
+
const requiredEnd = maxesOff + shardCount * T * 8;
|
|
198
|
+
if (requiredEnd > this._buffer.byteLength) {
|
|
199
|
+
throw new ReaderError('R_TRUNCATED', 'zone maps segment truncated');
|
|
200
|
+
}
|
|
201
|
+
const tracked = new Array(T);
|
|
202
|
+
const fieldToPos = new Map();
|
|
203
|
+
for (let t = 0; t < T; t++) {
|
|
204
|
+
const schemaFieldIdx = this._dv.getUint16(fieldTableOff + t * 2, true);
|
|
205
|
+
if (schemaFieldIdx >= this._schema.fields.length) {
|
|
206
|
+
throw new ReaderError('R_TRUNCATED', 'zone maps references field index ' + schemaFieldIdx);
|
|
207
|
+
}
|
|
208
|
+
tracked[t] = schemaFieldIdx;
|
|
209
|
+
fieldToPos.set(this._schema.fields[schemaFieldIdx].name, t);
|
|
210
|
+
}
|
|
211
|
+
// Zero-copy views over the container's underlying buffer.
|
|
212
|
+
this._zoneMapsTrackedFields = tracked;
|
|
213
|
+
this._zoneMapsFieldToPos = fieldToPos;
|
|
214
|
+
this._zoneMapsMins = new Float64Array(this._buffer, minsOff, shardCount * T);
|
|
215
|
+
this._zoneMapsMaxes = new Float64Array(this._buffer, maxesOff, shardCount * T);
|
|
216
|
+
this._zoneMapsT = T;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ---------- public API ----------
|
|
220
|
+
|
|
221
|
+
get schema() { return this._schema; }
|
|
222
|
+
get totalRows() { return this._totalRows; }
|
|
223
|
+
get shardCount() { return this._shardCount; }
|
|
224
|
+
get shards() { return this._shards; }
|
|
225
|
+
// ---------- internal accessors ----------
|
|
226
|
+
//
|
|
227
|
+
// These exist so sibling modules (Split.js) can do container surgery without
|
|
228
|
+
// reaching into underscore-prefixed fields. They are NOT part of the public
|
|
229
|
+
// API surface and carry no semver guarantee beyond "Split.js can rely on
|
|
230
|
+
// them"; consumers should use the documented public API instead.
|
|
231
|
+
|
|
232
|
+
/** @internal Byte offset of the shard directory. */
|
|
233
|
+
get shardDirectoryOffset() { return this._shardDirOff; }
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* @internal Raw zone-maps state, or null when the container has none.
|
|
237
|
+
* Returns { trackedFields: number[], laneCount: number,
|
|
238
|
+
* mins: Float64Array, maxes: Float64Array }
|
|
239
|
+
* where mins/maxes are row-major [shardIdx * laneCount + trackedPos].
|
|
240
|
+
*/
|
|
241
|
+
zoneMapsRaw() {
|
|
242
|
+
if (!this._zoneMapsTrackedFields) return null;
|
|
243
|
+
return {
|
|
244
|
+
trackedFields: this._zoneMapsTrackedFields,
|
|
245
|
+
laneCount: this._zoneMapsT,
|
|
246
|
+
mins: this._zoneMapsMins,
|
|
247
|
+
maxes: this._zoneMapsMaxes,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
get buffer() { return this._buffer; }
|
|
252
|
+
|
|
253
|
+
// Whether the container carries a zone maps segment. Consumers can pivot
|
|
254
|
+
// to a full-scan strategy when this is false.
|
|
255
|
+
get hasZoneMaps() { return this._zoneMapsTrackedFields !== null; }
|
|
256
|
+
|
|
257
|
+
// {min, max} for the given field within the given shard, or null if:
|
|
258
|
+
// - the container has no zone maps, or
|
|
259
|
+
// - the field is not tracked (currently: U32/string fields are never tracked in v1).
|
|
260
|
+
// For empty ranges (a shard where the field defaulted to 0), returns {min:0, max:0}.
|
|
261
|
+
shardBounds(shardIdx, fieldName) {
|
|
262
|
+
if (!this._zoneMapsTrackedFields) return null;
|
|
263
|
+
const t = this._zoneMapsFieldToPos.get(fieldName);
|
|
264
|
+
if (t === undefined) return null;
|
|
265
|
+
if (shardIdx < 0 || shardIdx >= this._shardCount) return null;
|
|
266
|
+
const pos = shardIdx * this._zoneMapsT + t;
|
|
267
|
+
return { min: this._zoneMapsMins[pos], max: this._zoneMapsMaxes[pos] };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Return the indices of every shard whose bounds overlap [min, max].
|
|
271
|
+
// When there are no zone maps or the field is not tracked, returns every
|
|
272
|
+
// shard (i.e. the query planner must fall back to full scan).
|
|
273
|
+
// Semantics: a shard is INCLUDED iff its [smin, smax] overlaps [min, max].
|
|
274
|
+
// A pass-through range ({min: -Inf, max: +Inf} or omitted) returns all shards.
|
|
275
|
+
findShards(fieldName, opts) {
|
|
276
|
+
const all = () => { const a = new Array(this._shardCount); for (let i = 0; i < this._shardCount; i++) a[i] = i; return a; };
|
|
277
|
+
if (!this._zoneMapsTrackedFields) return all();
|
|
278
|
+
const t = this._zoneMapsFieldToPos.get(fieldName);
|
|
279
|
+
if (t === undefined) return all();
|
|
280
|
+
const min = (opts && typeof opts.min === 'number') ? opts.min : Number.NEGATIVE_INFINITY;
|
|
281
|
+
const max = (opts && typeof opts.max === 'number') ? opts.max : Number.POSITIVE_INFINITY;
|
|
282
|
+
const T = this._zoneMapsT;
|
|
283
|
+
const out = [];
|
|
284
|
+
for (let s = 0; s < this._shardCount; s++) {
|
|
285
|
+
const smin = this._zoneMapsMins[s * T + t];
|
|
286
|
+
const smax = this._zoneMapsMaxes[s * T + t];
|
|
287
|
+
// Overlap iff smin <= max AND smax >= min
|
|
288
|
+
if (smin <= max && smax >= min) out.push(s);
|
|
289
|
+
}
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
fieldIndex(name) {
|
|
294
|
+
const i = this._fieldIndex.get(name);
|
|
295
|
+
if (i === undefined) throw new ReaderError('R_UNKNOWN_FIELD', 'no field named ' + JSON.stringify(name));
|
|
296
|
+
return i;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Random-access get across all shards. Returns the field's decoded value:
|
|
300
|
+
// F64 lanes -> number
|
|
301
|
+
// U32 lanes -> JS string (resolved via the shard's local string table)
|
|
302
|
+
// Not zero-alloc; intended for testing/introspection.
|
|
303
|
+
get(rowIdx, fieldName) {
|
|
304
|
+
let remaining = rowIdx;
|
|
305
|
+
const fieldIdx = this.fieldIndex(fieldName);
|
|
306
|
+
const field = this._schema.fields[fieldIdx];
|
|
307
|
+
for (let s = 0; s < this._shards.length; s++) {
|
|
308
|
+
const shard = this._shards[s];
|
|
309
|
+
if (remaining < shard.rowCount) {
|
|
310
|
+
const rowOff = remaining * this._schema.rowStride;
|
|
311
|
+
if (field.laneKind === LANE_F64) {
|
|
312
|
+
return shard.payloadDv.getFloat64(rowOff + field.offsetInRow, true);
|
|
313
|
+
} else {
|
|
314
|
+
const strIdx = shard.payloadDv.getUint32(rowOff + field.offsetInRow, true);
|
|
315
|
+
return shard.stringTable ? shard.stringTable.get(strIdx) : undefined;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
remaining -= shard.rowCount;
|
|
319
|
+
}
|
|
320
|
+
return undefined;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Hot-loop friendly: shard payload as DataView + stride + per-field byte offset.
|
|
324
|
+
shardPayload(shardIdx) { return this._shards[shardIdx].payloadDv; }
|
|
325
|
+
strideBytes() { return this._schema.rowStride; }
|
|
326
|
+
offsetBytes(fieldName) { return this._schema.fields[this.fieldIndex(fieldName)].offsetInRow; }
|
|
327
|
+
laneKind(fieldName) { return this._schema.fields[this.fieldIndex(fieldName)].laneKind; }
|
|
328
|
+
shardStringTable(shardIdx) { return this._shards[shardIdx].stringTable; }
|
|
329
|
+
|
|
330
|
+
// Back-compat with M2: F64-only reader helpers still work when the schema is
|
|
331
|
+
// all-F64 (rowStride is a multiple of 8). For mixed schemas these throw.
|
|
332
|
+
shardF64(shardIdx) {
|
|
333
|
+
const shard = this._shards[shardIdx];
|
|
334
|
+
if ((this._schema.rowStride & 7) !== 0)
|
|
335
|
+
throw new ReaderError('R_UNSUPPORTED_LANE', 'shardF64 not usable on mixed-lane schema');
|
|
336
|
+
return new Float64Array(this._buffer, shard.payloadOff, shard.payloadLen / 8);
|
|
337
|
+
}
|
|
338
|
+
strideF64() {
|
|
339
|
+
if ((this._schema.rowStride & 7) !== 0)
|
|
340
|
+
throw new ReaderError('R_UNSUPPORTED_LANE', 'strideF64 not usable on mixed-lane schema');
|
|
341
|
+
return this._schema.rowStride / 8;
|
|
342
|
+
}
|
|
343
|
+
offsetF64(fieldName) {
|
|
344
|
+
const f = this._schema.fields[this.fieldIndex(fieldName)];
|
|
345
|
+
if (f.laneKind !== LANE_F64) throw new ReaderError('R_UNSUPPORTED_LANE', fieldName + ' is not F64');
|
|
346
|
+
if ((f.offsetInRow & 7) !== 0) throw new ReaderError('R_UNSUPPORTED_LANE', fieldName + ' offset not F64-aligned');
|
|
347
|
+
return f.offsetInRow / 8;
|
|
348
|
+
}
|
|
349
|
+
}
|