@nmakarov/cli-toolkit 0.11.3 → 0.14.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.
@@ -584,20 +584,48 @@ var FileDatabase = class {
584
584
  }
585
585
  /**
586
586
  * Figure out what data to write and which file to use (for pagination)
587
+ * @param data - Data to write
588
+ * @param targetFileIndex - Optional index of existing file to overwrite (when customMetadata matches)
589
+ * @param forceNewFile - If true, always create a new file (when customMetadata provided but no match)
587
590
  */
588
- figureOutDataAndFileToWrite(data) {
591
+ figureOutDataAndFileToWrite(data, targetFileIndex = null, forceNewFile = false) {
589
592
  let dataToWrite;
590
593
  let dataLeftOver;
591
594
  const incomingDataType = detectDataType(data);
592
595
  if (this.metadata.dataType !== incomingDataType) {
593
596
  this.metadata.dataType = incomingDataType;
594
597
  }
595
- if (this.metadata.files.length === 0) {
598
+ if (targetFileIndex !== null && targetFileIndex < this.metadata.files.length) {
599
+ const targetFile = this.metadata.files[targetFileIndex];
600
+ if (!Array.isArray(data)) {
601
+ dataToWrite = data;
602
+ dataLeftOver = null;
603
+ return { dataToWrite, dataLeftOver, fileName: targetFile.fileName };
604
+ } else {
605
+ dataToWrite = data.slice(0, this.pageSize);
606
+ dataLeftOver = data.slice(this.pageSize);
607
+ this.lastFileData = dataToWrite;
608
+ return { dataToWrite, dataLeftOver, fileName: targetFile.fileName };
609
+ }
610
+ }
611
+ let newlyCreatedFileIndex = null;
612
+ if (forceNewFile) {
613
+ const filesBeforeCreate = this.metadata.files.length;
614
+ this.makeNewFile();
615
+ newlyCreatedFileIndex = filesBeforeCreate;
616
+ this.logger.debug?.(`[FileDatabase] Creating new file for unique custom metadata combination, fileNumber: ${this.currentFileNumber}`);
617
+ } else if (this.metadata.files.length === 0) {
596
618
  this.makeNewFile();
597
619
  }
598
620
  const lastFile = this.metadata.files[this.metadata.files.length - 1];
599
621
  const lastFileRecordsCount = lastFile.recordsCount;
600
- if (!Array.isArray(data)) {
622
+ if (forceNewFile && newlyCreatedFileIndex !== null) {
623
+ const newlyCreatedFile = this.metadata.files[newlyCreatedFileIndex];
624
+ if (newlyCreatedFile && newlyCreatedFile.fileName !== lastFile.fileName) {
625
+ this.logger.warn?.(`[FileDatabase] Warning: Newly created file ${newlyCreatedFile.fileName} doesn't match last file ${lastFile.fileName}`);
626
+ }
627
+ }
628
+ if (!Array.isArray(data) && !forceNewFile) {
601
629
  const lastFileExtension = import_path3.default.extname(lastFile.fileName);
602
630
  const expectedExtension = `.${getFileExtension(incomingDataType)}`;
603
631
  if (lastFileExtension !== expectedExtension) {
@@ -607,24 +635,35 @@ var FileDatabase = class {
607
635
  lastFile.fileName = `${this.currentFileNumber.toString().padStart(6, "0")}.${getFileExtension(incomingDataType)}`;
608
636
  }
609
637
  }
638
+ } else if (!Array.isArray(data) && forceNewFile) {
639
+ const lastFileExtension = import_path3.default.extname(lastFile.fileName);
640
+ const expectedExtension = `.${getFileExtension(incomingDataType)}`;
641
+ if (lastFileExtension !== expectedExtension) {
642
+ lastFile.fileName = `${this.currentFileNumber.toString().padStart(6, "0")}.${getFileExtension(incomingDataType)}`;
643
+ }
610
644
  }
611
645
  if (Array.isArray(data)) {
612
- if (lastFileRecordsCount < this.pageSize) {
646
+ if (forceNewFile) {
647
+ dataToWrite = data.slice(0, this.pageSize);
648
+ dataLeftOver = data.slice(this.pageSize);
649
+ this.lastFileData = dataToWrite;
650
+ } else if (lastFileRecordsCount < this.pageSize) {
613
651
  dataToWrite = [...this.lastFileData || [], ...data.slice(0, this.pageSize - lastFileRecordsCount)];
614
652
  dataLeftOver = data.slice(this.pageSize - lastFileRecordsCount);
653
+ this.lastFileData = dataToWrite;
615
654
  } else {
616
655
  this.makeNewFile();
617
656
  dataToWrite = data.slice(0, this.pageSize);
618
657
  dataLeftOver = data.slice(this.pageSize);
658
+ this.lastFileData = dataToWrite;
619
659
  }
620
- this.lastFileData = dataToWrite;
621
660
  } else {
622
661
  dataToWrite = data;
623
662
  dataLeftOver = null;
624
663
  }
625
664
  const fileName = this.metadata.files[this.metadata.files.length - 1].fileName;
626
665
  this.logger.debug?.(
627
- `[FileDatabase] figureOutDataAndFileToWrite: filename=${fileName}, dataToWrite.length=${Array.isArray(dataToWrite) ? dataToWrite.length : "N/A"}, lastFileRecordsCount=${lastFileRecordsCount}`
666
+ `[FileDatabase] figureOutDataAndFileToWrite: filename=${fileName}, forceNewFile=${forceNewFile}, targetFileIndex=${targetFileIndex}, dataToWrite.length=${Array.isArray(dataToWrite) ? dataToWrite.length : "N/A"}, lastFileRecordsCount=${lastFileRecordsCount}`
628
667
  );
629
668
  return { dataToWrite, dataLeftOver, fileName };
630
669
  }
@@ -652,7 +691,7 @@ var FileDatabase = class {
652
691
  /**
653
692
  * Update metadata after writing data
654
693
  */
655
- updateMetadata(dataToWrite, fileName) {
694
+ updateMetadata(dataToWrite, fileName, customMetadata) {
656
695
  let currentFile;
657
696
  if (fileName) {
658
697
  const foundFile = this.metadata.files.find((file) => file.fileName === fileName);
@@ -667,6 +706,9 @@ var FileDatabase = class {
667
706
  }
668
707
  const recordsCount = Array.isArray(dataToWrite) ? dataToWrite.length : 1;
669
708
  currentFile.recordsCount = recordsCount;
709
+ if (customMetadata) {
710
+ Object.assign(currentFile, customMetadata);
711
+ }
670
712
  const fileIndex = this.metadata.files.indexOf(currentFile);
671
713
  if (fileIndex !== -1) {
672
714
  this.calculateFileSynopsis(dataToWrite, fileIndex);
@@ -719,6 +761,11 @@ var FileDatabase = class {
719
761
  } else {
720
762
  if (!this.metadata.files.length) {
721
763
  this.metadata = await this.figureMetadata(this.currentVersion);
764
+ if (this.metadata.files && this.metadata.files.length > 0) {
765
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
766
+ } else {
767
+ this.currentFileNumber = 0;
768
+ }
722
769
  }
723
770
  }
724
771
  } else {
@@ -729,16 +776,22 @@ var FileDatabase = class {
729
776
  try {
730
777
  const rawData = await import_fs3.default.promises.readFile(metadataPath, "utf8");
731
778
  this.metadata = JSON.parse(rawData);
779
+ if (this.metadata.files && this.metadata.files.length > 0) {
780
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
781
+ } else {
782
+ this.currentFileNumber = 0;
783
+ }
732
784
  } catch (e) {
733
785
  this.metadata = this.getDefaultMetadata();
786
+ this.currentFileNumber = 0;
734
787
  }
735
788
  } else {
736
789
  this.metadata = this.getDefaultMetadata();
737
- this.makeNewFile();
790
+ this.currentFileNumber = 0;
738
791
  }
739
792
  } else {
740
793
  this.metadata = this.getDefaultMetadata();
741
- this.makeNewFile();
794
+ this.currentFileNumber = 0;
742
795
  }
743
796
  }
744
797
  } else if (read) {
@@ -757,6 +810,11 @@ var FileDatabase = class {
757
810
  }
758
811
  if (!this.metadata.files.length) {
759
812
  this.metadata = await this.figureMetadata(this.currentVersion);
813
+ if (this.metadata.files && this.metadata.files.length > 0) {
814
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
815
+ } else {
816
+ this.currentFileNumber = 0;
817
+ }
760
818
  }
761
819
  } else {
762
820
  this.currentVersion = null;
@@ -770,6 +828,11 @@ var FileDatabase = class {
770
828
  try {
771
829
  const rawData = await import_fs3.default.promises.readFile(metadataPath, "utf8");
772
830
  this.metadata = JSON.parse(rawData);
831
+ if (this.metadata.files && this.metadata.files.length > 0) {
832
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
833
+ } else {
834
+ this.currentFileNumber = 0;
835
+ }
773
836
  } catch (e) {
774
837
  throw new FileDatabaseError(`Failed to read metadata: ${e.message}`);
775
838
  }
@@ -778,6 +841,11 @@ var FileDatabase = class {
778
841
  }
779
842
  } else {
780
843
  this.metadata = await this.figureMetadataFromVersionFiles("");
844
+ if (this.metadata.files && this.metadata.files.length > 0) {
845
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
846
+ } else {
847
+ this.currentFileNumber = 0;
848
+ }
781
849
  }
782
850
  }
783
851
  }
@@ -799,14 +867,44 @@ var FileDatabase = class {
799
867
  this.metadata.dataType = incomingDataType;
800
868
  this.makeNewFile();
801
869
  }
802
- let { dataToWrite, dataLeftOver, fileName } = this.figureOutDataAndFileToWrite(data);
870
+ let targetFileIndex = null;
871
+ const hasCustomMetadata = options.customMetadata && Object.keys(options.customMetadata).length > 0;
872
+ if (hasCustomMetadata) {
873
+ for (let i = 0; i < this.metadata.files.length; i++) {
874
+ const fileEntry = this.metadata.files[i];
875
+ const matches = Object.keys(options.customMetadata).every((key) => {
876
+ return key in fileEntry && fileEntry[key] === options.customMetadata[key];
877
+ });
878
+ if (matches) {
879
+ targetFileIndex = i;
880
+ this.logger.debug?.(`[FileDatabase] Found existing file with matching custom metadata: ${fileEntry.fileName}, metadata: ${JSON.stringify(options.customMetadata)}`);
881
+ break;
882
+ } else {
883
+ this.logger.debug?.(`[FileDatabase] File ${fileEntry.fileName} does not match custom metadata: ${JSON.stringify(options.customMetadata)}`);
884
+ }
885
+ }
886
+ if (targetFileIndex === null) {
887
+ this.logger.debug?.(`[FileDatabase] No existing file found with custom metadata: ${JSON.stringify(options.customMetadata)}, will create new file`);
888
+ }
889
+ } else {
890
+ this.logger.debug?.(`[FileDatabase] No custom metadata provided, will create new file`);
891
+ }
892
+ if (targetFileIndex !== null) {
893
+ const targetFile = this.metadata.files[targetFileIndex];
894
+ this.currentFileNumber = targetFile.number;
895
+ this.lastFileData = null;
896
+ this.currentRecord = 0;
897
+ this.hasReadFirstPage = false;
898
+ }
899
+ const forceNewFile = hasCustomMetadata && targetFileIndex === null;
900
+ let { dataToWrite, dataLeftOver, fileName } = this.figureOutDataAndFileToWrite(data, targetFileIndex, forceNewFile);
803
901
  const destPath = this.getDestinationPath(this.currentVersion || void 0);
804
902
  await this.safeWrite(import_path3.default.join(destPath, fileName), dataToWrite);
805
- this.updateMetadata(dataToWrite, fileName);
806
- while (dataLeftOver && dataLeftOver.length > 0) {
903
+ this.updateMetadata(dataToWrite, fileName, options.customMetadata);
904
+ while (dataLeftOver && dataLeftOver.length > 0 && targetFileIndex === null) {
807
905
  const writeContext = this.figureOutDataAndFileToWrite(dataLeftOver);
808
906
  await this.safeWrite(import_path3.default.join(destPath, writeContext.fileName), writeContext.dataToWrite);
809
- this.updateMetadata(writeContext.dataToWrite, writeContext.fileName);
907
+ this.updateMetadata(writeContext.dataToWrite, writeContext.fileName, options.customMetadata);
810
908
  dataLeftOver = writeContext.dataLeftOver;
811
909
  }
812
910
  this.calculateVersionSynopsis();
@@ -923,6 +1021,63 @@ var FileDatabase = class {
923
1021
  getMetadata() {
924
1022
  return { ...this.metadata };
925
1023
  }
1024
+ /**
1025
+ * Find data by custom metadata fields
1026
+ * Searches through all versions and files to find entries matching the search criteria
1027
+ *
1028
+ * @param searchCriteria - Object with field names and values to search for (e.g., { ListingKey: "123", id: "456" })
1029
+ * @returns Array of found entries with their file paths and metadata
1030
+ */
1031
+ async findData(searchCriteria) {
1032
+ const results = [];
1033
+ if (!this.versioned) {
1034
+ await this.prepare({ read: true });
1035
+ const metadata = this.getMetadata();
1036
+ for (const fileEntry of metadata.files) {
1037
+ const matches = Object.keys(searchCriteria).every((key) => {
1038
+ return fileEntry[key] === searchCriteria[key];
1039
+ });
1040
+ if (matches) {
1041
+ const destPath = this.getDestinationPath();
1042
+ const filePath = import_path3.default.join(destPath, fileEntry.fileName);
1043
+ const fileData = await import_fs3.default.promises.readFile(filePath, "utf8");
1044
+ const data = deserializeData(fileData, metadata.dataType || "json-object");
1045
+ results.push({
1046
+ filePath,
1047
+ fileName: fileEntry.fileName,
1048
+ version: null,
1049
+ metadata: fileEntry,
1050
+ data
1051
+ });
1052
+ }
1053
+ }
1054
+ } else {
1055
+ const versions = await this.getVersions();
1056
+ for (const version of versions) {
1057
+ await this.prepare({ read: true, version });
1058
+ const metadata = this.getMetadata();
1059
+ for (const fileEntry of metadata.files) {
1060
+ const matches = Object.keys(searchCriteria).every((key) => {
1061
+ return fileEntry[key] === searchCriteria[key];
1062
+ });
1063
+ if (matches) {
1064
+ const destPath = this.getDestinationPath(version);
1065
+ const filePath = import_path3.default.join(destPath, fileEntry.fileName);
1066
+ const fileData = await import_fs3.default.promises.readFile(filePath, "utf8");
1067
+ const data = deserializeData(fileData, metadata.dataType || "json-object");
1068
+ results.push({
1069
+ filePath,
1070
+ fileName: fileEntry.fileName,
1071
+ version,
1072
+ metadata: fileEntry,
1073
+ data
1074
+ });
1075
+ }
1076
+ }
1077
+ }
1078
+ }
1079
+ return results;
1080
+ }
926
1081
  };
927
1082
 
928
1083
  // src/mock-server/catalog.ts