qvdjs 0.7.0 → 0.9.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/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import crypto from 'crypto';
4
4
  import xml2 from 'xml2js';
5
5
  import assert from 'assert';
6
6
  import os from 'os';
7
+ import v8 from 'v8';
7
8
 
8
9
  var __defProp = Object.defineProperty;
9
10
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -647,6 +648,9 @@ var init_bitUtils = __esm({
647
648
  "src/util/bitUtils.js"() {
648
649
  }
649
650
  });
651
+ function getHeapLimit() {
652
+ return v8.getHeapStatistics().heap_size_limit;
653
+ }
650
654
  function estimateMemoryUsage(symbolTableSize, maxRows, totalRows) {
651
655
  const FULL_PARSE_OVERHEAD = 6;
652
656
  const MINIMAL_OVERHEAD = 0.01;
@@ -659,11 +663,15 @@ function estimateMemoryUsage(symbolTableSize, maxRows, totalRows) {
659
663
  const skippedSymbolsMemory = symbolTableSize * (1 - symbolPercentage) * MINIMAL_OVERHEAD;
660
664
  return keptSymbolsMemory + skippedSymbolsMemory;
661
665
  }
662
- function validateMemoryAvailability(symbolTableSize, maxRows, totalRows, filePath) {
666
+ function validateMemoryAvailability(symbolTableSize, maxRows, totalRows, filePath, safetyFactor = 0.3) {
667
+ if (typeof safetyFactor !== "number" || safetyFactor < 0 || safetyFactor > 1) {
668
+ throw new QvdValidationError("safetyFactor must be a number between 0.0 and 1.0", { safetyFactor });
669
+ }
663
670
  const availableMemory = os.freemem();
671
+ const heapLimit = getHeapLimit();
664
672
  const estimatedMemory = estimateMemoryUsage(symbolTableSize, maxRows, totalRows);
665
- const MEMORY_SAFETY_FACTOR = 0.3;
666
- const maxAllowedMemory = availableMemory * MEMORY_SAFETY_FACTOR;
673
+ const effectiveLimit = Math.min(availableMemory, heapLimit);
674
+ const maxAllowedMemory = effectiveLimit * safetyFactor;
667
675
  if (estimatedMemory > maxAllowedMemory) {
668
676
  const safeSymbolPercentage = maxAllowedMemory / (symbolTableSize * 6);
669
677
  const safeRowPercentage = Math.pow(safeSymbolPercentage, 2);
@@ -671,14 +679,20 @@ function validateMemoryAvailability(symbolTableSize, maxRows, totalRows, filePat
671
679
  const sizeMB = Math.round(symbolTableSize / 1024 / 1024);
672
680
  const estimatedMB = Math.round(estimatedMemory / 1024 / 1024);
673
681
  const availableMB = Math.round(maxAllowedMemory / 1024 / 1024);
682
+ const heapLimitMB = Math.round(heapLimit / 1024 / 1024);
683
+ const availableRamMB = Math.round(availableMemory / 1024 / 1024);
684
+ const limitingFactor = heapLimit < availableMemory ? "V8 heap limit" : "available RAM";
674
685
  throw new QvdValidationError(
675
- `Insufficient memory to load file safely. Symbol table: ${sizeMB}MB, Estimated memory needed: ${estimatedMB}MB, Available: ${availableMB}MB. Try loading fewer rows using the maxRows parameter (recommended: ${recommendedMaxRows.toLocaleString()} rows or less).`,
686
+ `Insufficient memory to load file safely. Symbol table: ${sizeMB}MB, Estimated memory needed: ${estimatedMB}MB, Available: ${availableMB}MB (limited by ${limitingFactor}: ${heapLimitMB}MB heap / ${availableRamMB}MB RAM). Try loading fewer rows using the maxRows parameter (recommended: ${recommendedMaxRows.toLocaleString()} rows or less).`,
676
687
  {
677
688
  file: filePath,
678
689
  symbolTableSize,
679
690
  symbolTableSizeMB: sizeMB,
680
691
  estimatedMemoryMB: estimatedMB,
681
692
  availableMemoryMB: availableMB,
693
+ heapLimitMB,
694
+ availableRamMB,
695
+ limitingFactor,
682
696
  totalRows,
683
697
  maxRows,
684
698
  recommendedMaxRows
@@ -687,13 +701,15 @@ function validateMemoryAvailability(symbolTableSize, maxRows, totalRows, filePat
687
701
  }
688
702
  }
689
703
  function warnLargeSymbolTable(symbolTableSize, maxRows, totalRows) {
690
- const LARGE_SYMBOL_TABLE_WARNING = 500 * 1024 * 1024;
704
+ const heapLimit = getHeapLimit();
705
+ const LARGE_SYMBOL_TABLE_WARNING = heapLimit * 0.125;
691
706
  if (symbolTableSize > LARGE_SYMBOL_TABLE_WARNING && (maxRows === null || maxRows >= totalRows)) {
692
707
  const sizeMB = Math.round(symbolTableSize / 1024 / 1024);
693
708
  const estimatedMemory = estimateMemoryUsage(symbolTableSize, maxRows, totalRows);
694
709
  const estimatedMB = Math.round(estimatedMemory / 1024 / 1024);
710
+ const warnMB = Math.round(LARGE_SYMBOL_TABLE_WARNING / 1024 / 1024);
695
711
  console.warn(
696
- `\u26A0\uFE0F Large symbol table detected (${sizeMB}MB). Loading all ${totalRows.toLocaleString()} rows will use ~${estimatedMB}MB RAM. Consider using the maxRows parameter for better performance and lower memory usage.`
712
+ `\u26A0\uFE0F Large symbol table detected (${sizeMB}MB > ${warnMB}MB threshold). Loading all ${totalRows.toLocaleString()} rows will use ~${estimatedMB}MB RAM. Consider using the maxRows parameter for better performance and lower memory usage.`
697
713
  );
698
714
  }
699
715
  }
@@ -705,35 +721,43 @@ var init_memoryUtils = __esm({
705
721
 
706
722
  // src/util/validationUtils.js
707
723
  function validateSymbolTableSizeEarly(symbolTableLength, filePath) {
708
- const MAX_SYMBOL_TABLE_SIZE = 500 * 1024 * 1024;
724
+ const heapLimit = getHeapLimit();
725
+ const MAX_SYMBOL_TABLE_SIZE = heapLimit * 0.125;
709
726
  if (symbolTableLength > MAX_SYMBOL_TABLE_SIZE) {
710
727
  const sizeMB = Math.round(symbolTableLength / 1024 / 1024);
711
728
  const maxMB = Math.round(MAX_SYMBOL_TABLE_SIZE / 1024 / 1024);
729
+ const heapMB = Math.round(heapLimit / 1024 / 1024);
712
730
  throw new QvdValidationError(
713
- `Symbol table too large (${sizeMB}MB exceeds ${maxMB}MB limit). This QVD file contains extremely high-cardinality fields. Even with symbol filtering, this file cannot be loaded efficiently. Consider: (1) using a subset of the data, (2) aggregating high-cardinality fields, or (3) using a different data format.`,
731
+ `Symbol table too large (${sizeMB}MB exceeds ${maxMB}MB limit for lazy loading). This QVD file contains extremely high-cardinality fields. Limit scales with heap size (current: ${heapMB}MB, limit: 12.5% = ${maxMB}MB). Consider: (1) loading the full file without maxRows, (2) increasing heap size with --max-old-space-size, or (3) aggregating high-cardinality fields.`,
714
732
  {
715
733
  file: filePath,
716
734
  symbolTableSize: symbolTableLength,
717
735
  symbolTableSizeMB: sizeMB,
718
736
  maxAllowed: MAX_SYMBOL_TABLE_SIZE,
719
- maxAllowedMB: maxMB
737
+ maxAllowedMB: maxMB,
738
+ heapLimitMB: heapMB,
739
+ limitPercentage: 12.5
720
740
  }
721
741
  );
722
742
  }
723
743
  }
724
744
  function validateSymbolTableSize(symbolTableLength, filePath, totalRows) {
725
- const ABSOLUTE_MAX_SYMBOL_TABLE = 2 * 1024 * 1024 * 1024;
745
+ const heapLimit = getHeapLimit();
746
+ const ABSOLUTE_MAX_SYMBOL_TABLE = heapLimit * 0.5;
726
747
  if (symbolTableLength > ABSOLUTE_MAX_SYMBOL_TABLE) {
727
748
  const sizeMB = Math.round(symbolTableLength / 1024 / 1024);
728
749
  const maxMB = Math.round(ABSOLUTE_MAX_SYMBOL_TABLE / 1024 / 1024);
750
+ const heapMB = Math.round(heapLimit / 1024 / 1024);
729
751
  throw new QvdValidationError(
730
- `Symbol table exceeds absolute maximum size (${sizeMB}MB > ${maxMB}MB). This QVD file has pathological cardinality (likely a data modeling issue). Even with Phase 2.5 optimization, files this large cannot be processed safely. Consider: (1) aggregating high-cardinality fields, (2) splitting the data, or (3) using a different format.`,
752
+ `Symbol table exceeds absolute maximum size (${sizeMB}MB > ${maxMB}MB). This QVD file has pathological cardinality (likely a data modeling issue). Limit scales with heap size (current: ${heapMB}MB, limit: 50% = ${maxMB}MB). Consider: (1) increasing heap size with --max-old-space-size, (2) aggregating high-cardinality fields, (3) splitting the data, or (4) using a different format.`,
731
753
  {
732
754
  file: filePath,
733
755
  symbolTableSize: symbolTableLength,
734
756
  symbolTableSizeMB: sizeMB,
735
757
  maxAllowed: ABSOLUTE_MAX_SYMBOL_TABLE,
736
758
  maxAllowedMB: maxMB,
759
+ heapLimitMB: heapMB,
760
+ limitPercentage: 50,
737
761
  totalRows
738
762
  }
739
763
  );
@@ -870,6 +894,7 @@ function validateFieldBitMetadata(field, recordSize, filePath) {
870
894
  var init_validationUtils = __esm({
871
895
  "src/util/validationUtils.js"() {
872
896
  init_QvdErrors();
897
+ init_memoryUtils();
873
898
  }
874
899
  });
875
900
 
@@ -1152,10 +1177,14 @@ var init_QvdFileReader = __esm({
1152
1177
  * @param {Object} [options={}] Options for the reader.
1153
1178
  * @param {string} [options.allowedDir] Optional allowed directory path. If provided, the file
1154
1179
  * path must be within this directory. Defaults to current working directory.
1180
+ * @param {number} [options.memorySafetyFactor=0.3] Memory safety factor (0.0-1.0). Determines
1181
+ * what percentage of available memory (or V8 heap limit, whichever is smaller) can be used
1182
+ * for loading QVD files. Default is 0.3 (30%). Increase for larger heap configurations.
1155
1183
  */
1156
1184
  constructor(filePath, options = {}) {
1157
- const { allowedDir } = options;
1185
+ const { allowedDir, memorySafetyFactor = 0.3 } = options;
1158
1186
  this._path = validatePath(filePath, allowedDir);
1187
+ this._memorySafetyFactor = memorySafetyFactor;
1159
1188
  this._buffer = null;
1160
1189
  this._headerOffset = null;
1161
1190
  this._symbolTableOffset = null;
@@ -1379,7 +1408,7 @@ var init_QvdFileReader = __esm({
1379
1408
  const symbolTableSize = symbolBuffer.length;
1380
1409
  const totalRows = parseInt(this._header["QvdTableHeader"]["NoOfRecords"], 10);
1381
1410
  validateSymbolTableSize(symbolTableSize, this._path, totalRows);
1382
- validateMemoryAvailability(symbolTableSize, maxRows, totalRows, this._path);
1411
+ validateMemoryAvailability(symbolTableSize, maxRows, totalRows, this._path, this._memorySafetyFactor);
1383
1412
  warnLargeSymbolTable(symbolTableSize, maxRows, totalRows);
1384
1413
  if (!Array.isArray(fields)) {
1385
1414
  fields = [fields];
@@ -1960,11 +1989,17 @@ var init_QvdDataFrame = __esm({
1960
1989
  * @param {Object} [options] Optional loading options.
1961
1990
  * @param {number|null} [options.maxRows] The maximum number of rows to load. If not specified, all rows are loaded.
1962
1991
  * @param {string} [options.allowedDir] Optional allowed directory path. If provided, the file path must be within this directory.
1992
+ * @param {number} [options.memorySafetyFactor=0.3] Memory safety factor (0.0-1.0). Determines what percentage
1993
+ * of available memory (or V8 heap limit, whichever is smaller) can be used. Default is 0.3 (30%).
1994
+ * Increase this (e.g., to 0.5 or 0.7) when running with larger heap sizes via --max-old-space-size.
1963
1995
  * @return {Promise<QvdDataFrame>} The data frame of the QVD file.
1964
1996
  */
1965
1997
  static async fromQvd(path3, options = {}) {
1966
1998
  const { QvdFileReader: QvdFileReader2 } = await Promise.resolve().then(() => (init_QvdFileReader(), QvdFileReader_exports));
1967
- const readerOptions = { allowedDir: options.allowedDir };
1999
+ const readerOptions = {
2000
+ allowedDir: options.allowedDir,
2001
+ memorySafetyFactor: options.memorySafetyFactor
2002
+ };
1968
2003
  return await new QvdFileReader2(path3, readerOptions).load(options.maxRows !== void 0 ? options.maxRows : null);
1969
2004
  }
1970
2005
  /**