@nmakarov/cli-toolkit 0.2.0 → 0.3.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
@@ -1777,7 +1777,7 @@ function organizeFooterMessages(messages) {
1777
1777
  return lines;
1778
1778
  }
1779
1779
 
1780
- // src/filestore/index.ts
1780
+ // src/filedatabase/index.ts
1781
1781
  var import_fs4 = __toESM(require("fs"), 1);
1782
1782
  var import_path4 = __toESM(require("path"), 1);
1783
1783
 
@@ -1853,7 +1853,7 @@ function isTimestampFolder(folderName) {
1853
1853
  return !isNaN(date.getTime()) && date.getTime() > 0;
1854
1854
  }
1855
1855
 
1856
- // src/filestore/serializers.ts
1856
+ // src/filedatabase/serializers.ts
1857
1857
  function detectDataType(data) {
1858
1858
  if (Array.isArray(data)) {
1859
1859
  return "json-array";
@@ -1885,7 +1885,7 @@ function deserializeData(rawData, dataType) {
1885
1885
  }
1886
1886
  }
1887
1887
 
1888
- // src/filestore/synopsis-functions.ts
1888
+ // src/filedatabase/synopsis-functions.ts
1889
1889
  function defaultFileSynopsisFunction(fileEntry, data) {
1890
1890
  if (!Array.isArray(data) || data.length === 0) {
1891
1891
  return { ...fileEntry };
@@ -1953,7 +1953,7 @@ function defaultVersionSynopsisFunction(metadata) {
1953
1953
  return result;
1954
1954
  }
1955
1955
 
1956
- // src/filestore/index.ts
1956
+ // src/filedatabase/index.ts
1957
1957
  var FileDatabaseError = class extends Error {
1958
1958
  constructor(message) {
1959
1959
  super(message);
@@ -1964,6 +1964,7 @@ var FileDatabase = class {
1964
1964
  basePath;
1965
1965
  namespace;
1966
1966
  tableName = null;
1967
+ versioned;
1967
1968
  maxVersions;
1968
1969
  pageSize;
1969
1970
  useMetadata;
@@ -1987,6 +1988,7 @@ var FileDatabase = class {
1987
1988
  this.basePath = config2.basePath;
1988
1989
  this.namespace = config2.namespace || "default";
1989
1990
  this.tableName = config2.tableName || null;
1991
+ this.versioned = config2.versioned ?? true;
1990
1992
  this.maxVersions = config2.maxVersions || 5;
1991
1993
  this.pageSize = config2.pageSize || 5e3;
1992
1994
  this.useMetadata = config2.useMetadata !== false;
@@ -2009,14 +2011,21 @@ var FileDatabase = class {
2009
2011
  };
2010
2012
  }
2011
2013
  /**
2012
- * Get the destination path (basePath/namespace/tableName)
2014
+ * Get the destination path (basePath/namespace/tableName[/version])
2013
2015
  */
2014
- getDestinationPath() {
2016
+ getDestinationPath(version) {
2015
2017
  const errors = ["basePath", "namespace", "tableName"].filter((prop) => !this[prop]).map((prop) => `${prop} is not set`);
2016
2018
  if (errors.length) {
2017
2019
  throw new FileDatabaseError(`[FileDatabase] ${errors.join("; ")}`);
2018
2020
  }
2019
- return import_path4.default.resolve(this.basePath, this.namespace, this.tableName);
2021
+ let parts = [this.basePath, this.namespace];
2022
+ if (this.tableName) {
2023
+ parts.push(...this.tableName.split("/"));
2024
+ }
2025
+ if (this.versioned && version) {
2026
+ parts.push(version);
2027
+ }
2028
+ return import_path4.default.resolve(...parts);
2020
2029
  }
2021
2030
  /**
2022
2031
  * Set current version and version folder
@@ -2027,8 +2036,13 @@ var FileDatabase = class {
2027
2036
  }
2028
2037
  /**
2029
2038
  * Create a new version folder with comprehensive timestamp logic
2039
+ * Only works in versioned mode
2030
2040
  */
2031
2041
  async makeNewVersion() {
2042
+ if (!this.versioned) {
2043
+ throw new FileDatabaseError("makeNewVersion() only works in versioned mode");
2044
+ }
2045
+ this.metadata = this.getDefaultMetadata();
2032
2046
  const existingVersions = await this.getVersions();
2033
2047
  let versionName;
2034
2048
  if (existingVersions.length > 0) {
@@ -2056,8 +2070,12 @@ var FileDatabase = class {
2056
2070
  }
2057
2071
  /**
2058
2072
  * Get list of all versions (sorted chronologically)
2073
+ * Only works in versioned mode
2059
2074
  */
2060
2075
  async getVersions() {
2076
+ if (!this.versioned) {
2077
+ return [];
2078
+ }
2061
2079
  const destPath = this.getDestinationPath();
2062
2080
  try {
2063
2081
  await ensurePath(destPath);
@@ -2072,6 +2090,86 @@ var FileDatabase = class {
2072
2090
  return [];
2073
2091
  }
2074
2092
  }
2093
+ /**
2094
+ * Get the latest version (most recent timestamp)
2095
+ * Only works in versioned mode
2096
+ * @returns Latest version string or null if no versions
2097
+ */
2098
+ async getLatestVersion() {
2099
+ if (!this.versioned) {
2100
+ throw new FileDatabaseError("getLatestVersion() only works in versioned mode");
2101
+ }
2102
+ const versions = await this.getVersions();
2103
+ if (versions.length === 0) {
2104
+ return null;
2105
+ }
2106
+ return versions[versions.length - 1];
2107
+ }
2108
+ /**
2109
+ * Check if any data exists in this table
2110
+ * Works for both versioned and non-versioned modes
2111
+ * @returns true if data exists
2112
+ */
2113
+ async hasData() {
2114
+ const tablePath = this.getDestinationPath();
2115
+ if (!import_fs4.default.existsSync(tablePath)) {
2116
+ return false;
2117
+ }
2118
+ if (this.versioned) {
2119
+ const versions = await this.getVersions();
2120
+ return versions.length > 0;
2121
+ } else {
2122
+ const items = await import_fs4.default.promises.readdir(tablePath);
2123
+ return items.some(
2124
+ (item) => item === "metadata.json" || item.match(/^\d{6}\.(json|txt|xml)$/) || item.endsWith(".json")
2125
+ );
2126
+ }
2127
+ }
2128
+ /**
2129
+ * Auto-detect the data format in this table
2130
+ * Used when reading existing data
2131
+ * @returns Format detection result
2132
+ */
2133
+ async detectDataFormat() {
2134
+ const tablePath = this.getDestinationPath();
2135
+ if (!import_fs4.default.existsSync(tablePath)) {
2136
+ return { versioned: false, hasMetadata: false, dataType: null };
2137
+ }
2138
+ const items = await import_fs4.default.promises.readdir(tablePath);
2139
+ if (items.includes("metadata.json")) {
2140
+ const metadata = JSON.parse(
2141
+ await import_fs4.default.promises.readFile(import_path4.default.join(tablePath, "metadata.json"), "utf8")
2142
+ );
2143
+ return {
2144
+ versioned: false,
2145
+ hasMetadata: true,
2146
+ dataType: metadata.dataType || null
2147
+ };
2148
+ }
2149
+ const versionFolders = items.filter((item) => {
2150
+ const itemPath = import_path4.default.join(tablePath, item);
2151
+ const stat = import_fs4.default.statSync(itemPath);
2152
+ return stat.isDirectory() && isTimestampFolder(item);
2153
+ });
2154
+ if (versionFolders.length > 0) {
2155
+ const latestVersion = versionFolders.sort().pop();
2156
+ const versionMetadataPath = import_path4.default.join(tablePath, latestVersion, "metadata.json");
2157
+ return {
2158
+ versioned: true,
2159
+ hasMetadata: import_fs4.default.existsSync(versionMetadataPath),
2160
+ dataType: null
2161
+ };
2162
+ }
2163
+ const dataFiles = items.filter((f) => f.match(/^\d{6}\.(json|txt|xml)$/));
2164
+ if (dataFiles.length > 0) {
2165
+ return {
2166
+ versioned: false,
2167
+ hasMetadata: false,
2168
+ dataType: null
2169
+ };
2170
+ }
2171
+ return { versioned: false, hasMetadata: false, dataType: null };
2172
+ }
2075
2173
  /**
2076
2174
  * Load metadata from JSON file
2077
2175
  */
@@ -2215,11 +2313,19 @@ var FileDatabase = class {
2215
2313
  * Save version metadata to file
2216
2314
  */
2217
2315
  async saveVersionMetadata(metadata) {
2218
- if (!this.useMetadata || !this.currentVersion) {
2316
+ if (!this.useMetadata) {
2219
2317
  return;
2220
2318
  }
2221
2319
  const metadataToSave = metadata || this.metadata;
2222
- const metadataFile = import_path4.default.join(this.getDestinationPath(), this.currentVersion, "metadata.json");
2320
+ let metadataFile;
2321
+ if (this.versioned) {
2322
+ if (!this.currentVersion) {
2323
+ return;
2324
+ }
2325
+ metadataFile = import_path4.default.join(this.getDestinationPath(), this.currentVersion, "metadata.json");
2326
+ } else {
2327
+ metadataFile = import_path4.default.join(this.getDestinationPath(), "metadata.json");
2328
+ }
2223
2329
  await import_fs4.default.promises.writeFile(metadataFile, JSON.stringify(metadataToSave, null, 4), "utf8");
2224
2330
  }
2225
2331
  /**
@@ -2347,31 +2453,75 @@ var FileDatabase = class {
2347
2453
  */
2348
2454
  async prepare({ write, read, version }) {
2349
2455
  if (write) {
2350
- if (this.currentVersion === null) {
2351
- await this.makeNewVersion();
2352
- this.metadata = this.getDefaultMetadata();
2353
- this.metadata.version = this.currentVersion;
2354
- this.makeNewFile();
2456
+ if (this.versioned) {
2457
+ if (this.currentVersion === null) {
2458
+ await this.makeNewVersion();
2459
+ this.metadata = this.getDefaultMetadata();
2460
+ this.metadata.version = this.currentVersion;
2461
+ this.makeNewFile();
2462
+ } else {
2463
+ if (!this.metadata.files.length) {
2464
+ this.metadata = await this.figureMetadata(this.currentVersion);
2465
+ }
2466
+ }
2355
2467
  } else {
2356
- if (!this.metadata.files.length) {
2357
- this.metadata = await this.figureMetadata(this.currentVersion);
2468
+ await ensurePath(this.getDestinationPath());
2469
+ if (this.useMetadata === true) {
2470
+ const metadataPath = import_path4.default.join(this.getDestinationPath(), "metadata.json");
2471
+ if (import_fs4.default.existsSync(metadataPath)) {
2472
+ try {
2473
+ const rawData = await import_fs4.default.promises.readFile(metadataPath, "utf8");
2474
+ this.metadata = JSON.parse(rawData);
2475
+ } catch (e) {
2476
+ this.metadata = this.getDefaultMetadata();
2477
+ }
2478
+ } else {
2479
+ this.metadata = this.getDefaultMetadata();
2480
+ this.makeNewFile();
2481
+ }
2482
+ } else {
2483
+ this.metadata = this.getDefaultMetadata();
2484
+ this.makeNewFile();
2358
2485
  }
2359
2486
  }
2360
2487
  } else if (read) {
2361
- const versions = await this.getVersions();
2362
- if (versions.length === 0) {
2363
- throw new FileDatabaseError("[FileDatabase] No versions found, cannot read");
2364
- }
2365
- if (version) {
2366
- if (!versions.includes(version)) {
2367
- throw new FileDatabaseError(`[FileDatabase] Version "${version}" not found`);
2488
+ if (this.versioned) {
2489
+ const versions = await this.getVersions();
2490
+ if (versions.length === 0) {
2491
+ throw new FileDatabaseError("[FileDatabase] No versions found, cannot read");
2492
+ }
2493
+ if (version) {
2494
+ if (!versions.includes(version)) {
2495
+ throw new FileDatabaseError(`[FileDatabase] Version "${version}" not found`);
2496
+ }
2497
+ await this.setCurrentVersion(version);
2498
+ } else {
2499
+ await this.setCurrentVersion(versions[versions.length - 1]);
2500
+ }
2501
+ if (!this.metadata.files.length) {
2502
+ this.metadata = await this.figureMetadata(this.currentVersion);
2368
2503
  }
2369
- await this.setCurrentVersion(version);
2370
2504
  } else {
2371
- await this.setCurrentVersion(versions[versions.length - 1]);
2372
- }
2373
- if (!this.metadata.files.length) {
2374
- this.metadata = await this.figureMetadata(this.currentVersion);
2505
+ this.currentVersion = null;
2506
+ if (this.useMetadata === void 0) {
2507
+ const format = await this.detectDataFormat();
2508
+ this.useMetadata = format.hasMetadata;
2509
+ }
2510
+ if (this.useMetadata) {
2511
+ const metadataPath = import_path4.default.join(this.getDestinationPath(), "metadata.json");
2512
+ if (import_fs4.default.existsSync(metadataPath)) {
2513
+ try {
2514
+ const rawData = await import_fs4.default.promises.readFile(metadataPath, "utf8");
2515
+ this.metadata = JSON.parse(rawData);
2516
+ } catch (e) {
2517
+ throw new FileDatabaseError(`Failed to read metadata: ${e.message}`);
2518
+ }
2519
+ } else {
2520
+ throw new FileDatabaseError("[FileDatabase] No metadata found in non-versioned mode");
2521
+ }
2522
+ } else {
2523
+ this.metadata = await this.figureMetadataFromVersionFiles("");
2524
+ }
2375
2525
  }
2376
2526
  }
2377
2527
  }
@@ -2379,6 +2529,9 @@ var FileDatabase = class {
2379
2529
  * Write data to the file database
2380
2530
  */
2381
2531
  async write(data, options = {}) {
2532
+ if (options.forceNewVersion && !this.versioned) {
2533
+ throw new FileDatabaseError("Cannot use forceNewVersion in non-versioned mode");
2534
+ }
2382
2535
  await this.prepare({ write: true });
2383
2536
  if (options.forceNewVersion) {
2384
2537
  await this.makeNewVersion();
@@ -2387,11 +2540,12 @@ var FileDatabase = class {
2387
2540
  this.makeNewFile();
2388
2541
  }
2389
2542
  let { dataToWrite, dataLeftOver, fileName } = this.figureOutDataAndFileToWrite(data);
2390
- await this.safeWrite(import_path4.default.join(this.currentVersionFolder, fileName), dataToWrite);
2543
+ const destPath = this.getDestinationPath(this.currentVersion || void 0);
2544
+ await this.safeWrite(import_path4.default.join(destPath, fileName), dataToWrite);
2391
2545
  this.updateMetadata(dataToWrite, fileName);
2392
2546
  while (dataLeftOver && dataLeftOver.length > 0) {
2393
2547
  const writeContext = this.figureOutDataAndFileToWrite(dataLeftOver);
2394
- await this.safeWrite(import_path4.default.join(this.currentVersionFolder, writeContext.fileName), writeContext.dataToWrite);
2548
+ await this.safeWrite(import_path4.default.join(destPath, writeContext.fileName), writeContext.dataToWrite);
2395
2549
  this.updateMetadata(writeContext.dataToWrite, writeContext.fileName);
2396
2550
  dataLeftOver = writeContext.dataLeftOver;
2397
2551
  }
@@ -2409,7 +2563,7 @@ var FileDatabase = class {
2409
2563
  const isNonPaginatedData = this.metadata.dataType === "text" || this.metadata.dataType === "xml" || this.metadata.dataType === "json-object";
2410
2564
  if (isNonPaginatedData) {
2411
2565
  const file = this.metadata.files[0];
2412
- const filePath = import_path4.default.join(this.getDestinationPath(), this.currentVersion, file.fileName);
2566
+ const filePath = import_path4.default.join(this.getDestinationPath(this.currentVersion || void 0), file.fileName);
2413
2567
  try {
2414
2568
  const rawData = await import_fs4.default.promises.readFile(filePath, "utf8");
2415
2569
  return deserializeData(rawData, this.metadata.dataType);
@@ -2447,7 +2601,7 @@ var FileDatabase = class {
2447
2601
  let cumulativeRecords = currentFileOffset;
2448
2602
  for (let i = currentFileIndex; i < this.metadata.files.length && recordsRead < effectivePageSize; i++) {
2449
2603
  const file = this.metadata.files[i];
2450
- const filePath = import_path4.default.join(this.getDestinationPath(), this.currentVersion, file.fileName);
2604
+ const filePath = import_path4.default.join(this.getDestinationPath(this.currentVersion || void 0), file.fileName);
2451
2605
  try {
2452
2606
  const rawData = await import_fs4.default.promises.readFile(filePath, "utf8");
2453
2607
  const fileData = deserializeData(rawData, this.metadata.dataType);