@nmakarov/cli-toolkit 0.16.0 → 0.18.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.
@@ -325,7 +325,7 @@ var FileDatabase = class _FileDatabase {
325
325
  const versions = await this.getVersions();
326
326
  while (versions.length > this.maxVersions) {
327
327
  const versionToDelete = path3.resolve(this.getDestinationPath(), versions.shift());
328
- this.logger.debug?.(`[FileDatabase] Deleting old version: ${versionToDelete}`);
328
+ this.logger.silly?.(`[FileDatabase] Deleting old version: ${versionToDelete}`);
329
329
  await fs3.promises.rm(versionToDelete, { recursive: true, force: true });
330
330
  }
331
331
  return versionName;
@@ -603,7 +603,7 @@ var FileDatabase = class _FileDatabase {
603
603
  };
604
604
  this.metadata.files.push(fileEntry);
605
605
  this.lastFileData = null;
606
- this.logger.debug?.(`[FileDatabase] Created new file: ${fileEntry.fileName}, fileNumber: ${this.currentFileNumber}`);
606
+ this.logger.silly?.(`[FileDatabase] Created new file: ${fileEntry.fileName}, fileNumber: ${this.currentFileNumber}`);
607
607
  }
608
608
  /**
609
609
  * Figure out what data to write and which file to use (for pagination)
@@ -636,7 +636,7 @@ var FileDatabase = class _FileDatabase {
636
636
  const filesBeforeCreate = this.metadata.files.length;
637
637
  this.makeNewFile();
638
638
  newlyCreatedFileIndex = filesBeforeCreate;
639
- this.logger.debug?.(`[FileDatabase] Creating new file for unique custom metadata combination, fileNumber: ${this.currentFileNumber}`);
639
+ this.logger.silly?.(`[FileDatabase] Creating new file for unique custom metadata combination, fileNumber: ${this.currentFileNumber}`);
640
640
  } else if (this.metadata.files.length === 0) {
641
641
  this.makeNewFile();
642
642
  }
@@ -685,7 +685,7 @@ var FileDatabase = class _FileDatabase {
685
685
  dataLeftOver = null;
686
686
  }
687
687
  const fileName = this.metadata.files[this.metadata.files.length - 1].fileName;
688
- this.logger.debug?.(
688
+ this.logger.silly?.(
689
689
  `[FileDatabase] figureOutDataAndFileToWrite: filename=${fileName}, forceNewFile=${forceNewFile}, targetFileIndex=${targetFileIndex}, dataToWrite.length=${Array.isArray(dataToWrite) ? dataToWrite.length : "N/A"}, lastFileRecordsCount=${lastFileRecordsCount}`
690
690
  );
691
691
  return { dataToWrite, dataLeftOver, fileName };
@@ -740,7 +740,7 @@ var FileDatabase = class _FileDatabase {
740
740
  this.metadata.modifiedAt = (/* @__PURE__ */ new Date()).toISOString();
741
741
  this.metadata.dataType = detectDataType(dataToWrite);
742
742
  this.metadata.totalRecords = this.metadata.files.reduce((sum, file) => sum + (file.recordsCount || 0), 0);
743
- this.logger.debug?.(
743
+ this.logger.silly?.(
744
744
  `[FileDatabase] Updated metadata for file ${currentFile.fileName}: recordsCount=${recordsCount}, totalRecords=${this.metadata.totalRecords}`
745
745
  );
746
746
  }
@@ -764,7 +764,7 @@ var FileDatabase = class _FileDatabase {
764
764
  }
765
765
  try {
766
766
  await fs3.promises.writeFile(filePath, serializedData, "utf8");
767
- this.logger.debug?.(`[FileDatabase] Wrote ${bytesToHumanReadable(requiredBytes)} to ${filePath}`);
767
+ this.logger.silly?.(`[FileDatabase] Wrote ${bytesToHumanReadable(requiredBytes)} to ${filePath}`);
768
768
  } catch (error) {
769
769
  throw new FileDatabaseError(`Failed to write file ${filePath}: ${error.message}`);
770
770
  }
@@ -846,7 +846,8 @@ var FileDatabase = class _FileDatabase {
846
846
  this.useMetadata = format.hasMetadata;
847
847
  }
848
848
  if (this.useMetadata) {
849
- const metadataPath = path3.join(this.getDestinationPath(), "metadata.json");
849
+ const destPath = this.getDestinationPath();
850
+ const metadataPath = path3.join(destPath, "metadata.json");
850
851
  if (fs3.existsSync(metadataPath)) {
851
852
  try {
852
853
  const rawData = await fs3.promises.readFile(metadataPath, "utf8");
@@ -860,7 +861,9 @@ var FileDatabase = class _FileDatabase {
860
861
  throw new FileDatabaseError(`Failed to read metadata: ${e.message}`);
861
862
  }
862
863
  } else {
863
- throw new FileDatabaseError("[FileDatabase] No metadata found in non-versioned mode");
864
+ throw new FileDatabaseError(
865
+ `[FileDatabase] No metadata found in non-versioned mode. Looked for: ${metadataPath} (table path: ${destPath})`
866
+ );
864
867
  }
865
868
  } else {
866
869
  this.metadata = await this.figureMetadataFromVersionFiles("");
@@ -877,6 +880,13 @@ var FileDatabase = class _FileDatabase {
877
880
  * Write data to the file database
878
881
  */
879
882
  async write(data, options = {}) {
883
+ if (options.filename) {
884
+ const destPath2 = this.getDestinationPath();
885
+ await ensurePath(destPath2);
886
+ const filePath = path3.join(destPath2, options.filename);
887
+ await this.safeWrite(filePath, data);
888
+ return;
889
+ }
880
890
  if (options.forceNewVersion && !this.versioned) {
881
891
  throw new FileDatabaseError("Cannot use forceNewVersion in non-versioned mode");
882
892
  }
@@ -900,17 +910,17 @@ var FileDatabase = class _FileDatabase {
900
910
  });
901
911
  if (matches) {
902
912
  targetFileIndex = i;
903
- this.logger.debug?.(`[FileDatabase] Found existing file with matching custom metadata: ${fileEntry.fileName}, metadata: ${JSON.stringify(options.customMetadata)}`);
913
+ this.logger.silly?.(`[FileDatabase] Found existing file with matching custom metadata: ${fileEntry.fileName}, metadata: ${JSON.stringify(options.customMetadata)}`);
904
914
  break;
905
915
  } else {
906
- this.logger.debug?.(`[FileDatabase] File ${fileEntry.fileName} does not match custom metadata: ${JSON.stringify(options.customMetadata)}`);
916
+ this.logger.silly?.(`[FileDatabase] File ${fileEntry.fileName} does not match custom metadata: ${JSON.stringify(options.customMetadata)}`);
907
917
  }
908
918
  }
909
919
  if (targetFileIndex === null) {
910
- this.logger.debug?.(`[FileDatabase] No existing file found with custom metadata: ${JSON.stringify(options.customMetadata)}, will create new file`);
920
+ this.logger.silly?.(`[FileDatabase] No existing file found with custom metadata: ${JSON.stringify(options.customMetadata)}, will create new file`);
911
921
  }
912
922
  } else {
913
- this.logger.debug?.(`[FileDatabase] No custom metadata provided, will create new file`);
923
+ this.logger.silly?.(`[FileDatabase] No custom metadata provided, will create new file`);
914
924
  }
915
925
  if (targetFileIndex !== null) {
916
926
  const targetFile = this.metadata.files[targetFileIndex];
@@ -939,7 +949,17 @@ var FileDatabase = class _FileDatabase {
939
949
  * Read data from the file database
940
950
  */
941
951
  async read(options = {}) {
942
- const { version, nextPage = false, pageSize } = options;
952
+ const { version, nextPage = false, pageSize, filename } = options;
953
+ if (filename) {
954
+ const destPath = this.getDestinationPath(version);
955
+ const filePath = path3.join(destPath, filename);
956
+ try {
957
+ const rawData = await fs3.promises.readFile(filePath, "utf8");
958
+ return JSON.parse(rawData);
959
+ } catch (error) {
960
+ throw new FileDatabaseError(`Failed to read file ${filename}: ${error.message}`);
961
+ }
962
+ }
943
963
  await this.prepare({ read: true, version });
944
964
  const isNonPaginatedData = this.metadata.dataType === "text" || this.metadata.dataType === "xml" || this.metadata.dataType === "json-object";
945
965
  if (isNonPaginatedData) {
@@ -1020,6 +1040,67 @@ var FileDatabase = class _FileDatabase {
1020
1040
  this.currentRecord = 0;
1021
1041
  this.hasReadFirstPage = false;
1022
1042
  }
1043
+ /**
1044
+ * List filenames in the table directory.
1045
+ * For catalog/key-value usage (files written with { filename }).
1046
+ * Returns data file names (.json, .txt, .xml) excluding metadata.json.
1047
+ */
1048
+ async listFilenames() {
1049
+ const destPath = this.versioned && this.currentVersion ? path3.join(this.getDestinationPath(), this.currentVersion) : this.getDestinationPath();
1050
+ try {
1051
+ const entries = await fs3.promises.readdir(destPath, { withFileTypes: true });
1052
+ return entries.filter((e) => e.isFile() && e.name !== "metadata.json" && /\.(json|txt|xml)$/i.test(e.name)).map((e) => e.name);
1053
+ } catch (err) {
1054
+ if (err?.code === "ENOENT") return [];
1055
+ throw new FileDatabaseError(`Failed to list files: ${err.message}`);
1056
+ }
1057
+ }
1058
+ /**
1059
+ * Remove a file from the table directory (catalog mode).
1060
+ * Use with listFilenames() to manage individual files.
1061
+ */
1062
+ async removeFile(filename) {
1063
+ const destPath = this.versioned && this.currentVersion ? path3.join(this.getDestinationPath(), this.currentVersion) : this.getDestinationPath();
1064
+ const filePath = path3.join(destPath, filename);
1065
+ try {
1066
+ await fs3.promises.unlink(filePath);
1067
+ } catch (err) {
1068
+ if (err?.code === "ENOENT") return;
1069
+ throw new FileDatabaseError(`Failed to remove file ${filename}: ${err.message}`);
1070
+ }
1071
+ }
1072
+ /**
1073
+ * Remove a file and its metadata entry (non-versioned mode with useMetadata).
1074
+ * Use with findData() to get fileName, then call removeFileEntry to delete.
1075
+ */
1076
+ async removeFileEntry(filename) {
1077
+ if (this.versioned) {
1078
+ throw new FileDatabaseError("removeFileEntry is only supported in non-versioned mode");
1079
+ }
1080
+ await this.prepare({ read: true });
1081
+ const idx = this.metadata.files.findIndex((f) => f.fileName === filename);
1082
+ if (idx === -1) {
1083
+ throw new FileDatabaseError(`File entry ${filename} not found in metadata`);
1084
+ }
1085
+ const entry = this.metadata.files[idx];
1086
+ const recordsCount = entry.recordsCount || 0;
1087
+ this.metadata.files.splice(idx, 1);
1088
+ this.metadata.totalRecords = Math.max(0, (this.metadata.totalRecords || 0) - recordsCount);
1089
+ const destPath = this.getDestinationPath();
1090
+ const filePath = path3.join(destPath, filename);
1091
+ try {
1092
+ await fs3.promises.unlink(filePath);
1093
+ } catch (err) {
1094
+ if (err?.code === "ENOENT") {
1095
+ this.logger.warn?.(`[FileDatabase] File ${filename} already missing on disk`);
1096
+ } else {
1097
+ throw new FileDatabaseError(`Failed to remove file ${filename}: ${err.message}`);
1098
+ }
1099
+ }
1100
+ if (this.useMetadata) {
1101
+ await this.saveVersionMetadata(this.metadata);
1102
+ }
1103
+ }
1023
1104
  /**
1024
1105
  * Set file-level synopsis calculation function
1025
1106
  */