@decohere/core 0.0.1-dev.125 → 0.0.1-dev.5
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/package.json +40 -15
- package/src/caching/cache-manager.ts +102 -0
- package/src/caching/cache.decorator.ts +56 -0
- package/src/caching/cache.ts +11 -0
- package/{es6/src/caching/index.js → src/caching/index.ts} +1 -1
- package/src/caching/invalidate-cache.decorator.ts +81 -0
- package/src/caching/memoize.decorator.ts +34 -0
- package/src/caching/node-cache-memoize.adapter.ts +47 -0
- package/src/cli/action.ts +77 -0
- package/src/cli/command.ts +173 -0
- package/src/cli/index.ts +30 -0
- package/src/cli/module.type.ts +48 -0
- package/src/cli/options.ts +81 -0
- package/src/collections/find-duplicates.ts +16 -0
- package/src/collections/merge-arrays.ts +9 -0
- package/src/collections/sum.ts +71 -0
- package/src/collections/unique-objects.ts +23 -0
- package/src/composition/index.ts +48 -0
- package/src/configuration/configuration.types.ts +13 -0
- package/src/diagnostics/index.ts +1 -0
- package/src/diagnostics/trace.ts +97 -0
- package/{es6/src/extensibility/decorator-factory-base.js → src/extensibility/decorator-factory-base.ts} +17 -9
- package/src/extensibility/decorator-factory.ts +44 -0
- package/src/extensibility/extension.ts +34 -0
- package/src/extensibility/mixwith.ts +42 -0
- package/src/extensibility/pipeline.ts +60 -0
- package/{types/src/extensibility/step.d.ts → src/extensibility/step.ts} +6 -4
- package/src/interop/docker/docker.ts +57 -0
- package/src/interop/errors/interop.error.ts +1 -0
- package/src/interop/git/git.ts +98 -0
- package/src/interop/git/git.types.ts +17 -0
- package/src/interop/index.ts +22 -0
- package/src/interop/node/index.ts +1 -0
- package/src/interop/node/node.ts +38 -0
- package/src/interop/npm/npm.ts +168 -0
- package/src/interop/npm/npm.types.ts +28 -0
- package/src/interop/pnpm/pnpm.ts +22 -0
- package/src/interop/pnpm/pnpm.types.ts +3 -0
- package/src/interop/shell/shell.argv.ts +31 -0
- package/src/interop/shell/shell.ts +100 -0
- package/src/interop/shell/shell.types.ts +9 -0
- package/src/interop/types/detect-package-manager.ts +45 -0
- package/src/interop/types/package-manager.ts +7 -0
- package/src/interop/yarn/yarn.ts +28 -0
- package/src/interop/yarn/yarn.types.ts +3 -0
- package/src/io/copy-file.ts +13 -0
- package/src/io/delete-file.ts +4 -0
- package/src/io/directory-exists.ts +9 -0
- package/src/io/file-exists.ts +10 -0
- package/src/io/find-directories.ts +13 -0
- package/src/io/find-file.ts +37 -0
- package/{es6/src/io/io.js → src/io/io.ts} +3 -0
- package/src/io/read-file.ts +25 -0
- package/src/io/remove-dir.ts +11 -0
- package/src/io/types/directory.type.ts +4 -0
- package/src/io/types/file.ts +34 -0
- package/src/io/url-to-path.ts +17 -0
- package/src/io/watch.ts +290 -0
- package/src/io/write-file.ts +21 -0
- package/src/objects/deep-merge.ts +131 -0
- package/{es6/src/reflection/index.js → src/reflection/index.ts} +1 -0
- package/src/reflection/types/reflection.types.ts +28 -0
- package/src/reflection/utilities.ts +92 -0
- package/src/semver/index.ts +1 -0
- package/src/semver/semver.ts +96 -0
- package/src/system/camel-case.ts +3 -0
- package/src/system/debounce.ts +13 -0
- package/src/system/decorator.ts +41 -0
- package/src/system/environment.ts +6 -0
- package/{types/src/system/function.types.d.ts → src/system/function.types.ts} +10 -2
- package/src/system/retry.decorator.ts +43 -0
- package/src/system/sleep.ts +6 -0
- package/src/system/type.utils.ts +5 -0
- package/src/testing/flush-promises.ts +7 -0
- package/src/testing/glob-with-memfs.ts +25 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/memfs.ts +28 -0
- package/src/testing/mock-logger.ts +42 -0
- package/src/testing/spy-on-constructor.ts +33 -0
- package/tests/caching/cache-manager.spec.ts +147 -0
- package/tests/caching/cache-test-class.ts +91 -0
- package/tests/caching/cache.decorator.spec.ts +259 -0
- package/tests/caching/invalidate-cache-test-class.ts +52 -0
- package/tests/caching/invalidate-cache.decorator.spec.ts +120 -0
- package/tests/caching/memoize-test-class.ts +99 -0
- package/tests/caching/memoize.decorator.spec.ts +203 -0
- package/tests/cli/command.model.spec.ts +91 -0
- package/tests/collections/merge-arrays.spec.ts +50 -0
- package/tests/collections/sum.spec.ts +86 -0
- package/tests/collections/unique-objects.spec.ts +233 -0
- package/tests/extensibility/extension.spec.ts +29 -0
- package/tests/extensibility/pipeline.spec.ts +379 -0
- package/tests/interop/detect-package-manager.spec.ts +70 -0
- package/tests/interop/npm.spec.ts +129 -0
- package/tests/interop/pnpm.spec.ts +30 -0
- package/tests/interop/shell.spec.ts +47 -0
- package/tests/interop/yarn.spec.ts +18 -0
- package/tests/io/find-file.spec.ts +188 -0
- package/tests/io/watch.real-fs.spec.ts +76 -0
- package/tests/io/watch.spec.ts +291 -0
- package/tests/mock-prototype.ts +12 -0
- package/tests/semver/semver.spec.ts +51 -0
- package/tests/system/decorator.spec.ts +250 -0
- package/tests/system/retry.decorator.spec.ts +57 -0
- package/LICENSE +0 -15
- package/commonjs/src/caching/cache-manager.js +0 -98
- package/commonjs/src/caching/cache.decorator.js +0 -69
- package/commonjs/src/caching/cache.js +0 -6
- package/commonjs/src/caching/index.js +0 -23
- package/commonjs/src/caching/invalidate-cache.decorator.js +0 -92
- package/commonjs/src/caching/map-cache.js +0 -39
- package/commonjs/src/caching/memoize.decorator.js +0 -36
- package/commonjs/src/collections/collections.js +0 -21
- package/commonjs/src/collections/find-duplicates.js +0 -35
- package/commonjs/src/collections/index.js +0 -52
- package/commonjs/src/collections/merge-arrays.js +0 -34
- package/commonjs/src/collections/sum.js +0 -63
- package/commonjs/src/collections/unique-objects.js +0 -39
- package/commonjs/src/composition/index.js +0 -4
- package/commonjs/src/configuration/configuration.types.js +0 -11
- package/commonjs/src/configuration/index.js +0 -18
- package/commonjs/src/extensibility/decorator-factory-base.js +0 -22
- package/commonjs/src/extensibility/decorator-factory.js +0 -31
- package/commonjs/src/extensibility/extension.js +0 -37
- package/commonjs/src/extensibility/index.js +0 -23
- package/commonjs/src/extensibility/mixwith.js +0 -36
- package/commonjs/src/extensibility/pipeline.js +0 -56
- package/commonjs/src/extensibility/step.js +0 -18
- package/commonjs/src/io/copy-file.js +0 -25
- package/commonjs/src/io/delete-file.js +0 -21
- package/commonjs/src/io/directory-exists.js +0 -24
- package/commonjs/src/io/file-exists.js +0 -19
- package/commonjs/src/io/index.js +0 -66
- package/commonjs/src/io/io.js +0 -25
- package/commonjs/src/io/read-file.js +0 -39
- package/commonjs/src/io/remove-dir.js +0 -22
- package/commonjs/src/io/types/directory.type.js +0 -4
- package/commonjs/src/io/types/file.js +0 -28
- package/commonjs/src/io/types/index.js +0 -19
- package/commonjs/src/io/url-to-path.js +0 -33
- package/commonjs/src/io/write-file.js +0 -30
- package/commonjs/src/objects/deep-merge.js +0 -67
- package/commonjs/src/objects/index.js +0 -11
- package/commonjs/src/reflection/index.js +0 -20
- package/commonjs/src/reflection/types/index.js +0 -18
- package/commonjs/src/reflection/types/reflection.types.js +0 -26
- package/commonjs/src/reflection/utilities.js +0 -118
- package/commonjs/src/system/camel-case.js +0 -13
- package/commonjs/src/system/debounce.js +0 -19
- package/commonjs/src/system/decorator.js +0 -46
- package/commonjs/src/system/environment.js +0 -23
- package/commonjs/src/system/function.types.js +0 -18
- package/commonjs/src/system/index.js +0 -24
- package/commonjs/src/system/retry.decorator.js +0 -40
- package/commonjs/src/system/sleep.js +0 -13
- package/commonjs/src/system/type.utils.js +0 -4
- package/es6/src/caching/cache-manager.js +0 -82
- package/es6/src/caching/cache.decorator.js +0 -51
- package/es6/src/caching/cache.js +0 -3
- package/es6/src/caching/invalidate-cache.decorator.js +0 -71
- package/es6/src/caching/map-cache.js +0 -35
- package/es6/src/caching/memoize.decorator.js +0 -27
- package/es6/src/collections/find-duplicates.js +0 -17
- package/es6/src/collections/merge-arrays.js +0 -16
- package/es6/src/collections/sum.js +0 -45
- package/es6/src/collections/unique-objects.js +0 -21
- package/es6/src/composition/index.js +0 -1
- package/es6/src/configuration/configuration.types.js +0 -1
- package/es6/src/extensibility/decorator-factory.js +0 -21
- package/es6/src/extensibility/extension.js +0 -27
- package/es6/src/extensibility/mixwith.js +0 -26
- package/es6/src/extensibility/pipeline.js +0 -46
- package/es6/src/extensibility/step.js +0 -16
- package/es6/src/io/copy-file.js +0 -10
- package/es6/src/io/delete-file.js +0 -6
- package/es6/src/io/directory-exists.js +0 -9
- package/es6/src/io/file-exists.js +0 -9
- package/es6/src/io/read-file.js +0 -13
- package/es6/src/io/remove-dir.js +0 -7
- package/es6/src/io/types/directory.type.js +0 -1
- package/es6/src/io/types/file.js +0 -18
- package/es6/src/io/url-to-path.js +0 -15
- package/es6/src/io/write-file.js +0 -15
- package/es6/src/objects/deep-merge.js +0 -57
- package/es6/src/reflection/types/reflection.types.js +0 -8
- package/es6/src/reflection/utilities.js +0 -74
- package/es6/src/system/camel-case.js +0 -3
- package/es6/src/system/debounce.js +0 -9
- package/es6/src/system/decorator.js +0 -30
- package/es6/src/system/environment.js +0 -5
- package/es6/src/system/function.types.js +0 -26
- package/es6/src/system/retry.decorator.js +0 -30
- package/es6/src/system/sleep.js +0 -3
- package/es6/src/system/type.utils.js +0 -1
- package/types/src/caching/cache-manager.d.ts +0 -59
- package/types/src/caching/cache.d.ts +0 -11
- package/types/src/caching/cache.decorator.d.ts +0 -18
- package/types/src/caching/index.d.ts +0 -6
- package/types/src/caching/invalidate-cache.decorator.d.ts +0 -17
- package/types/src/caching/map-cache.d.ts +0 -23
- package/types/src/caching/memoize.decorator.d.ts +0 -16
- package/types/src/collections/collections.d.ts +0 -4
- package/types/src/collections/find-duplicates.d.ts +0 -2
- package/types/src/collections/index.d.ts +0 -1
- package/types/src/collections/merge-arrays.d.ts +0 -2
- package/types/src/collections/sum.d.ts +0 -4
- package/types/src/collections/unique-objects.d.ts +0 -2
- package/types/src/composition/index.d.ts +0 -37
- package/types/src/configuration/configuration.types.d.ts +0 -10
- package/types/src/configuration/index.d.ts +0 -1
- package/types/src/extensibility/decorator-factory-base.d.ts +0 -39
- package/types/src/extensibility/decorator-factory.d.ts +0 -19
- package/types/src/extensibility/extension.d.ts +0 -9
- package/types/src/extensibility/index.d.ts +0 -6
- package/types/src/extensibility/mixwith.d.ts +0 -6
- package/types/src/extensibility/pipeline.d.ts +0 -12
- package/types/src/io/copy-file.d.ts +0 -4
- package/types/src/io/delete-file.d.ts +0 -1
- package/types/src/io/directory-exists.d.ts +0 -1
- package/types/src/io/file-exists.d.ts +0 -1
- package/types/src/io/index.d.ts +0 -2
- package/types/src/io/io.d.ts +0 -8
- package/types/src/io/read-file.d.ts +0 -4
- package/types/src/io/remove-dir.d.ts +0 -5
- package/types/src/io/types/directory.type.d.ts +0 -4
- package/types/src/io/types/file.d.ts +0 -20
- package/types/src/io/types/index.d.ts +0 -2
- package/types/src/io/url-to-path.d.ts +0 -2
- package/types/src/io/write-file.d.ts +0 -5
- package/types/src/objects/deep-merge.d.ts +0 -41
- package/types/src/objects/index.d.ts +0 -1
- package/types/src/reflection/index.d.ts +0 -3
- package/types/src/reflection/types/index.d.ts +0 -1
- package/types/src/reflection/types/reflection.types.d.ts +0 -21
- package/types/src/reflection/utilities.d.ts +0 -27
- package/types/src/system/camel-case.d.ts +0 -1
- package/types/src/system/debounce.d.ts +0 -1
- package/types/src/system/decorator.d.ts +0 -15
- package/types/src/system/environment.d.ts +0 -6
- package/types/src/system/index.d.ts +0 -7
- package/types/src/system/retry.decorator.d.ts +0 -6
- package/types/src/system/sleep.d.ts +0 -1
- package/types/src/system/type.utils.d.ts +0 -1
- /package/{es6/src/collections/collections.js → src/collections/collections.ts} +0 -0
- /package/{es6/src/collections/index.js → src/collections/index.ts} +0 -0
- /package/{es6/src/configuration/index.js → src/configuration/index.ts} +0 -0
- /package/{es6/src/extensibility/index.js → src/extensibility/index.ts} +0 -0
- /package/{es6/src/io/index.js → src/io/index.ts} +0 -0
- /package/{es6/src/io/types/index.js → src/io/types/index.ts} +0 -0
- /package/{es6/src/objects/index.js → src/objects/index.ts} +0 -0
- /package/{es6/src/reflection/types/index.js → src/reflection/types/index.ts} +0 -0
- /package/{es6/src/system/index.js → src/system/index.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,24 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decohere/core",
|
|
3
|
-
"version": "0.0.1-dev.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.1-dev.5",
|
|
4
|
+
"description": "Core utility library — collections, caching, IO, CLI primitives, reflection, and composition",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
7
7
|
"utilities",
|
|
8
|
-
"
|
|
9
|
-
"composition",
|
|
8
|
+
"collections",
|
|
10
9
|
"caching",
|
|
10
|
+
"io",
|
|
11
|
+
"cli",
|
|
12
|
+
"reflection",
|
|
11
13
|
"decohere"
|
|
12
14
|
],
|
|
13
|
-
"homepage": "https://github.com/typesmiths/decohere/tree/main/projects/core#readme",
|
|
14
|
-
"bugs": {
|
|
15
|
-
"url": "https://github.com/typesmiths/decohere/issues"
|
|
16
|
-
},
|
|
17
|
-
"repository": {
|
|
18
|
-
"type": "git",
|
|
19
|
-
"url": "git+https://github.com/typesmiths/decohere.git",
|
|
20
|
-
"directory": "projects/core"
|
|
21
|
-
},
|
|
22
15
|
"license": "ISC",
|
|
23
16
|
"author": "Typesmiths",
|
|
24
17
|
"type": "module",
|
|
@@ -44,6 +37,11 @@
|
|
|
44
37
|
"import": "./es6/src/extensibility/index.js",
|
|
45
38
|
"require": "./commonjs/src/extensibility/index.js"
|
|
46
39
|
},
|
|
40
|
+
"./interop": {
|
|
41
|
+
"types": "./types/src/interop/index.d.ts",
|
|
42
|
+
"import": "./es6/src/interop/index.js",
|
|
43
|
+
"require": "./commonjs/src/interop/index.js"
|
|
44
|
+
},
|
|
47
45
|
"./io": {
|
|
48
46
|
"types": "./types/src/io/index.d.ts",
|
|
49
47
|
"import": "./es6/src/io/index.js",
|
|
@@ -64,18 +62,45 @@
|
|
|
64
62
|
"import": "./es6/src/system/index.js",
|
|
65
63
|
"require": "./commonjs/src/system/index.js"
|
|
66
64
|
},
|
|
65
|
+
"./semver": {
|
|
66
|
+
"types": "./types/src/semver/index.d.ts",
|
|
67
|
+
"import": "./es6/src/semver/index.js",
|
|
68
|
+
"require": "./commonjs/src/semver/index.js"
|
|
69
|
+
},
|
|
70
|
+
"./testing": {
|
|
71
|
+
"types": "./types/src/testing/index.d.ts",
|
|
72
|
+
"import": "./es6/src/testing/index.js",
|
|
73
|
+
"require": "./commonjs/src/testing/index.js"
|
|
74
|
+
},
|
|
67
75
|
"./composition": {
|
|
68
76
|
"types": "./types/src/composition/index.d.ts",
|
|
69
77
|
"import": "./es6/src/composition/index.js",
|
|
70
78
|
"require": "./commonjs/src/composition/index.js"
|
|
79
|
+
},
|
|
80
|
+
"./cli": {
|
|
81
|
+
"types": "./types/src/cli/index.d.ts",
|
|
82
|
+
"import": "./es6/src/cli/index.js",
|
|
83
|
+
"require": "./commonjs/src/cli/index.js"
|
|
84
|
+
},
|
|
85
|
+
"./diagnostics": {
|
|
86
|
+
"types": "./types/src/diagnostics/index.d.ts",
|
|
87
|
+
"import": "./es6/src/diagnostics/index.js",
|
|
88
|
+
"require": "./commonjs/src/diagnostics/index.js"
|
|
71
89
|
}
|
|
72
90
|
},
|
|
73
91
|
"scripts": {
|
|
74
92
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
75
93
|
},
|
|
76
94
|
"dependencies": {
|
|
77
|
-
"@
|
|
95
|
+
"@cacheable/node-cache": "^2.0.1",
|
|
96
|
+
"@decohere/scribe": "0.0.1-dev.5",
|
|
97
|
+
"commander": "^13.1.0",
|
|
98
|
+
"glob": "^13.0.1",
|
|
99
|
+
"picomatch": "^4.0.4",
|
|
78
100
|
"reflect-metadata": "^0.2.2"
|
|
79
101
|
},
|
|
80
|
-
"devDependencies": {
|
|
102
|
+
"devDependencies": {
|
|
103
|
+
"memfs": "^4.17.0",
|
|
104
|
+
"vitest": "^3.2.6"
|
|
105
|
+
}
|
|
81
106
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { ICache } from "./cache.js";
|
|
2
|
+
import { NodeCacheAdapter } from "./node-cache-memoize.adapter.js";
|
|
3
|
+
|
|
4
|
+
export const Caches = {
|
|
5
|
+
default: "DEFAULT",
|
|
6
|
+
memoize: "MEMOIZE_CACHE",
|
|
7
|
+
} as const;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Central cache manager for managing named cache instances
|
|
11
|
+
*/
|
|
12
|
+
class CacheManager {
|
|
13
|
+
private caches = new Map<string, ICache>();
|
|
14
|
+
private readonly defaultCacheName = Caches.default;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Set a cache instance with the given name
|
|
18
|
+
* @param name - The name of the cache
|
|
19
|
+
* @param cache - The cache instance
|
|
20
|
+
*/
|
|
21
|
+
public setCache(name: string, cache: ICache): void {
|
|
22
|
+
this.caches.set(name, cache);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get a cache instance by name
|
|
27
|
+
* @param name - The name of the cache, defaults to "DEFAULT"
|
|
28
|
+
* @returns The cache instance
|
|
29
|
+
*/
|
|
30
|
+
public getCache(name?: string, memoize: boolean = false): ICache {
|
|
31
|
+
const cacheName = name ?? this.defaultCacheName;
|
|
32
|
+
|
|
33
|
+
if (!this.caches.has(cacheName)) {
|
|
34
|
+
const cache = NodeCacheAdapter.create({ enableMemoization: memoize });
|
|
35
|
+
this.caches.set(cacheName, cache);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return this.caches.get(cacheName);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Clear a cache by name. If no name is provided, clears the default cache.
|
|
43
|
+
* Note: This only works if the cache implements a clear() method.
|
|
44
|
+
* @param name - The name of the cache to clear
|
|
45
|
+
*/
|
|
46
|
+
public flush(name?: string): void {
|
|
47
|
+
const cacheName = name ?? this.defaultCacheName;
|
|
48
|
+
const cache = this.caches.get(cacheName);
|
|
49
|
+
cache.flushAll();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public flushAllCaches(): void {
|
|
53
|
+
for (const [, cache] of this.caches) {
|
|
54
|
+
cache.flushAll();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Remove a cache instance by name
|
|
60
|
+
* @param name - The name of the cache to remove
|
|
61
|
+
*/
|
|
62
|
+
public removeCache(name: string): void {
|
|
63
|
+
this.caches.delete(name);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check if a cache with the given name exists
|
|
68
|
+
* @param name - The name of the cache
|
|
69
|
+
* @returns True if the cache exists, false otherwise
|
|
70
|
+
*/
|
|
71
|
+
public hasCache(name: string): boolean {
|
|
72
|
+
return this.caches.has(name);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private readonly instanceIds = new WeakMap<object, number>();
|
|
76
|
+
private instanceIdCounter = 0;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Generate a cache key for a given instance, function name, and arguments.
|
|
80
|
+
* Each instance receives a stable numeric ID so cache entries are never
|
|
81
|
+
* shared across instances of the same class.
|
|
82
|
+
* @param instance - The class instance (`this` at call time)
|
|
83
|
+
* @param functionName - The name of the decorated method
|
|
84
|
+
* @param args - The method arguments
|
|
85
|
+
* @returns A unique cache key string
|
|
86
|
+
*/
|
|
87
|
+
public generateCacheKey(instance: object, functionName: string, args: unknown[]): string {
|
|
88
|
+
if (!this.instanceIds.has(instance)) {
|
|
89
|
+
this.instanceIds.set(instance, ++this.instanceIdCounter);
|
|
90
|
+
}
|
|
91
|
+
const instanceId = this.instanceIds.get(instance);
|
|
92
|
+
const argsStr = JSON.stringify({ args }, (_key, val) =>
|
|
93
|
+
typeof val === "function" ? `[Function:${val.name}]` : val,
|
|
94
|
+
);
|
|
95
|
+
return `${instance.constructor.name}#${instanceId}|${functionName}|${argsStr}`;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Singleton instance of the cache manager
|
|
101
|
+
*/
|
|
102
|
+
export const Caching = new CacheManager();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { isPromise } from "node:util/types";
|
|
2
|
+
import { FunctionDecorator, ignoreIf } from "../system/decorator.js";
|
|
3
|
+
import type { ICache } from "./cache.js";
|
|
4
|
+
import { Caching } from "./cache-manager.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Options for configuring the @cache() decorator
|
|
8
|
+
*/
|
|
9
|
+
export interface CacheOptions {
|
|
10
|
+
cache?: ICache;
|
|
11
|
+
name?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Custom key function. When provided, replaces the default instance-scoped key
|
|
14
|
+
* with a caller-defined key, enabling cross-instance cache sharing.
|
|
15
|
+
*/
|
|
16
|
+
keyFn?: (...args: unknown[]) => string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class CacheDecorator extends FunctionDecorator<CacheOptions> {
|
|
20
|
+
@ignoreIf("FORGE_DISABLE_CACHE")
|
|
21
|
+
public decorator(_target: object, propertyKey: string, descriptor: PropertyDescriptor) {
|
|
22
|
+
const cacheInstance = this.options?.cache ?? Caching.getCache(this.options?.name);
|
|
23
|
+
const keyFn = this.options?.keyFn;
|
|
24
|
+
const originalMethod = descriptor.value;
|
|
25
|
+
|
|
26
|
+
const wrapped = function (...args: unknown[]) {
|
|
27
|
+
const cacheKey = keyFn
|
|
28
|
+
? keyFn(...args)
|
|
29
|
+
: Caching.generateCacheKey(this, propertyKey, args);
|
|
30
|
+
if (cacheInstance.has(cacheKey)) {
|
|
31
|
+
const val = cacheInstance.get(cacheKey);
|
|
32
|
+
return val;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const resultOrPromise = originalMethod.apply(this, args);
|
|
36
|
+
if (isPromise(resultOrPromise)) {
|
|
37
|
+
resultOrPromise.then(
|
|
38
|
+
(result) => {
|
|
39
|
+
cacheInstance.set(cacheKey, result);
|
|
40
|
+
},
|
|
41
|
+
() => {
|
|
42
|
+
/* rejection propagates via the returned promise */
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
} else {
|
|
46
|
+
cacheInstance.set(cacheKey, resultOrPromise);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return resultOrPromise;
|
|
50
|
+
};
|
|
51
|
+
Object.defineProperty(wrapped, "name", { value: propertyKey });
|
|
52
|
+
descriptor.value = wrapped;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const cache = new CacheDecorator().decoratorFactory;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface for cache implementations used by the @cache() decorator
|
|
3
|
+
*/
|
|
4
|
+
export interface ICache {
|
|
5
|
+
get(key: string): unknown;
|
|
6
|
+
set(key: string, value: unknown): void;
|
|
7
|
+
has(key: string): boolean;
|
|
8
|
+
del(key: string): void;
|
|
9
|
+
flushAll();
|
|
10
|
+
get isMemoize(): boolean;
|
|
11
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { isPromise } from "node:util/types";
|
|
2
|
+
import { FunctionDecorator, ignoreIf } from "../system/decorator.js";
|
|
3
|
+
import type { CacheOptions } from "./cache.decorator.js";
|
|
4
|
+
import { Caches, Caching } from "./cache-manager.js";
|
|
5
|
+
|
|
6
|
+
type InvalidateCacheOptions = CacheOptions & {
|
|
7
|
+
getKey: (instance: object, args: unknown[]) => string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export class InvalidateCacheDecorator extends FunctionDecorator<InvalidateCacheOptions> {
|
|
11
|
+
@ignoreIf("FORGE_DISABLE_CACHE")
|
|
12
|
+
public decorator(_target: object, _propertyKey: string, descriptor: PropertyDescriptor) {
|
|
13
|
+
const { getKey, cache, name } = this.options;
|
|
14
|
+
const cacheInstance = cache ?? Caching.getCache(name);
|
|
15
|
+
const originalMethod = descriptor.value;
|
|
16
|
+
|
|
17
|
+
descriptor.value = function (...args: unknown[]) {
|
|
18
|
+
const resultOrPromise = originalMethod.apply(this, args);
|
|
19
|
+
const evict = () => {
|
|
20
|
+
const key = getKey(this, args);
|
|
21
|
+
cacheInstance.del(key);
|
|
22
|
+
};
|
|
23
|
+
if (isPromise(resultOrPromise)) {
|
|
24
|
+
resultOrPromise.then(evict);
|
|
25
|
+
} else {
|
|
26
|
+
evict();
|
|
27
|
+
}
|
|
28
|
+
return resultOrPromise;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const buildGetKey = (
|
|
34
|
+
method: string | ((...args: never[]) => unknown),
|
|
35
|
+
keyFn?: (...args: unknown[]) => string,
|
|
36
|
+
) => {
|
|
37
|
+
if (typeof method === "string") return () => method;
|
|
38
|
+
if (keyFn) return (_instance: object, args: unknown[]) => keyFn(...args);
|
|
39
|
+
return (instance: object, args: unknown[]) =>
|
|
40
|
+
Caching.generateCacheKey(instance, method.name, args);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const invalidateCache = (
|
|
44
|
+
method: string | ((...args: never[]) => unknown),
|
|
45
|
+
options?: CacheOptions,
|
|
46
|
+
) => {
|
|
47
|
+
const cache = options?.cache ?? Caching.getCache(options?.name);
|
|
48
|
+
return new InvalidateCacheDecorator().decoratorFactory({
|
|
49
|
+
getKey: buildGetKey(method, options?.keyFn),
|
|
50
|
+
...options,
|
|
51
|
+
cache,
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
invalidateCache.evict = (
|
|
55
|
+
instance: object,
|
|
56
|
+
method: (...args: never[]) => unknown,
|
|
57
|
+
args: unknown[] = [],
|
|
58
|
+
) => {
|
|
59
|
+
const key = Caching.generateCacheKey(instance, method.name, args);
|
|
60
|
+
Caching.getCache().del(key);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const invalidateMemoize = (
|
|
64
|
+
method: string | ((...args: never[]) => unknown),
|
|
65
|
+
options?: CacheOptions,
|
|
66
|
+
) => {
|
|
67
|
+
const cache = options?.cache ?? Caching.getCache(options?.name ?? Caches.memoize, true);
|
|
68
|
+
return new InvalidateCacheDecorator().decoratorFactory({
|
|
69
|
+
getKey: buildGetKey(method),
|
|
70
|
+
...options,
|
|
71
|
+
cache,
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
invalidateMemoize.evict = (
|
|
75
|
+
instance: object,
|
|
76
|
+
method: (...args: never[]) => unknown,
|
|
77
|
+
args: unknown[] = [],
|
|
78
|
+
) => {
|
|
79
|
+
const key = Caching.generateCacheKey(instance, method.name, args);
|
|
80
|
+
Caching.getCache(Caches.memoize, true).del(key);
|
|
81
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { CacheDecorator } from "./cache.decorator.js";
|
|
2
|
+
import { Caches, Caching } from "./cache-manager.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Memoize decorator class that extends CacheDecorator with memoization support
|
|
6
|
+
* Automatically uses a memoization-capable cache (NodeCache with useClones: false)
|
|
7
|
+
*/
|
|
8
|
+
export class MemoizeDecorator extends CacheDecorator {
|
|
9
|
+
public decorator(target: object, propertyKey: string, descriptor: PropertyDescriptor) {
|
|
10
|
+
const cache =
|
|
11
|
+
this.options?.cache ?? Caching.getCache(this.options?.name ?? Caches.memoize, true);
|
|
12
|
+
if (!cache.isMemoize) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
"Memoize decorator requires a cache that supports memoization (stores references, not clones)",
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Pass the resolved cache to the base class by updating options
|
|
19
|
+
// This ensures the base class uses CacheNames.memoize when no cache was provided
|
|
20
|
+
this.options = { ...this.options, cache };
|
|
21
|
+
|
|
22
|
+
return super.decorator(target, propertyKey, descriptor);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Memoize decorator factory
|
|
28
|
+
* Provides true memoization by storing object references instead of clones
|
|
29
|
+
*
|
|
30
|
+
* @param options - Optional configuration for the memoize decorator
|
|
31
|
+
* @returns Decorator function
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
export const memoize = new MemoizeDecorator().decoratorFactory;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import NodeCache from "@cacheable/node-cache";
|
|
2
|
+
import type { ICache } from "./cache.js";
|
|
3
|
+
|
|
4
|
+
type NodeCacheMemoizeOptions = {
|
|
5
|
+
enableMemoization: boolean;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* NodeCache adapter configured for memoization (useClones: false)
|
|
10
|
+
* This allows storing object references instead of clones for true memoization
|
|
11
|
+
*/
|
|
12
|
+
export class NodeCacheAdapter implements ICache {
|
|
13
|
+
public static create(options: NodeCacheMemoizeOptions = { enableMemoization: false }) {
|
|
14
|
+
const nodeCache = new NodeCache({ useClones: !options.enableMemoization });
|
|
15
|
+
return new NodeCacheAdapter(nodeCache);
|
|
16
|
+
}
|
|
17
|
+
public readonly supportsMemoization = true as const;
|
|
18
|
+
private cache: NodeCache<unknown>;
|
|
19
|
+
|
|
20
|
+
public constructor(inst: NodeCache<unknown> = new NodeCache()) {
|
|
21
|
+
this.cache = inst;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public get isMemoize() {
|
|
25
|
+
return !this.cache.options.useClones;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public get(key: string): unknown {
|
|
29
|
+
return this.cache.get(key);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public set(key: string, value: unknown): void {
|
|
33
|
+
this.cache.set(key, value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public has(key: string): boolean {
|
|
37
|
+
return this.cache.has(key);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public del(key: string): void {
|
|
41
|
+
this.cache.del(key);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public flushAll(): void {
|
|
45
|
+
this.cache.flushAll();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Constructable } from "../reflection/index.js";
|
|
2
|
+
import type { UnionToIntersection } from "../system/type.utils.js";
|
|
3
|
+
|
|
4
|
+
export const ActionMap = Symbol("ActionMap");
|
|
5
|
+
|
|
6
|
+
export type ActionClassMap = Record<string, Constructable<Action>>;
|
|
7
|
+
|
|
8
|
+
export interface ResultStatus {
|
|
9
|
+
succeeded: boolean;
|
|
10
|
+
error?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface Result {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly succeeded: boolean;
|
|
16
|
+
readonly skipped: boolean;
|
|
17
|
+
readonly message: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class ActionResponse<TData = unknown> implements Result {
|
|
21
|
+
public static Success<TData>(name: string, data?: TData[]) {
|
|
22
|
+
return new ActionResponse<TData>({ name, data, succeeded: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public static Skipped(name: string) {
|
|
26
|
+
return new ActionResponse({ name, skipped: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public static Blocking(name: string) {
|
|
30
|
+
return new ActionResponse({ name, succeeded: true, blocking: true });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public static Error<TData = unknown>(name: string, message: string): ActionResponse<TData>;
|
|
34
|
+
public static Error<TData extends ResultStatus>(
|
|
35
|
+
name: string,
|
|
36
|
+
data: TData[],
|
|
37
|
+
): ActionResponse<TData>;
|
|
38
|
+
public static Error<TData extends ResultStatus>(
|
|
39
|
+
name: string,
|
|
40
|
+
messageOrData: string | TData[],
|
|
41
|
+
data?: TData[],
|
|
42
|
+
) {
|
|
43
|
+
if (Array.isArray(messageOrData)) {
|
|
44
|
+
const message = messageOrData
|
|
45
|
+
.filter((r) => !r.succeeded)
|
|
46
|
+
.map((r) => r.error ?? "Unknown error")
|
|
47
|
+
.join("\n");
|
|
48
|
+
return new ActionResponse<TData>({ name, message, data: messageOrData });
|
|
49
|
+
}
|
|
50
|
+
return new ActionResponse<TData>({ name, message: messageOrData, data });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public constructor(data: Partial<ActionResponse<TData>>) {
|
|
54
|
+
Object.assign(this, data);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public readonly name: string;
|
|
58
|
+
public readonly succeeded: boolean = false;
|
|
59
|
+
public readonly skipped: boolean = false;
|
|
60
|
+
public readonly blocking: boolean = false;
|
|
61
|
+
public readonly message: string;
|
|
62
|
+
public readonly data: TData[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface Action<T extends object = object> {
|
|
66
|
+
run(options: T): Promise<ActionResponse>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type Actions = Record<string, Action>;
|
|
70
|
+
|
|
71
|
+
export type ActionOptions<A extends Action> = A extends Action<infer T> ? T : never;
|
|
72
|
+
|
|
73
|
+
export type MergeOptions<TActions extends Record<string, Action>> = UnionToIntersection<
|
|
74
|
+
{
|
|
75
|
+
[K in keyof TActions]: ActionOptions<TActions[K]>;
|
|
76
|
+
}[keyof TActions]
|
|
77
|
+
>;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { Command as CommanderType, type OptionValues } from "commander";
|
|
2
|
+
import type { Constructable } from "../reflection/index.js";
|
|
3
|
+
import type { ActionResponse, Actions } from "./action.js";
|
|
4
|
+
|
|
5
|
+
export type CommandType = Constructable<DecohereCommand>;
|
|
6
|
+
|
|
7
|
+
export type CommandHandler = (opts: OptionValues, cmd: DecohereCommand) => Promise<void>;
|
|
8
|
+
|
|
9
|
+
export type CommandResponse<TActions extends Actions = Actions> = {
|
|
10
|
+
[ActionName in keyof TActions]?: ActionResponse;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const CommandResponse = {
|
|
14
|
+
success<T extends Actions>(
|
|
15
|
+
_ctx: unknown,
|
|
16
|
+
responses: CommandResponse<T>,
|
|
17
|
+
): CommandResponse<T> {
|
|
18
|
+
return responses;
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
help<T extends Actions = Actions>(): CommandResponse<T> {
|
|
22
|
+
return {} as CommandResponse<T>;
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
fromAction(name: string, response: ActionResponse): CommandResponse {
|
|
26
|
+
return { [name]: response };
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Thrown when a command execution fails due to action errors. */
|
|
31
|
+
export class CommandError extends Error {}
|
|
32
|
+
|
|
33
|
+
export class DecohereCommand<TActions extends Actions = Actions> extends CommanderType {
|
|
34
|
+
public get actions(): readonly Constructable<import("./action.js").Action>[] {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public constructor(
|
|
39
|
+
name?: string | Constructable | Record<string, CommandType>,
|
|
40
|
+
handler?: CommandHandler,
|
|
41
|
+
) {
|
|
42
|
+
super();
|
|
43
|
+
if (typeof name === "string" || typeof name === "function") {
|
|
44
|
+
this.name(this.parseCommandName(name));
|
|
45
|
+
} else {
|
|
46
|
+
this.name(this.parseCommandName(this.constructor as unknown as Constructable));
|
|
47
|
+
if (name) {
|
|
48
|
+
this.registerSubcommand(name, handler);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (handler && !this.commands.length) {
|
|
52
|
+
this.action(handler);
|
|
53
|
+
}
|
|
54
|
+
this.exitOverride();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Whether the command, with these options, runs until it is stopped (`flux serve`,
|
|
59
|
+
* `forge build --watch`). The CLI then listens for SIGINT and SIGTERM from the moment it
|
|
60
|
+
* starts, so a stop during start-up still shuts down cleanly; other commands keep Node's
|
|
61
|
+
* default, where Ctrl+C ends them at once.
|
|
62
|
+
*/
|
|
63
|
+
public runsUntilStopped(_options: OptionValues): boolean {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public async run(
|
|
68
|
+
actions: TActions,
|
|
69
|
+
options: OptionValues,
|
|
70
|
+
): Promise<CommandResponse<TActions>> {
|
|
71
|
+
const responses = await this.runActions(actions, options);
|
|
72
|
+
this.throwOnErrors(responses);
|
|
73
|
+
return CommandResponse.success(options, responses);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public registerSubcommand(command: CommandType, onAction?: CommandHandler): void;
|
|
77
|
+
public registerSubcommand(
|
|
78
|
+
commands: Record<string, DecohereCommand | CommandType>,
|
|
79
|
+
onAction?: CommandHandler,
|
|
80
|
+
): void;
|
|
81
|
+
public registerSubcommand(
|
|
82
|
+
commands: CommandType | Record<string, DecohereCommand | CommandType>,
|
|
83
|
+
onAction?: CommandHandler,
|
|
84
|
+
): void {
|
|
85
|
+
if (typeof commands === "function") {
|
|
86
|
+
const instance = new commands(commands, onAction);
|
|
87
|
+
this.addCommand(instance);
|
|
88
|
+
if (onAction && !instance.commands.length) {
|
|
89
|
+
instance.action(onAction);
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const [key, command] of Object.entries(commands)) {
|
|
95
|
+
const instance =
|
|
96
|
+
command instanceof DecohereCommand ? command : new command(key, onAction);
|
|
97
|
+
this.addCommand(instance);
|
|
98
|
+
if (onAction && !instance.commands.length) {
|
|
99
|
+
instance.action(onAction);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
protected async runActions(
|
|
105
|
+
actions: TActions,
|
|
106
|
+
options: OptionValues,
|
|
107
|
+
): Promise<CommandResponse<TActions>> {
|
|
108
|
+
const selectedActions = this.getSelectedActions(actions);
|
|
109
|
+
const responses = {} as CommandResponse<TActions>;
|
|
110
|
+
for (const [actionKey, action] of selectedActions) {
|
|
111
|
+
responses[actionKey] = (await action.run(
|
|
112
|
+
options,
|
|
113
|
+
)) as CommandResponse<TActions>[keyof TActions];
|
|
114
|
+
}
|
|
115
|
+
return responses as CommandResponse<TActions>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
protected getSelectedActions(
|
|
119
|
+
actions: TActions,
|
|
120
|
+
): Array<[keyof TActions, TActions[keyof TActions]]> {
|
|
121
|
+
if (!this.actions.length) {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const actionEntries = Object.entries(actions) as Array<
|
|
126
|
+
[keyof TActions, TActions[keyof TActions]]
|
|
127
|
+
>;
|
|
128
|
+
return this.actions.map((ActionClass) => {
|
|
129
|
+
const selectedAction = actionEntries.find(([, action]) => action instanceof ActionClass);
|
|
130
|
+
if (!selectedAction) {
|
|
131
|
+
throw new CommandError(
|
|
132
|
+
`${this.name()} command missing action instance for ${ActionClass.name}`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return selectedAction;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
protected throwOnErrors(responses: CommandResponse<TActions>): void {
|
|
141
|
+
const failures = Object.entries(responses).filter(([, response]) => {
|
|
142
|
+
if (!response) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return !response.succeeded && !response.skipped;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (!failures.length) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
throw new CommandError(
|
|
154
|
+
`${this.name()} command failed. ${failures
|
|
155
|
+
.map(
|
|
156
|
+
([actionName, response]) => `${actionName}: ${response?.message ?? "Unknown error"}`,
|
|
157
|
+
)
|
|
158
|
+
.join("\n")}`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private parseCommandName(stringLike: string | Constructable) {
|
|
163
|
+
const command =
|
|
164
|
+
typeof stringLike === "string"
|
|
165
|
+
? stringLike
|
|
166
|
+
: (stringLike.name?.toLowerCase().replace("command", "") ?? "");
|
|
167
|
+
|
|
168
|
+
if (command === "") {
|
|
169
|
+
throw new CommandError(`Could not parse command name from ${stringLike.toString()}`);
|
|
170
|
+
}
|
|
171
|
+
return command;
|
|
172
|
+
}
|
|
173
|
+
}
|