qvdjs 0.6.2 → 0.8.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
@@ -3,6 +3,8 @@ import fs2 from 'fs';
3
3
  import crypto from 'crypto';
4
4
  import xml2 from 'xml2js';
5
5
  import assert from 'assert';
6
+ import os from 'os';
7
+ import v8 from 'v8';
6
8
 
7
9
  var __defProp = Object.defineProperty;
8
10
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -635,6 +637,520 @@ var init_QvdFileWriter = __esm({
635
637
  }
636
638
  });
637
639
 
640
+ // src/util/bitUtils.js
641
+ function convertBitsToInt32(bits) {
642
+ if (bits.length === 0) {
643
+ return 0;
644
+ }
645
+ return bits.reduce((value, bit, index) => value += bit * Math.pow(2, index), 0);
646
+ }
647
+ var init_bitUtils = __esm({
648
+ "src/util/bitUtils.js"() {
649
+ }
650
+ });
651
+ function getHeapLimit() {
652
+ return v8.getHeapStatistics().heap_size_limit;
653
+ }
654
+ function estimateMemoryUsage(symbolTableSize, maxRows, totalRows) {
655
+ const FULL_PARSE_OVERHEAD = 6;
656
+ const MINIMAL_OVERHEAD = 0.01;
657
+ if (maxRows === null || maxRows >= totalRows) {
658
+ return symbolTableSize * FULL_PARSE_OVERHEAD;
659
+ }
660
+ const rowPercentage = maxRows / totalRows;
661
+ const symbolPercentage = Math.sqrt(rowPercentage);
662
+ const keptSymbolsMemory = symbolTableSize * symbolPercentage * FULL_PARSE_OVERHEAD;
663
+ const skippedSymbolsMemory = symbolTableSize * (1 - symbolPercentage) * MINIMAL_OVERHEAD;
664
+ return keptSymbolsMemory + skippedSymbolsMemory;
665
+ }
666
+ function validateMemoryAvailability(symbolTableSize, maxRows, totalRows, filePath, safetyFactor = 0.3) {
667
+ const availableMemory = os.freemem();
668
+ const heapLimit = getHeapLimit();
669
+ const estimatedMemory = estimateMemoryUsage(symbolTableSize, maxRows, totalRows);
670
+ const effectiveLimit = Math.min(availableMemory, heapLimit);
671
+ const maxAllowedMemory = effectiveLimit * safetyFactor;
672
+ if (estimatedMemory > maxAllowedMemory) {
673
+ const safeSymbolPercentage = maxAllowedMemory / (symbolTableSize * 6);
674
+ const safeRowPercentage = Math.pow(safeSymbolPercentage, 2);
675
+ const recommendedMaxRows = Math.max(1, Math.floor(totalRows * safeRowPercentage));
676
+ const sizeMB = Math.round(symbolTableSize / 1024 / 1024);
677
+ const estimatedMB = Math.round(estimatedMemory / 1024 / 1024);
678
+ const availableMB = Math.round(maxAllowedMemory / 1024 / 1024);
679
+ const heapLimitMB = Math.round(heapLimit / 1024 / 1024);
680
+ const availableRamMB = Math.round(availableMemory / 1024 / 1024);
681
+ const limitingFactor = heapLimit < availableMemory ? "V8 heap limit" : "available RAM";
682
+ throw new QvdValidationError(
683
+ `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).`,
684
+ {
685
+ file: filePath,
686
+ symbolTableSize,
687
+ symbolTableSizeMB: sizeMB,
688
+ estimatedMemoryMB: estimatedMB,
689
+ availableMemoryMB: availableMB,
690
+ heapLimitMB,
691
+ availableRamMB,
692
+ limitingFactor,
693
+ totalRows,
694
+ maxRows,
695
+ recommendedMaxRows
696
+ }
697
+ );
698
+ }
699
+ }
700
+ function warnLargeSymbolTable(symbolTableSize, maxRows, totalRows) {
701
+ const heapLimit = getHeapLimit();
702
+ const LARGE_SYMBOL_TABLE_WARNING = heapLimit * 0.125;
703
+ if (symbolTableSize > LARGE_SYMBOL_TABLE_WARNING && (maxRows === null || maxRows >= totalRows)) {
704
+ const sizeMB = Math.round(symbolTableSize / 1024 / 1024);
705
+ const estimatedMemory = estimateMemoryUsage(symbolTableSize, maxRows, totalRows);
706
+ const estimatedMB = Math.round(estimatedMemory / 1024 / 1024);
707
+ const warnMB = Math.round(LARGE_SYMBOL_TABLE_WARNING / 1024 / 1024);
708
+ console.warn(
709
+ `\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.`
710
+ );
711
+ }
712
+ }
713
+ var init_memoryUtils = __esm({
714
+ "src/util/memoryUtils.js"() {
715
+ init_QvdErrors();
716
+ }
717
+ });
718
+
719
+ // src/util/validationUtils.js
720
+ function validateSymbolTableSizeEarly(symbolTableLength, filePath) {
721
+ const heapLimit = getHeapLimit();
722
+ const MAX_SYMBOL_TABLE_SIZE = heapLimit * 0.125;
723
+ if (symbolTableLength > MAX_SYMBOL_TABLE_SIZE) {
724
+ const sizeMB = Math.round(symbolTableLength / 1024 / 1024);
725
+ const maxMB = Math.round(MAX_SYMBOL_TABLE_SIZE / 1024 / 1024);
726
+ const heapMB = Math.round(heapLimit / 1024 / 1024);
727
+ throw new QvdValidationError(
728
+ `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.`,
729
+ {
730
+ file: filePath,
731
+ symbolTableSize: symbolTableLength,
732
+ symbolTableSizeMB: sizeMB,
733
+ maxAllowed: MAX_SYMBOL_TABLE_SIZE,
734
+ maxAllowedMB: maxMB,
735
+ heapLimitMB: heapMB,
736
+ limitPercentage: 12.5
737
+ }
738
+ );
739
+ }
740
+ }
741
+ function validateSymbolTableSize(symbolTableLength, filePath, totalRows) {
742
+ const heapLimit = getHeapLimit();
743
+ const ABSOLUTE_MAX_SYMBOL_TABLE = heapLimit * 0.5;
744
+ if (symbolTableLength > ABSOLUTE_MAX_SYMBOL_TABLE) {
745
+ const sizeMB = Math.round(symbolTableLength / 1024 / 1024);
746
+ const maxMB = Math.round(ABSOLUTE_MAX_SYMBOL_TABLE / 1024 / 1024);
747
+ const heapMB = Math.round(heapLimit / 1024 / 1024);
748
+ throw new QvdValidationError(
749
+ `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.`,
750
+ {
751
+ file: filePath,
752
+ symbolTableSize: symbolTableLength,
753
+ symbolTableSizeMB: sizeMB,
754
+ maxAllowed: ABSOLUTE_MAX_SYMBOL_TABLE,
755
+ maxAllowedMB: maxMB,
756
+ heapLimitMB: heapMB,
757
+ limitPercentage: 50,
758
+ totalRows
759
+ }
760
+ );
761
+ }
762
+ }
763
+ function validateFieldMetadata(field, symbolBufferLength, filePath) {
764
+ const symbolsOffset = parseInt(field["Offset"], 10);
765
+ const symbolsLength = parseInt(field["Length"], 10);
766
+ if (isNaN(symbolsOffset) || !Number.isSafeInteger(symbolsOffset) || symbolsOffset < 0) {
767
+ throw new QvdCorruptedError("Invalid symbol offset", {
768
+ field: field["FieldName"],
769
+ offset: symbolsOffset,
770
+ file: filePath,
771
+ stage: "parseSymbolTable"
772
+ });
773
+ }
774
+ if (isNaN(symbolsLength) || !Number.isSafeInteger(symbolsLength) || symbolsLength < 0) {
775
+ throw new QvdCorruptedError("Invalid symbol length", {
776
+ field: field["FieldName"],
777
+ length: symbolsLength,
778
+ file: filePath,
779
+ stage: "parseSymbolTable"
780
+ });
781
+ }
782
+ if (symbolsOffset + symbolsLength > symbolBufferLength) {
783
+ throw new QvdCorruptedError("Symbol data extends beyond buffer", {
784
+ field: field["FieldName"],
785
+ offset: symbolsOffset,
786
+ length: symbolsLength,
787
+ bufferSize: symbolBufferLength,
788
+ file: filePath,
789
+ stage: "parseSymbolTable"
790
+ });
791
+ }
792
+ }
793
+ function validateIndexTableMetadata(recordSize, totalRows, indexTableLength, indexTableOffset, bufferLength, rowsToLoad, filePath) {
794
+ if (isNaN(recordSize) || !Number.isSafeInteger(recordSize) || recordSize < 0) {
795
+ throw new QvdCorruptedError("Invalid record byte size", {
796
+ recordSize,
797
+ file: filePath,
798
+ stage: "parseIndexTable"
799
+ });
800
+ }
801
+ if (isNaN(totalRows) || !Number.isSafeInteger(totalRows) || totalRows < 0) {
802
+ throw new QvdCorruptedError("Invalid number of records", {
803
+ totalRows,
804
+ file: filePath,
805
+ stage: "parseIndexTable"
806
+ });
807
+ }
808
+ if (recordSize === 0 && totalRows > 0) {
809
+ throw new QvdCorruptedError("Record byte size cannot be zero when records exist", {
810
+ recordSize,
811
+ totalRows,
812
+ file: filePath,
813
+ stage: "parseIndexTable"
814
+ });
815
+ }
816
+ if (recordSize > 1048576) {
817
+ throw new QvdCorruptedError("Record byte size exceeds maximum", {
818
+ recordSize,
819
+ maxSize: 1048576,
820
+ file: filePath,
821
+ stage: "parseIndexTable"
822
+ });
823
+ }
824
+ if (isNaN(indexTableLength) || !Number.isSafeInteger(indexTableLength) || indexTableLength < 0) {
825
+ throw new QvdCorruptedError("Invalid index table length", {
826
+ length: indexTableLength,
827
+ file: filePath,
828
+ stage: "parseIndexTable"
829
+ });
830
+ }
831
+ if (indexTableOffset > bufferLength) {
832
+ throw new QvdCorruptedError("Index table offset beyond buffer", {
833
+ indexTableOffset,
834
+ bufferSize: bufferLength,
835
+ file: filePath,
836
+ stage: "parseIndexTable"
837
+ });
838
+ }
839
+ const maxReasonableOverage = 100 * 1024 * 1024;
840
+ if (indexTableLength > bufferLength + maxReasonableOverage) {
841
+ throw new QvdCorruptedError("Index table length unreasonably large", {
842
+ indexTableLength,
843
+ bufferSize: bufferLength,
844
+ file: filePath,
845
+ stage: "parseIndexTable"
846
+ });
847
+ }
848
+ const requiredIndexBytes = rowsToLoad * recordSize;
849
+ if (indexTableLength < requiredIndexBytes) {
850
+ throw new QvdCorruptedError("Index table length smaller than required", {
851
+ indexTableLength,
852
+ requiredBytes: requiredIndexBytes,
853
+ rowsToLoad,
854
+ recordSize,
855
+ file: filePath,
856
+ stage: "parseIndexTable"
857
+ });
858
+ }
859
+ }
860
+ function validateFieldBitMetadata(field, recordSize, filePath) {
861
+ const bitOffset = parseInt(field["BitOffset"], 10);
862
+ const bitWidth = parseInt(field["BitWidth"], 10);
863
+ if (isNaN(bitOffset) || !Number.isSafeInteger(bitOffset) || bitOffset < 0) {
864
+ throw new QvdCorruptedError("Invalid bit offset", {
865
+ field: field["FieldName"],
866
+ bitOffset,
867
+ file: filePath,
868
+ stage: "parseIndexTable"
869
+ });
870
+ }
871
+ if (isNaN(bitWidth) || !Number.isSafeInteger(bitWidth) || bitWidth < 0) {
872
+ throw new QvdCorruptedError("Invalid bit width", {
873
+ field: field["FieldName"],
874
+ bitWidth,
875
+ file: filePath,
876
+ stage: "parseIndexTable"
877
+ });
878
+ }
879
+ const recordSizeInBits = recordSize * 8;
880
+ if (bitOffset + bitWidth > recordSizeInBits) {
881
+ throw new QvdCorruptedError("Bit field extends beyond record size", {
882
+ field: field["FieldName"],
883
+ bitOffset,
884
+ bitWidth,
885
+ recordSizeInBits,
886
+ file: filePath,
887
+ stage: "parseIndexTable"
888
+ });
889
+ }
890
+ }
891
+ var init_validationUtils = __esm({
892
+ "src/util/validationUtils.js"() {
893
+ init_QvdErrors();
894
+ init_memoryUtils();
895
+ }
896
+ });
897
+
898
+ // src/util/symbolParser.js
899
+ function parseIntegerSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath) {
900
+ if (pointer + 4 > bufferLength) {
901
+ throw new QvdCorruptedError("Buffer overflow reading integer symbol", {
902
+ field: fieldName,
903
+ pointer,
904
+ bufferSize: bufferLength,
905
+ file: filePath,
906
+ stage: "parseSymbolTable"
907
+ });
908
+ }
909
+ const byteData = new Int32Array(symbolBuffer.subarray(pointer, pointer + 4));
910
+ const value = Buffer.from(byteData).readIntLE(0, byteData.length);
911
+ return { symbol: QvdSymbol.fromIntValue(value), bytesRead: 4 };
912
+ }
913
+ function parseDoubleSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath) {
914
+ if (pointer + 8 > bufferLength) {
915
+ throw new QvdCorruptedError("Buffer overflow reading double symbol", {
916
+ field: fieldName,
917
+ pointer,
918
+ bufferSize: bufferLength,
919
+ file: filePath,
920
+ stage: "parseSymbolTable"
921
+ });
922
+ }
923
+ const byteData = new Int32Array(symbolBuffer.subarray(pointer, pointer + 8));
924
+ const value = Buffer.from(byteData).readDoubleLE(0);
925
+ return { symbol: QvdSymbol.fromDoubleValue(value), bytesRead: 8 };
926
+ }
927
+ function parseStringSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath) {
928
+ const startPointer = pointer;
929
+ const maxStringLength = 1048576;
930
+ let stringLength = 0;
931
+ while (pointer < bufferLength && symbolBuffer[pointer] !== 0) {
932
+ if (stringLength >= maxStringLength) {
933
+ throw new QvdCorruptedError("String symbol exceeds maximum length", {
934
+ field: fieldName,
935
+ maxLength: maxStringLength,
936
+ file: filePath,
937
+ stage: "parseSymbolTable"
938
+ });
939
+ }
940
+ pointer++;
941
+ stringLength++;
942
+ }
943
+ if (pointer >= bufferLength) {
944
+ throw new QvdCorruptedError("String symbol not null-terminated", {
945
+ field: fieldName,
946
+ pointer,
947
+ bufferSize: bufferLength,
948
+ file: filePath,
949
+ stage: "parseSymbolTable"
950
+ });
951
+ }
952
+ const value = symbolBuffer.subarray(startPointer, pointer).toString("utf-8");
953
+ return { symbol: QvdSymbol.fromStringValue(value), bytesRead: pointer - startPointer + 1 };
954
+ }
955
+ function skipStringSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath) {
956
+ const startPointer = pointer;
957
+ const maxStringLength = 1048576;
958
+ let stringLength = 0;
959
+ while (pointer < bufferLength && symbolBuffer[pointer] !== 0) {
960
+ if (stringLength >= maxStringLength) {
961
+ throw new QvdCorruptedError("String symbol exceeds maximum length", {
962
+ field: fieldName,
963
+ maxLength: maxStringLength,
964
+ file: filePath,
965
+ stage: "parseSymbolTable"
966
+ });
967
+ }
968
+ pointer++;
969
+ stringLength++;
970
+ }
971
+ if (pointer >= bufferLength) {
972
+ throw new QvdCorruptedError("String symbol not null-terminated", {
973
+ field: fieldName,
974
+ pointer,
975
+ bufferSize: bufferLength,
976
+ file: filePath,
977
+ stage: "parseSymbolTable"
978
+ });
979
+ }
980
+ return pointer - startPointer + 1;
981
+ }
982
+ function parseDualIntegerSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath) {
983
+ if (pointer + 4 > bufferLength) {
984
+ throw new QvdCorruptedError("Buffer overflow reading dual integer symbol", {
985
+ field: fieldName,
986
+ pointer,
987
+ bufferSize: bufferLength,
988
+ file: filePath,
989
+ stage: "parseSymbolTable"
990
+ });
991
+ }
992
+ const intByteData = new Int32Array(symbolBuffer.subarray(pointer, pointer + 4));
993
+ const intValue = Buffer.from(intByteData).readIntLE(0, intByteData.length);
994
+ pointer += 4;
995
+ const stringStart = pointer;
996
+ const maxStringLength = 1048576;
997
+ let stringLength = 0;
998
+ while (pointer < bufferLength && symbolBuffer[pointer] !== 0) {
999
+ if (stringLength >= maxStringLength) {
1000
+ throw new QvdCorruptedError("Dual string symbol exceeds maximum length", {
1001
+ field: fieldName,
1002
+ maxLength: maxStringLength,
1003
+ file: filePath,
1004
+ stage: "parseSymbolTable"
1005
+ });
1006
+ }
1007
+ pointer++;
1008
+ stringLength++;
1009
+ }
1010
+ if (pointer >= bufferLength) {
1011
+ throw new QvdCorruptedError("Dual string symbol not null-terminated", {
1012
+ field: fieldName,
1013
+ pointer,
1014
+ bufferSize: bufferLength,
1015
+ file: filePath,
1016
+ stage: "parseSymbolTable"
1017
+ });
1018
+ }
1019
+ const stringValue = symbolBuffer.subarray(stringStart, pointer).toString("utf-8");
1020
+ return { symbol: QvdSymbol.fromDualIntValue(intValue, stringValue), bytesRead: pointer - (stringStart - 4) + 1 };
1021
+ }
1022
+ function parseDualDoubleSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath) {
1023
+ if (pointer + 8 > bufferLength) {
1024
+ throw new QvdCorruptedError("Buffer overflow reading dual double symbol", {
1025
+ field: fieldName,
1026
+ pointer,
1027
+ bufferSize: bufferLength,
1028
+ file: filePath,
1029
+ stage: "parseSymbolTable"
1030
+ });
1031
+ }
1032
+ const doubleByteData = new Int32Array(symbolBuffer.subarray(pointer, pointer + 8));
1033
+ const doubleValue = Buffer.from(doubleByteData).readDoubleLE(0);
1034
+ pointer += 8;
1035
+ const stringStart = pointer;
1036
+ const maxStringLength = 1048576;
1037
+ let stringLength = 0;
1038
+ while (pointer < bufferLength && symbolBuffer[pointer] !== 0) {
1039
+ if (stringLength >= maxStringLength) {
1040
+ throw new QvdCorruptedError("Dual string symbol exceeds maximum length", {
1041
+ field: fieldName,
1042
+ maxLength: maxStringLength,
1043
+ file: filePath,
1044
+ stage: "parseSymbolTable"
1045
+ });
1046
+ }
1047
+ pointer++;
1048
+ stringLength++;
1049
+ }
1050
+ if (pointer >= bufferLength) {
1051
+ throw new QvdCorruptedError("Dual string symbol not null-terminated", {
1052
+ field: fieldName,
1053
+ pointer,
1054
+ bufferSize: bufferLength,
1055
+ file: filePath,
1056
+ stage: "parseSymbolTable"
1057
+ });
1058
+ }
1059
+ const stringValue = symbolBuffer.subarray(stringStart, pointer).toString("utf-8");
1060
+ return {
1061
+ symbol: QvdSymbol.fromDualDoubleValue(doubleValue, stringValue),
1062
+ bytesRead: pointer - (stringStart - 8) + 1
1063
+ };
1064
+ }
1065
+ function skipDualSymbol(symbolBuffer, pointer, bufferLength, numericBytes, fieldName, filePath) {
1066
+ if (pointer + numericBytes > bufferLength) {
1067
+ throw new QvdCorruptedError(`Buffer overflow reading dual symbol`, {
1068
+ field: fieldName,
1069
+ pointer,
1070
+ bufferSize: bufferLength,
1071
+ file: filePath,
1072
+ stage: "parseSymbolTable"
1073
+ });
1074
+ }
1075
+ const startPointer = pointer;
1076
+ pointer += numericBytes;
1077
+ const maxStringLength = 1048576;
1078
+ let stringLength = 0;
1079
+ while (pointer < bufferLength && symbolBuffer[pointer] !== 0) {
1080
+ if (stringLength >= maxStringLength) {
1081
+ throw new QvdCorruptedError("Dual string symbol exceeds maximum length", {
1082
+ field: fieldName,
1083
+ maxLength: maxStringLength,
1084
+ file: filePath,
1085
+ stage: "parseSymbolTable"
1086
+ });
1087
+ }
1088
+ pointer++;
1089
+ stringLength++;
1090
+ }
1091
+ if (pointer >= bufferLength) {
1092
+ throw new QvdCorruptedError("Dual string symbol not null-terminated", {
1093
+ field: fieldName,
1094
+ pointer,
1095
+ bufferSize: bufferLength,
1096
+ file: filePath,
1097
+ stage: "parseSymbolTable"
1098
+ });
1099
+ }
1100
+ return pointer - startPointer + 1;
1101
+ }
1102
+ function parseSymbol(typeByte, symbolBuffer, pointer, bufferLength, fieldName, filePath, shouldParse) {
1103
+ switch (typeByte) {
1104
+ case 1: {
1105
+ if (shouldParse) {
1106
+ return parseIntegerSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath);
1107
+ }
1108
+ return { symbol: null, bytesRead: 4 };
1109
+ }
1110
+ case 2: {
1111
+ if (shouldParse) {
1112
+ return parseDoubleSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath);
1113
+ }
1114
+ return { symbol: null, bytesRead: 8 };
1115
+ }
1116
+ case 4: {
1117
+ if (shouldParse) {
1118
+ return parseStringSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath);
1119
+ }
1120
+ const bytesRead = skipStringSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath);
1121
+ return { symbol: null, bytesRead };
1122
+ }
1123
+ case 5: {
1124
+ if (shouldParse) {
1125
+ return parseDualIntegerSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath);
1126
+ }
1127
+ const bytesRead = skipDualSymbol(symbolBuffer, pointer, bufferLength, 4, fieldName, filePath);
1128
+ return { symbol: null, bytesRead };
1129
+ }
1130
+ case 6: {
1131
+ if (shouldParse) {
1132
+ return parseDualDoubleSymbol(symbolBuffer, pointer, bufferLength, fieldName, filePath);
1133
+ }
1134
+ const bytesRead = skipDualSymbol(symbolBuffer, pointer, bufferLength, 8, fieldName, filePath);
1135
+ return { symbol: null, bytesRead };
1136
+ }
1137
+ default: {
1138
+ throw new QvdParseError("Unknown symbol type byte", {
1139
+ typeByte: typeByte.toString(16),
1140
+ offset: pointer - 1,
1141
+ file: filePath,
1142
+ stage: "parseSymbolTable"
1143
+ });
1144
+ }
1145
+ }
1146
+ }
1147
+ var init_symbolParser = __esm({
1148
+ "src/util/symbolParser.js"() {
1149
+ init_QvdSymbol();
1150
+ init_QvdErrors();
1151
+ }
1152
+ });
1153
+
638
1154
  // src/QvdFileReader.js
639
1155
  var QvdFileReader_exports = {};
640
1156
  __export(QvdFileReader_exports, {
@@ -643,10 +1159,13 @@ __export(QvdFileReader_exports, {
643
1159
  var QvdFileReader;
644
1160
  var init_QvdFileReader = __esm({
645
1161
  "src/QvdFileReader.js"() {
646
- init_QvdSymbol();
647
1162
  init_QvdDataFrame();
648
1163
  init_QvdErrors();
649
1164
  init_validatePath();
1165
+ init_bitUtils();
1166
+ init_memoryUtils();
1167
+ init_validationUtils();
1168
+ init_symbolParser();
650
1169
  QvdFileReader = class {
651
1170
  /**
652
1171
  * Constructs a new QVD file parser.
@@ -655,10 +1174,14 @@ var init_QvdFileReader = __esm({
655
1174
  * @param {Object} [options={}] Options for the reader.
656
1175
  * @param {string} [options.allowedDir] Optional allowed directory path. If provided, the file
657
1176
  * path must be within this directory. Defaults to current working directory.
1177
+ * @param {number} [options.memorySafetyFactor=0.3] Memory safety factor (0.0-1.0). Determines
1178
+ * what percentage of available memory (or V8 heap limit, whichever is smaller) can be used
1179
+ * for loading QVD files. Default is 0.3 (30%). Increase for larger heap configurations.
658
1180
  */
659
1181
  constructor(filePath, options = {}) {
660
- const { allowedDir } = options;
1182
+ const { allowedDir, memorySafetyFactor = 0.3 } = options;
661
1183
  this._path = validatePath(filePath, allowedDir);
1184
+ this._memorySafetyFactor = memorySafetyFactor;
662
1185
  this._buffer = null;
663
1186
  this._headerOffset = null;
664
1187
  this._symbolTableOffset = null;
@@ -744,6 +1267,7 @@ var init_QvdFileReader = __esm({
744
1267
  const recordSize = parseInt(headerObj["QvdTableHeader"]["RecordByteSize"], 10);
745
1268
  const totalRows = parseInt(headerObj["QvdTableHeader"]["NoOfRecords"], 10);
746
1269
  const rowsToLoad = Math.min(maxRows, totalRows);
1270
+ validateSymbolTableSizeEarly(symbolTableLength, this._path);
747
1271
  const indexTableBytesToRead = rowsToLoad * recordSize;
748
1272
  const totalBytesToRead = indexTableOffset + indexTableBytesToRead;
749
1273
  const fd = await fs2.promises.open(this._path, "r");
@@ -793,11 +1317,80 @@ var init_QvdFileReader = __esm({
793
1317
  this._symbolTableOffset = headerEndIndex;
794
1318
  this._indexTableOffset = this._symbolTableOffset + parseInt(this._header["QvdTableHeader"]["Offset"], 10);
795
1319
  }
1320
+ /**
1321
+ * Estimates the memory usage for loading a QVD file with given parameters.
1322
+ * Takes into account Phase 2.5 optimization which skips parsing unused symbols.
1323
+ *
1324
+ * @param {number} symbolTableSize Size of symbol table in bytes
1325
+ * @param {number|null} maxRows Maximum number of rows to load (null = all rows)
1326
+ * @param {number} totalRows Total number of rows in the file
1327
+ * @returns {number} Estimated memory usage in bytes
1328
+ * @private
1329
+ */
1330
+ _estimateMemoryUsage(symbolTableSize, maxRows, totalRows) {
1331
+ return estimateMemoryUsage(symbolTableSize, maxRows, totalRows);
1332
+ }
1333
+ /**
1334
+ * Analyzes the index table to determine which symbols are actually needed.
1335
+ * This is used for two-pass symbol filtering optimization.
1336
+ *
1337
+ * @param {number} maxRows The maximum number of rows to analyze.
1338
+ * @returns {Promise<Map<string, Set<number>>>} Map of field names to Set of needed symbol indices.
1339
+ * @private
1340
+ */
1341
+ async _analyzeIndexTableSymbolUsage(maxRows) {
1342
+ if (!this._buffer || !this._header || !this._indexTableOffset) {
1343
+ throw new QvdCorruptedError(
1344
+ "The QVD file has not been loaded in the proper order or has not been loaded at all.",
1345
+ {
1346
+ file: this._path,
1347
+ stage: "analyzeIndexTableSymbolUsage"
1348
+ }
1349
+ );
1350
+ }
1351
+ let fields = this._header["QvdTableHeader"]["Fields"]["QvdFieldHeader"];
1352
+ if (!Array.isArray(fields)) {
1353
+ fields = [fields];
1354
+ }
1355
+ const recordSize = parseInt(this._header["QvdTableHeader"]["RecordByteSize"], 10);
1356
+ const totalRows = parseInt(this._header["QvdTableHeader"]["NoOfRecords"], 10);
1357
+ const rowsToAnalyze = Math.min(maxRows, totalRows);
1358
+ const indexTableLength = parseInt(this._header["QvdTableHeader"]["Length"], 10);
1359
+ const indexBuffer = this._buffer.subarray(this._indexTableOffset, this._indexTableOffset + indexTableLength + 1);
1360
+ const symbolUsage = /* @__PURE__ */ new Map();
1361
+ fields.forEach((field) => {
1362
+ symbolUsage.set(field["FieldName"], /* @__PURE__ */ new Set());
1363
+ });
1364
+ for (let pointer = 0, rowCount = 0; pointer < indexBuffer.length && rowCount < rowsToAnalyze; pointer += recordSize, rowCount++) {
1365
+ const bytes = new Int32Array(indexBuffer.subarray(pointer, pointer + recordSize));
1366
+ bytes.reverse();
1367
+ const mask = bytes.reduce((bits, byte) => bits + ("00000000" + byte.toString(2)).slice(-8), "").split("").reverse().map((bit) => parseInt(bit));
1368
+ fields.forEach((field) => {
1369
+ const bitOffset = parseInt(field["BitOffset"], 10);
1370
+ const bitWidth = parseInt(field["BitWidth"], 10);
1371
+ const bias = parseInt(field["Bias"], 10);
1372
+ let symbolIndex;
1373
+ if (bitWidth === 0) {
1374
+ symbolIndex = 0;
1375
+ } else {
1376
+ symbolIndex = this._convertBitsToInt32(mask.slice(bitOffset, bitOffset + bitWidth));
1377
+ }
1378
+ symbolIndex -= bias;
1379
+ const fieldName = field["FieldName"];
1380
+ symbolUsage.get(fieldName)?.add(symbolIndex);
1381
+ });
1382
+ }
1383
+ return symbolUsage;
1384
+ }
796
1385
  /**
797
1386
  * Parses the symbol table of the QVD file. This method is part of the parsing process
798
1387
  * and should not be called directly.
1388
+ *
1389
+ * @param {Map<string, Set<number>>|null} symbolsToKeep Optional map of field names to symbol indices to keep.
1390
+ * If provided, only these symbols will be parsed (two-pass filtering optimization).
1391
+ * @param {number|null} maxRows Optional maximum number of rows being loaded (for memory estimation).
799
1392
  */
800
- async _parseSymbolTable() {
1393
+ async _parseSymbolTable(symbolsToKeep = null, maxRows = null) {
801
1394
  if (!this._buffer || !this._header || !this._symbolTableOffset || !this._indexTableOffset) {
802
1395
  throw new QvdCorruptedError(
803
1396
  "The QVD file has not been loaded in the proper order or has not been loaded at all.",
@@ -809,192 +1402,40 @@ var init_QvdFileReader = __esm({
809
1402
  }
810
1403
  let fields = this._header["QvdTableHeader"]["Fields"]["QvdFieldHeader"];
811
1404
  const symbolBuffer = this._buffer.subarray(this._symbolTableOffset, this._indexTableOffset);
1405
+ const symbolTableSize = symbolBuffer.length;
1406
+ const totalRows = parseInt(this._header["QvdTableHeader"]["NoOfRecords"], 10);
1407
+ validateSymbolTableSize(symbolTableSize, this._path, totalRows);
1408
+ validateMemoryAvailability(symbolTableSize, maxRows, totalRows, this._path, this._memorySafetyFactor);
1409
+ warnLargeSymbolTable(symbolTableSize, maxRows, totalRows);
812
1410
  if (!Array.isArray(fields)) {
813
1411
  fields = [fields];
814
1412
  }
815
1413
  for (const field of fields) {
816
- const symbolsOffset = parseInt(field["Offset"], 10);
817
- const symbolsLength = parseInt(field["Length"], 10);
818
- if (isNaN(symbolsOffset) || !Number.isSafeInteger(symbolsOffset) || symbolsOffset < 0) {
819
- throw new QvdCorruptedError("Invalid symbol offset", {
820
- field: field["FieldName"],
821
- offset: symbolsOffset,
822
- file: this._path,
823
- stage: "parseSymbolTable"
824
- });
825
- }
826
- if (isNaN(symbolsLength) || !Number.isSafeInteger(symbolsLength) || symbolsLength < 0) {
827
- throw new QvdCorruptedError("Invalid symbol length", {
828
- field: field["FieldName"],
829
- length: symbolsLength,
830
- file: this._path,
831
- stage: "parseSymbolTable"
832
- });
833
- }
834
- if (symbolsOffset + symbolsLength > symbolBuffer.length) {
835
- throw new QvdCorruptedError("Symbol data extends beyond buffer", {
836
- field: field["FieldName"],
837
- offset: symbolsOffset,
838
- length: symbolsLength,
839
- bufferSize: symbolBuffer.length,
840
- file: this._path,
841
- stage: "parseSymbolTable"
842
- });
843
- }
1414
+ validateFieldMetadata(field, symbolBuffer.length, this._path);
844
1415
  }
845
1416
  this._symbolTable = fields.map((field) => {
846
1417
  const symbolsOffset = parseInt(field["Offset"], 10);
847
1418
  const symbolsLength = parseInt(field["Length"], 10);
1419
+ const fieldName = field["FieldName"];
1420
+ const neededSymbols = symbolsToKeep ? symbolsToKeep.get(fieldName) : null;
1421
+ const filteringEnabled = neededSymbols !== null;
848
1422
  const symbols = [];
1423
+ let symbolIndex = 0;
849
1424
  for (let pointer = symbolsOffset; pointer < symbolsOffset + symbolsLength; pointer++) {
850
1425
  const typeByte = symbolBuffer[pointer++];
851
- switch (typeByte) {
852
- case 1: {
853
- if (pointer + 4 > symbolBuffer.length) {
854
- throw new QvdCorruptedError("Buffer overflow reading integer symbol", {
855
- field: field["FieldName"],
856
- pointer,
857
- bufferSize: symbolBuffer.length,
858
- file: this._path,
859
- stage: "parseSymbolTable"
860
- });
861
- }
862
- const byteData = new Int32Array(symbolBuffer.subarray(pointer, pointer + 4));
863
- const value = Buffer.from(byteData).readIntLE(0, byteData.length);
864
- pointer += 3;
865
- symbols.push(QvdSymbol.fromIntValue(value));
866
- break;
867
- }
868
- case 2: {
869
- if (pointer + 8 > symbolBuffer.length) {
870
- throw new QvdCorruptedError("Buffer overflow reading double symbol", {
871
- field: field["FieldName"],
872
- pointer,
873
- bufferSize: symbolBuffer.length,
874
- file: this._path,
875
- stage: "parseSymbolTable"
876
- });
877
- }
878
- const byteData = new Int32Array(symbolBuffer.subarray(pointer, pointer + 8));
879
- const value = Buffer.from(byteData).readDoubleLE(0);
880
- pointer += 7;
881
- symbols.push(QvdSymbol.fromDoubleValue(value));
882
- break;
883
- }
884
- case 4: {
885
- const byteData = [];
886
- const maxStringLength = 1048576;
887
- while (pointer < symbolBuffer.length && symbolBuffer[pointer] !== 0) {
888
- if (byteData.length >= maxStringLength) {
889
- throw new QvdCorruptedError("String symbol exceeds maximum length", {
890
- field: field["FieldName"],
891
- maxLength: maxStringLength,
892
- file: this._path,
893
- stage: "parseSymbolTable"
894
- });
895
- }
896
- byteData.push(symbolBuffer[pointer++]);
897
- }
898
- if (pointer >= symbolBuffer.length) {
899
- throw new QvdCorruptedError("String symbol not null-terminated", {
900
- field: field["FieldName"],
901
- pointer,
902
- bufferSize: symbolBuffer.length,
903
- file: this._path,
904
- stage: "parseSymbolTable"
905
- });
906
- }
907
- const value = Buffer.from(byteData).toString("utf-8");
908
- symbols.push(QvdSymbol.fromStringValue(value));
909
- break;
910
- }
911
- case 5: {
912
- if (pointer + 4 > symbolBuffer.length) {
913
- throw new QvdCorruptedError("Buffer overflow reading dual integer symbol", {
914
- field: field["FieldName"],
915
- pointer,
916
- bufferSize: symbolBuffer.length,
917
- file: this._path,
918
- stage: "parseSymbolTable"
919
- });
920
- }
921
- const intByteData = new Int32Array(symbolBuffer.subarray(pointer, pointer + 4));
922
- const intValue = Buffer.from(intByteData).readIntLE(0, intByteData.length);
923
- pointer += 4;
924
- const stringByteData = [];
925
- const maxStringLength = 1048576;
926
- while (pointer < symbolBuffer.length && symbolBuffer[pointer] !== 0) {
927
- if (stringByteData.length >= maxStringLength) {
928
- throw new QvdCorruptedError("Dual string symbol exceeds maximum length", {
929
- field: field["FieldName"],
930
- maxLength: maxStringLength,
931
- file: this._path,
932
- stage: "parseSymbolTable"
933
- });
934
- }
935
- stringByteData.push(symbolBuffer[pointer++]);
936
- }
937
- if (pointer >= symbolBuffer.length) {
938
- throw new QvdCorruptedError("Dual string symbol not null-terminated", {
939
- field: field["FieldName"],
940
- pointer,
941
- bufferSize: symbolBuffer.length,
942
- file: this._path,
943
- stage: "parseSymbolTable"
944
- });
945
- }
946
- const stringValue = Buffer.from(stringByteData).toString("utf-8");
947
- symbols.push(QvdSymbol.fromDualIntValue(intValue, stringValue));
948
- break;
949
- }
950
- case 6: {
951
- if (pointer + 8 > symbolBuffer.length) {
952
- throw new QvdCorruptedError("Buffer overflow reading dual double symbol", {
953
- field: field["FieldName"],
954
- pointer,
955
- bufferSize: symbolBuffer.length,
956
- file: this._path,
957
- stage: "parseSymbolTable"
958
- });
959
- }
960
- const doubleByteData = new Int32Array(symbolBuffer.subarray(pointer, pointer + 8));
961
- const doubleValue = Buffer.from(doubleByteData).readDoubleLE(0);
962
- pointer += 8;
963
- const stringByteData = [];
964
- const maxStringLength = 1048576;
965
- while (pointer < symbolBuffer.length && symbolBuffer[pointer] !== 0) {
966
- if (stringByteData.length >= maxStringLength) {
967
- throw new QvdCorruptedError("Dual string symbol exceeds maximum length", {
968
- field: field["FieldName"],
969
- maxLength: maxStringLength,
970
- file: this._path,
971
- stage: "parseSymbolTable"
972
- });
973
- }
974
- stringByteData.push(symbolBuffer[pointer++]);
975
- }
976
- if (pointer >= symbolBuffer.length) {
977
- throw new QvdCorruptedError("Dual string symbol not null-terminated", {
978
- field: field["FieldName"],
979
- pointer,
980
- bufferSize: symbolBuffer.length,
981
- file: this._path,
982
- stage: "parseSymbolTable"
983
- });
984
- }
985
- const stringValue = Buffer.from(stringByteData).toString("utf-8");
986
- symbols.push(QvdSymbol.fromDualDoubleValue(doubleValue, stringValue));
987
- break;
988
- }
989
- default: {
990
- throw new QvdParseError("Unknown symbol type byte", {
991
- typeByte: typeByte.toString(16),
992
- offset: pointer,
993
- file: this._path,
994
- stage: "parseSymbolTable"
995
- });
996
- }
997
- }
1426
+ const shouldKeepSymbol = !filteringEnabled || !!(neededSymbols && neededSymbols.has(symbolIndex));
1427
+ const { symbol, bytesRead } = parseSymbol(
1428
+ typeByte,
1429
+ symbolBuffer,
1430
+ pointer,
1431
+ symbolBuffer.length,
1432
+ fieldName,
1433
+ this._path,
1434
+ shouldKeepSymbol
1435
+ );
1436
+ symbols.push(symbol);
1437
+ pointer += bytesRead - 1;
1438
+ symbolIndex++;
998
1439
  }
999
1440
  return symbols;
1000
1441
  });
@@ -1006,10 +1447,7 @@ var init_QvdFileReader = __esm({
1006
1447
  * @return {Number} The integer value
1007
1448
  */
1008
1449
  _convertBitsToInt32(bits) {
1009
- if (bits.length === 0) {
1010
- return 0;
1011
- }
1012
- return bits.reduce((value, bit, index) => value += bit * Math.pow(2, index), 0);
1450
+ return convertBitsToInt32(bits);
1013
1451
  }
1014
1452
  /**
1015
1453
  * Parses the bit stuffed index table of the QVD file. This method is part of the parsing process
@@ -1032,105 +1470,21 @@ var init_QvdFileReader = __esm({
1032
1470
  fields = [fields];
1033
1471
  }
1034
1472
  const recordSize = parseInt(this._header["QvdTableHeader"]["RecordByteSize"], 10);
1035
- if (isNaN(recordSize) || !Number.isSafeInteger(recordSize) || recordSize < 0) {
1036
- throw new QvdCorruptedError("Invalid record byte size", {
1037
- recordSize,
1038
- file: this._path,
1039
- stage: "parseIndexTable"
1040
- });
1041
- }
1042
1473
  const totalRows = parseInt(this._header["QvdTableHeader"]["NoOfRecords"], 10);
1043
- if (isNaN(totalRows) || !Number.isSafeInteger(totalRows) || totalRows < 0) {
1044
- throw new QvdCorruptedError("Invalid number of records", {
1045
- totalRows,
1046
- file: this._path,
1047
- stage: "parseIndexTable"
1048
- });
1049
- }
1050
- if (recordSize === 0 && totalRows > 0) {
1051
- throw new QvdCorruptedError("Record byte size cannot be zero when records exist", {
1052
- recordSize,
1053
- totalRows,
1054
- file: this._path,
1055
- stage: "parseIndexTable"
1056
- });
1057
- }
1058
- if (recordSize > 1048576) {
1059
- throw new QvdCorruptedError("Record byte size exceeds maximum", {
1060
- recordSize,
1061
- maxSize: 1048576,
1062
- file: this._path,
1063
- stage: "parseIndexTable"
1064
- });
1065
- }
1066
1474
  const rowsToLoad = maxRows !== null ? Math.min(maxRows, totalRows) : totalRows;
1067
1475
  const indexTableLength = parseInt(this._header["QvdTableHeader"]["Length"], 10);
1068
- if (isNaN(indexTableLength) || !Number.isSafeInteger(indexTableLength) || indexTableLength < 0) {
1069
- throw new QvdCorruptedError("Invalid index table length", {
1070
- length: indexTableLength,
1071
- file: this._path,
1072
- stage: "parseIndexTable"
1073
- });
1074
- }
1075
- if (this._indexTableOffset > this._buffer.length) {
1076
- throw new QvdCorruptedError("Index table offset beyond buffer", {
1077
- indexTableOffset: this._indexTableOffset,
1078
- bufferSize: this._buffer.length,
1079
- file: this._path,
1080
- stage: "parseIndexTable"
1081
- });
1082
- }
1083
- const maxReasonableOverage = 100 * 1024 * 1024;
1084
- if (indexTableLength > this._buffer.length + maxReasonableOverage) {
1085
- throw new QvdCorruptedError("Index table length unreasonably large", {
1086
- indexTableLength,
1087
- bufferSize: this._buffer.length,
1088
- file: this._path,
1089
- stage: "parseIndexTable"
1090
- });
1091
- }
1092
- const requiredIndexBytes = rowsToLoad * recordSize;
1093
- if (indexTableLength < requiredIndexBytes) {
1094
- throw new QvdCorruptedError("Index table length smaller than required", {
1095
- indexTableLength,
1096
- requiredBytes: requiredIndexBytes,
1097
- rowsToLoad,
1098
- recordSize,
1099
- file: this._path,
1100
- stage: "parseIndexTable"
1101
- });
1102
- }
1476
+ validateIndexTableMetadata(
1477
+ recordSize,
1478
+ totalRows,
1479
+ indexTableLength,
1480
+ this._indexTableOffset,
1481
+ this._buffer.length,
1482
+ rowsToLoad,
1483
+ this._path
1484
+ );
1103
1485
  const indexBuffer = this._buffer.subarray(this._indexTableOffset, this._indexTableOffset + indexTableLength + 1);
1104
1486
  for (const field of fields) {
1105
- const bitOffset = parseInt(field["BitOffset"], 10);
1106
- const bitWidth = parseInt(field["BitWidth"], 10);
1107
- if (isNaN(bitOffset) || !Number.isSafeInteger(bitOffset) || bitOffset < 0) {
1108
- throw new QvdCorruptedError("Invalid bit offset", {
1109
- field: field["FieldName"],
1110
- bitOffset,
1111
- file: this._path,
1112
- stage: "parseIndexTable"
1113
- });
1114
- }
1115
- if (isNaN(bitWidth) || !Number.isSafeInteger(bitWidth) || bitWidth < 0) {
1116
- throw new QvdCorruptedError("Invalid bit width", {
1117
- field: field["FieldName"],
1118
- bitWidth,
1119
- file: this._path,
1120
- stage: "parseIndexTable"
1121
- });
1122
- }
1123
- const recordSizeInBits = recordSize * 8;
1124
- if (bitOffset + bitWidth > recordSizeInBits) {
1125
- throw new QvdCorruptedError("Bit field extends beyond record size", {
1126
- field: field["FieldName"],
1127
- bitOffset,
1128
- bitWidth,
1129
- recordSizeInBits,
1130
- file: this._path,
1131
- stage: "parseIndexTable"
1132
- });
1133
- }
1487
+ validateFieldBitMetadata(field, recordSize, this._path);
1134
1488
  }
1135
1489
  this._indexTable = [];
1136
1490
  for (let pointer = 0, rowCount = 0; pointer < indexBuffer.length && rowCount < rowsToLoad; pointer += recordSize, rowCount++) {
@@ -1172,7 +1526,19 @@ var init_QvdFileReader = __esm({
1172
1526
  async load(maxRows = null) {
1173
1527
  await this._readData(maxRows);
1174
1528
  await this._parseHeader();
1175
- await this._parseSymbolTable();
1529
+ let symbolsToKeep = null;
1530
+ if (maxRows !== null && this._header) {
1531
+ const symbolTableLength = parseInt(this._header["QvdTableHeader"]["Offset"], 10);
1532
+ const SYMBOL_FILTERING_THRESHOLD = 50 * 1024 * 1024;
1533
+ if (symbolTableLength > SYMBOL_FILTERING_THRESHOLD) {
1534
+ symbolsToKeep = await this._analyzeIndexTableSymbolUsage(maxRows);
1535
+ const totalSymbols = Array.from(symbolsToKeep.values()).reduce((sum, set) => sum + set.size, 0);
1536
+ console.log(
1537
+ `[Phase 2.5 Optimization] Using stream-and-skip parsing: keeping ${totalSymbols} symbols from ${(symbolTableLength / 1024 / 1024).toFixed(1)}MB symbol table`
1538
+ );
1539
+ }
1540
+ }
1541
+ await this._parseSymbolTable(symbolsToKeep, maxRows);
1176
1542
  await this._parseIndexTable(maxRows);
1177
1543
  assert(this._header, "The QVD file header has not been parsed.");
1178
1544
  assert(this._symbolTable, "The QVD file symbol table has not been parsed.");
@@ -1620,11 +1986,17 @@ var init_QvdDataFrame = __esm({
1620
1986
  * @param {Object} [options] Optional loading options.
1621
1987
  * @param {number|null} [options.maxRows] The maximum number of rows to load. If not specified, all rows are loaded.
1622
1988
  * @param {string} [options.allowedDir] Optional allowed directory path. If provided, the file path must be within this directory.
1989
+ * @param {number} [options.memorySafetyFactor=0.3] Memory safety factor (0.0-1.0). Determines what percentage
1990
+ * of available memory (or V8 heap limit, whichever is smaller) can be used. Default is 0.3 (30%).
1991
+ * Increase this (e.g., to 0.5 or 0.7) when running with larger heap sizes via --max-old-space-size.
1623
1992
  * @return {Promise<QvdDataFrame>} The data frame of the QVD file.
1624
1993
  */
1625
1994
  static async fromQvd(path3, options = {}) {
1626
1995
  const { QvdFileReader: QvdFileReader2 } = await Promise.resolve().then(() => (init_QvdFileReader(), QvdFileReader_exports));
1627
- const readerOptions = { allowedDir: options.allowedDir };
1996
+ const readerOptions = {
1997
+ allowedDir: options.allowedDir,
1998
+ memorySafetyFactor: options.memorySafetyFactor
1999
+ };
1628
2000
  return await new QvdFileReader2(path3, readerOptions).load(options.maxRows !== void 0 ? options.maxRows : null);
1629
2001
  }
1630
2002
  /**