@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/README.md +112 -1
- package/dist/{filestore.cjs → filedatabase.cjs} +191 -37
- package/dist/filedatabase.cjs.map +1 -0
- package/dist/{filestore.js → filedatabase.js} +187 -33
- package/dist/filedatabase.js.map +1 -0
- package/dist/http-client.cjs +356 -0
- package/dist/http-client.cjs.map +1 -0
- package/dist/http-client.js +318 -0
- package/dist/http-client.js.map +1 -0
- package/dist/index.cjs +186 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +186 -32
- package/dist/index.js.map +1 -1
- package/dist/mock-server.cjs +1483 -0
- package/dist/mock-server.cjs.map +1 -0
- package/dist/mock-server.js +1445 -0
- package/dist/mock-server.js.map +1 -0
- package/package.json +30 -11
- package/dist/filestore.cjs.map +0 -1
- package/dist/filestore.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1714,7 +1714,7 @@ function organizeFooterMessages(messages) {
|
|
|
1714
1714
|
return lines;
|
|
1715
1715
|
}
|
|
1716
1716
|
|
|
1717
|
-
// src/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
-
|
|
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
|
|
2253
|
+
if (!this.useMetadata) {
|
|
2156
2254
|
return;
|
|
2157
2255
|
}
|
|
2158
2256
|
const metadataToSave = metadata || this.metadata;
|
|
2159
|
-
|
|
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.
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
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
|
-
|
|
2294
|
-
|
|
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
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
if (
|
|
2304
|
-
|
|
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
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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);
|