@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.
@@ -366,7 +366,7 @@ var FileDatabase = class _FileDatabase {
366
366
  const versions = await this.getVersions();
367
367
  while (versions.length > this.maxVersions) {
368
368
  const versionToDelete = import_path3.default.resolve(this.getDestinationPath(), versions.shift());
369
- this.logger.debug?.(`[FileDatabase] Deleting old version: ${versionToDelete}`);
369
+ this.logger.silly?.(`[FileDatabase] Deleting old version: ${versionToDelete}`);
370
370
  await import_fs3.default.promises.rm(versionToDelete, { recursive: true, force: true });
371
371
  }
372
372
  return versionName;
@@ -644,7 +644,7 @@ var FileDatabase = class _FileDatabase {
644
644
  };
645
645
  this.metadata.files.push(fileEntry);
646
646
  this.lastFileData = null;
647
- this.logger.debug?.(`[FileDatabase] Created new file: ${fileEntry.fileName}, fileNumber: ${this.currentFileNumber}`);
647
+ this.logger.silly?.(`[FileDatabase] Created new file: ${fileEntry.fileName}, fileNumber: ${this.currentFileNumber}`);
648
648
  }
649
649
  /**
650
650
  * Figure out what data to write and which file to use (for pagination)
@@ -677,7 +677,7 @@ var FileDatabase = class _FileDatabase {
677
677
  const filesBeforeCreate = this.metadata.files.length;
678
678
  this.makeNewFile();
679
679
  newlyCreatedFileIndex = filesBeforeCreate;
680
- this.logger.debug?.(`[FileDatabase] Creating new file for unique custom metadata combination, fileNumber: ${this.currentFileNumber}`);
680
+ this.logger.silly?.(`[FileDatabase] Creating new file for unique custom metadata combination, fileNumber: ${this.currentFileNumber}`);
681
681
  } else if (this.metadata.files.length === 0) {
682
682
  this.makeNewFile();
683
683
  }
@@ -726,7 +726,7 @@ var FileDatabase = class _FileDatabase {
726
726
  dataLeftOver = null;
727
727
  }
728
728
  const fileName = this.metadata.files[this.metadata.files.length - 1].fileName;
729
- this.logger.debug?.(
729
+ this.logger.silly?.(
730
730
  `[FileDatabase] figureOutDataAndFileToWrite: filename=${fileName}, forceNewFile=${forceNewFile}, targetFileIndex=${targetFileIndex}, dataToWrite.length=${Array.isArray(dataToWrite) ? dataToWrite.length : "N/A"}, lastFileRecordsCount=${lastFileRecordsCount}`
731
731
  );
732
732
  return { dataToWrite, dataLeftOver, fileName };
@@ -781,7 +781,7 @@ var FileDatabase = class _FileDatabase {
781
781
  this.metadata.modifiedAt = (/* @__PURE__ */ new Date()).toISOString();
782
782
  this.metadata.dataType = detectDataType(dataToWrite);
783
783
  this.metadata.totalRecords = this.metadata.files.reduce((sum, file) => sum + (file.recordsCount || 0), 0);
784
- this.logger.debug?.(
784
+ this.logger.silly?.(
785
785
  `[FileDatabase] Updated metadata for file ${currentFile.fileName}: recordsCount=${recordsCount}, totalRecords=${this.metadata.totalRecords}`
786
786
  );
787
787
  }
@@ -805,7 +805,7 @@ var FileDatabase = class _FileDatabase {
805
805
  }
806
806
  try {
807
807
  await import_fs3.default.promises.writeFile(filePath, serializedData, "utf8");
808
- this.logger.debug?.(`[FileDatabase] Wrote ${bytesToHumanReadable(requiredBytes)} to ${filePath}`);
808
+ this.logger.silly?.(`[FileDatabase] Wrote ${bytesToHumanReadable(requiredBytes)} to ${filePath}`);
809
809
  } catch (error) {
810
810
  throw new FileDatabaseError(`Failed to write file ${filePath}: ${error.message}`);
811
811
  }
@@ -887,7 +887,8 @@ var FileDatabase = class _FileDatabase {
887
887
  this.useMetadata = format.hasMetadata;
888
888
  }
889
889
  if (this.useMetadata) {
890
- const metadataPath = import_path3.default.join(this.getDestinationPath(), "metadata.json");
890
+ const destPath = this.getDestinationPath();
891
+ const metadataPath = import_path3.default.join(destPath, "metadata.json");
891
892
  if (import_fs3.default.existsSync(metadataPath)) {
892
893
  try {
893
894
  const rawData = await import_fs3.default.promises.readFile(metadataPath, "utf8");
@@ -901,7 +902,9 @@ var FileDatabase = class _FileDatabase {
901
902
  throw new FileDatabaseError(`Failed to read metadata: ${e.message}`);
902
903
  }
903
904
  } else {
904
- throw new FileDatabaseError("[FileDatabase] No metadata found in non-versioned mode");
905
+ throw new FileDatabaseError(
906
+ `[FileDatabase] No metadata found in non-versioned mode. Looked for: ${metadataPath} (table path: ${destPath})`
907
+ );
905
908
  }
906
909
  } else {
907
910
  this.metadata = await this.figureMetadataFromVersionFiles("");
@@ -918,6 +921,13 @@ var FileDatabase = class _FileDatabase {
918
921
  * Write data to the file database
919
922
  */
920
923
  async write(data, options = {}) {
924
+ if (options.filename) {
925
+ const destPath2 = this.getDestinationPath();
926
+ await ensurePath(destPath2);
927
+ const filePath = import_path3.default.join(destPath2, options.filename);
928
+ await this.safeWrite(filePath, data);
929
+ return;
930
+ }
921
931
  if (options.forceNewVersion && !this.versioned) {
922
932
  throw new FileDatabaseError("Cannot use forceNewVersion in non-versioned mode");
923
933
  }
@@ -941,17 +951,17 @@ var FileDatabase = class _FileDatabase {
941
951
  });
942
952
  if (matches) {
943
953
  targetFileIndex = i;
944
- this.logger.debug?.(`[FileDatabase] Found existing file with matching custom metadata: ${fileEntry.fileName}, metadata: ${JSON.stringify(options.customMetadata)}`);
954
+ this.logger.silly?.(`[FileDatabase] Found existing file with matching custom metadata: ${fileEntry.fileName}, metadata: ${JSON.stringify(options.customMetadata)}`);
945
955
  break;
946
956
  } else {
947
- this.logger.debug?.(`[FileDatabase] File ${fileEntry.fileName} does not match custom metadata: ${JSON.stringify(options.customMetadata)}`);
957
+ this.logger.silly?.(`[FileDatabase] File ${fileEntry.fileName} does not match custom metadata: ${JSON.stringify(options.customMetadata)}`);
948
958
  }
949
959
  }
950
960
  if (targetFileIndex === null) {
951
- this.logger.debug?.(`[FileDatabase] No existing file found with custom metadata: ${JSON.stringify(options.customMetadata)}, will create new file`);
961
+ this.logger.silly?.(`[FileDatabase] No existing file found with custom metadata: ${JSON.stringify(options.customMetadata)}, will create new file`);
952
962
  }
953
963
  } else {
954
- this.logger.debug?.(`[FileDatabase] No custom metadata provided, will create new file`);
964
+ this.logger.silly?.(`[FileDatabase] No custom metadata provided, will create new file`);
955
965
  }
956
966
  if (targetFileIndex !== null) {
957
967
  const targetFile = this.metadata.files[targetFileIndex];
@@ -980,7 +990,17 @@ var FileDatabase = class _FileDatabase {
980
990
  * Read data from the file database
981
991
  */
982
992
  async read(options = {}) {
983
- const { version, nextPage = false, pageSize } = options;
993
+ const { version, nextPage = false, pageSize, filename } = options;
994
+ if (filename) {
995
+ const destPath = this.getDestinationPath(version);
996
+ const filePath = import_path3.default.join(destPath, filename);
997
+ try {
998
+ const rawData = await import_fs3.default.promises.readFile(filePath, "utf8");
999
+ return JSON.parse(rawData);
1000
+ } catch (error) {
1001
+ throw new FileDatabaseError(`Failed to read file ${filename}: ${error.message}`);
1002
+ }
1003
+ }
984
1004
  await this.prepare({ read: true, version });
985
1005
  const isNonPaginatedData = this.metadata.dataType === "text" || this.metadata.dataType === "xml" || this.metadata.dataType === "json-object";
986
1006
  if (isNonPaginatedData) {
@@ -1061,6 +1081,67 @@ var FileDatabase = class _FileDatabase {
1061
1081
  this.currentRecord = 0;
1062
1082
  this.hasReadFirstPage = false;
1063
1083
  }
1084
+ /**
1085
+ * List filenames in the table directory.
1086
+ * For catalog/key-value usage (files written with { filename }).
1087
+ * Returns data file names (.json, .txt, .xml) excluding metadata.json.
1088
+ */
1089
+ async listFilenames() {
1090
+ const destPath = this.versioned && this.currentVersion ? import_path3.default.join(this.getDestinationPath(), this.currentVersion) : this.getDestinationPath();
1091
+ try {
1092
+ const entries = await import_fs3.default.promises.readdir(destPath, { withFileTypes: true });
1093
+ return entries.filter((e) => e.isFile() && e.name !== "metadata.json" && /\.(json|txt|xml)$/i.test(e.name)).map((e) => e.name);
1094
+ } catch (err) {
1095
+ if (err?.code === "ENOENT") return [];
1096
+ throw new FileDatabaseError(`Failed to list files: ${err.message}`);
1097
+ }
1098
+ }
1099
+ /**
1100
+ * Remove a file from the table directory (catalog mode).
1101
+ * Use with listFilenames() to manage individual files.
1102
+ */
1103
+ async removeFile(filename) {
1104
+ const destPath = this.versioned && this.currentVersion ? import_path3.default.join(this.getDestinationPath(), this.currentVersion) : this.getDestinationPath();
1105
+ const filePath = import_path3.default.join(destPath, filename);
1106
+ try {
1107
+ await import_fs3.default.promises.unlink(filePath);
1108
+ } catch (err) {
1109
+ if (err?.code === "ENOENT") return;
1110
+ throw new FileDatabaseError(`Failed to remove file ${filename}: ${err.message}`);
1111
+ }
1112
+ }
1113
+ /**
1114
+ * Remove a file and its metadata entry (non-versioned mode with useMetadata).
1115
+ * Use with findData() to get fileName, then call removeFileEntry to delete.
1116
+ */
1117
+ async removeFileEntry(filename) {
1118
+ if (this.versioned) {
1119
+ throw new FileDatabaseError("removeFileEntry is only supported in non-versioned mode");
1120
+ }
1121
+ await this.prepare({ read: true });
1122
+ const idx = this.metadata.files.findIndex((f) => f.fileName === filename);
1123
+ if (idx === -1) {
1124
+ throw new FileDatabaseError(`File entry ${filename} not found in metadata`);
1125
+ }
1126
+ const entry = this.metadata.files[idx];
1127
+ const recordsCount = entry.recordsCount || 0;
1128
+ this.metadata.files.splice(idx, 1);
1129
+ this.metadata.totalRecords = Math.max(0, (this.metadata.totalRecords || 0) - recordsCount);
1130
+ const destPath = this.getDestinationPath();
1131
+ const filePath = import_path3.default.join(destPath, filename);
1132
+ try {
1133
+ await import_fs3.default.promises.unlink(filePath);
1134
+ } catch (err) {
1135
+ if (err?.code === "ENOENT") {
1136
+ this.logger.warn?.(`[FileDatabase] File ${filename} already missing on disk`);
1137
+ } else {
1138
+ throw new FileDatabaseError(`Failed to remove file ${filename}: ${err.message}`);
1139
+ }
1140
+ }
1141
+ if (this.useMetadata) {
1142
+ await this.saveVersionMetadata(this.metadata);
1143
+ }
1144
+ }
1064
1145
  /**
1065
1146
  * Set file-level synopsis calculation function
1066
1147
  */