@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
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { vi } from "vitest";
|
|
2
|
+
import { cache } from "../../src/caching/cache.decorator.js";
|
|
3
|
+
|
|
4
|
+
// Simple cache implementation for testing
|
|
5
|
+
class MockCache {
|
|
6
|
+
public get isMemoize(): boolean {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
private data = new Map<string, unknown>();
|
|
10
|
+
|
|
11
|
+
public get(key: string): unknown {
|
|
12
|
+
return this.data.get(key);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public set(key: string, value: unknown): void {
|
|
16
|
+
this.data.set(key, value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public has(key: string): boolean {
|
|
20
|
+
return this.data.has(key);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public del(key: string): void {
|
|
24
|
+
this.data.delete(key);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public flushAll(): void {
|
|
28
|
+
this.data.clear();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const CACHE = new MockCache();
|
|
33
|
+
|
|
34
|
+
export class CacheTestClass {
|
|
35
|
+
public callCount = 0;
|
|
36
|
+
public counter = vi.fn();
|
|
37
|
+
|
|
38
|
+
@cache({ cache: CACHE })
|
|
39
|
+
public syncMethod(value: number): number {
|
|
40
|
+
this.callCount++;
|
|
41
|
+
this.counter();
|
|
42
|
+
return value * 2;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@cache({ cache: CACHE })
|
|
46
|
+
public async asyncMethod(value: number): Promise<number> {
|
|
47
|
+
this.callCount++;
|
|
48
|
+
this.counter();
|
|
49
|
+
return value * 3;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@cache({ cache: CACHE })
|
|
53
|
+
public multiArgMethod(arg1: number, arg2: number): number {
|
|
54
|
+
this.callCount++;
|
|
55
|
+
this.counter();
|
|
56
|
+
return arg1 + arg2;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@cache({ cache: CACHE })
|
|
60
|
+
public undefinedMethod(): undefined {
|
|
61
|
+
this.callCount++;
|
|
62
|
+
this.counter();
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@cache({ cache: CACHE })
|
|
67
|
+
public falsyMethod(): number {
|
|
68
|
+
this.callCount++;
|
|
69
|
+
this.counter();
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@cache({ name: "TEST_CACHE" })
|
|
74
|
+
public namedCacheMethod(value: number): number {
|
|
75
|
+
this.callCount++;
|
|
76
|
+
this.counter();
|
|
77
|
+
return value * 5;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@cache()
|
|
81
|
+
public defaultOptionsMethod(value: string): string {
|
|
82
|
+
this.callCount++;
|
|
83
|
+
this.counter();
|
|
84
|
+
return `test-${value}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public reset(): void {
|
|
88
|
+
this.callCount = 0;
|
|
89
|
+
this.counter.mockReset();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { Caching } from "../../src/caching/cache-manager.js";
|
|
4
|
+
import { pathToFileUrl } from "../../src/io/url-to-path.js";
|
|
5
|
+
import { CACHE } from "./cache-test-class.js";
|
|
6
|
+
|
|
7
|
+
// Test class import path constant
|
|
8
|
+
const CACHE_TEST_CLASS_PATH = "./cache-test-class.js";
|
|
9
|
+
|
|
10
|
+
// Function-based import URL for easier refactoring
|
|
11
|
+
const importUrl = () => getImportUrl(CACHE_TEST_CLASS_PATH);
|
|
12
|
+
|
|
13
|
+
// Set up the test cache in Caching manager
|
|
14
|
+
Caching.setCache("TEST_CACHE", CACHE);
|
|
15
|
+
|
|
16
|
+
// Helper function to create a versioned import URL for cache busting
|
|
17
|
+
function getImportUrl(filename: string): string {
|
|
18
|
+
const modulePath = path.resolve(__dirname, filename);
|
|
19
|
+
const moduleUrl = pathToFileUrl(modulePath);
|
|
20
|
+
return `${moduleUrl}?version=${Date.now()}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("cache decorator", () => {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
vi.resetAllMocks();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
Caching.flushAllCaches();
|
|
30
|
+
CACHE.flushAll();
|
|
31
|
+
delete process.env.FORGE_DISABLE_CACHE;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("FORGE_DISABLE_CACHE environment variable behavior", () => {
|
|
35
|
+
it("should cache normally by default", async () => {
|
|
36
|
+
const { CacheTestClass } = await import(importUrl());
|
|
37
|
+
const instance = new CacheTestClass();
|
|
38
|
+
|
|
39
|
+
const result1 = instance.syncMethod(5);
|
|
40
|
+
const result2 = instance.syncMethod(5);
|
|
41
|
+
|
|
42
|
+
expect(result1).toBe(10);
|
|
43
|
+
expect(result2).toBe(10);
|
|
44
|
+
expect(instance.callCount).toBe(1); // Should be cached
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should disable caching when FORGE_DISABLE_CACHE=true", async () => {
|
|
48
|
+
process.env.FORGE_DISABLE_CACHE = "true";
|
|
49
|
+
|
|
50
|
+
const { CacheTestClass } = await import(importUrl());
|
|
51
|
+
const instance = new CacheTestClass();
|
|
52
|
+
|
|
53
|
+
const result1 = instance.syncMethod(5);
|
|
54
|
+
const result2 = instance.syncMethod(5);
|
|
55
|
+
|
|
56
|
+
expect(result1).toBe(10);
|
|
57
|
+
expect(result2).toBe(10);
|
|
58
|
+
expect(instance.callCount).toBe(2); // Should NOT be cached
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should disable caching for async methods when FORGE_DISABLE_CACHE=true", async () => {
|
|
62
|
+
process.env.FORGE_DISABLE_CACHE = "true";
|
|
63
|
+
|
|
64
|
+
const { CacheTestClass } = await import(importUrl());
|
|
65
|
+
const instance = new CacheTestClass();
|
|
66
|
+
|
|
67
|
+
const result1 = await instance.asyncMethod(5);
|
|
68
|
+
const result2 = await instance.asyncMethod(5);
|
|
69
|
+
|
|
70
|
+
expect(result1).toBe(15);
|
|
71
|
+
expect(result2).toBe(15);
|
|
72
|
+
expect(instance.callCount).toBe(2); // Should NOT be cached
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should not interact with cache when disabled", async () => {
|
|
76
|
+
process.env.FORGE_DISABLE_CACHE = "true";
|
|
77
|
+
|
|
78
|
+
const setSpy = vi.spyOn(CACHE, "set");
|
|
79
|
+
const getSpy = vi.spyOn(CACHE, "get");
|
|
80
|
+
const hasSpy = vi.spyOn(CACHE, "has");
|
|
81
|
+
|
|
82
|
+
const { CacheTestClass } = await import(importUrl());
|
|
83
|
+
const instance = new CacheTestClass();
|
|
84
|
+
|
|
85
|
+
instance.defaultOptionsMethod("value");
|
|
86
|
+
instance.defaultOptionsMethod("value");
|
|
87
|
+
|
|
88
|
+
expect(setSpy).not.toHaveBeenCalled();
|
|
89
|
+
expect(getSpy).not.toHaveBeenCalled();
|
|
90
|
+
expect(hasSpy).not.toHaveBeenCalled();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("synchronous methods", () => {
|
|
95
|
+
it("should cache sync method results", async () => {
|
|
96
|
+
const { CacheTestClass } = await import(importUrl());
|
|
97
|
+
const instance = new CacheTestClass();
|
|
98
|
+
|
|
99
|
+
const result1 = instance.syncMethod(5);
|
|
100
|
+
const result2 = instance.syncMethod(5);
|
|
101
|
+
|
|
102
|
+
expect(result1).toBe(10);
|
|
103
|
+
expect(result2).toBe(10);
|
|
104
|
+
expect(instance.callCount).toBe(1);
|
|
105
|
+
expect(instance.counter).toHaveBeenCalledTimes(1);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should cache different arguments separately", async () => {
|
|
109
|
+
const { CacheTestClass } = await import(importUrl());
|
|
110
|
+
const instance = new CacheTestClass();
|
|
111
|
+
|
|
112
|
+
const result1 = instance.syncMethod(5);
|
|
113
|
+
const result2 = instance.syncMethod(10);
|
|
114
|
+
const result3 = instance.syncMethod(5);
|
|
115
|
+
|
|
116
|
+
expect(result1).toBe(10);
|
|
117
|
+
expect(result2).toBe(20);
|
|
118
|
+
expect(result3).toBe(10);
|
|
119
|
+
expect(instance.callCount).toBe(2); // Called for 5 and 10, but cached for second 5
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should cache multiple arguments", async () => {
|
|
123
|
+
const { CacheTestClass } = await import(importUrl());
|
|
124
|
+
const instance = new CacheTestClass();
|
|
125
|
+
|
|
126
|
+
const result1 = instance.multiArgMethod(3, 4);
|
|
127
|
+
const result2 = instance.multiArgMethod(3, 4);
|
|
128
|
+
const result3 = instance.multiArgMethod(5, 6);
|
|
129
|
+
|
|
130
|
+
expect(result1).toBe(7);
|
|
131
|
+
expect(result2).toBe(7);
|
|
132
|
+
expect(result3).toBe(11);
|
|
133
|
+
expect(instance.callCount).toBe(2); // Called for (3,4) and (5,6)
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("should cache undefined return values", async () => {
|
|
137
|
+
const { CacheTestClass } = await import(importUrl());
|
|
138
|
+
const instance = new CacheTestClass();
|
|
139
|
+
|
|
140
|
+
const result1 = instance.undefinedMethod();
|
|
141
|
+
const result2 = instance.undefinedMethod();
|
|
142
|
+
|
|
143
|
+
expect(result1).toBeUndefined();
|
|
144
|
+
expect(result2).toBeUndefined();
|
|
145
|
+
expect(instance.callCount).toBe(1); // Should be cached
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should cache falsy return values", async () => {
|
|
149
|
+
const { CacheTestClass } = await import(importUrl());
|
|
150
|
+
const instance = new CacheTestClass();
|
|
151
|
+
|
|
152
|
+
const result1 = instance.falsyMethod();
|
|
153
|
+
const result2 = instance.falsyMethod();
|
|
154
|
+
|
|
155
|
+
expect(result1).toBe(0);
|
|
156
|
+
expect(result2).toBe(0);
|
|
157
|
+
expect(instance.callCount).toBe(1); // Should be cached
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("asynchronous methods", () => {
|
|
162
|
+
it("should cache async method results", async () => {
|
|
163
|
+
const { CacheTestClass } = await import(importUrl());
|
|
164
|
+
const instance = new CacheTestClass();
|
|
165
|
+
|
|
166
|
+
const result1 = await instance.asyncMethod(5);
|
|
167
|
+
const result2 = await instance.asyncMethod(5);
|
|
168
|
+
|
|
169
|
+
expect(result1).toBe(15);
|
|
170
|
+
expect(result2).toBe(15);
|
|
171
|
+
expect(instance.callCount).toBe(1);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("should return Promise for async methods", async () => {
|
|
175
|
+
const { CacheTestClass } = await import(importUrl());
|
|
176
|
+
const instance = new CacheTestClass();
|
|
177
|
+
|
|
178
|
+
const result = instance.asyncMethod(5);
|
|
179
|
+
|
|
180
|
+
expect(result).toBeInstanceOf(Promise);
|
|
181
|
+
await result; // Wait for completion
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("should cache different async arguments separately", async () => {
|
|
185
|
+
const { CacheTestClass } = await import(importUrl());
|
|
186
|
+
const instance = new CacheTestClass();
|
|
187
|
+
|
|
188
|
+
const result1 = await instance.asyncMethod(5);
|
|
189
|
+
const result2 = await instance.asyncMethod(10);
|
|
190
|
+
const result3 = await instance.asyncMethod(5);
|
|
191
|
+
|
|
192
|
+
expect(result1).toBe(15);
|
|
193
|
+
expect(result2).toBe(30);
|
|
194
|
+
expect(result3).toBe(15);
|
|
195
|
+
expect(instance.callCount).toBe(2);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe("cache isolation and sharing", () => {
|
|
200
|
+
it("should isolate cache per instance", async () => {
|
|
201
|
+
const { CacheTestClass } = await import(importUrl());
|
|
202
|
+
const instance1 = new CacheTestClass();
|
|
203
|
+
const instance2 = new CacheTestClass();
|
|
204
|
+
|
|
205
|
+
const result1 = instance1.syncMethod(5);
|
|
206
|
+
const result2 = instance2.syncMethod(5);
|
|
207
|
+
|
|
208
|
+
expect(result1).toBe(10);
|
|
209
|
+
expect(result2).toBe(10);
|
|
210
|
+
// Each instance has its own cache entry — both execute the method
|
|
211
|
+
expect(instance1.callCount).toBe(1);
|
|
212
|
+
expect(instance2.callCount).toBe(1);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe("named cache support", () => {
|
|
217
|
+
it("should use named cache from Caching manager", async () => {
|
|
218
|
+
const { CacheTestClass } = await import(importUrl());
|
|
219
|
+
const instance = new CacheTestClass();
|
|
220
|
+
|
|
221
|
+
const result1 = instance.namedCacheMethod(5);
|
|
222
|
+
const result2 = instance.namedCacheMethod(5);
|
|
223
|
+
|
|
224
|
+
expect(result1).toBe(25);
|
|
225
|
+
expect(result2).toBe(25);
|
|
226
|
+
expect(instance.callCount).toBe(1); // Should be cached
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("should clear named cache via Caching manager", async () => {
|
|
230
|
+
const { CacheTestClass } = await import(importUrl());
|
|
231
|
+
const instance = new CacheTestClass();
|
|
232
|
+
|
|
233
|
+
const result1 = instance.namedCacheMethod(5);
|
|
234
|
+
expect(result1).toBe(25);
|
|
235
|
+
expect(instance.callCount).toBe(1);
|
|
236
|
+
|
|
237
|
+
// Clear the cache
|
|
238
|
+
Caching.flushAllCaches();
|
|
239
|
+
|
|
240
|
+
const result2 = instance.namedCacheMethod(5);
|
|
241
|
+
expect(result2).toBe(25);
|
|
242
|
+
expect(instance.callCount).toBe(2); // Should be called again after clear
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe("default cache options", () => {
|
|
247
|
+
it("should work with default options", async () => {
|
|
248
|
+
const { CacheTestClass } = await import(importUrl());
|
|
249
|
+
const instance = new CacheTestClass();
|
|
250
|
+
|
|
251
|
+
const result1 = instance.defaultOptionsMethod("value");
|
|
252
|
+
const result2 = instance.defaultOptionsMethod("value");
|
|
253
|
+
|
|
254
|
+
expect(result1).toBe("test-value");
|
|
255
|
+
expect(result2).toBe("test-value");
|
|
256
|
+
expect(instance.callCount).toBe(1); // Should be cached with default cache
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ICache } from "../../src/caching/cache.js";
|
|
2
|
+
import { invalidateCache, memoize } from "../../src/caching/index.js";
|
|
3
|
+
|
|
4
|
+
class MockCache implements ICache {
|
|
5
|
+
public get isMemoize(): boolean {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
private data = new Map<string, unknown>();
|
|
10
|
+
|
|
11
|
+
public get(key: string): unknown {
|
|
12
|
+
return this.data.get(key);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public set(key: string, value: unknown): void {
|
|
16
|
+
this.data.set(key, value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public has(key: string): boolean {
|
|
20
|
+
return this.data.has(key);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public del(key: string): void {
|
|
24
|
+
this.data.delete(key);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public flushAll(): void {
|
|
28
|
+
this.data.clear();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const TEST_CACHE = new MockCache();
|
|
33
|
+
|
|
34
|
+
export class InvalidateCacheTestClass {
|
|
35
|
+
public callCount = 0;
|
|
36
|
+
|
|
37
|
+
@memoize({ cache: TEST_CACHE })
|
|
38
|
+
public getValue(): string {
|
|
39
|
+
this.callCount++;
|
|
40
|
+
return `value_${this.callCount}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@invalidateCache("getValue", { cache: TEST_CACHE })
|
|
44
|
+
public setValue(_value: string): void {
|
|
45
|
+
// synchronous write — invalidation happens after this returns
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@invalidateCache("getValue", { cache: TEST_CACHE })
|
|
49
|
+
public async setValueAsync(_value: string): Promise<void> {
|
|
50
|
+
// async write — invalidation happens after the promise resolves
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
import type { ICache } from "../../src/caching/cache.js";
|
|
3
|
+
import { Caching } from "../../src/caching/cache-manager.js";
|
|
4
|
+
import { invalidateCache, invalidateMemoize, memoize } from "../../src/caching/index.js";
|
|
5
|
+
|
|
6
|
+
class TestCache implements ICache {
|
|
7
|
+
public get isMemoize() {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
private data = new Map<string, unknown>();
|
|
11
|
+
public get(key: string) {
|
|
12
|
+
return this.data.get(key);
|
|
13
|
+
}
|
|
14
|
+
public set(key: string, value: unknown) {
|
|
15
|
+
this.data.set(key, value);
|
|
16
|
+
}
|
|
17
|
+
public has(key: string) {
|
|
18
|
+
return this.data.has(key);
|
|
19
|
+
}
|
|
20
|
+
public del(key: string) {
|
|
21
|
+
this.data.delete(key);
|
|
22
|
+
}
|
|
23
|
+
public flushAll() {
|
|
24
|
+
this.data.clear();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const TEST_CACHE = new TestCache();
|
|
29
|
+
|
|
30
|
+
// Function reference: apply invalidateCache after class definition so the prototype is available
|
|
31
|
+
class MemoizedClass {
|
|
32
|
+
public callCount = 0;
|
|
33
|
+
|
|
34
|
+
@memoize({ cache: TEST_CACHE })
|
|
35
|
+
public getValue(): string {
|
|
36
|
+
this.callCount++;
|
|
37
|
+
return `value_${this.callCount}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public invalidate(): void {}
|
|
41
|
+
public async invalidateAsync(): Promise<void> {}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const key of ["invalidate", "invalidateAsync"] as const) {
|
|
45
|
+
const desc = Object.getOwnPropertyDescriptor(MemoizedClass.prototype, key)!;
|
|
46
|
+
invalidateMemoize(MemoizedClass.prototype.getValue, { cache: TEST_CACHE })(
|
|
47
|
+
MemoizedClass.prototype,
|
|
48
|
+
key,
|
|
49
|
+
desc,
|
|
50
|
+
);
|
|
51
|
+
Object.defineProperty(MemoizedClass.prototype, key, desc);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// String overload: raw cache key eviction (no generateCacheKey semantics)
|
|
55
|
+
class RawKeyClass {
|
|
56
|
+
@invalidateCache("myRawKey", { cache: TEST_CACHE })
|
|
57
|
+
public clearEntry(): void {}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe("invalidateCache decorator", () => {
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
TEST_CACHE.flushAll();
|
|
63
|
+
Caching.flushAllCaches();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("function reference", () => {
|
|
67
|
+
it("should evict cache for the referenced method after synchronous invalidator runs", () => {
|
|
68
|
+
const instance = new MemoizedClass();
|
|
69
|
+
|
|
70
|
+
instance.getValue(); // callCount = 1, cached
|
|
71
|
+
instance.getValue(); // from cache
|
|
72
|
+
expect(instance.callCount).toBe(1);
|
|
73
|
+
|
|
74
|
+
instance.invalidate();
|
|
75
|
+
|
|
76
|
+
instance.getValue(); // evicted — re-executes
|
|
77
|
+
expect(instance.callCount).toBe(2);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should evict cache for the referenced method after async invalidator resolves", async () => {
|
|
81
|
+
const instance = new MemoizedClass();
|
|
82
|
+
|
|
83
|
+
instance.getValue(); // callCount = 1, cached
|
|
84
|
+
instance.getValue(); // from cache
|
|
85
|
+
expect(instance.callCount).toBe(1);
|
|
86
|
+
|
|
87
|
+
await instance.invalidateAsync();
|
|
88
|
+
|
|
89
|
+
instance.getValue(); // evicted — re-executes
|
|
90
|
+
expect(instance.callCount).toBe(2);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should use the custom cache supplied via options", () => {
|
|
94
|
+
const instance = new MemoizedClass();
|
|
95
|
+
|
|
96
|
+
instance.getValue(); // callCount = 1, cached in TEST_CACHE
|
|
97
|
+
expect(instance.callCount).toBe(1);
|
|
98
|
+
|
|
99
|
+
instance.invalidate(); // evicts TEST_CACHE entry
|
|
100
|
+
|
|
101
|
+
instance.getValue(); // callCount = 2
|
|
102
|
+
expect(instance.callCount).toBe(2);
|
|
103
|
+
|
|
104
|
+
TEST_CACHE.flushAll();
|
|
105
|
+
instance.getValue(); // callCount = 3
|
|
106
|
+
expect(instance.callCount).toBe(3);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("string (raw cache key)", () => {
|
|
111
|
+
it("should delete the exact cache key when passed a string", () => {
|
|
112
|
+
TEST_CACHE.set("myRawKey", "someValue");
|
|
113
|
+
expect(TEST_CACHE.has("myRawKey")).toBe(true);
|
|
114
|
+
|
|
115
|
+
new RawKeyClass().clearEntry();
|
|
116
|
+
|
|
117
|
+
expect(TEST_CACHE.has("myRawKey")).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { vi } from "vitest";
|
|
2
|
+
import { memoize } from "../../src/caching/index.js";
|
|
3
|
+
|
|
4
|
+
// Simple memoization cache implementation for testing
|
|
5
|
+
class MockMemoizeCache {
|
|
6
|
+
public get isMemoize(): boolean {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
private data = new Map<string, unknown>();
|
|
10
|
+
|
|
11
|
+
public get(key: string): unknown {
|
|
12
|
+
return this.data.get(key);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public set(key: string, value: unknown): void {
|
|
16
|
+
this.data.set(key, value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public has(key: string): boolean {
|
|
20
|
+
return this.data.has(key);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public del(key: string): void {
|
|
24
|
+
this.data.delete(key);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public flushAll(): void {
|
|
28
|
+
this.data.clear();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const MEMOIZE_CACHE = new MockMemoizeCache();
|
|
33
|
+
|
|
34
|
+
export class MemoizeTestClass {
|
|
35
|
+
public callCount = 0;
|
|
36
|
+
public counter = vi.fn();
|
|
37
|
+
|
|
38
|
+
public reset(): void {
|
|
39
|
+
this.callCount = 0;
|
|
40
|
+
this.counter.mockClear();
|
|
41
|
+
MEMOIZE_CACHE.flushAll();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@memoize({ cache: MEMOIZE_CACHE })
|
|
45
|
+
public syncMethod(value: number): number {
|
|
46
|
+
this.callCount++;
|
|
47
|
+
this.counter();
|
|
48
|
+
return value * 2;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@memoize({ cache: MEMOIZE_CACHE })
|
|
52
|
+
public async asyncMethod(value: number): Promise<number> {
|
|
53
|
+
this.callCount++;
|
|
54
|
+
this.counter();
|
|
55
|
+
return value * 3;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@memoize({ cache: MEMOIZE_CACHE })
|
|
59
|
+
public multiArgMethod(arg1: number, arg2: number): number {
|
|
60
|
+
this.callCount++;
|
|
61
|
+
this.counter();
|
|
62
|
+
return arg1 + arg2;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@memoize({ cache: MEMOIZE_CACHE })
|
|
66
|
+
public undefinedMethod(): undefined {
|
|
67
|
+
this.callCount++;
|
|
68
|
+
this.counter();
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@memoize({ cache: MEMOIZE_CACHE })
|
|
73
|
+
public falsyMethod(): number {
|
|
74
|
+
this.callCount++;
|
|
75
|
+
this.counter();
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@memoize({ cache: MEMOIZE_CACHE, name: "custom-cache" })
|
|
80
|
+
public namedCacheMethod(value: number): number {
|
|
81
|
+
this.callCount++;
|
|
82
|
+
this.counter();
|
|
83
|
+
return value * 4;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@memoize()
|
|
87
|
+
public defaultCacheMethod(value: number): number {
|
|
88
|
+
this.callCount++;
|
|
89
|
+
this.counter();
|
|
90
|
+
return value * 5;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@memoize()
|
|
94
|
+
public objectMethod(id: number): { id: number; value: number } {
|
|
95
|
+
this.callCount++;
|
|
96
|
+
this.counter();
|
|
97
|
+
return { id, value: id * 10 };
|
|
98
|
+
}
|
|
99
|
+
}
|