qvdjs 0.9.0 → 0.9.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/index.cjs +191 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +190 -40
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import path from 'path';
|
|
2
2
|
import fs2 from 'fs';
|
|
3
3
|
import crypto from 'crypto';
|
|
4
4
|
import xml2 from 'xml2js';
|
|
@@ -250,20 +250,42 @@ var init_QvdSymbol = __esm({
|
|
|
250
250
|
};
|
|
251
251
|
}
|
|
252
252
|
});
|
|
253
|
+
function isWithinDirectory(resolvedBaseDir, resolvedPath) {
|
|
254
|
+
const isCaseInsensitiveFS = process.platform === "win32" || process.platform === "darwin";
|
|
255
|
+
const base = isCaseInsensitiveFS ? resolvedBaseDir.toLowerCase() : resolvedBaseDir;
|
|
256
|
+
const target = isCaseInsensitiveFS ? resolvedPath.toLowerCase() : resolvedPath;
|
|
257
|
+
const relative = path.relative(base, target);
|
|
258
|
+
if (relative === "") {
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
if (path.isAbsolute(relative)) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
return relative !== ".." && !relative.startsWith(`..${path.sep}`);
|
|
265
|
+
}
|
|
253
266
|
function validatePath(filePath, allowedDir) {
|
|
267
|
+
if (typeof filePath !== "string" || filePath.length === 0) {
|
|
268
|
+
throw new QvdValidationError("filePath must be a non-empty string", {
|
|
269
|
+
provided: filePath,
|
|
270
|
+
type: typeof filePath
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
if (allowedDir !== void 0 && allowedDir !== null && typeof allowedDir !== "string") {
|
|
274
|
+
throw new QvdValidationError("allowedDir must be a string, null or undefined", {
|
|
275
|
+
provided: allowedDir,
|
|
276
|
+
type: typeof allowedDir
|
|
277
|
+
});
|
|
278
|
+
}
|
|
254
279
|
if (filePath.includes("\0")) {
|
|
255
280
|
throw new QvdSecurityError("Path traversal detected: Null byte in path", {
|
|
256
281
|
path: filePath,
|
|
257
282
|
reason: "null_byte"
|
|
258
283
|
});
|
|
259
284
|
}
|
|
285
|
+
const resolvedPath = path.resolve(filePath);
|
|
260
286
|
const baseDir = allowedDir || process.cwd();
|
|
261
|
-
const resolvedBaseDir =
|
|
262
|
-
|
|
263
|
-
const isCaseInsensitiveFS = process.platform === "win32" || process.platform === "darwin";
|
|
264
|
-
const baseForComparison = isCaseInsensitiveFS ? resolvedBaseDir.toLowerCase() : resolvedBaseDir;
|
|
265
|
-
const pathForComparison = isCaseInsensitiveFS ? resolvedPath.toLowerCase() : resolvedPath;
|
|
266
|
-
if (!pathForComparison.startsWith(baseForComparison + path2.sep) && pathForComparison !== baseForComparison) {
|
|
287
|
+
const resolvedBaseDir = path.resolve(baseDir);
|
|
288
|
+
if (!isWithinDirectory(resolvedBaseDir, resolvedPath)) {
|
|
267
289
|
throw new QvdSecurityError("Path traversal detected: Access denied", {
|
|
268
290
|
path: filePath,
|
|
269
291
|
resolvedPath,
|
|
@@ -297,7 +319,9 @@ var init_QvdFileWriter = __esm({
|
|
|
297
319
|
* @param {QvdDataFrame} df The data frame to write to the QVD file.
|
|
298
320
|
* @param {Object} [options={}] Options for the writer.
|
|
299
321
|
* @param {string} [options.allowedDir] Optional allowed directory path. If provided, the file
|
|
300
|
-
* path must be within this directory. Defaults to current working directory.
|
|
322
|
+
* path must be within this directory. Defaults to the current working directory. To permit
|
|
323
|
+
* an entire volume, pass its root explicitly ('/' on POSIX, 'C:\\' on Windows); a null or
|
|
324
|
+
* empty value falls back to the working directory rather than removing the restriction.
|
|
301
325
|
* @param {Function} [options.onProgress] Optional progress callback function.
|
|
302
326
|
*/
|
|
303
327
|
constructor(filePath, df, options = {}) {
|
|
@@ -369,7 +393,7 @@ var init_QvdFileWriter = __esm({
|
|
|
369
393
|
SourceFileUtcTime: existingMetadata.SourceFileUtcTime || "",
|
|
370
394
|
SourceFileSize: existingMetadata.SourceFileSize || -1,
|
|
371
395
|
StaleUtcTime: existingMetadata.StaleUtcTime || "",
|
|
372
|
-
TableName: existingMetadata.TableName ||
|
|
396
|
+
TableName: existingMetadata.TableName || path.basename(this._path, path.extname(this._path)),
|
|
373
397
|
Compression: existingMetadata.Compression || "",
|
|
374
398
|
Comment: existingMetadata.Comment || "",
|
|
375
399
|
EncryptionInfo: existingMetadata.EncryptionInfo || "",
|
|
@@ -389,7 +413,7 @@ var init_QvdFileWriter = __esm({
|
|
|
389
413
|
SourceFileUtcTime: "",
|
|
390
414
|
SourceFileSize: -1,
|
|
391
415
|
StaleUtcTime: "",
|
|
392
|
-
TableName:
|
|
416
|
+
TableName: path.basename(this._path, path.extname(this._path)),
|
|
393
417
|
Compression: "",
|
|
394
418
|
Comment: "",
|
|
395
419
|
EncryptionInfo: "",
|
|
@@ -559,21 +583,22 @@ var init_QvdFileWriter = __esm({
|
|
|
559
583
|
this._emitProgress("index-table", processedRows, numRows);
|
|
560
584
|
}
|
|
561
585
|
});
|
|
562
|
-
this._df.columns.forEach((column) => {
|
|
563
|
-
const bitOffset = this._indexTableMetadata?.slice(0,
|
|
586
|
+
this._df.columns.forEach((column, columnIndex) => {
|
|
587
|
+
const bitOffset = this._indexTableMetadata?.slice(0, columnIndex).reduce((sum, metadata) => sum + metadata[1], 0);
|
|
564
588
|
assert(this._indexTable, "The QVD file header has not been parsed.");
|
|
565
|
-
const
|
|
566
|
-
...this._indexTable.map(
|
|
567
|
-
(indices) => indices[this._df.columns.indexOf(column)].length
|
|
568
|
-
)
|
|
569
|
-
) : 0;
|
|
570
|
-
const fieldContainsNull = this._symbolTableMetadata?.[this._df.columns.indexOf(column)][2];
|
|
589
|
+
const fieldContainsNull = this._symbolTableMetadata?.[columnIndex][2];
|
|
571
590
|
const bias = fieldContainsNull ? -2 : 0;
|
|
591
|
+
const symbolCount = this._symbolTable?.[columnIndex].length ?? 0;
|
|
592
|
+
const maxStoredIndex = symbolCount === 0 ? 0 : symbolCount - 1 + (fieldContainsNull ? 2 : 0);
|
|
593
|
+
const bitWidth = maxStoredIndex === 0 ? 0 : 32 - Math.clz32(maxStoredIndex);
|
|
572
594
|
this._indexTableMetadata?.push([bitOffset, bitWidth, bias]);
|
|
573
595
|
this._indexTable.forEach((indices) => {
|
|
574
|
-
const bitString = indices[
|
|
575
|
-
|
|
576
|
-
|
|
596
|
+
const bitString = indices[columnIndex];
|
|
597
|
+
assert(
|
|
598
|
+
bitString === "0" || bitString.length <= bitWidth,
|
|
599
|
+
`Stored index for column '${column}' needs ${bitString.length} bits, but BitWidth was derived as ${bitWidth} from ${symbolCount} symbols. The symbol table and the index table are out of sync.`
|
|
600
|
+
);
|
|
601
|
+
indices[columnIndex] = bitWidth === 0 ? "" : bitString.padStart(bitWidth, "0");
|
|
577
602
|
});
|
|
578
603
|
});
|
|
579
604
|
this._indexBuffer = Buffer.concat(
|
|
@@ -581,6 +606,9 @@ var init_QvdFileWriter = __esm({
|
|
|
581
606
|
this._indexTable.map((indices) => {
|
|
582
607
|
indices.reverse();
|
|
583
608
|
const bits = indices.join("");
|
|
609
|
+
if (bits.length === 0) {
|
|
610
|
+
return Buffer.from(Uint8Array.from([0]));
|
|
611
|
+
}
|
|
584
612
|
const paddingWidth = (8 - bits.length % 8) % 8;
|
|
585
613
|
const paddedBits = bits.padStart(bits.length + paddingWidth, "0");
|
|
586
614
|
const bytes = paddedBits.match(/.{1,8}/g)?.map((byte) => parseInt(byte, 2));
|
|
@@ -793,7 +821,7 @@ function validateFieldMetadata(field, symbolBufferLength, filePath) {
|
|
|
793
821
|
});
|
|
794
822
|
}
|
|
795
823
|
}
|
|
796
|
-
function validateIndexTableMetadata(recordSize, totalRows, indexTableLength, indexTableOffset, bufferLength, rowsToLoad, filePath) {
|
|
824
|
+
function validateIndexTableMetadata(recordSize, totalRows, indexTableLength, indexTableOffset, bufferLength, rowsToLoad, filePath, fileSize = null) {
|
|
797
825
|
if (isNaN(recordSize) || !Number.isSafeInteger(recordSize) || recordSize < 0) {
|
|
798
826
|
throw new QvdCorruptedError("Invalid record byte size", {
|
|
799
827
|
recordSize,
|
|
@@ -839,16 +867,40 @@ function validateIndexTableMetadata(recordSize, totalRows, indexTableLength, ind
|
|
|
839
867
|
stage: "parseIndexTable"
|
|
840
868
|
});
|
|
841
869
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
throw new QvdCorruptedError("Index table length unreasonably large", {
|
|
870
|
+
if (indexTableLength !== totalRows * recordSize) {
|
|
871
|
+
throw new QvdCorruptedError("Index table length inconsistent with record count", {
|
|
845
872
|
indexTableLength,
|
|
846
|
-
|
|
873
|
+
expectedLength: totalRows * recordSize,
|
|
874
|
+
totalRows,
|
|
875
|
+
recordSize,
|
|
847
876
|
file: filePath,
|
|
848
877
|
stage: "parseIndexTable"
|
|
849
878
|
});
|
|
850
879
|
}
|
|
880
|
+
if (fileSize !== null) {
|
|
881
|
+
if (indexTableOffset + indexTableLength > fileSize) {
|
|
882
|
+
throw new QvdCorruptedError("Index table extends beyond the end of the file", {
|
|
883
|
+
indexTableOffset,
|
|
884
|
+
indexTableLength,
|
|
885
|
+
fileSize,
|
|
886
|
+
file: filePath,
|
|
887
|
+
stage: "parseIndexTable"
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
}
|
|
851
891
|
const requiredIndexBytes = rowsToLoad * recordSize;
|
|
892
|
+
if (indexTableOffset + requiredIndexBytes > bufferLength) {
|
|
893
|
+
throw new QvdCorruptedError("Index table truncated", {
|
|
894
|
+
indexTableOffset,
|
|
895
|
+
requiredBytes: requiredIndexBytes,
|
|
896
|
+
availableBytes: Math.max(0, bufferLength - indexTableOffset),
|
|
897
|
+
rowsToLoad,
|
|
898
|
+
recordSize,
|
|
899
|
+
bufferSize: bufferLength,
|
|
900
|
+
file: filePath,
|
|
901
|
+
stage: "parseIndexTable"
|
|
902
|
+
});
|
|
903
|
+
}
|
|
852
904
|
if (indexTableLength < requiredIndexBytes) {
|
|
853
905
|
throw new QvdCorruptedError("Index table length smaller than required", {
|
|
854
906
|
indexTableLength,
|
|
@@ -1159,7 +1211,7 @@ var QvdFileReader_exports = {};
|
|
|
1159
1211
|
__export(QvdFileReader_exports, {
|
|
1160
1212
|
QvdFileReader: () => QvdFileReader
|
|
1161
1213
|
});
|
|
1162
|
-
var QvdFileReader;
|
|
1214
|
+
var MAX_HEADER_SIZE, READ_CHUNK_SIZE, QvdFileReader;
|
|
1163
1215
|
var init_QvdFileReader = __esm({
|
|
1164
1216
|
"src/QvdFileReader.js"() {
|
|
1165
1217
|
init_QvdDataFrame();
|
|
@@ -1169,6 +1221,8 @@ var init_QvdFileReader = __esm({
|
|
|
1169
1221
|
init_memoryUtils();
|
|
1170
1222
|
init_validationUtils();
|
|
1171
1223
|
init_symbolParser();
|
|
1224
|
+
MAX_HEADER_SIZE = 16 * 1024 * 1024;
|
|
1225
|
+
READ_CHUNK_SIZE = 512 * 1024 * 1024;
|
|
1172
1226
|
QvdFileReader = class {
|
|
1173
1227
|
/**
|
|
1174
1228
|
* Constructs a new QVD file parser.
|
|
@@ -1176,7 +1230,9 @@ var init_QvdFileReader = __esm({
|
|
|
1176
1230
|
* @param {string} filePath The path to the QVD file to load.
|
|
1177
1231
|
* @param {Object} [options={}] Options for the reader.
|
|
1178
1232
|
* @param {string} [options.allowedDir] Optional allowed directory path. If provided, the file
|
|
1179
|
-
* path must be within this directory. Defaults to current working directory.
|
|
1233
|
+
* path must be within this directory. Defaults to the current working directory. To permit
|
|
1234
|
+
* an entire volume, pass its root explicitly ('/' on POSIX, 'C:\\' on Windows); a null or
|
|
1235
|
+
* empty value falls back to the working directory rather than removing the restriction.
|
|
1180
1236
|
* @param {number} [options.memorySafetyFactor=0.3] Memory safety factor (0.0-1.0). Determines
|
|
1181
1237
|
* what percentage of available memory (or V8 heap limit, whichever is smaller) can be used
|
|
1182
1238
|
* for loading QVD files. Default is 0.3 (30%). Increase for larger heap configurations.
|
|
@@ -1192,6 +1248,7 @@ var init_QvdFileReader = __esm({
|
|
|
1192
1248
|
this._header = null;
|
|
1193
1249
|
this._symbolTable = null;
|
|
1194
1250
|
this._indexTable = null;
|
|
1251
|
+
this._fileSize = null;
|
|
1195
1252
|
}
|
|
1196
1253
|
/**
|
|
1197
1254
|
* Reads the binary data of the QVD file.
|
|
@@ -1223,6 +1280,7 @@ var init_QvdFileReader = __esm({
|
|
|
1223
1280
|
async _readData(maxRows = null) {
|
|
1224
1281
|
if (maxRows === null) {
|
|
1225
1282
|
this._buffer = await fs2.promises.readFile(this._path);
|
|
1283
|
+
this._fileSize = this._buffer.length;
|
|
1226
1284
|
return;
|
|
1227
1285
|
}
|
|
1228
1286
|
const HEADER_DELIMITER = "\r\n\0";
|
|
@@ -1230,19 +1288,41 @@ var init_QvdFileReader = __esm({
|
|
|
1230
1288
|
const stream = fs2.createReadStream(this._path, {
|
|
1231
1289
|
highWaterMark: CHUNK_SIZE
|
|
1232
1290
|
});
|
|
1291
|
+
const headerChunks = [];
|
|
1292
|
+
let headerBytes = 0;
|
|
1293
|
+
let tail = Buffer.alloc(0);
|
|
1233
1294
|
let headerBuffer = Buffer.alloc(0);
|
|
1234
1295
|
let headerDelimiterIndex = -1;
|
|
1235
1296
|
try {
|
|
1236
1297
|
for await (const chunk of stream) {
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1298
|
+
const chunkStart = headerBytes;
|
|
1299
|
+
const searchBuffer = tail.length > 0 ? Buffer.concat([tail, chunk]) : chunk;
|
|
1300
|
+
const foundInSearch = searchBuffer.indexOf(HEADER_DELIMITER);
|
|
1301
|
+
headerChunks.push(chunk);
|
|
1302
|
+
headerBytes += chunk.length;
|
|
1303
|
+
if (foundInSearch !== -1) {
|
|
1304
|
+
headerDelimiterIndex = chunkStart - tail.length + foundInSearch;
|
|
1240
1305
|
stream.destroy();
|
|
1241
1306
|
break;
|
|
1242
1307
|
}
|
|
1308
|
+
tail = Buffer.from(searchBuffer.subarray(-(HEADER_DELIMITER.length - 1)));
|
|
1309
|
+
if (headerBytes > MAX_HEADER_SIZE) {
|
|
1310
|
+
stream.destroy();
|
|
1311
|
+
throw new QvdCorruptedError(
|
|
1312
|
+
`The XML header delimiter was not found within the first ${MAX_HEADER_SIZE / (1024 * 1024)}MB of the file.`,
|
|
1313
|
+
{
|
|
1314
|
+
file: this._path,
|
|
1315
|
+
bytesSearched: headerBytes,
|
|
1316
|
+
maxHeaderSize: MAX_HEADER_SIZE,
|
|
1317
|
+
stage: "readData"
|
|
1318
|
+
}
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1243
1321
|
}
|
|
1244
1322
|
} catch (error) {
|
|
1245
|
-
|
|
1323
|
+
const isExpectedEarlyClose = headerDelimiterIndex !== -1 && error !== null && typeof error === "object" && /** @type {{code?: unknown}} */
|
|
1324
|
+
error.code === "ERR_STREAM_PREMATURE_CLOSE";
|
|
1325
|
+
if (!isExpectedEarlyClose) {
|
|
1246
1326
|
throw error;
|
|
1247
1327
|
}
|
|
1248
1328
|
}
|
|
@@ -1255,6 +1335,7 @@ var init_QvdFileReader = __esm({
|
|
|
1255
1335
|
}
|
|
1256
1336
|
);
|
|
1257
1337
|
}
|
|
1338
|
+
headerBuffer = Buffer.concat(headerChunks);
|
|
1258
1339
|
const headerEndIndex = headerDelimiterIndex + HEADER_DELIMITER.length;
|
|
1259
1340
|
const headerXml = headerBuffer.subarray(0, headerEndIndex).toString();
|
|
1260
1341
|
const headerObj = await xml2.parseStringPromise(headerXml, { explicitArray: false });
|
|
@@ -1271,12 +1352,50 @@ var init_QvdFileReader = __esm({
|
|
|
1271
1352
|
const totalRows = parseInt(headerObj["QvdTableHeader"]["NoOfRecords"], 10);
|
|
1272
1353
|
const rowsToLoad = Math.min(maxRows, totalRows);
|
|
1273
1354
|
validateSymbolTableSizeEarly(symbolTableLength, this._path);
|
|
1355
|
+
for (const [name, value] of [
|
|
1356
|
+
["Offset", symbolTableLength],
|
|
1357
|
+
["RecordByteSize", recordSize],
|
|
1358
|
+
["NoOfRecords", totalRows]
|
|
1359
|
+
]) {
|
|
1360
|
+
if (!Number.isSafeInteger(value) || Number(value) < 0) {
|
|
1361
|
+
throw new QvdCorruptedError(`Invalid header value: ${name}`, {
|
|
1362
|
+
name,
|
|
1363
|
+
value,
|
|
1364
|
+
file: this._path,
|
|
1365
|
+
stage: "readData"
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1274
1369
|
const indexTableBytesToRead = rowsToLoad * recordSize;
|
|
1275
1370
|
const totalBytesToRead = indexTableOffset + indexTableBytesToRead;
|
|
1276
1371
|
const fd = await fs2.promises.open(this._path, "r");
|
|
1277
1372
|
try {
|
|
1278
|
-
|
|
1279
|
-
|
|
1373
|
+
const { size: fileSize } = await fd.stat();
|
|
1374
|
+
this._fileSize = fileSize;
|
|
1375
|
+
if (totalBytesToRead > fileSize) {
|
|
1376
|
+
throw new QvdCorruptedError("The file is shorter than its header claims.", {
|
|
1377
|
+
file: this._path,
|
|
1378
|
+
fileSize,
|
|
1379
|
+
requiredBytes: totalBytesToRead,
|
|
1380
|
+
stage: "readData"
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
this._buffer = Buffer.alloc(totalBytesToRead);
|
|
1384
|
+
let position = 0;
|
|
1385
|
+
while (position < totalBytesToRead) {
|
|
1386
|
+
const length = Math.min(READ_CHUNK_SIZE, totalBytesToRead - position);
|
|
1387
|
+
const { bytesRead } = await fd.read(this._buffer, position, length, position);
|
|
1388
|
+
if (bytesRead === 0) {
|
|
1389
|
+
throw new QvdCorruptedError("Unexpected end of file while reading QVD data.", {
|
|
1390
|
+
file: this._path,
|
|
1391
|
+
fileSize,
|
|
1392
|
+
bytesRead: position,
|
|
1393
|
+
requiredBytes: totalBytesToRead,
|
|
1394
|
+
stage: "readData"
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
position += bytesRead;
|
|
1398
|
+
}
|
|
1280
1399
|
} finally {
|
|
1281
1400
|
await fd.close();
|
|
1282
1401
|
}
|
|
@@ -1378,7 +1497,10 @@ var init_QvdFileReader = __esm({
|
|
|
1378
1497
|
} else {
|
|
1379
1498
|
symbolIndex = this._convertBitsToInt32(mask.slice(bitOffset, bitOffset + bitWidth));
|
|
1380
1499
|
}
|
|
1381
|
-
symbolIndex
|
|
1500
|
+
symbolIndex += bias;
|
|
1501
|
+
if (symbolIndex < 0) {
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1382
1504
|
const fieldName = field["FieldName"];
|
|
1383
1505
|
symbolUsage.get(fieldName)?.add(symbolIndex);
|
|
1384
1506
|
});
|
|
@@ -1483,7 +1605,8 @@ var init_QvdFileReader = __esm({
|
|
|
1483
1605
|
this._indexTableOffset,
|
|
1484
1606
|
this._buffer.length,
|
|
1485
1607
|
rowsToLoad,
|
|
1486
|
-
this._path
|
|
1608
|
+
this._path,
|
|
1609
|
+
this._fileSize
|
|
1487
1610
|
);
|
|
1488
1611
|
const indexBuffer = this._buffer.subarray(this._indexTableOffset, this._indexTableOffset + indexTableLength + 1);
|
|
1489
1612
|
for (const field of fields) {
|
|
@@ -1519,14 +1642,33 @@ var init_QvdFileReader = __esm({
|
|
|
1519
1642
|
});
|
|
1520
1643
|
this._indexTable.push(symbolIndices);
|
|
1521
1644
|
}
|
|
1645
|
+
if (this._indexTable.length !== rowsToLoad) {
|
|
1646
|
+
throw new QvdCorruptedError("Index table contains fewer records than expected", {
|
|
1647
|
+
expectedRows: rowsToLoad,
|
|
1648
|
+
actualRows: this._indexTable.length,
|
|
1649
|
+
totalRows,
|
|
1650
|
+
recordSize,
|
|
1651
|
+
file: this._path,
|
|
1652
|
+
stage: "parseIndexTable"
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1522
1655
|
}
|
|
1523
1656
|
/**
|
|
1524
1657
|
* Loads the QVD file into memory and parses it.
|
|
1525
1658
|
*
|
|
1526
1659
|
* @param {number|null} maxRows The maximum number of rows to load. If null, all rows are loaded.
|
|
1660
|
+
* Must be a non-negative integer when given.
|
|
1661
|
+
* @throws {QvdValidationError} If maxRows is neither null nor a non-negative integer.
|
|
1527
1662
|
* @return {Promise<QvdDataFrame>} The loaded QVD file.
|
|
1528
1663
|
*/
|
|
1529
1664
|
async load(maxRows = null) {
|
|
1665
|
+
if (maxRows !== null && (typeof maxRows !== "number" || !Number.isInteger(maxRows) || maxRows < 0)) {
|
|
1666
|
+
throw new QvdValidationError("maxRows must be a non-negative integer, or null to load all rows", {
|
|
1667
|
+
provided: maxRows,
|
|
1668
|
+
type: typeof maxRows,
|
|
1669
|
+
file: this._path
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1530
1672
|
await this._readData(maxRows);
|
|
1531
1673
|
await this._parseHeader();
|
|
1532
1674
|
let symbolsToKeep = null;
|
|
@@ -1561,7 +1703,7 @@ var init_QvdFileReader = __esm({
|
|
|
1561
1703
|
const symbol = this._symbolTable?.[fieldIndex]?.[symbolIndex];
|
|
1562
1704
|
const value = symbol?.toPrimaryValue();
|
|
1563
1705
|
if (typeof value === "string") {
|
|
1564
|
-
if (!isNaN(Number(value))) {
|
|
1706
|
+
if (value.trim() !== "" && !isNaN(Number(value))) {
|
|
1565
1707
|
return Number(value);
|
|
1566
1708
|
}
|
|
1567
1709
|
}
|
|
@@ -1971,7 +2113,10 @@ var init_QvdDataFrame = __esm({
|
|
|
1971
2113
|
*
|
|
1972
2114
|
* @param {string} path The path to the QVD file.
|
|
1973
2115
|
* @param {Object} [options] Optional writing options.
|
|
1974
|
-
* @param {string} [options.allowedDir] Optional allowed directory path. If provided, the file path
|
|
2116
|
+
* @param {string} [options.allowedDir] Optional allowed directory path. If provided, the file path
|
|
2117
|
+
* must be within this directory. Defaults to the current working directory. To permit an entire
|
|
2118
|
+
* volume, pass its root explicitly ('/' on POSIX, 'C:\\' on Windows); a null or empty value falls
|
|
2119
|
+
* back to the working directory rather than removing the restriction.
|
|
1975
2120
|
* @param {Function} [options.onProgress] Optional progress callback function that receives progress updates during write operations.
|
|
1976
2121
|
*/
|
|
1977
2122
|
async toQvd(path3, options = {}) {
|
|
@@ -1987,11 +2132,16 @@ var init_QvdDataFrame = __esm({
|
|
|
1987
2132
|
*
|
|
1988
2133
|
* @param {string} path The path to the QVD file.
|
|
1989
2134
|
* @param {Object} [options] Optional loading options.
|
|
1990
|
-
* @param {number|null} [options.maxRows] The maximum number of rows to load.
|
|
1991
|
-
*
|
|
2135
|
+
* @param {number|null} [options.maxRows] The maximum number of rows to load. Must be a non-negative
|
|
2136
|
+
* integer; if not specified or null, all rows are loaded. Anything else throws a QvdValidationError.
|
|
2137
|
+
* @param {string} [options.allowedDir] Optional allowed directory path. If provided, the file path
|
|
2138
|
+
* must be within this directory. Defaults to the current working directory. To permit an entire
|
|
2139
|
+
* volume, pass its root explicitly ('/' on POSIX, 'C:\\' on Windows); a null or empty value falls
|
|
2140
|
+
* back to the working directory rather than removing the restriction.
|
|
1992
2141
|
* @param {number} [options.memorySafetyFactor=0.3] Memory safety factor (0.0-1.0). Determines what percentage
|
|
1993
2142
|
* of available memory (or V8 heap limit, whichever is smaller) can be used. Default is 0.3 (30%).
|
|
1994
2143
|
* Increase this (e.g., to 0.5 or 0.7) when running with larger heap sizes via --max-old-space-size.
|
|
2144
|
+
* @throws {QvdValidationError} If options.maxRows is neither null/undefined nor a non-negative integer.
|
|
1995
2145
|
* @return {Promise<QvdDataFrame>} The data frame of the QVD file.
|
|
1996
2146
|
*/
|
|
1997
2147
|
static async fromQvd(path3, options = {}) {
|