@synstack/fs-cache 1.2.4 → 1.4.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/fs-cache.index.cjs +18 -5
- package/dist/fs-cache.index.cjs.map +1 -1
- package/dist/fs-cache.index.d.cts +7 -1
- package/dist/fs-cache.index.d.ts +7 -1
- package/dist/fs-cache.index.js +18 -5
- package/dist/fs-cache.index.js.map +1 -1
- package/package.json +6 -6
- package/src/fs-cache.lib.ts +35 -9
package/dist/fs-cache.index.cjs
CHANGED
@@ -58,7 +58,14 @@ function isPrimitive(obj) {
|
|
58
58
|
var FsCache = class _FsCache {
|
59
59
|
_config;
|
60
60
|
constructor(config) {
|
61
|
-
this._config =
|
61
|
+
this._config = {
|
62
|
+
...config
|
63
|
+
};
|
64
|
+
}
|
65
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
66
|
+
async serializeInput(...args) {
|
67
|
+
if (this._config.signatureFn) return this._config.signatureFn(...args);
|
68
|
+
return args;
|
62
69
|
}
|
63
70
|
static cwd(cwd) {
|
64
71
|
if (cwd instanceof import_fs.FsDir) return new _FsCache({ cwd: cwd.path });
|
@@ -67,6 +74,9 @@ var FsCache = class _FsCache {
|
|
67
74
|
key(key) {
|
68
75
|
return new _FsCache({ ...this._config, key });
|
69
76
|
}
|
77
|
+
signatureFn(signatureFn) {
|
78
|
+
return new _FsCache({ ...this._config, signatureFn });
|
79
|
+
}
|
70
80
|
pretty(pretty) {
|
71
81
|
return new _FsCache({ ...this._config, pretty });
|
72
82
|
}
|
@@ -86,8 +96,8 @@ var FsCache = class _FsCache {
|
|
86
96
|
}
|
87
97
|
const value = await file.read.json();
|
88
98
|
await file.write.json({
|
89
|
-
|
90
|
-
|
99
|
+
...value,
|
100
|
+
isLocked
|
91
101
|
});
|
92
102
|
}
|
93
103
|
async get(args) {
|
@@ -96,7 +106,7 @@ var FsCache = class _FsCache {
|
|
96
106
|
if (!await cacheDir.file(relativePath).exists()) return ["miss", null];
|
97
107
|
const value = await cacheDir.file(relativePath).read.json();
|
98
108
|
if (value === null) return ["miss", null];
|
99
|
-
if (value.isLocked || deepEqual(value.input, args))
|
109
|
+
if (value.isLocked || deepEqual(value.input, await this.serializeInput(...args)))
|
100
110
|
return ["hit", value.output];
|
101
111
|
return ["miss", null];
|
102
112
|
}
|
@@ -110,7 +120,10 @@ var FsCache = class _FsCache {
|
|
110
120
|
const file = (0, import_fs.dir)(this._config.cwd).file(relativePath);
|
111
121
|
return file.write.text(
|
112
122
|
JSON.stringify(
|
113
|
-
{
|
123
|
+
{
|
124
|
+
input: await this.serializeInput(...args),
|
125
|
+
output: value
|
126
|
+
},
|
114
127
|
null,
|
115
128
|
this._config.pretty ? 2 : void 0
|
116
129
|
)
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../src/fs-cache.index.ts","../src/fs-cache.lib.ts","../src/deepEqual.lib.ts"],"sourcesContent":["export * from \"./fs-cache.lib.ts\";\n","import {
|
1
|
+
{"version":3,"sources":["../src/fs-cache.index.ts","../src/fs-cache.lib.ts","../src/deepEqual.lib.ts"],"sourcesContent":["export * from \"./fs-cache.lib.ts\";\n","import { dir, FsDir } from \"@synstack/fs\";\nimport { type OneToN } from \"../../shared/src/ts.utils.ts\";\nimport { deepEqual } from \"./deepEqual.lib.ts\";\n\ntype $Partial<T> = Partial<T>;\n\ninterface CacheValue<TValue = any> {\n isLocked?: boolean;\n input: any;\n output: TValue;\n}\n\ndeclare namespace FsCache {\n export type KeyFn<TFnArgs extends any[] = any[]> =\n | string\n | ((...args: TFnArgs) => string);\n\n export type Key<TFnArgs extends any[] = any[]> = OneToN<KeyFn<TFnArgs>>;\n\n export type SignatureFn<TFnArgs extends any[] = any[]> = (\n ...args: TFnArgs\n ) => any;\n\n interface Options<TFnArgs extends any[] = any[]> {\n cwd: string;\n key: Key<TFnArgs>;\n signatureFn?: SignatureFn<TFnArgs>;\n pretty?: boolean;\n }\n\n namespace Options {\n export type Partial = $Partial<Options>;\n }\n}\n\nexport class FsCache<TConfig extends FsCache.Options.Partial> {\n private readonly _config: TConfig;\n\n private constructor(config: TConfig) {\n this._config = {\n ...config,\n } as TConfig & { signatureFn: FsCache.SignatureFn };\n }\n\n // eslint-disable-next-line @typescript-eslint/require-await\n private async serializeInput<TFnArgs extends any[]>(...args: TFnArgs) {\n if (this._config.signatureFn) return this._config.signatureFn(...args);\n return args;\n }\n\n public static cwd(this: void, cwd: string | FsDir) {\n if (cwd instanceof FsDir) return new FsCache({ cwd: cwd.path });\n return new FsCache({ cwd });\n }\n\n public key<TFnArgs extends any[], TKey extends FsCache.Key<TFnArgs>>(\n key: TKey,\n ) {\n return new FsCache({ ...this._config, key });\n }\n\n public signatureFn<TFnArgs extends any[]>(\n signatureFn: FsCache.SignatureFn<TFnArgs>,\n ) {\n return new FsCache({ ...this._config, signatureFn });\n }\n\n public pretty(pretty: boolean) {\n return new FsCache({ ...this._config, pretty });\n }\n\n private static keyToRelativePath(key: FsCache.Key, args: any[]) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n return `./${key.map((k) => (k instanceof Function ? k(...args) : k)).join(\"/\")}.json`;\n }\n\n public async lock<TFnArgs extends any[]>(\n this: FsCache<FsCache.Options<TFnArgs>>,\n isLocked: boolean,\n args: TFnArgs,\n ) {\n const relativePath = FsCache.keyToRelativePath(this._config.key, args);\n const cacheDir = dir(this._config.cwd);\n const file = cacheDir.file(relativePath);\n\n if (!(await file.exists())) {\n console.warn(\n \"[fs-cache] Failed to update lock on non-existing file\",\n file.path,\n );\n return;\n }\n\n const value = await file.read.json<CacheValue>();\n await file.write.json({\n ...value,\n isLocked,\n });\n }\n\n public async get<TFnArgs extends any[], TValue = any>(\n this: FsCache<FsCache.Options<TFnArgs>>,\n args: TFnArgs,\n ): Promise<[\"miss\", null] | [\"hit\", TValue]> {\n const relativePath = FsCache.keyToRelativePath(this._config.key, args);\n const cacheDir = dir(this._config.cwd);\n\n if (!(await cacheDir.file(relativePath).exists())) return [\"miss\", null];\n\n const value = await cacheDir\n .file(relativePath)\n .read.json<CacheValue<TValue>>();\n\n if (value === null) return [\"miss\", null];\n\n if (\n value.isLocked ||\n deepEqual(value.input, await this.serializeInput(...args))\n )\n return [\"hit\", value.output];\n\n return [\"miss\", null];\n }\n\n public async setDefault<TFnArgs extends any[], TValue = any>(\n this: FsCache<FsCache.Options<TFnArgs>>,\n args: TFnArgs,\n defaultValue: TValue,\n ) {\n const [status] = await this.get<TFnArgs, TValue>(args);\n if (status === \"hit\") return false;\n return this.set(args, defaultValue);\n }\n\n public async set<TFnArgs extends any[]>(\n this: FsCache<FsCache.Options<TFnArgs>>,\n args: TFnArgs,\n value: any,\n ) {\n const relativePath = FsCache.keyToRelativePath(this._config.key, args);\n const file = dir(this._config.cwd).file(relativePath);\n return file.write.text(\n JSON.stringify(\n {\n input: await this.serializeInput(...args),\n output: value,\n } satisfies CacheValue,\n null,\n this._config.pretty ? 2 : undefined,\n ),\n );\n }\n\n public fn<TFnArgs extends any[], TFnOutput>(\n this: FsCache<FsCache.Options<TFnArgs>>,\n fn: (...args: TFnArgs) => TFnOutput,\n ): (...args: TFnArgs) => Promise<Awaited<TFnOutput>> {\n return async (...args: TFnArgs): Promise<Awaited<TFnOutput>> => {\n const [status, value] = await this.get<TFnArgs, TFnOutput>(args);\n if (status === \"hit\") return value as Awaited<TFnOutput>;\n\n const output = await Promise.resolve(fn(...args));\n await this.set(args, output);\n return output;\n };\n }\n}\n\nexport const fsCache = FsCache.cwd;\n","export function deepEqual(obj1: any, obj2: any) {\n if (obj1 === obj2) {\n return true;\n }\n\n if (isPrimitive(obj1) && isPrimitive(obj2)) {\n return obj1 === obj2;\n }\n\n if (obj1 == null || obj2 == null) {\n return false;\n }\n\n const keys1 = Object.keys(obj1 as {}).filter((k) => obj1[k] !== undefined);\n const keys2 = Object.keys(obj2 as {}).filter((k) => obj2[k] !== undefined);\n\n if (keys1.length !== keys2.length) return false;\n\n for (const key of keys1) {\n if (!keys2.includes(key)) return false;\n if (!deepEqual(obj1[key], obj2[key])) {\n return false;\n }\n }\n\n return true;\n}\n\n//check if value is primitive\nfunction isPrimitive(obj: any) {\n return obj !== Object(obj);\n}\n"],"mappings":"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","names":[]}
|
@@ -9,9 +9,11 @@ type $Partial<T> = Partial<T>;
|
|
9
9
|
declare namespace FsCache {
|
10
10
|
type KeyFn<TFnArgs extends any[] = any[]> = string | ((...args: TFnArgs) => string);
|
11
11
|
type Key<TFnArgs extends any[] = any[]> = OneToN<KeyFn<TFnArgs>>;
|
12
|
+
type SignatureFn<TFnArgs extends any[] = any[]> = (...args: TFnArgs) => any;
|
12
13
|
interface Options<TFnArgs extends any[] = any[]> {
|
13
14
|
cwd: string;
|
14
15
|
key: Key<TFnArgs>;
|
16
|
+
signatureFn?: SignatureFn<TFnArgs>;
|
15
17
|
pretty?: boolean;
|
16
18
|
}
|
17
19
|
namespace Options {
|
@@ -21,12 +23,16 @@ declare namespace FsCache {
|
|
21
23
|
declare class FsCache<TConfig extends FsCache.Options.Partial> {
|
22
24
|
private readonly _config;
|
23
25
|
private constructor();
|
26
|
+
private serializeInput;
|
24
27
|
static cwd(this: void, cwd: string | FsDir): FsCache<{
|
25
28
|
cwd: string;
|
26
29
|
}>;
|
27
30
|
key<TFnArgs extends any[], TKey extends FsCache.Key<TFnArgs>>(key: TKey): FsCache<TConfig & {
|
28
31
|
key: TKey;
|
29
32
|
}>;
|
33
|
+
signatureFn<TFnArgs extends any[]>(signatureFn: FsCache.SignatureFn<TFnArgs>): FsCache<TConfig & {
|
34
|
+
signatureFn: FsCache.SignatureFn<TFnArgs>;
|
35
|
+
}>;
|
30
36
|
pretty(pretty: boolean): FsCache<TConfig & {
|
31
37
|
pretty: boolean;
|
32
38
|
}>;
|
@@ -35,7 +41,7 @@ declare class FsCache<TConfig extends FsCache.Options.Partial> {
|
|
35
41
|
get<TFnArgs extends any[], TValue = any>(this: FsCache<FsCache.Options<TFnArgs>>, args: TFnArgs): Promise<["miss", null] | ["hit", TValue]>;
|
36
42
|
setDefault<TFnArgs extends any[], TValue = any>(this: FsCache<FsCache.Options<TFnArgs>>, args: TFnArgs, defaultValue: TValue): Promise<false | void>;
|
37
43
|
set<TFnArgs extends any[]>(this: FsCache<FsCache.Options<TFnArgs>>, args: TFnArgs, value: any): Promise<void>;
|
38
|
-
fn<TFnArgs extends any[], TFnOutput>(this: FsCache<FsCache.Options<TFnArgs>>, fn: (...args: TFnArgs) =>
|
44
|
+
fn<TFnArgs extends any[], TFnOutput>(this: FsCache<FsCache.Options<TFnArgs>>, fn: (...args: TFnArgs) => TFnOutput): (...args: TFnArgs) => Promise<Awaited<TFnOutput>>;
|
39
45
|
}
|
40
46
|
declare const fsCache: typeof FsCache.cwd;
|
41
47
|
|
package/dist/fs-cache.index.d.ts
CHANGED
@@ -9,9 +9,11 @@ type $Partial<T> = Partial<T>;
|
|
9
9
|
declare namespace FsCache {
|
10
10
|
type KeyFn<TFnArgs extends any[] = any[]> = string | ((...args: TFnArgs) => string);
|
11
11
|
type Key<TFnArgs extends any[] = any[]> = OneToN<KeyFn<TFnArgs>>;
|
12
|
+
type SignatureFn<TFnArgs extends any[] = any[]> = (...args: TFnArgs) => any;
|
12
13
|
interface Options<TFnArgs extends any[] = any[]> {
|
13
14
|
cwd: string;
|
14
15
|
key: Key<TFnArgs>;
|
16
|
+
signatureFn?: SignatureFn<TFnArgs>;
|
15
17
|
pretty?: boolean;
|
16
18
|
}
|
17
19
|
namespace Options {
|
@@ -21,12 +23,16 @@ declare namespace FsCache {
|
|
21
23
|
declare class FsCache<TConfig extends FsCache.Options.Partial> {
|
22
24
|
private readonly _config;
|
23
25
|
private constructor();
|
26
|
+
private serializeInput;
|
24
27
|
static cwd(this: void, cwd: string | FsDir): FsCache<{
|
25
28
|
cwd: string;
|
26
29
|
}>;
|
27
30
|
key<TFnArgs extends any[], TKey extends FsCache.Key<TFnArgs>>(key: TKey): FsCache<TConfig & {
|
28
31
|
key: TKey;
|
29
32
|
}>;
|
33
|
+
signatureFn<TFnArgs extends any[]>(signatureFn: FsCache.SignatureFn<TFnArgs>): FsCache<TConfig & {
|
34
|
+
signatureFn: FsCache.SignatureFn<TFnArgs>;
|
35
|
+
}>;
|
30
36
|
pretty(pretty: boolean): FsCache<TConfig & {
|
31
37
|
pretty: boolean;
|
32
38
|
}>;
|
@@ -35,7 +41,7 @@ declare class FsCache<TConfig extends FsCache.Options.Partial> {
|
|
35
41
|
get<TFnArgs extends any[], TValue = any>(this: FsCache<FsCache.Options<TFnArgs>>, args: TFnArgs): Promise<["miss", null] | ["hit", TValue]>;
|
36
42
|
setDefault<TFnArgs extends any[], TValue = any>(this: FsCache<FsCache.Options<TFnArgs>>, args: TFnArgs, defaultValue: TValue): Promise<false | void>;
|
37
43
|
set<TFnArgs extends any[]>(this: FsCache<FsCache.Options<TFnArgs>>, args: TFnArgs, value: any): Promise<void>;
|
38
|
-
fn<TFnArgs extends any[], TFnOutput>(this: FsCache<FsCache.Options<TFnArgs>>, fn: (...args: TFnArgs) =>
|
44
|
+
fn<TFnArgs extends any[], TFnOutput>(this: FsCache<FsCache.Options<TFnArgs>>, fn: (...args: TFnArgs) => TFnOutput): (...args: TFnArgs) => Promise<Awaited<TFnOutput>>;
|
39
45
|
}
|
40
46
|
declare const fsCache: typeof FsCache.cwd;
|
41
47
|
|
package/dist/fs-cache.index.js
CHANGED
@@ -31,7 +31,14 @@ function isPrimitive(obj) {
|
|
31
31
|
var FsCache = class _FsCache {
|
32
32
|
_config;
|
33
33
|
constructor(config) {
|
34
|
-
this._config =
|
34
|
+
this._config = {
|
35
|
+
...config
|
36
|
+
};
|
37
|
+
}
|
38
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
39
|
+
async serializeInput(...args) {
|
40
|
+
if (this._config.signatureFn) return this._config.signatureFn(...args);
|
41
|
+
return args;
|
35
42
|
}
|
36
43
|
static cwd(cwd) {
|
37
44
|
if (cwd instanceof FsDir) return new _FsCache({ cwd: cwd.path });
|
@@ -40,6 +47,9 @@ var FsCache = class _FsCache {
|
|
40
47
|
key(key) {
|
41
48
|
return new _FsCache({ ...this._config, key });
|
42
49
|
}
|
50
|
+
signatureFn(signatureFn) {
|
51
|
+
return new _FsCache({ ...this._config, signatureFn });
|
52
|
+
}
|
43
53
|
pretty(pretty) {
|
44
54
|
return new _FsCache({ ...this._config, pretty });
|
45
55
|
}
|
@@ -59,8 +69,8 @@ var FsCache = class _FsCache {
|
|
59
69
|
}
|
60
70
|
const value = await file.read.json();
|
61
71
|
await file.write.json({
|
62
|
-
|
63
|
-
|
72
|
+
...value,
|
73
|
+
isLocked
|
64
74
|
});
|
65
75
|
}
|
66
76
|
async get(args) {
|
@@ -69,7 +79,7 @@ var FsCache = class _FsCache {
|
|
69
79
|
if (!await cacheDir.file(relativePath).exists()) return ["miss", null];
|
70
80
|
const value = await cacheDir.file(relativePath).read.json();
|
71
81
|
if (value === null) return ["miss", null];
|
72
|
-
if (value.isLocked || deepEqual(value.input, args))
|
82
|
+
if (value.isLocked || deepEqual(value.input, await this.serializeInput(...args)))
|
73
83
|
return ["hit", value.output];
|
74
84
|
return ["miss", null];
|
75
85
|
}
|
@@ -83,7 +93,10 @@ var FsCache = class _FsCache {
|
|
83
93
|
const file = dir(this._config.cwd).file(relativePath);
|
84
94
|
return file.write.text(
|
85
95
|
JSON.stringify(
|
86
|
-
{
|
96
|
+
{
|
97
|
+
input: await this.serializeInput(...args),
|
98
|
+
output: value
|
99
|
+
},
|
87
100
|
null,
|
88
101
|
this._config.pretty ? 2 : void 0
|
89
102
|
)
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../src/fs-cache.lib.ts","../src/deepEqual.lib.ts"],"sourcesContent":["import {
|
1
|
+
{"version":3,"sources":["../src/fs-cache.lib.ts","../src/deepEqual.lib.ts"],"sourcesContent":["import { dir, FsDir } from \"@synstack/fs\";\nimport { type OneToN } from \"../../shared/src/ts.utils.ts\";\nimport { deepEqual } from \"./deepEqual.lib.ts\";\n\ntype $Partial<T> = Partial<T>;\n\ninterface CacheValue<TValue = any> {\n isLocked?: boolean;\n input: any;\n output: TValue;\n}\n\ndeclare namespace FsCache {\n export type KeyFn<TFnArgs extends any[] = any[]> =\n | string\n | ((...args: TFnArgs) => string);\n\n export type Key<TFnArgs extends any[] = any[]> = OneToN<KeyFn<TFnArgs>>;\n\n export type SignatureFn<TFnArgs extends any[] = any[]> = (\n ...args: TFnArgs\n ) => any;\n\n interface Options<TFnArgs extends any[] = any[]> {\n cwd: string;\n key: Key<TFnArgs>;\n signatureFn?: SignatureFn<TFnArgs>;\n pretty?: boolean;\n }\n\n namespace Options {\n export type Partial = $Partial<Options>;\n }\n}\n\nexport class FsCache<TConfig extends FsCache.Options.Partial> {\n private readonly _config: TConfig;\n\n private constructor(config: TConfig) {\n this._config = {\n ...config,\n } as TConfig & { signatureFn: FsCache.SignatureFn };\n }\n\n // eslint-disable-next-line @typescript-eslint/require-await\n private async serializeInput<TFnArgs extends any[]>(...args: TFnArgs) {\n if (this._config.signatureFn) return this._config.signatureFn(...args);\n return args;\n }\n\n public static cwd(this: void, cwd: string | FsDir) {\n if (cwd instanceof FsDir) return new FsCache({ cwd: cwd.path });\n return new FsCache({ cwd });\n }\n\n public key<TFnArgs extends any[], TKey extends FsCache.Key<TFnArgs>>(\n key: TKey,\n ) {\n return new FsCache({ ...this._config, key });\n }\n\n public signatureFn<TFnArgs extends any[]>(\n signatureFn: FsCache.SignatureFn<TFnArgs>,\n ) {\n return new FsCache({ ...this._config, signatureFn });\n }\n\n public pretty(pretty: boolean) {\n return new FsCache({ ...this._config, pretty });\n }\n\n private static keyToRelativePath(key: FsCache.Key, args: any[]) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n return `./${key.map((k) => (k instanceof Function ? k(...args) : k)).join(\"/\")}.json`;\n }\n\n public async lock<TFnArgs extends any[]>(\n this: FsCache<FsCache.Options<TFnArgs>>,\n isLocked: boolean,\n args: TFnArgs,\n ) {\n const relativePath = FsCache.keyToRelativePath(this._config.key, args);\n const cacheDir = dir(this._config.cwd);\n const file = cacheDir.file(relativePath);\n\n if (!(await file.exists())) {\n console.warn(\n \"[fs-cache] Failed to update lock on non-existing file\",\n file.path,\n );\n return;\n }\n\n const value = await file.read.json<CacheValue>();\n await file.write.json({\n ...value,\n isLocked,\n });\n }\n\n public async get<TFnArgs extends any[], TValue = any>(\n this: FsCache<FsCache.Options<TFnArgs>>,\n args: TFnArgs,\n ): Promise<[\"miss\", null] | [\"hit\", TValue]> {\n const relativePath = FsCache.keyToRelativePath(this._config.key, args);\n const cacheDir = dir(this._config.cwd);\n\n if (!(await cacheDir.file(relativePath).exists())) return [\"miss\", null];\n\n const value = await cacheDir\n .file(relativePath)\n .read.json<CacheValue<TValue>>();\n\n if (value === null) return [\"miss\", null];\n\n if (\n value.isLocked ||\n deepEqual(value.input, await this.serializeInput(...args))\n )\n return [\"hit\", value.output];\n\n return [\"miss\", null];\n }\n\n public async setDefault<TFnArgs extends any[], TValue = any>(\n this: FsCache<FsCache.Options<TFnArgs>>,\n args: TFnArgs,\n defaultValue: TValue,\n ) {\n const [status] = await this.get<TFnArgs, TValue>(args);\n if (status === \"hit\") return false;\n return this.set(args, defaultValue);\n }\n\n public async set<TFnArgs extends any[]>(\n this: FsCache<FsCache.Options<TFnArgs>>,\n args: TFnArgs,\n value: any,\n ) {\n const relativePath = FsCache.keyToRelativePath(this._config.key, args);\n const file = dir(this._config.cwd).file(relativePath);\n return file.write.text(\n JSON.stringify(\n {\n input: await this.serializeInput(...args),\n output: value,\n } satisfies CacheValue,\n null,\n this._config.pretty ? 2 : undefined,\n ),\n );\n }\n\n public fn<TFnArgs extends any[], TFnOutput>(\n this: FsCache<FsCache.Options<TFnArgs>>,\n fn: (...args: TFnArgs) => TFnOutput,\n ): (...args: TFnArgs) => Promise<Awaited<TFnOutput>> {\n return async (...args: TFnArgs): Promise<Awaited<TFnOutput>> => {\n const [status, value] = await this.get<TFnArgs, TFnOutput>(args);\n if (status === \"hit\") return value as Awaited<TFnOutput>;\n\n const output = await Promise.resolve(fn(...args));\n await this.set(args, output);\n return output;\n };\n }\n}\n\nexport const fsCache = FsCache.cwd;\n","export function deepEqual(obj1: any, obj2: any) {\n if (obj1 === obj2) {\n return true;\n }\n\n if (isPrimitive(obj1) && isPrimitive(obj2)) {\n return obj1 === obj2;\n }\n\n if (obj1 == null || obj2 == null) {\n return false;\n }\n\n const keys1 = Object.keys(obj1 as {}).filter((k) => obj1[k] !== undefined);\n const keys2 = Object.keys(obj2 as {}).filter((k) => obj2[k] !== undefined);\n\n if (keys1.length !== keys2.length) return false;\n\n for (const key of keys1) {\n if (!keys2.includes(key)) return false;\n if (!deepEqual(obj1[key], obj2[key])) {\n return false;\n }\n }\n\n return true;\n}\n\n//check if value is primitive\nfunction isPrimitive(obj: any) {\n return obj !== Object(obj);\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
"publishConfig": {
|
5
5
|
"access": "public"
|
6
6
|
},
|
7
|
-
"version": "1.
|
7
|
+
"version": "1.4.0",
|
8
8
|
"description": "Human-friendly file system caching",
|
9
9
|
"keywords": [
|
10
10
|
"cache",
|
@@ -25,8 +25,8 @@
|
|
25
25
|
"build": "tsup",
|
26
26
|
"build:watch": "tsup --watch",
|
27
27
|
"test:types": "tsc --noEmit",
|
28
|
-
"test:unit": "node --experimental-strip-types --test src/**/*.test.ts",
|
29
|
-
"test:unit:watch": "node --experimental-strip-types --watch --test --watch src/**/*.test.ts",
|
28
|
+
"test:unit": "node --experimental-strip-types --experimental-test-snapshots --no-warnings --test src/**/*.test.ts",
|
29
|
+
"test:unit:watch": "node --experimental-strip-types --experimental-test-snapshots --no-warnings --watch --test --watch src/**/*.test.ts",
|
30
30
|
"test": "yarn test:types && yarn test:unit",
|
31
31
|
"prepare": "yarn test && yarn build"
|
32
32
|
},
|
@@ -43,10 +43,10 @@
|
|
43
43
|
}
|
44
44
|
},
|
45
45
|
"dependencies": {
|
46
|
-
"@synstack/fs": "1.
|
46
|
+
"@synstack/fs": "1.4.0"
|
47
47
|
},
|
48
48
|
"devDependencies": {
|
49
|
-
"@types/node": "^22.
|
49
|
+
"@types/node": "^22.10.1",
|
50
50
|
"tsup": "^8.3.5",
|
51
51
|
"typescript": "^5.7.2"
|
52
52
|
},
|
@@ -55,5 +55,5 @@
|
|
55
55
|
"!src/**/*.test.ts",
|
56
56
|
"dist/**/*"
|
57
57
|
],
|
58
|
-
"gitHead": "
|
58
|
+
"gitHead": "bd0573bc2521371c355953766911b6523a3a4709"
|
59
59
|
}
|
package/src/fs-cache.lib.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
import { type OneToN } from "../../shared/src/ts.utils.ts";
|
2
1
|
import { dir, FsDir } from "@synstack/fs";
|
2
|
+
import { type OneToN } from "../../shared/src/ts.utils.ts";
|
3
3
|
import { deepEqual } from "./deepEqual.lib.ts";
|
4
4
|
|
5
5
|
type $Partial<T> = Partial<T>;
|
@@ -14,11 +14,17 @@ declare namespace FsCache {
|
|
14
14
|
export type KeyFn<TFnArgs extends any[] = any[]> =
|
15
15
|
| string
|
16
16
|
| ((...args: TFnArgs) => string);
|
17
|
+
|
17
18
|
export type Key<TFnArgs extends any[] = any[]> = OneToN<KeyFn<TFnArgs>>;
|
18
19
|
|
20
|
+
export type SignatureFn<TFnArgs extends any[] = any[]> = (
|
21
|
+
...args: TFnArgs
|
22
|
+
) => any;
|
23
|
+
|
19
24
|
interface Options<TFnArgs extends any[] = any[]> {
|
20
25
|
cwd: string;
|
21
26
|
key: Key<TFnArgs>;
|
27
|
+
signatureFn?: SignatureFn<TFnArgs>;
|
22
28
|
pretty?: boolean;
|
23
29
|
}
|
24
30
|
|
@@ -31,7 +37,15 @@ export class FsCache<TConfig extends FsCache.Options.Partial> {
|
|
31
37
|
private readonly _config: TConfig;
|
32
38
|
|
33
39
|
private constructor(config: TConfig) {
|
34
|
-
this._config =
|
40
|
+
this._config = {
|
41
|
+
...config,
|
42
|
+
} as TConfig & { signatureFn: FsCache.SignatureFn };
|
43
|
+
}
|
44
|
+
|
45
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
46
|
+
private async serializeInput<TFnArgs extends any[]>(...args: TFnArgs) {
|
47
|
+
if (this._config.signatureFn) return this._config.signatureFn(...args);
|
48
|
+
return args;
|
35
49
|
}
|
36
50
|
|
37
51
|
public static cwd(this: void, cwd: string | FsDir) {
|
@@ -45,6 +59,12 @@ export class FsCache<TConfig extends FsCache.Options.Partial> {
|
|
45
59
|
return new FsCache({ ...this._config, key });
|
46
60
|
}
|
47
61
|
|
62
|
+
public signatureFn<TFnArgs extends any[]>(
|
63
|
+
signatureFn: FsCache.SignatureFn<TFnArgs>,
|
64
|
+
) {
|
65
|
+
return new FsCache({ ...this._config, signatureFn });
|
66
|
+
}
|
67
|
+
|
48
68
|
public pretty(pretty: boolean) {
|
49
69
|
return new FsCache({ ...this._config, pretty });
|
50
70
|
}
|
@@ -73,8 +93,8 @@ export class FsCache<TConfig extends FsCache.Options.Partial> {
|
|
73
93
|
|
74
94
|
const value = await file.read.json<CacheValue>();
|
75
95
|
await file.write.json({
|
76
|
-
isLocked,
|
77
96
|
...value,
|
97
|
+
isLocked,
|
78
98
|
});
|
79
99
|
}
|
80
100
|
|
@@ -93,7 +113,10 @@ export class FsCache<TConfig extends FsCache.Options.Partial> {
|
|
93
113
|
|
94
114
|
if (value === null) return ["miss", null];
|
95
115
|
|
96
|
-
if (
|
116
|
+
if (
|
117
|
+
value.isLocked ||
|
118
|
+
deepEqual(value.input, await this.serializeInput(...args))
|
119
|
+
)
|
97
120
|
return ["hit", value.output];
|
98
121
|
|
99
122
|
return ["miss", null];
|
@@ -118,7 +141,10 @@ export class FsCache<TConfig extends FsCache.Options.Partial> {
|
|
118
141
|
const file = dir(this._config.cwd).file(relativePath);
|
119
142
|
return file.write.text(
|
120
143
|
JSON.stringify(
|
121
|
-
{
|
144
|
+
{
|
145
|
+
input: await this.serializeInput(...args),
|
146
|
+
output: value,
|
147
|
+
} satisfies CacheValue,
|
122
148
|
null,
|
123
149
|
this._config.pretty ? 2 : undefined,
|
124
150
|
),
|
@@ -127,11 +153,11 @@ export class FsCache<TConfig extends FsCache.Options.Partial> {
|
|
127
153
|
|
128
154
|
public fn<TFnArgs extends any[], TFnOutput>(
|
129
155
|
this: FsCache<FsCache.Options<TFnArgs>>,
|
130
|
-
fn: (...args: TFnArgs) =>
|
131
|
-
): (...args: TFnArgs) => Promise<TFnOutput
|
132
|
-
return async (...args: TFnArgs): Promise<TFnOutput
|
156
|
+
fn: (...args: TFnArgs) => TFnOutput,
|
157
|
+
): (...args: TFnArgs) => Promise<Awaited<TFnOutput>> {
|
158
|
+
return async (...args: TFnArgs): Promise<Awaited<TFnOutput>> => {
|
133
159
|
const [status, value] = await this.get<TFnArgs, TFnOutput>(args);
|
134
|
-
if (status === "hit") return value
|
160
|
+
if (status === "hit") return value as Awaited<TFnOutput>;
|
135
161
|
|
136
162
|
const output = await Promise.resolve(fn(...args));
|
137
163
|
await this.set(args, output);
|