qvdjs 0.9.2 → 0.9.3
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/README.md +180 -35
- package/dist/index.cjs +84 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +84 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -734,6 +734,28 @@ var init_bitUtils = __esm({
|
|
|
734
734
|
function getHeapLimit() {
|
|
735
735
|
return v8.getHeapStatistics().heap_size_limit;
|
|
736
736
|
}
|
|
737
|
+
function heapLimitIsMeaningful() {
|
|
738
|
+
return !process.versions.bun && !process.versions.deno;
|
|
739
|
+
}
|
|
740
|
+
function getMemoryBudget() {
|
|
741
|
+
const candidates = [];
|
|
742
|
+
if (heapLimitIsMeaningful()) {
|
|
743
|
+
candidates.push({ source: "V8 heap limit", bytes: getHeapLimit() });
|
|
744
|
+
}
|
|
745
|
+
const constrained = typeof process.constrainedMemory === "function" ? process.constrainedMemory() : 0;
|
|
746
|
+
if (constrained > 0 && constrained < os.totalmem()) {
|
|
747
|
+
candidates.push({ source: "container memory limit", bytes: constrained });
|
|
748
|
+
}
|
|
749
|
+
if (candidates.length === 0) {
|
|
750
|
+
candidates.push({ source: "total system memory", bytes: os.totalmem() });
|
|
751
|
+
}
|
|
752
|
+
const observed = [{ source: "free memory (os.freemem)", bytes: os.freemem() }];
|
|
753
|
+
if (typeof process.availableMemory === "function") {
|
|
754
|
+
observed.push({ source: "available memory", bytes: process.availableMemory() });
|
|
755
|
+
}
|
|
756
|
+
const binding = candidates.reduce((lowest, candidate) => candidate.bytes < lowest.bytes ? candidate : lowest);
|
|
757
|
+
return { bytes: binding.bytes, limitedBy: binding.source, candidates, observed };
|
|
758
|
+
}
|
|
737
759
|
function estimateMemoryUsage(symbolTableSize, maxRows, totalRows) {
|
|
738
760
|
const FULL_PARSE_OVERHEAD = 6;
|
|
739
761
|
const MINIMAL_OVERHEAD = 0.01;
|
|
@@ -750,11 +772,14 @@ function validateMemoryAvailability(symbolTableSize, maxRows, totalRows, filePat
|
|
|
750
772
|
if (typeof safetyFactor !== "number" || safetyFactor < 0 || safetyFactor > 1) {
|
|
751
773
|
throw new QvdValidationError("safetyFactor must be a number between 0.0 and 1.0", { safetyFactor });
|
|
752
774
|
}
|
|
753
|
-
|
|
775
|
+
if (safetyFactor === 0) {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
const budget = getMemoryBudget();
|
|
754
779
|
const heapLimit = getHeapLimit();
|
|
780
|
+
const availableMemory = budget.bytes;
|
|
755
781
|
const estimatedMemory = estimateMemoryUsage(symbolTableSize, maxRows, totalRows);
|
|
756
|
-
const
|
|
757
|
-
const maxAllowedMemory = effectiveLimit * safetyFactor;
|
|
782
|
+
const maxAllowedMemory = budget.bytes * safetyFactor;
|
|
758
783
|
if (estimatedMemory > maxAllowedMemory) {
|
|
759
784
|
const safeSymbolPercentage = maxAllowedMemory / (symbolTableSize * 6);
|
|
760
785
|
const safeRowPercentage = Math.pow(safeSymbolPercentage, 2);
|
|
@@ -764,9 +789,12 @@ function validateMemoryAvailability(symbolTableSize, maxRows, totalRows, filePat
|
|
|
764
789
|
const availableMB = Math.round(maxAllowedMemory / 1024 / 1024);
|
|
765
790
|
const heapLimitMB = Math.round(heapLimit / 1024 / 1024);
|
|
766
791
|
const availableRamMB = Math.round(availableMemory / 1024 / 1024);
|
|
767
|
-
const limitingFactor =
|
|
792
|
+
const limitingFactor = budget.limitedBy;
|
|
793
|
+
const budgetBreakdown = budget.candidates.map((candidate) => `${candidate.source} ${Math.round(candidate.bytes / 1024 / 1024)}MB`).join(", ");
|
|
794
|
+
const observedBreakdown = budget.observed.map((entry) => `${entry.source} ${Math.round(entry.bytes / 1024 / 1024)}MB`).join(", ");
|
|
795
|
+
const advice = limitingFactor === "container memory limit" ? `The binding limit is the container's, so raising --max-old-space-size would let V8 grow past it and be killed by the OOM killer instead. Set it below the container limit, raise the limit, or load fewer rows with maxRows (recommended: ${recommendedMaxRows.toLocaleString()} rows or less).` : `Try loading fewer rows using the maxRows parameter (recommended: ${recommendedMaxRows.toLocaleString()} rows or less), or raise the heap with --max-old-space-size.`;
|
|
768
796
|
throw new QvdValidationError(
|
|
769
|
-
`Insufficient memory to load file safely. Symbol table: ${sizeMB}MB, Estimated memory needed: ${estimatedMB}MB, Available: ${availableMB}MB (limited by ${limitingFactor}: ${
|
|
797
|
+
`Insufficient memory to load file safely. Symbol table: ${sizeMB}MB, Estimated memory needed: ${estimatedMB}MB, Available: ${availableMB}MB (limited by ${limitingFactor}; considered: ${budgetBreakdown}; observed but not used: ${observedBreakdown}). ` + advice,
|
|
770
798
|
{
|
|
771
799
|
file: filePath,
|
|
772
800
|
symbolTableSize,
|
|
@@ -776,6 +804,8 @@ function validateMemoryAvailability(symbolTableSize, maxRows, totalRows, filePat
|
|
|
776
804
|
heapLimitMB,
|
|
777
805
|
availableRamMB,
|
|
778
806
|
limitingFactor,
|
|
807
|
+
memoryBudget: budget.candidates,
|
|
808
|
+
memoryObserved: budget.observed,
|
|
779
809
|
totalRows,
|
|
780
810
|
maxRows,
|
|
781
811
|
recommendedMaxRows
|
|
@@ -1289,14 +1319,22 @@ var init_QvdFileReader = __esm({
|
|
|
1289
1319
|
* points outside it is rejected. Defaults to the current working directory. To permit
|
|
1290
1320
|
* an entire volume, pass its root explicitly ('/' on POSIX, 'C:\\' on Windows); a null or
|
|
1291
1321
|
* empty value falls back to the working directory rather than removing the restriction.
|
|
1292
|
-
* @param {number} [options.memorySafetyFactor=0.3]
|
|
1293
|
-
*
|
|
1294
|
-
*
|
|
1322
|
+
* @param {number} [options.memorySafetyFactor=0.3] Fraction (0.0-1.0) of the memory budget a
|
|
1323
|
+
* load may use. The budget is the smallest of the V8 heap limit, any container memory limit,
|
|
1324
|
+
* and the memory the OS reports as available. Default is 0.3. **Zero disables the memory
|
|
1325
|
+
* check entirely**, which is the escape hatch for runtimes whose limits cannot be measured -
|
|
1326
|
+
* Bun reports its current heap as its heap limit - and for callers who would rather manage
|
|
1327
|
+
* memory themselves than trust the estimate.
|
|
1328
|
+
* @param {number} [options.symbolFilteringThreshold=52428800] Symbol table size, in bytes,
|
|
1329
|
+
* above which a lazy load switches to the two-pass filtering path. The default of 50MB is
|
|
1330
|
+
* the point where the extra analysis pass pays for itself; lower it to use filtering on
|
|
1331
|
+
* smaller files, raise it to keep the simpler single-pass read for longer.
|
|
1295
1332
|
*/
|
|
1296
1333
|
constructor(filePath, options = {}) {
|
|
1297
|
-
const { allowedDir, memorySafetyFactor = 0.3 } = options;
|
|
1334
|
+
const { allowedDir, memorySafetyFactor = 0.3, symbolFilteringThreshold = 50 * 1024 * 1024 } = options;
|
|
1298
1335
|
this._path = validatePath(filePath, allowedDir);
|
|
1299
1336
|
this._memorySafetyFactor = memorySafetyFactor;
|
|
1337
|
+
this._symbolFilteringThreshold = symbolFilteringThreshold;
|
|
1300
1338
|
this._buffer = null;
|
|
1301
1339
|
this._headerOffset = null;
|
|
1302
1340
|
this._symbolTableOffset = null;
|
|
@@ -1728,15 +1766,12 @@ var init_QvdFileReader = __esm({
|
|
|
1728
1766
|
await this._readData(maxRows);
|
|
1729
1767
|
await this._parseHeader();
|
|
1730
1768
|
let symbolsToKeep = null;
|
|
1769
|
+
let symbolsKept = null;
|
|
1731
1770
|
if (maxRows !== null && this._header) {
|
|
1732
1771
|
const symbolTableLength = parseInt(this._header["QvdTableHeader"]["Offset"], 10);
|
|
1733
|
-
|
|
1734
|
-
if (symbolTableLength > SYMBOL_FILTERING_THRESHOLD) {
|
|
1772
|
+
if (symbolTableLength > this._symbolFilteringThreshold) {
|
|
1735
1773
|
symbolsToKeep = await this._analyzeIndexTableSymbolUsage(maxRows);
|
|
1736
|
-
|
|
1737
|
-
console.log(
|
|
1738
|
-
`[Phase 2.5 Optimization] Using stream-and-skip parsing: keeping ${totalSymbols} symbols from ${(symbolTableLength / 1024 / 1024).toFixed(1)}MB symbol table`
|
|
1739
|
-
);
|
|
1774
|
+
symbolsKept = Array.from(symbolsToKeep.values()).reduce((sum, set) => sum + set.size, 0);
|
|
1740
1775
|
}
|
|
1741
1776
|
}
|
|
1742
1777
|
await this._parseSymbolTable(symbolsToKeep, maxRows);
|
|
@@ -1773,7 +1808,14 @@ var init_QvdFileReader = __esm({
|
|
|
1773
1808
|
const columns = fields.map((field) => field["FieldName"]);
|
|
1774
1809
|
const data = this._indexTable.map((_, index) => getRow(index));
|
|
1775
1810
|
const metadata = this._header["QvdTableHeader"];
|
|
1776
|
-
|
|
1811
|
+
const loadStats = {
|
|
1812
|
+
symbolTableBytes: parseInt(this._header["QvdTableHeader"]["Offset"], 10),
|
|
1813
|
+
totalRows: parseInt(this._header["QvdTableHeader"]["NoOfRecords"], 10),
|
|
1814
|
+
rowsLoaded: data.length,
|
|
1815
|
+
symbolFiltering: symbolsToKeep !== null,
|
|
1816
|
+
symbolsKept
|
|
1817
|
+
};
|
|
1818
|
+
return new QvdDataFrame(data, columns, metadata, loadStats);
|
|
1777
1819
|
}
|
|
1778
1820
|
};
|
|
1779
1821
|
}
|
|
@@ -1790,11 +1832,13 @@ var init_QvdDataFrame = __esm({
|
|
|
1790
1832
|
* @param {Array<Array<any>>} data The data of the data frame.
|
|
1791
1833
|
* @param {Array<string>} columns The columns of the data frame.
|
|
1792
1834
|
* @param {QvdMetadata|null} metadata The metadata from the QVD file header (optional).
|
|
1835
|
+
* @param {QvdLoadStats|null} loadStats Statistics about the read (optional).
|
|
1793
1836
|
*/
|
|
1794
|
-
constructor(data, columns, metadata = null) {
|
|
1837
|
+
constructor(data, columns, metadata = null, loadStats = null) {
|
|
1795
1838
|
this._data = data;
|
|
1796
1839
|
this._columns = columns;
|
|
1797
1840
|
this._metadata = metadata;
|
|
1841
|
+
this._loadStats = loadStats;
|
|
1798
1842
|
}
|
|
1799
1843
|
/**
|
|
1800
1844
|
* Returns the data of the data frame.
|
|
@@ -1821,6 +1865,21 @@ var init_QvdDataFrame = __esm({
|
|
|
1821
1865
|
get metadata() {
|
|
1822
1866
|
return this._metadata;
|
|
1823
1867
|
}
|
|
1868
|
+
/**
|
|
1869
|
+
* Returns statistics about the read that produced this data frame.
|
|
1870
|
+
*
|
|
1871
|
+
* Only a frame returned by fromQvd() carries these; fromDict(), head() and tail() produce
|
|
1872
|
+
* frames that describe no particular read, and report null rather than a stale figure.
|
|
1873
|
+
*
|
|
1874
|
+
* The main use is confirming that a lazy load actually filtered the symbol table:
|
|
1875
|
+
* `symbolFiltering` says whether the two-pass path ran, and `symbolsKept` how many symbols
|
|
1876
|
+
* survived it, which for a small maxRows should be a tiny fraction of the file's total.
|
|
1877
|
+
*
|
|
1878
|
+
* @return {QvdLoadStats|null} Load statistics, or null if this frame did not come from a file.
|
|
1879
|
+
*/
|
|
1880
|
+
get loadStats() {
|
|
1881
|
+
return this._loadStats;
|
|
1882
|
+
}
|
|
1824
1883
|
/**
|
|
1825
1884
|
* Returns file-level metadata from the QVD header.
|
|
1826
1885
|
* @return {Object} File-level metadata properties.
|
|
@@ -2196,9 +2255,12 @@ var init_QvdDataFrame = __esm({
|
|
|
2196
2255
|
* outside it is rejected. Defaults to the current working directory. To permit an entire
|
|
2197
2256
|
* volume, pass its root explicitly ('/' on POSIX, 'C:\\' on Windows); a null or empty value falls
|
|
2198
2257
|
* back to the working directory rather than removing the restriction.
|
|
2199
|
-
* @param {number} [options.memorySafetyFactor=0.3]
|
|
2200
|
-
*
|
|
2201
|
-
*
|
|
2258
|
+
* @param {number} [options.memorySafetyFactor=0.3] Fraction (0.0-1.0) of the memory budget a load
|
|
2259
|
+
* may use. The budget is the smallest of the V8 heap limit, any container memory limit, and the
|
|
2260
|
+
* memory the OS reports as available. Default is 0.3; raise it when running with a larger heap via
|
|
2261
|
+
* --max-old-space-size. **Zero disables the memory check entirely.**
|
|
2262
|
+
* @param {number} [options.symbolFilteringThreshold=52428800] Symbol table size, in bytes, above which
|
|
2263
|
+
* a lazy load switches to the two-pass filtering path. Defaults to 50MB.
|
|
2202
2264
|
* @throws {QvdValidationError} If options.maxRows is neither null/undefined nor a non-negative integer.
|
|
2203
2265
|
* @return {Promise<QvdDataFrame>} The data frame of the QVD file.
|
|
2204
2266
|
*/
|
|
@@ -2206,7 +2268,8 @@ var init_QvdDataFrame = __esm({
|
|
|
2206
2268
|
const { QvdFileReader: QvdFileReader2 } = await Promise.resolve().then(() => (init_QvdFileReader(), QvdFileReader_exports));
|
|
2207
2269
|
const readerOptions = {
|
|
2208
2270
|
allowedDir: options.allowedDir,
|
|
2209
|
-
memorySafetyFactor: options.memorySafetyFactor
|
|
2271
|
+
memorySafetyFactor: options.memorySafetyFactor,
|
|
2272
|
+
symbolFilteringThreshold: options.symbolFilteringThreshold
|
|
2210
2273
|
};
|
|
2211
2274
|
return await new QvdFileReader2(path3, readerOptions).load(options.maxRows !== void 0 ? options.maxRows : null);
|
|
2212
2275
|
}
|