@nmakarov/cli-toolkit 0.11.4 → 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.
package/dist/index.cjs CHANGED
@@ -1120,6 +1120,8 @@ __export(src_exports, {
1120
1120
  h: () => import_react5.createElement,
1121
1121
  joiEdateType: () => joiEdateType,
1122
1122
  joiStringArrayType: () => joiStringArrayType,
1123
+ listSources: () => listSources,
1124
+ listTables: () => listTables,
1123
1125
  load: () => load,
1124
1126
  organizeFooterMessages: () => organizeFooterMessages,
1125
1127
  setupContext: () => setupContext,
@@ -2587,20 +2589,48 @@ var FileDatabase = class {
2587
2589
  }
2588
2590
  /**
2589
2591
  * Figure out what data to write and which file to use (for pagination)
2592
+ * @param data - Data to write
2593
+ * @param targetFileIndex - Optional index of existing file to overwrite (when customMetadata matches)
2594
+ * @param forceNewFile - If true, always create a new file (when customMetadata provided but no match)
2590
2595
  */
2591
- figureOutDataAndFileToWrite(data) {
2596
+ figureOutDataAndFileToWrite(data, targetFileIndex = null, forceNewFile = false) {
2592
2597
  let dataToWrite;
2593
2598
  let dataLeftOver;
2594
2599
  const incomingDataType = detectDataType(data);
2595
2600
  if (this.metadata.dataType !== incomingDataType) {
2596
2601
  this.metadata.dataType = incomingDataType;
2597
2602
  }
2598
- if (this.metadata.files.length === 0) {
2603
+ if (targetFileIndex !== null && targetFileIndex < this.metadata.files.length) {
2604
+ const targetFile = this.metadata.files[targetFileIndex];
2605
+ if (!Array.isArray(data)) {
2606
+ dataToWrite = data;
2607
+ dataLeftOver = null;
2608
+ return { dataToWrite, dataLeftOver, fileName: targetFile.fileName };
2609
+ } else {
2610
+ dataToWrite = data.slice(0, this.pageSize);
2611
+ dataLeftOver = data.slice(this.pageSize);
2612
+ this.lastFileData = dataToWrite;
2613
+ return { dataToWrite, dataLeftOver, fileName: targetFile.fileName };
2614
+ }
2615
+ }
2616
+ let newlyCreatedFileIndex = null;
2617
+ if (forceNewFile) {
2618
+ const filesBeforeCreate = this.metadata.files.length;
2619
+ this.makeNewFile();
2620
+ newlyCreatedFileIndex = filesBeforeCreate;
2621
+ this.logger.debug?.(`[FileDatabase] Creating new file for unique custom metadata combination, fileNumber: ${this.currentFileNumber}`);
2622
+ } else if (this.metadata.files.length === 0) {
2599
2623
  this.makeNewFile();
2600
2624
  }
2601
2625
  const lastFile = this.metadata.files[this.metadata.files.length - 1];
2602
2626
  const lastFileRecordsCount = lastFile.recordsCount;
2603
- if (!Array.isArray(data)) {
2627
+ if (forceNewFile && newlyCreatedFileIndex !== null) {
2628
+ const newlyCreatedFile = this.metadata.files[newlyCreatedFileIndex];
2629
+ if (newlyCreatedFile && newlyCreatedFile.fileName !== lastFile.fileName) {
2630
+ this.logger.warn?.(`[FileDatabase] Warning: Newly created file ${newlyCreatedFile.fileName} doesn't match last file ${lastFile.fileName}`);
2631
+ }
2632
+ }
2633
+ if (!Array.isArray(data) && !forceNewFile) {
2604
2634
  const lastFileExtension = import_path4.default.extname(lastFile.fileName);
2605
2635
  const expectedExtension = `.${getFileExtension(incomingDataType)}`;
2606
2636
  if (lastFileExtension !== expectedExtension) {
@@ -2610,24 +2640,35 @@ var FileDatabase = class {
2610
2640
  lastFile.fileName = `${this.currentFileNumber.toString().padStart(6, "0")}.${getFileExtension(incomingDataType)}`;
2611
2641
  }
2612
2642
  }
2643
+ } else if (!Array.isArray(data) && forceNewFile) {
2644
+ const lastFileExtension = import_path4.default.extname(lastFile.fileName);
2645
+ const expectedExtension = `.${getFileExtension(incomingDataType)}`;
2646
+ if (lastFileExtension !== expectedExtension) {
2647
+ lastFile.fileName = `${this.currentFileNumber.toString().padStart(6, "0")}.${getFileExtension(incomingDataType)}`;
2648
+ }
2613
2649
  }
2614
2650
  if (Array.isArray(data)) {
2615
- if (lastFileRecordsCount < this.pageSize) {
2651
+ if (forceNewFile) {
2652
+ dataToWrite = data.slice(0, this.pageSize);
2653
+ dataLeftOver = data.slice(this.pageSize);
2654
+ this.lastFileData = dataToWrite;
2655
+ } else if (lastFileRecordsCount < this.pageSize) {
2616
2656
  dataToWrite = [...this.lastFileData || [], ...data.slice(0, this.pageSize - lastFileRecordsCount)];
2617
2657
  dataLeftOver = data.slice(this.pageSize - lastFileRecordsCount);
2658
+ this.lastFileData = dataToWrite;
2618
2659
  } else {
2619
2660
  this.makeNewFile();
2620
2661
  dataToWrite = data.slice(0, this.pageSize);
2621
2662
  dataLeftOver = data.slice(this.pageSize);
2663
+ this.lastFileData = dataToWrite;
2622
2664
  }
2623
- this.lastFileData = dataToWrite;
2624
2665
  } else {
2625
2666
  dataToWrite = data;
2626
2667
  dataLeftOver = null;
2627
2668
  }
2628
2669
  const fileName = this.metadata.files[this.metadata.files.length - 1].fileName;
2629
2670
  this.logger.debug?.(
2630
- `[FileDatabase] figureOutDataAndFileToWrite: filename=${fileName}, dataToWrite.length=${Array.isArray(dataToWrite) ? dataToWrite.length : "N/A"}, lastFileRecordsCount=${lastFileRecordsCount}`
2671
+ `[FileDatabase] figureOutDataAndFileToWrite: filename=${fileName}, forceNewFile=${forceNewFile}, targetFileIndex=${targetFileIndex}, dataToWrite.length=${Array.isArray(dataToWrite) ? dataToWrite.length : "N/A"}, lastFileRecordsCount=${lastFileRecordsCount}`
2631
2672
  );
2632
2673
  return { dataToWrite, dataLeftOver, fileName };
2633
2674
  }
@@ -2655,7 +2696,7 @@ var FileDatabase = class {
2655
2696
  /**
2656
2697
  * Update metadata after writing data
2657
2698
  */
2658
- updateMetadata(dataToWrite, fileName) {
2699
+ updateMetadata(dataToWrite, fileName, customMetadata) {
2659
2700
  let currentFile;
2660
2701
  if (fileName) {
2661
2702
  const foundFile = this.metadata.files.find((file) => file.fileName === fileName);
@@ -2670,6 +2711,9 @@ var FileDatabase = class {
2670
2711
  }
2671
2712
  const recordsCount = Array.isArray(dataToWrite) ? dataToWrite.length : 1;
2672
2713
  currentFile.recordsCount = recordsCount;
2714
+ if (customMetadata) {
2715
+ Object.assign(currentFile, customMetadata);
2716
+ }
2673
2717
  const fileIndex = this.metadata.files.indexOf(currentFile);
2674
2718
  if (fileIndex !== -1) {
2675
2719
  this.calculateFileSynopsis(dataToWrite, fileIndex);
@@ -2722,6 +2766,11 @@ var FileDatabase = class {
2722
2766
  } else {
2723
2767
  if (!this.metadata.files.length) {
2724
2768
  this.metadata = await this.figureMetadata(this.currentVersion);
2769
+ if (this.metadata.files && this.metadata.files.length > 0) {
2770
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
2771
+ } else {
2772
+ this.currentFileNumber = 0;
2773
+ }
2725
2774
  }
2726
2775
  }
2727
2776
  } else {
@@ -2732,16 +2781,22 @@ var FileDatabase = class {
2732
2781
  try {
2733
2782
  const rawData = await import_fs4.default.promises.readFile(metadataPath, "utf8");
2734
2783
  this.metadata = JSON.parse(rawData);
2784
+ if (this.metadata.files && this.metadata.files.length > 0) {
2785
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
2786
+ } else {
2787
+ this.currentFileNumber = 0;
2788
+ }
2735
2789
  } catch (e) {
2736
2790
  this.metadata = this.getDefaultMetadata();
2791
+ this.currentFileNumber = 0;
2737
2792
  }
2738
2793
  } else {
2739
2794
  this.metadata = this.getDefaultMetadata();
2740
- this.makeNewFile();
2795
+ this.currentFileNumber = 0;
2741
2796
  }
2742
2797
  } else {
2743
2798
  this.metadata = this.getDefaultMetadata();
2744
- this.makeNewFile();
2799
+ this.currentFileNumber = 0;
2745
2800
  }
2746
2801
  }
2747
2802
  } else if (read) {
@@ -2760,6 +2815,11 @@ var FileDatabase = class {
2760
2815
  }
2761
2816
  if (!this.metadata.files.length) {
2762
2817
  this.metadata = await this.figureMetadata(this.currentVersion);
2818
+ if (this.metadata.files && this.metadata.files.length > 0) {
2819
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
2820
+ } else {
2821
+ this.currentFileNumber = 0;
2822
+ }
2763
2823
  }
2764
2824
  } else {
2765
2825
  this.currentVersion = null;
@@ -2773,6 +2833,11 @@ var FileDatabase = class {
2773
2833
  try {
2774
2834
  const rawData = await import_fs4.default.promises.readFile(metadataPath, "utf8");
2775
2835
  this.metadata = JSON.parse(rawData);
2836
+ if (this.metadata.files && this.metadata.files.length > 0) {
2837
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
2838
+ } else {
2839
+ this.currentFileNumber = 0;
2840
+ }
2776
2841
  } catch (e) {
2777
2842
  throw new FileDatabaseError(`Failed to read metadata: ${e.message}`);
2778
2843
  }
@@ -2781,6 +2846,11 @@ var FileDatabase = class {
2781
2846
  }
2782
2847
  } else {
2783
2848
  this.metadata = await this.figureMetadataFromVersionFiles("");
2849
+ if (this.metadata.files && this.metadata.files.length > 0) {
2850
+ this.currentFileNumber = Math.max(...this.metadata.files.map((f) => f.number || 0));
2851
+ } else {
2852
+ this.currentFileNumber = 0;
2853
+ }
2784
2854
  }
2785
2855
  }
2786
2856
  }
@@ -2802,14 +2872,44 @@ var FileDatabase = class {
2802
2872
  this.metadata.dataType = incomingDataType;
2803
2873
  this.makeNewFile();
2804
2874
  }
2805
- let { dataToWrite, dataLeftOver, fileName } = this.figureOutDataAndFileToWrite(data);
2875
+ let targetFileIndex = null;
2876
+ const hasCustomMetadata = options.customMetadata && Object.keys(options.customMetadata).length > 0;
2877
+ if (hasCustomMetadata) {
2878
+ for (let i = 0; i < this.metadata.files.length; i++) {
2879
+ const fileEntry = this.metadata.files[i];
2880
+ const matches = Object.keys(options.customMetadata).every((key) => {
2881
+ return key in fileEntry && fileEntry[key] === options.customMetadata[key];
2882
+ });
2883
+ if (matches) {
2884
+ targetFileIndex = i;
2885
+ this.logger.debug?.(`[FileDatabase] Found existing file with matching custom metadata: ${fileEntry.fileName}, metadata: ${JSON.stringify(options.customMetadata)}`);
2886
+ break;
2887
+ } else {
2888
+ this.logger.debug?.(`[FileDatabase] File ${fileEntry.fileName} does not match custom metadata: ${JSON.stringify(options.customMetadata)}`);
2889
+ }
2890
+ }
2891
+ if (targetFileIndex === null) {
2892
+ this.logger.debug?.(`[FileDatabase] No existing file found with custom metadata: ${JSON.stringify(options.customMetadata)}, will create new file`);
2893
+ }
2894
+ } else {
2895
+ this.logger.debug?.(`[FileDatabase] No custom metadata provided, will create new file`);
2896
+ }
2897
+ if (targetFileIndex !== null) {
2898
+ const targetFile = this.metadata.files[targetFileIndex];
2899
+ this.currentFileNumber = targetFile.number;
2900
+ this.lastFileData = null;
2901
+ this.currentRecord = 0;
2902
+ this.hasReadFirstPage = false;
2903
+ }
2904
+ const forceNewFile = hasCustomMetadata && targetFileIndex === null;
2905
+ let { dataToWrite, dataLeftOver, fileName } = this.figureOutDataAndFileToWrite(data, targetFileIndex, forceNewFile);
2806
2906
  const destPath = this.getDestinationPath(this.currentVersion || void 0);
2807
2907
  await this.safeWrite(import_path4.default.join(destPath, fileName), dataToWrite);
2808
- this.updateMetadata(dataToWrite, fileName);
2809
- while (dataLeftOver && dataLeftOver.length > 0) {
2908
+ this.updateMetadata(dataToWrite, fileName, options.customMetadata);
2909
+ while (dataLeftOver && dataLeftOver.length > 0 && targetFileIndex === null) {
2810
2910
  const writeContext = this.figureOutDataAndFileToWrite(dataLeftOver);
2811
2911
  await this.safeWrite(import_path4.default.join(destPath, writeContext.fileName), writeContext.dataToWrite);
2812
- this.updateMetadata(writeContext.dataToWrite, writeContext.fileName);
2912
+ this.updateMetadata(writeContext.dataToWrite, writeContext.fileName, options.customMetadata);
2813
2913
  dataLeftOver = writeContext.dataLeftOver;
2814
2914
  }
2815
2915
  this.calculateVersionSynopsis();
@@ -2926,7 +3026,85 @@ var FileDatabase = class {
2926
3026
  getMetadata() {
2927
3027
  return { ...this.metadata };
2928
3028
  }
3029
+ /**
3030
+ * Find data by custom metadata fields
3031
+ * Searches through all versions and files to find entries matching the search criteria
3032
+ *
3033
+ * @param searchCriteria - Object with field names and values to search for (e.g., { ListingKey: "123", id: "456" })
3034
+ * @returns Array of found entries with their file paths and metadata
3035
+ */
3036
+ async findData(searchCriteria) {
3037
+ const results = [];
3038
+ if (!this.versioned) {
3039
+ await this.prepare({ read: true });
3040
+ const metadata = this.getMetadata();
3041
+ for (const fileEntry of metadata.files) {
3042
+ const matches = Object.keys(searchCriteria).every((key) => {
3043
+ return fileEntry[key] === searchCriteria[key];
3044
+ });
3045
+ if (matches) {
3046
+ const destPath = this.getDestinationPath();
3047
+ const filePath = import_path4.default.join(destPath, fileEntry.fileName);
3048
+ const fileData = await import_fs4.default.promises.readFile(filePath, "utf8");
3049
+ const data = deserializeData(fileData, metadata.dataType || "json-object");
3050
+ results.push({
3051
+ filePath,
3052
+ fileName: fileEntry.fileName,
3053
+ version: null,
3054
+ metadata: fileEntry,
3055
+ data
3056
+ });
3057
+ }
3058
+ }
3059
+ } else {
3060
+ const versions = await this.getVersions();
3061
+ for (const version of versions) {
3062
+ await this.prepare({ read: true, version });
3063
+ const metadata = this.getMetadata();
3064
+ for (const fileEntry of metadata.files) {
3065
+ const matches = Object.keys(searchCriteria).every((key) => {
3066
+ return fileEntry[key] === searchCriteria[key];
3067
+ });
3068
+ if (matches) {
3069
+ const destPath = this.getDestinationPath(version);
3070
+ const filePath = import_path4.default.join(destPath, fileEntry.fileName);
3071
+ const fileData = await import_fs4.default.promises.readFile(filePath, "utf8");
3072
+ const data = deserializeData(fileData, metadata.dataType || "json-object");
3073
+ results.push({
3074
+ filePath,
3075
+ fileName: fileEntry.fileName,
3076
+ version,
3077
+ metadata: fileEntry,
3078
+ data
3079
+ });
3080
+ }
3081
+ }
3082
+ }
3083
+ }
3084
+ return results;
3085
+ }
2929
3086
  };
3087
+ function listTables(basePath, namespace) {
3088
+ const namespacePath = import_path4.default.join(basePath, namespace);
3089
+ if (!import_fs4.default.existsSync(namespacePath)) {
3090
+ return [];
3091
+ }
3092
+ try {
3093
+ return import_fs4.default.readdirSync(namespacePath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
3094
+ } catch (error) {
3095
+ return [];
3096
+ }
3097
+ }
3098
+ function listSources(basePath) {
3099
+ if (!import_fs4.default.existsSync(basePath)) {
3100
+ return [];
3101
+ }
3102
+ try {
3103
+ return import_fs4.default.readdirSync(basePath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
3104
+ } catch (error) {
3105
+ return [];
3106
+ }
3107
+ }
2930
3108
  function fileDatabaseInit(context, options = {}) {
2931
3109
  return new FileDatabase(context, options);
2932
3110
  }
@@ -3719,6 +3897,8 @@ function setupContext(opts = {}) {
3719
3897
  h,
3720
3898
  joiEdateType,
3721
3899
  joiStringArrayType,
3900
+ listSources,
3901
+ listTables,
3722
3902
  load,
3723
3903
  organizeFooterMessages,
3724
3904
  setupContext,