@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.js CHANGED
@@ -1714,7 +1714,7 @@ function organizeFooterMessages(messages) {
1714
1714
  return lines;
1715
1715
  }
1716
1716
 
1717
- // src/filestore/index.ts
1717
+ // src/filedatabase/index.ts
1718
1718
  import fs3 from "fs";
1719
1719
  import path3 from "path";
1720
1720
 
@@ -1790,7 +1790,7 @@ function isTimestampFolder(folderName) {
1790
1790
  return !isNaN(date.getTime()) && date.getTime() > 0;
1791
1791
  }
1792
1792
 
1793
- // src/filestore/serializers.ts
1793
+ // src/filedatabase/serializers.ts
1794
1794
  function detectDataType(data) {
1795
1795
  if (Array.isArray(data)) {
1796
1796
  return "json-array";
@@ -1822,7 +1822,7 @@ function deserializeData(rawData, dataType) {
1822
1822
  }
1823
1823
  }
1824
1824
 
1825
- // src/filestore/synopsis-functions.ts
1825
+ // src/filedatabase/synopsis-functions.ts
1826
1826
  function defaultFileSynopsisFunction(fileEntry, data) {
1827
1827
  if (!Array.isArray(data) || data.length === 0) {
1828
1828
  return { ...fileEntry };
@@ -1890,7 +1890,7 @@ function defaultVersionSynopsisFunction(metadata) {
1890
1890
  return result;
1891
1891
  }
1892
1892
 
1893
- // src/filestore/index.ts
1893
+ // src/filedatabase/index.ts
1894
1894
  var FileDatabaseError = class extends Error {
1895
1895
  constructor(message) {
1896
1896
  super(message);
@@ -1901,6 +1901,7 @@ var FileDatabase = class {
1901
1901
  basePath;
1902
1902
  namespace;
1903
1903
  tableName = null;
1904
+ versioned;
1904
1905
  maxVersions;
1905
1906
  pageSize;
1906
1907
  useMetadata;
@@ -1924,6 +1925,7 @@ var FileDatabase = class {
1924
1925
  this.basePath = config2.basePath;
1925
1926
  this.namespace = config2.namespace || "default";
1926
1927
  this.tableName = config2.tableName || null;
1928
+ this.versioned = config2.versioned ?? true;
1927
1929
  this.maxVersions = config2.maxVersions || 5;
1928
1930
  this.pageSize = config2.pageSize || 5e3;
1929
1931
  this.useMetadata = config2.useMetadata !== false;
@@ -1946,14 +1948,21 @@ var FileDatabase = class {
1946
1948
  };
1947
1949
  }
1948
1950
  /**
1949
- * Get the destination path (basePath/namespace/tableName)
1951
+ * Get the destination path (basePath/namespace/tableName[/version])
1950
1952
  */
1951
- getDestinationPath() {
1953
+ getDestinationPath(version) {
1952
1954
  const errors = ["basePath", "namespace", "tableName"].filter((prop) => !this[prop]).map((prop) => `${prop} is not set`);
1953
1955
  if (errors.length) {
1954
1956
  throw new FileDatabaseError(`[FileDatabase] ${errors.join("; ")}`);
1955
1957
  }
1956
- return path3.resolve(this.basePath, this.namespace, this.tableName);
1958
+ let parts = [this.basePath, this.namespace];
1959
+ if (this.tableName) {
1960
+ parts.push(...this.tableName.split("/"));
1961
+ }
1962
+ if (this.versioned && version) {
1963
+ parts.push(version);
1964
+ }
1965
+ return path3.resolve(...parts);
1957
1966
  }
1958
1967
  /**
1959
1968
  * Set current version and version folder
@@ -1964,8 +1973,13 @@ var FileDatabase = class {
1964
1973
  }
1965
1974
  /**
1966
1975
  * Create a new version folder with comprehensive timestamp logic
1976
+ * Only works in versioned mode
1967
1977
  */
1968
1978
  async makeNewVersion() {
1979
+ if (!this.versioned) {
1980
+ throw new FileDatabaseError("makeNewVersion() only works in versioned mode");
1981
+ }
1982
+ this.metadata = this.getDefaultMetadata();
1969
1983
  const existingVersions = await this.getVersions();
1970
1984
  let versionName;
1971
1985
  if (existingVersions.length > 0) {
@@ -1993,8 +2007,12 @@ var FileDatabase = class {
1993
2007
  }
1994
2008
  /**
1995
2009
  * Get list of all versions (sorted chronologically)
2010
+ * Only works in versioned mode
1996
2011
  */
1997
2012
  async getVersions() {
2013
+ if (!this.versioned) {
2014
+ return [];
2015
+ }
1998
2016
  const destPath = this.getDestinationPath();
1999
2017
  try {
2000
2018
  await ensurePath(destPath);
@@ -2009,6 +2027,86 @@ var FileDatabase = class {
2009
2027
  return [];
2010
2028
  }
2011
2029
  }
2030
+ /**
2031
+ * Get the latest version (most recent timestamp)
2032
+ * Only works in versioned mode
2033
+ * @returns Latest version string or null if no versions
2034
+ */
2035
+ async getLatestVersion() {
2036
+ if (!this.versioned) {
2037
+ throw new FileDatabaseError("getLatestVersion() only works in versioned mode");
2038
+ }
2039
+ const versions = await this.getVersions();
2040
+ if (versions.length === 0) {
2041
+ return null;
2042
+ }
2043
+ return versions[versions.length - 1];
2044
+ }
2045
+ /**
2046
+ * Check if any data exists in this table
2047
+ * Works for both versioned and non-versioned modes
2048
+ * @returns true if data exists
2049
+ */
2050
+ async hasData() {
2051
+ const tablePath = this.getDestinationPath();
2052
+ if (!fs3.existsSync(tablePath)) {
2053
+ return false;
2054
+ }
2055
+ if (this.versioned) {
2056
+ const versions = await this.getVersions();
2057
+ return versions.length > 0;
2058
+ } else {
2059
+ const items = await fs3.promises.readdir(tablePath);
2060
+ return items.some(
2061
+ (item) => item === "metadata.json" || item.match(/^\d{6}\.(json|txt|xml)$/) || item.endsWith(".json")
2062
+ );
2063
+ }
2064
+ }
2065
+ /**
2066
+ * Auto-detect the data format in this table
2067
+ * Used when reading existing data
2068
+ * @returns Format detection result
2069
+ */
2070
+ async detectDataFormat() {
2071
+ const tablePath = this.getDestinationPath();
2072
+ if (!fs3.existsSync(tablePath)) {
2073
+ return { versioned: false, hasMetadata: false, dataType: null };
2074
+ }
2075
+ const items = await fs3.promises.readdir(tablePath);
2076
+ if (items.includes("metadata.json")) {
2077
+ const metadata = JSON.parse(
2078
+ await fs3.promises.readFile(path3.join(tablePath, "metadata.json"), "utf8")
2079
+ );
2080
+ return {
2081
+ versioned: false,
2082
+ hasMetadata: true,
2083
+ dataType: metadata.dataType || null
2084
+ };
2085
+ }
2086
+ const versionFolders = items.filter((item) => {
2087
+ const itemPath = path3.join(tablePath, item);
2088
+ const stat = fs3.statSync(itemPath);
2089
+ return stat.isDirectory() && isTimestampFolder(item);
2090
+ });
2091
+ if (versionFolders.length > 0) {
2092
+ const latestVersion = versionFolders.sort().pop();
2093
+ const versionMetadataPath = path3.join(tablePath, latestVersion, "metadata.json");
2094
+ return {
2095
+ versioned: true,
2096
+ hasMetadata: fs3.existsSync(versionMetadataPath),
2097
+ dataType: null
2098
+ };
2099
+ }
2100
+ const dataFiles = items.filter((f) => f.match(/^\d{6}\.(json|txt|xml)$/));
2101
+ if (dataFiles.length > 0) {
2102
+ return {
2103
+ versioned: false,
2104
+ hasMetadata: false,
2105
+ dataType: null
2106
+ };
2107
+ }
2108
+ return { versioned: false, hasMetadata: false, dataType: null };
2109
+ }
2012
2110
  /**
2013
2111
  * Load metadata from JSON file
2014
2112
  */
@@ -2152,11 +2250,19 @@ var FileDatabase = class {
2152
2250
  * Save version metadata to file
2153
2251
  */
2154
2252
  async saveVersionMetadata(metadata) {
2155
- if (!this.useMetadata || !this.currentVersion) {
2253
+ if (!this.useMetadata) {
2156
2254
  return;
2157
2255
  }
2158
2256
  const metadataToSave = metadata || this.metadata;
2159
- const metadataFile = path3.join(this.getDestinationPath(), this.currentVersion, "metadata.json");
2257
+ let metadataFile;
2258
+ if (this.versioned) {
2259
+ if (!this.currentVersion) {
2260
+ return;
2261
+ }
2262
+ metadataFile = path3.join(this.getDestinationPath(), this.currentVersion, "metadata.json");
2263
+ } else {
2264
+ metadataFile = path3.join(this.getDestinationPath(), "metadata.json");
2265
+ }
2160
2266
  await fs3.promises.writeFile(metadataFile, JSON.stringify(metadataToSave, null, 4), "utf8");
2161
2267
  }
2162
2268
  /**
@@ -2284,31 +2390,75 @@ var FileDatabase = class {
2284
2390
  */
2285
2391
  async prepare({ write, read, version }) {
2286
2392
  if (write) {
2287
- if (this.currentVersion === null) {
2288
- await this.makeNewVersion();
2289
- this.metadata = this.getDefaultMetadata();
2290
- this.metadata.version = this.currentVersion;
2291
- this.makeNewFile();
2393
+ if (this.versioned) {
2394
+ if (this.currentVersion === null) {
2395
+ await this.makeNewVersion();
2396
+ this.metadata = this.getDefaultMetadata();
2397
+ this.metadata.version = this.currentVersion;
2398
+ this.makeNewFile();
2399
+ } else {
2400
+ if (!this.metadata.files.length) {
2401
+ this.metadata = await this.figureMetadata(this.currentVersion);
2402
+ }
2403
+ }
2292
2404
  } else {
2293
- if (!this.metadata.files.length) {
2294
- this.metadata = await this.figureMetadata(this.currentVersion);
2405
+ await ensurePath(this.getDestinationPath());
2406
+ if (this.useMetadata === true) {
2407
+ const metadataPath = path3.join(this.getDestinationPath(), "metadata.json");
2408
+ if (fs3.existsSync(metadataPath)) {
2409
+ try {
2410
+ const rawData = await fs3.promises.readFile(metadataPath, "utf8");
2411
+ this.metadata = JSON.parse(rawData);
2412
+ } catch (e) {
2413
+ this.metadata = this.getDefaultMetadata();
2414
+ }
2415
+ } else {
2416
+ this.metadata = this.getDefaultMetadata();
2417
+ this.makeNewFile();
2418
+ }
2419
+ } else {
2420
+ this.metadata = this.getDefaultMetadata();
2421
+ this.makeNewFile();
2295
2422
  }
2296
2423
  }
2297
2424
  } else if (read) {
2298
- const versions = await this.getVersions();
2299
- if (versions.length === 0) {
2300
- throw new FileDatabaseError("[FileDatabase] No versions found, cannot read");
2301
- }
2302
- if (version) {
2303
- if (!versions.includes(version)) {
2304
- throw new FileDatabaseError(`[FileDatabase] Version "${version}" not found`);
2425
+ if (this.versioned) {
2426
+ const versions = await this.getVersions();
2427
+ if (versions.length === 0) {
2428
+ throw new FileDatabaseError("[FileDatabase] No versions found, cannot read");
2429
+ }
2430
+ if (version) {
2431
+ if (!versions.includes(version)) {
2432
+ throw new FileDatabaseError(`[FileDatabase] Version "${version}" not found`);
2433
+ }
2434
+ await this.setCurrentVersion(version);
2435
+ } else {
2436
+ await this.setCurrentVersion(versions[versions.length - 1]);
2437
+ }
2438
+ if (!this.metadata.files.length) {
2439
+ this.metadata = await this.figureMetadata(this.currentVersion);
2305
2440
  }
2306
- await this.setCurrentVersion(version);
2307
2441
  } else {
2308
- await this.setCurrentVersion(versions[versions.length - 1]);
2309
- }
2310
- if (!this.metadata.files.length) {
2311
- this.metadata = await this.figureMetadata(this.currentVersion);
2442
+ this.currentVersion = null;
2443
+ if (this.useMetadata === void 0) {
2444
+ const format = await this.detectDataFormat();
2445
+ this.useMetadata = format.hasMetadata;
2446
+ }
2447
+ if (this.useMetadata) {
2448
+ const metadataPath = path3.join(this.getDestinationPath(), "metadata.json");
2449
+ if (fs3.existsSync(metadataPath)) {
2450
+ try {
2451
+ const rawData = await fs3.promises.readFile(metadataPath, "utf8");
2452
+ this.metadata = JSON.parse(rawData);
2453
+ } catch (e) {
2454
+ throw new FileDatabaseError(`Failed to read metadata: ${e.message}`);
2455
+ }
2456
+ } else {
2457
+ throw new FileDatabaseError("[FileDatabase] No metadata found in non-versioned mode");
2458
+ }
2459
+ } else {
2460
+ this.metadata = await this.figureMetadataFromVersionFiles("");
2461
+ }
2312
2462
  }
2313
2463
  }
2314
2464
  }
@@ -2316,6 +2466,9 @@ var FileDatabase = class {
2316
2466
  * Write data to the file database
2317
2467
  */
2318
2468
  async write(data, options = {}) {
2469
+ if (options.forceNewVersion && !this.versioned) {
2470
+ throw new FileDatabaseError("Cannot use forceNewVersion in non-versioned mode");
2471
+ }
2319
2472
  await this.prepare({ write: true });
2320
2473
  if (options.forceNewVersion) {
2321
2474
  await this.makeNewVersion();
@@ -2324,11 +2477,12 @@ var FileDatabase = class {
2324
2477
  this.makeNewFile();
2325
2478
  }
2326
2479
  let { dataToWrite, dataLeftOver, fileName } = this.figureOutDataAndFileToWrite(data);
2327
- await this.safeWrite(path3.join(this.currentVersionFolder, fileName), dataToWrite);
2480
+ const destPath = this.getDestinationPath(this.currentVersion || void 0);
2481
+ await this.safeWrite(path3.join(destPath, fileName), dataToWrite);
2328
2482
  this.updateMetadata(dataToWrite, fileName);
2329
2483
  while (dataLeftOver && dataLeftOver.length > 0) {
2330
2484
  const writeContext = this.figureOutDataAndFileToWrite(dataLeftOver);
2331
- await this.safeWrite(path3.join(this.currentVersionFolder, writeContext.fileName), writeContext.dataToWrite);
2485
+ await this.safeWrite(path3.join(destPath, writeContext.fileName), writeContext.dataToWrite);
2332
2486
  this.updateMetadata(writeContext.dataToWrite, writeContext.fileName);
2333
2487
  dataLeftOver = writeContext.dataLeftOver;
2334
2488
  }
@@ -2346,7 +2500,7 @@ var FileDatabase = class {
2346
2500
  const isNonPaginatedData = this.metadata.dataType === "text" || this.metadata.dataType === "xml" || this.metadata.dataType === "json-object";
2347
2501
  if (isNonPaginatedData) {
2348
2502
  const file = this.metadata.files[0];
2349
- const filePath = path3.join(this.getDestinationPath(), this.currentVersion, file.fileName);
2503
+ const filePath = path3.join(this.getDestinationPath(this.currentVersion || void 0), file.fileName);
2350
2504
  try {
2351
2505
  const rawData = await fs3.promises.readFile(filePath, "utf8");
2352
2506
  return deserializeData(rawData, this.metadata.dataType);
@@ -2384,7 +2538,7 @@ var FileDatabase = class {
2384
2538
  let cumulativeRecords = currentFileOffset;
2385
2539
  for (let i = currentFileIndex; i < this.metadata.files.length && recordsRead < effectivePageSize; i++) {
2386
2540
  const file = this.metadata.files[i];
2387
- const filePath = path3.join(this.getDestinationPath(), this.currentVersion, file.fileName);
2541
+ const filePath = path3.join(this.getDestinationPath(this.currentVersion || void 0), file.fileName);
2388
2542
  try {
2389
2543
  const rawData = await fs3.promises.readFile(filePath, "utf8");
2390
2544
  const fileData = deserializeData(rawData, this.metadata.dataType);