@nmakarov/cli-toolkit 0.11.4 → 0.14.2

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.
@@ -547,20 +547,48 @@ var FileDatabase = class {
547
547
  }
548
548
  /**
549
549
  * Figure out what data to write and which file to use (for pagination)
550
+ * @param data - Data to write
551
+ * @param targetFileIndex - Optional index of existing file to overwrite (when customMetadata matches)
552
+ * @param forceNewFile - If true, always create a new file (when customMetadata provided but no match)
550
553
  */
551
- figureOutDataAndFileToWrite(data) {
554
+ figureOutDataAndFileToWrite(data, targetFileIndex = null, forceNewFile = false) {
552
555
  let dataToWrite;
553
556
  let dataLeftOver;
554
557
  const incomingDataType = detectDataType(data);
555
558
  if (this.metadata.dataType !== incomingDataType) {
556
559
  this.metadata.dataType = incomingDataType;
557
560
  }
558
- if (this.metadata.files.length === 0) {
561
+ if (targetFileIndex !== null && targetFileIndex < this.metadata.files.length) {
562
+ const targetFile = this.metadata.files[targetFileIndex];
563
+ if (!Array.isArray(data)) {
564
+ dataToWrite = data;
565
+ dataLeftOver = null;
566
+ return { dataToWrite, dataLeftOver, fileName: targetFile.fileName };
567
+ } else {
568
+ dataToWrite = data.slice(0, this.pageSize);
569
+ dataLeftOver = data.slice(this.pageSize);
570
+ this.lastFileData = dataToWrite;
571
+ return { dataToWrite, dataLeftOver, fileName: targetFile.fileName };
572
+ }
573
+ }
574
+ let newlyCreatedFileIndex = null;
575
+ if (forceNewFile) {
576
+ const filesBeforeCreate = this.metadata.files.length;
577
+ this.makeNewFile();
578
+ newlyCreatedFileIndex = filesBeforeCreate;
579
+ this.logger.debug?.(`[FileDatabase] Creating new file for unique custom metadata combination, fileNumber: ${this.currentFileNumber}`);
580
+ } else if (this.metadata.files.length === 0) {
559
581
  this.makeNewFile();
560
582
  }
561
583
  const lastFile = this.metadata.files[this.metadata.files.length - 1];
562
584
  const lastFileRecordsCount = lastFile.recordsCount;
563
- if (!Array.isArray(data)) {
585
+ if (forceNewFile && newlyCreatedFileIndex !== null) {
586
+ const newlyCreatedFile = this.metadata.files[newlyCreatedFileIndex];
587
+ if (newlyCreatedFile && newlyCreatedFile.fileName !== lastFile.fileName) {
588
+ this.logger.warn?.(`[FileDatabase] Warning: Newly created file ${newlyCreatedFile.fileName} doesn't match last file ${lastFile.fileName}`);
589
+ }
590
+ }
591
+ if (!Array.isArray(data) && !forceNewFile) {
564
592
  const lastFileExtension = path3.extname(lastFile.fileName);
565
593
  const expectedExtension = `.${getFileExtension(incomingDataType)}`;
566
594
  if (lastFileExtension !== expectedExtension) {
@@ -570,24 +598,35 @@ var FileDatabase = class {
570
598
  lastFile.fileName = `${this.currentFileNumber.toString().padStart(6, "0")}.${getFileExtension(incomingDataType)}`;
571
599
  }
572
600
  }
601
+ } else if (!Array.isArray(data) && forceNewFile) {
602
+ const lastFileExtension = path3.extname(lastFile.fileName);
603
+ const expectedExtension = `.${getFileExtension(incomingDataType)}`;
604
+ if (lastFileExtension !== expectedExtension) {
605
+ lastFile.fileName = `${this.currentFileNumber.toString().padStart(6, "0")}.${getFileExtension(incomingDataType)}`;
606
+ }
573
607
  }
574
608
  if (Array.isArray(data)) {
575
- if (lastFileRecordsCount < this.pageSize) {
609
+ if (forceNewFile) {
610
+ dataToWrite = data.slice(0, this.pageSize);
611
+ dataLeftOver = data.slice(this.pageSize);
612
+ this.lastFileData = dataToWrite;
613
+ } else if (lastFileRecordsCount < this.pageSize) {
576
614
  dataToWrite = [...this.lastFileData || [], ...data.slice(0, this.pageSize - lastFileRecordsCount)];
577
615
  dataLeftOver = data.slice(this.pageSize - lastFileRecordsCount);
616
+ this.lastFileData = dataToWrite;
578
617
  } else {
579
618
  this.makeNewFile();
580
619
  dataToWrite = data.slice(0, this.pageSize);
581
620
  dataLeftOver = data.slice(this.pageSize);
621
+ this.lastFileData = dataToWrite;
582
622
  }
583
- this.lastFileData = dataToWrite;
584
623
  } else {
585
624
  dataToWrite = data;
586
625
  dataLeftOver = null;
587
626
  }
588
627
  const fileName = this.metadata.files[this.metadata.files.length - 1].fileName;
589
628
  this.logger.debug?.(
590
- `[FileDatabase] figureOutDataAndFileToWrite: filename=${fileName}, dataToWrite.length=${Array.isArray(dataToWrite) ? dataToWrite.length : "N/A"}, lastFileRecordsCount=${lastFileRecordsCount}`
629
+ `[FileDatabase] figureOutDataAndFileToWrite: filename=${fileName}, forceNewFile=${forceNewFile}, targetFileIndex=${targetFileIndex}, dataToWrite.length=${Array.isArray(dataToWrite) ? dataToWrite.length : "N/A"}, lastFileRecordsCount=${lastFileRecordsCount}`
591
630
  );
592
631
  return { dataToWrite, dataLeftOver, fileName };
593
632
  }
@@ -615,7 +654,7 @@ var FileDatabase = class {
615
654
  /**
616
655
  * Update metadata after writing data
617
656
  */
618
- updateMetadata(dataToWrite, fileName) {
657
+ updateMetadata(dataToWrite, fileName, customMetadata) {
619
658
  let currentFile;
620
659
  if (fileName) {
621
660
  const foundFile = this.metadata.files.find((file) => file.fileName === fileName);
@@ -630,6 +669,9 @@ var FileDatabase = class {
630
669
  }
631
670
  const recordsCount = Array.isArray(dataToWrite) ? dataToWrite.length : 1;
632
671
  currentFile.recordsCount = recordsCount;
672
+ if (customMetadata) {
673
+ Object.assign(currentFile, customMetadata);
674
+ }
633
675
  const fileIndex = this.metadata.files.indexOf(currentFile);
634
676
  if (fileIndex !== -1) {
635
677
  this.calculateFileSynopsis(dataToWrite, fileIndex);
@@ -682,6 +724,11 @@ var FileDatabase = class {
682
724
  } else {
683
725
  if (!this.metadata.files.length) {
684
726
  this.metadata = await this.figureMetadata(this.currentVersion);
727
+ if (this.metadata.files && this.metadata.files.length > 0) {
728
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
729
+ } else {
730
+ this.currentFileNumber = 0;
731
+ }
685
732
  }
686
733
  }
687
734
  } else {
@@ -692,16 +739,22 @@ var FileDatabase = class {
692
739
  try {
693
740
  const rawData = await fs3.promises.readFile(metadataPath, "utf8");
694
741
  this.metadata = JSON.parse(rawData);
742
+ if (this.metadata.files && this.metadata.files.length > 0) {
743
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
744
+ } else {
745
+ this.currentFileNumber = 0;
746
+ }
695
747
  } catch (e) {
696
748
  this.metadata = this.getDefaultMetadata();
749
+ this.currentFileNumber = 0;
697
750
  }
698
751
  } else {
699
752
  this.metadata = this.getDefaultMetadata();
700
- this.makeNewFile();
753
+ this.currentFileNumber = 0;
701
754
  }
702
755
  } else {
703
756
  this.metadata = this.getDefaultMetadata();
704
- this.makeNewFile();
757
+ this.currentFileNumber = 0;
705
758
  }
706
759
  }
707
760
  } else if (read) {
@@ -720,6 +773,11 @@ var FileDatabase = class {
720
773
  }
721
774
  if (!this.metadata.files.length) {
722
775
  this.metadata = await this.figureMetadata(this.currentVersion);
776
+ if (this.metadata.files && this.metadata.files.length > 0) {
777
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
778
+ } else {
779
+ this.currentFileNumber = 0;
780
+ }
723
781
  }
724
782
  } else {
725
783
  this.currentVersion = null;
@@ -733,6 +791,11 @@ var FileDatabase = class {
733
791
  try {
734
792
  const rawData = await fs3.promises.readFile(metadataPath, "utf8");
735
793
  this.metadata = JSON.parse(rawData);
794
+ if (this.metadata.files && this.metadata.files.length > 0) {
795
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
796
+ } else {
797
+ this.currentFileNumber = 0;
798
+ }
736
799
  } catch (e) {
737
800
  throw new FileDatabaseError(`Failed to read metadata: ${e.message}`);
738
801
  }
@@ -741,6 +804,11 @@ var FileDatabase = class {
741
804
  }
742
805
  } else {
743
806
  this.metadata = await this.figureMetadataFromVersionFiles("");
807
+ if (this.metadata.files && this.metadata.files.length > 0) {
808
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
809
+ } else {
810
+ this.currentFileNumber = 0;
811
+ }
744
812
  }
745
813
  }
746
814
  }
@@ -762,14 +830,44 @@ var FileDatabase = class {
762
830
  this.metadata.dataType = incomingDataType;
763
831
  this.makeNewFile();
764
832
  }
765
- let { dataToWrite, dataLeftOver, fileName } = this.figureOutDataAndFileToWrite(data);
833
+ let targetFileIndex = null;
834
+ const hasCustomMetadata = options.customMetadata && Object.keys(options.customMetadata).length > 0;
835
+ if (hasCustomMetadata) {
836
+ for (let i = 0; i < this.metadata.files.length; i++) {
837
+ const fileEntry = this.metadata.files[i];
838
+ const matches = Object.keys(options.customMetadata).every((key) => {
839
+ return key in fileEntry && fileEntry[key] === options.customMetadata[key];
840
+ });
841
+ if (matches) {
842
+ targetFileIndex = i;
843
+ this.logger.debug?.(`[FileDatabase] Found existing file with matching custom metadata: ${fileEntry.fileName}, metadata: ${JSON.stringify(options.customMetadata)}`);
844
+ break;
845
+ } else {
846
+ this.logger.debug?.(`[FileDatabase] File ${fileEntry.fileName} does not match custom metadata: ${JSON.stringify(options.customMetadata)}`);
847
+ }
848
+ }
849
+ if (targetFileIndex === null) {
850
+ this.logger.debug?.(`[FileDatabase] No existing file found with custom metadata: ${JSON.stringify(options.customMetadata)}, will create new file`);
851
+ }
852
+ } else {
853
+ this.logger.debug?.(`[FileDatabase] No custom metadata provided, will create new file`);
854
+ }
855
+ if (targetFileIndex !== null) {
856
+ const targetFile = this.metadata.files[targetFileIndex];
857
+ this.currentFileNumber = targetFile.number;
858
+ this.lastFileData = null;
859
+ this.currentRecord = 0;
860
+ this.hasReadFirstPage = false;
861
+ }
862
+ const forceNewFile = hasCustomMetadata && targetFileIndex === null;
863
+ let { dataToWrite, dataLeftOver, fileName } = this.figureOutDataAndFileToWrite(data, targetFileIndex, forceNewFile);
766
864
  const destPath = this.getDestinationPath(this.currentVersion || void 0);
767
865
  await this.safeWrite(path3.join(destPath, fileName), dataToWrite);
768
- this.updateMetadata(dataToWrite, fileName);
769
- while (dataLeftOver && dataLeftOver.length > 0) {
866
+ this.updateMetadata(dataToWrite, fileName, options.customMetadata);
867
+ while (dataLeftOver && dataLeftOver.length > 0 && targetFileIndex === null) {
770
868
  const writeContext = this.figureOutDataAndFileToWrite(dataLeftOver);
771
869
  await this.safeWrite(path3.join(destPath, writeContext.fileName), writeContext.dataToWrite);
772
- this.updateMetadata(writeContext.dataToWrite, writeContext.fileName);
870
+ this.updateMetadata(writeContext.dataToWrite, writeContext.fileName, options.customMetadata);
773
871
  dataLeftOver = writeContext.dataLeftOver;
774
872
  }
775
873
  this.calculateVersionSynopsis();
@@ -886,6 +984,63 @@ var FileDatabase = class {
886
984
  getMetadata() {
887
985
  return { ...this.metadata };
888
986
  }
987
+ /**
988
+ * Find data by custom metadata fields
989
+ * Searches through all versions and files to find entries matching the search criteria
990
+ *
991
+ * @param searchCriteria - Object with field names and values to search for (e.g., { ListingKey: "123", id: "456" })
992
+ * @returns Array of found entries with their file paths and metadata
993
+ */
994
+ async findData(searchCriteria) {
995
+ const results = [];
996
+ if (!this.versioned) {
997
+ await this.prepare({ read: true });
998
+ const metadata = this.getMetadata();
999
+ for (const fileEntry of metadata.files) {
1000
+ const matches = Object.keys(searchCriteria).every((key) => {
1001
+ return fileEntry[key] === searchCriteria[key];
1002
+ });
1003
+ if (matches) {
1004
+ const destPath = this.getDestinationPath();
1005
+ const filePath = path3.join(destPath, fileEntry.fileName);
1006
+ const fileData = await fs3.promises.readFile(filePath, "utf8");
1007
+ const data = deserializeData(fileData, metadata.dataType || "json-object");
1008
+ results.push({
1009
+ filePath,
1010
+ fileName: fileEntry.fileName,
1011
+ version: null,
1012
+ metadata: fileEntry,
1013
+ data
1014
+ });
1015
+ }
1016
+ }
1017
+ } else {
1018
+ const versions = await this.getVersions();
1019
+ for (const version of versions) {
1020
+ await this.prepare({ read: true, version });
1021
+ const metadata = this.getMetadata();
1022
+ for (const fileEntry of metadata.files) {
1023
+ const matches = Object.keys(searchCriteria).every((key) => {
1024
+ return fileEntry[key] === searchCriteria[key];
1025
+ });
1026
+ if (matches) {
1027
+ const destPath = this.getDestinationPath(version);
1028
+ const filePath = path3.join(destPath, fileEntry.fileName);
1029
+ const fileData = await fs3.promises.readFile(filePath, "utf8");
1030
+ const data = deserializeData(fileData, metadata.dataType || "json-object");
1031
+ results.push({
1032
+ filePath,
1033
+ fileName: fileEntry.fileName,
1034
+ version,
1035
+ metadata: fileEntry,
1036
+ data
1037
+ });
1038
+ }
1039
+ }
1040
+ }
1041
+ }
1042
+ return results;
1043
+ }
889
1044
  };
890
1045
 
891
1046
  // src/mock-server/catalog.ts