@synstack/fs-cache 1.0.1-alpha.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +1 -0
- package/dist/fs-cache.index.cjs +116 -0
- package/dist/fs-cache.index.cjs.map +1 -0
- package/dist/fs-cache.index.d.cts +39 -0
- package/dist/fs-cache.index.d.ts +39 -0
- package/dist/fs-cache.index.js +88 -0
- package/dist/fs-cache.index.js.map +1 -0
- package/package.json +62 -0
- package/src/deepEqual.lib.ts +32 -0
- package/src/fs-cache.index.ts +1 -0
- package/src/fs-cache.lib.ts +117 -0
package/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# @synstack/fs-cache
|
@@ -0,0 +1,116 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __defProp = Object.defineProperty;
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
6
|
+
var __export = (target, all) => {
|
7
|
+
for (var name in all)
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
9
|
+
};
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
12
|
+
for (let key of __getOwnPropNames(from))
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
15
|
+
}
|
16
|
+
return to;
|
17
|
+
};
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
19
|
+
|
20
|
+
// src/fs-cache.index.ts
|
21
|
+
var fs_cache_index_exports = {};
|
22
|
+
__export(fs_cache_index_exports, {
|
23
|
+
FsCache: () => FsCache,
|
24
|
+
fsCache: () => fsCache
|
25
|
+
});
|
26
|
+
module.exports = __toCommonJS(fs_cache_index_exports);
|
27
|
+
|
28
|
+
// src/fs-cache.lib.ts
|
29
|
+
var import_fs = require("@synstack/fs");
|
30
|
+
|
31
|
+
// src/deepEqual.lib.ts
|
32
|
+
function deepEqual(obj1, obj2) {
|
33
|
+
if (obj1 === obj2) {
|
34
|
+
return true;
|
35
|
+
}
|
36
|
+
if (isPrimitive(obj1) && isPrimitive(obj2)) {
|
37
|
+
return obj1 === obj2;
|
38
|
+
}
|
39
|
+
if (obj1 == null || obj2 == null) {
|
40
|
+
return false;
|
41
|
+
}
|
42
|
+
const keys1 = Object.keys(obj1).filter((k) => obj1[k] !== void 0);
|
43
|
+
const keys2 = Object.keys(obj2).filter((k) => obj2[k] !== void 0);
|
44
|
+
if (keys1.length !== keys2.length) return false;
|
45
|
+
for (const key of keys1) {
|
46
|
+
if (!keys2.includes(key)) return false;
|
47
|
+
if (!deepEqual(obj1[key], obj2[key])) {
|
48
|
+
return false;
|
49
|
+
}
|
50
|
+
}
|
51
|
+
return true;
|
52
|
+
}
|
53
|
+
function isPrimitive(obj) {
|
54
|
+
return obj !== Object(obj);
|
55
|
+
}
|
56
|
+
|
57
|
+
// src/fs-cache.lib.ts
|
58
|
+
var FsCache = class _FsCache {
|
59
|
+
_config;
|
60
|
+
constructor(config) {
|
61
|
+
this._config = config;
|
62
|
+
}
|
63
|
+
static cwd(cwd) {
|
64
|
+
return new _FsCache({ cwd });
|
65
|
+
}
|
66
|
+
key(key) {
|
67
|
+
return new _FsCache({ ...this._config, key });
|
68
|
+
}
|
69
|
+
pretty(pretty) {
|
70
|
+
return new _FsCache({ ...this._config, pretty });
|
71
|
+
}
|
72
|
+
static keyToRelativePath(key, args) {
|
73
|
+
return `./${key.map((k) => k instanceof Function ? k(...args) : k).join("/")}.json`;
|
74
|
+
}
|
75
|
+
async get(args) {
|
76
|
+
const relativePath = _FsCache.keyToRelativePath(this._config.key, args);
|
77
|
+
const cacheDir = (0, import_fs.dir)(this._config.cwd);
|
78
|
+
if (!await cacheDir.file(relativePath).exists()) return ["miss", null];
|
79
|
+
const value = await cacheDir.file(relativePath).read.json();
|
80
|
+
if (value === null) return ["miss", null];
|
81
|
+
if (!deepEqual(value.input, args)) return ["miss", null];
|
82
|
+
return ["hit", value.output];
|
83
|
+
}
|
84
|
+
async setDefault(args, defaultValue) {
|
85
|
+
const [status] = await this.get(args);
|
86
|
+
if (status === "hit") return false;
|
87
|
+
return this.set(args, defaultValue);
|
88
|
+
}
|
89
|
+
async set(args, value) {
|
90
|
+
const relativePath = _FsCache.keyToRelativePath(this._config.key, args);
|
91
|
+
const file = (0, import_fs.dir)(this._config.cwd).file(relativePath);
|
92
|
+
return file.write.text(
|
93
|
+
JSON.stringify(
|
94
|
+
{ input: args, output: value },
|
95
|
+
null,
|
96
|
+
this._config.pretty ? 2 : void 0
|
97
|
+
)
|
98
|
+
);
|
99
|
+
}
|
100
|
+
fn(fn) {
|
101
|
+
return async (...args) => {
|
102
|
+
const [status, value] = await this.get(args);
|
103
|
+
if (status === "hit") return value;
|
104
|
+
const output = await Promise.resolve(fn(...args));
|
105
|
+
await this.set(args, output);
|
106
|
+
return output;
|
107
|
+
};
|
108
|
+
}
|
109
|
+
};
|
110
|
+
var fsCache = FsCache.cwd;
|
111
|
+
// Annotate the CommonJS export names for ESM import in node:
|
112
|
+
0 && (module.exports = {
|
113
|
+
FsCache,
|
114
|
+
fsCache
|
115
|
+
});
|
116
|
+
//# sourceMappingURL=fs-cache.index.cjs.map
|
@@ -0,0 +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\";\n","import { OneToN } from \"@shared/src/ts.utils\";\nimport { dir } from \"@synstack/fs\";\nimport { deepEqual } from \"./deepEqual.lib\";\n\ntype $Partial<T> = Partial<T>;\n\ninterface CacheValue<TValue = any> {\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 export type Key<TFnArgs extends any[] = any[]> = OneToN<KeyFn<TFnArgs>>;\n\n interface Options<TFnArgs extends any[] = any[]> {\n cwd: string;\n key: Key<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 = config;\n }\n\n public static cwd(this: void, cwd: string) {\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 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 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 // Todo: FIX CACHE HIT\n if (!deepEqual(value.input, args)) return [\"miss\", null];\n\n return [\"hit\", value.output];\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 { input: args, output: value } 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) => Promise<TFnOutput> | TFnOutput,\n ): (...args: TFnArgs) => Promise<TFnOutput> {\n return async (...args: TFnArgs): Promise<TFnOutput> => {\n const [status, value] = await this.get<TFnArgs, TFnOutput>(args);\n if (status === \"hit\") return value;\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":[]}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
/**
|
2
|
+
* One or more values
|
3
|
+
*/
|
4
|
+
type OneToN<T> = [...T[], T];
|
5
|
+
|
6
|
+
type $Partial<T> = Partial<T>;
|
7
|
+
declare namespace FsCache {
|
8
|
+
type KeyFn<TFnArgs extends any[] = any[]> = string | ((...args: TFnArgs) => string);
|
9
|
+
type Key<TFnArgs extends any[] = any[]> = OneToN<KeyFn<TFnArgs>>;
|
10
|
+
interface Options<TFnArgs extends any[] = any[]> {
|
11
|
+
cwd: string;
|
12
|
+
key: Key<TFnArgs>;
|
13
|
+
pretty?: boolean;
|
14
|
+
}
|
15
|
+
namespace Options {
|
16
|
+
type Partial = $Partial<Options>;
|
17
|
+
}
|
18
|
+
}
|
19
|
+
declare class FsCache<TConfig extends FsCache.Options.Partial> {
|
20
|
+
private readonly _config;
|
21
|
+
private constructor();
|
22
|
+
static cwd(this: void, cwd: string): FsCache<{
|
23
|
+
cwd: string;
|
24
|
+
}>;
|
25
|
+
key<TFnArgs extends any[], TKey extends FsCache.Key<TFnArgs>>(key: TKey): FsCache<TConfig & {
|
26
|
+
key: TKey;
|
27
|
+
}>;
|
28
|
+
pretty(pretty: boolean): FsCache<TConfig & {
|
29
|
+
pretty: boolean;
|
30
|
+
}>;
|
31
|
+
private static keyToRelativePath;
|
32
|
+
get<TFnArgs extends any[], TValue = any>(this: FsCache<FsCache.Options<TFnArgs>>, args: TFnArgs): Promise<["miss", null] | ["hit", TValue]>;
|
33
|
+
setDefault<TFnArgs extends any[], TValue = any>(this: FsCache<FsCache.Options<TFnArgs>>, args: TFnArgs, defaultValue: TValue): Promise<false | void>;
|
34
|
+
set<TFnArgs extends any[]>(this: FsCache<FsCache.Options<TFnArgs>>, args: TFnArgs, value: any): Promise<void>;
|
35
|
+
fn<TFnArgs extends any[], TFnOutput>(this: FsCache<FsCache.Options<TFnArgs>>, fn: (...args: TFnArgs) => Promise<TFnOutput> | TFnOutput): (...args: TFnArgs) => Promise<TFnOutput>;
|
36
|
+
}
|
37
|
+
declare const fsCache: typeof FsCache.cwd;
|
38
|
+
|
39
|
+
export { FsCache, fsCache };
|
@@ -0,0 +1,39 @@
|
|
1
|
+
/**
|
2
|
+
* One or more values
|
3
|
+
*/
|
4
|
+
type OneToN<T> = [...T[], T];
|
5
|
+
|
6
|
+
type $Partial<T> = Partial<T>;
|
7
|
+
declare namespace FsCache {
|
8
|
+
type KeyFn<TFnArgs extends any[] = any[]> = string | ((...args: TFnArgs) => string);
|
9
|
+
type Key<TFnArgs extends any[] = any[]> = OneToN<KeyFn<TFnArgs>>;
|
10
|
+
interface Options<TFnArgs extends any[] = any[]> {
|
11
|
+
cwd: string;
|
12
|
+
key: Key<TFnArgs>;
|
13
|
+
pretty?: boolean;
|
14
|
+
}
|
15
|
+
namespace Options {
|
16
|
+
type Partial = $Partial<Options>;
|
17
|
+
}
|
18
|
+
}
|
19
|
+
declare class FsCache<TConfig extends FsCache.Options.Partial> {
|
20
|
+
private readonly _config;
|
21
|
+
private constructor();
|
22
|
+
static cwd(this: void, cwd: string): FsCache<{
|
23
|
+
cwd: string;
|
24
|
+
}>;
|
25
|
+
key<TFnArgs extends any[], TKey extends FsCache.Key<TFnArgs>>(key: TKey): FsCache<TConfig & {
|
26
|
+
key: TKey;
|
27
|
+
}>;
|
28
|
+
pretty(pretty: boolean): FsCache<TConfig & {
|
29
|
+
pretty: boolean;
|
30
|
+
}>;
|
31
|
+
private static keyToRelativePath;
|
32
|
+
get<TFnArgs extends any[], TValue = any>(this: FsCache<FsCache.Options<TFnArgs>>, args: TFnArgs): Promise<["miss", null] | ["hit", TValue]>;
|
33
|
+
setDefault<TFnArgs extends any[], TValue = any>(this: FsCache<FsCache.Options<TFnArgs>>, args: TFnArgs, defaultValue: TValue): Promise<false | void>;
|
34
|
+
set<TFnArgs extends any[]>(this: FsCache<FsCache.Options<TFnArgs>>, args: TFnArgs, value: any): Promise<void>;
|
35
|
+
fn<TFnArgs extends any[], TFnOutput>(this: FsCache<FsCache.Options<TFnArgs>>, fn: (...args: TFnArgs) => Promise<TFnOutput> | TFnOutput): (...args: TFnArgs) => Promise<TFnOutput>;
|
36
|
+
}
|
37
|
+
declare const fsCache: typeof FsCache.cwd;
|
38
|
+
|
39
|
+
export { FsCache, fsCache };
|
@@ -0,0 +1,88 @@
|
|
1
|
+
// src/fs-cache.lib.ts
|
2
|
+
import { dir } from "@synstack/fs";
|
3
|
+
|
4
|
+
// src/deepEqual.lib.ts
|
5
|
+
function deepEqual(obj1, obj2) {
|
6
|
+
if (obj1 === obj2) {
|
7
|
+
return true;
|
8
|
+
}
|
9
|
+
if (isPrimitive(obj1) && isPrimitive(obj2)) {
|
10
|
+
return obj1 === obj2;
|
11
|
+
}
|
12
|
+
if (obj1 == null || obj2 == null) {
|
13
|
+
return false;
|
14
|
+
}
|
15
|
+
const keys1 = Object.keys(obj1).filter((k) => obj1[k] !== void 0);
|
16
|
+
const keys2 = Object.keys(obj2).filter((k) => obj2[k] !== void 0);
|
17
|
+
if (keys1.length !== keys2.length) return false;
|
18
|
+
for (const key of keys1) {
|
19
|
+
if (!keys2.includes(key)) return false;
|
20
|
+
if (!deepEqual(obj1[key], obj2[key])) {
|
21
|
+
return false;
|
22
|
+
}
|
23
|
+
}
|
24
|
+
return true;
|
25
|
+
}
|
26
|
+
function isPrimitive(obj) {
|
27
|
+
return obj !== Object(obj);
|
28
|
+
}
|
29
|
+
|
30
|
+
// src/fs-cache.lib.ts
|
31
|
+
var FsCache = class _FsCache {
|
32
|
+
_config;
|
33
|
+
constructor(config) {
|
34
|
+
this._config = config;
|
35
|
+
}
|
36
|
+
static cwd(cwd) {
|
37
|
+
return new _FsCache({ cwd });
|
38
|
+
}
|
39
|
+
key(key) {
|
40
|
+
return new _FsCache({ ...this._config, key });
|
41
|
+
}
|
42
|
+
pretty(pretty) {
|
43
|
+
return new _FsCache({ ...this._config, pretty });
|
44
|
+
}
|
45
|
+
static keyToRelativePath(key, args) {
|
46
|
+
return `./${key.map((k) => k instanceof Function ? k(...args) : k).join("/")}.json`;
|
47
|
+
}
|
48
|
+
async get(args) {
|
49
|
+
const relativePath = _FsCache.keyToRelativePath(this._config.key, args);
|
50
|
+
const cacheDir = dir(this._config.cwd);
|
51
|
+
if (!await cacheDir.file(relativePath).exists()) return ["miss", null];
|
52
|
+
const value = await cacheDir.file(relativePath).read.json();
|
53
|
+
if (value === null) return ["miss", null];
|
54
|
+
if (!deepEqual(value.input, args)) return ["miss", null];
|
55
|
+
return ["hit", value.output];
|
56
|
+
}
|
57
|
+
async setDefault(args, defaultValue) {
|
58
|
+
const [status] = await this.get(args);
|
59
|
+
if (status === "hit") return false;
|
60
|
+
return this.set(args, defaultValue);
|
61
|
+
}
|
62
|
+
async set(args, value) {
|
63
|
+
const relativePath = _FsCache.keyToRelativePath(this._config.key, args);
|
64
|
+
const file = dir(this._config.cwd).file(relativePath);
|
65
|
+
return file.write.text(
|
66
|
+
JSON.stringify(
|
67
|
+
{ input: args, output: value },
|
68
|
+
null,
|
69
|
+
this._config.pretty ? 2 : void 0
|
70
|
+
)
|
71
|
+
);
|
72
|
+
}
|
73
|
+
fn(fn) {
|
74
|
+
return async (...args) => {
|
75
|
+
const [status, value] = await this.get(args);
|
76
|
+
if (status === "hit") return value;
|
77
|
+
const output = await Promise.resolve(fn(...args));
|
78
|
+
await this.set(args, output);
|
79
|
+
return output;
|
80
|
+
};
|
81
|
+
}
|
82
|
+
};
|
83
|
+
var fsCache = FsCache.cwd;
|
84
|
+
export {
|
85
|
+
FsCache,
|
86
|
+
fsCache
|
87
|
+
};
|
88
|
+
//# sourceMappingURL=fs-cache.index.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"sources":["../src/fs-cache.lib.ts","../src/deepEqual.lib.ts"],"sourcesContent":["import { OneToN } from \"@shared/src/ts.utils\";\nimport { dir } from \"@synstack/fs\";\nimport { deepEqual } from \"./deepEqual.lib\";\n\ntype $Partial<T> = Partial<T>;\n\ninterface CacheValue<TValue = any> {\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 export type Key<TFnArgs extends any[] = any[]> = OneToN<KeyFn<TFnArgs>>;\n\n interface Options<TFnArgs extends any[] = any[]> {\n cwd: string;\n key: Key<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 = config;\n }\n\n public static cwd(this: void, cwd: string) {\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 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 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 // Todo: FIX CACHE HIT\n if (!deepEqual(value.input, args)) return [\"miss\", null];\n\n return [\"hit\", value.output];\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 { input: args, output: value } 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) => Promise<TFnOutput> | TFnOutput,\n ): (...args: TFnArgs) => Promise<TFnOutput> {\n return async (...args: TFnArgs): Promise<TFnOutput> => {\n const [status, value] = await this.get<TFnArgs, TFnOutput>(args);\n if (status === \"hit\") return value;\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
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
{
|
2
|
+
"name": "@synstack/fs-cache",
|
3
|
+
"type": "module",
|
4
|
+
"publishConfig": {
|
5
|
+
"access": "public"
|
6
|
+
},
|
7
|
+
"packageManager": "yarn@4.4.0",
|
8
|
+
"version": "1.0.1-alpha.0",
|
9
|
+
"description": "Human-friendly file system caching",
|
10
|
+
"keywords": [
|
11
|
+
"cache",
|
12
|
+
"memoize"
|
13
|
+
],
|
14
|
+
"author": {
|
15
|
+
"name": "pAIrprog",
|
16
|
+
"url": "https://pairprog.io"
|
17
|
+
},
|
18
|
+
"homepage": "https://github.com/pAIrprogio/synscript/tree/main/packages/fs-cache",
|
19
|
+
"repository": {
|
20
|
+
"type": "git",
|
21
|
+
"url": "https://github.com/pAIrprogio/syn-stack.git",
|
22
|
+
"directory": "packages/fs-cache"
|
23
|
+
},
|
24
|
+
"license": "Apache-2.0",
|
25
|
+
"scripts": {
|
26
|
+
"publish": "yarn npm publish --access public",
|
27
|
+
"prepublish": "yarn test && yarn build",
|
28
|
+
"build": "tsup",
|
29
|
+
"build:watch": "tsup --watch",
|
30
|
+
"test:types": "tsc --noEmit",
|
31
|
+
"test:unit": "node --import tsx --test src/**/*.test.ts",
|
32
|
+
"test:unit:watch": "node --import tsx --watch --test src/**/*.test.ts",
|
33
|
+
"test": "yarn test:types && yarn test:unit"
|
34
|
+
},
|
35
|
+
"exports": {
|
36
|
+
".": {
|
37
|
+
"import": {
|
38
|
+
"types": "./dist/fs-cache.index.d.ts",
|
39
|
+
"default": "./dist/fs-cache.index.js"
|
40
|
+
},
|
41
|
+
"require": {
|
42
|
+
"types": "./dist/fs-cache.index.d.cts",
|
43
|
+
"default": "./dist/fs-cache.index.cjs"
|
44
|
+
}
|
45
|
+
}
|
46
|
+
},
|
47
|
+
"dependencies": {
|
48
|
+
"@synstack/fs": "1.0.1-alpha.0"
|
49
|
+
},
|
50
|
+
"devDependencies": {
|
51
|
+
"@types/node": "^22.7.0",
|
52
|
+
"tsup": "^8.3.0",
|
53
|
+
"tsx": "^4.19.1",
|
54
|
+
"typescript": "^5.6.2"
|
55
|
+
},
|
56
|
+
"files": [
|
57
|
+
"src/**/*.ts",
|
58
|
+
"!src/**/*.test.ts",
|
59
|
+
"dist/**/*"
|
60
|
+
],
|
61
|
+
"gitHead": "85f9945f77a0427bc4cfc1f7046aba430c8a8b9f"
|
62
|
+
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
export function deepEqual(obj1: any, obj2: any) {
|
2
|
+
if (obj1 === obj2) {
|
3
|
+
return true;
|
4
|
+
}
|
5
|
+
|
6
|
+
if (isPrimitive(obj1) && isPrimitive(obj2)) {
|
7
|
+
return obj1 === obj2;
|
8
|
+
}
|
9
|
+
|
10
|
+
if (obj1 == null || obj2 == null) {
|
11
|
+
return false;
|
12
|
+
}
|
13
|
+
|
14
|
+
const keys1 = Object.keys(obj1 as {}).filter((k) => obj1[k] !== undefined);
|
15
|
+
const keys2 = Object.keys(obj2 as {}).filter((k) => obj2[k] !== undefined);
|
16
|
+
|
17
|
+
if (keys1.length !== keys2.length) return false;
|
18
|
+
|
19
|
+
for (const key of keys1) {
|
20
|
+
if (!keys2.includes(key)) return false;
|
21
|
+
if (!deepEqual(obj1[key], obj2[key])) {
|
22
|
+
return false;
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
return true;
|
27
|
+
}
|
28
|
+
|
29
|
+
//check if value is primitive
|
30
|
+
function isPrimitive(obj: any) {
|
31
|
+
return obj !== Object(obj);
|
32
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from "./fs-cache.lib";
|
@@ -0,0 +1,117 @@
|
|
1
|
+
import { OneToN } from "@shared/src/ts.utils";
|
2
|
+
import { dir } from "@synstack/fs";
|
3
|
+
import { deepEqual } from "./deepEqual.lib";
|
4
|
+
|
5
|
+
type $Partial<T> = Partial<T>;
|
6
|
+
|
7
|
+
interface CacheValue<TValue = any> {
|
8
|
+
input: any;
|
9
|
+
output: TValue;
|
10
|
+
}
|
11
|
+
|
12
|
+
declare namespace FsCache {
|
13
|
+
export type KeyFn<TFnArgs extends any[] = any[]> =
|
14
|
+
| string
|
15
|
+
| ((...args: TFnArgs) => string);
|
16
|
+
export type Key<TFnArgs extends any[] = any[]> = OneToN<KeyFn<TFnArgs>>;
|
17
|
+
|
18
|
+
interface Options<TFnArgs extends any[] = any[]> {
|
19
|
+
cwd: string;
|
20
|
+
key: Key<TFnArgs>;
|
21
|
+
pretty?: boolean;
|
22
|
+
}
|
23
|
+
|
24
|
+
namespace Options {
|
25
|
+
export type Partial = $Partial<Options>;
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
export class FsCache<TConfig extends FsCache.Options.Partial> {
|
30
|
+
private readonly _config: TConfig;
|
31
|
+
|
32
|
+
private constructor(config: TConfig) {
|
33
|
+
this._config = config;
|
34
|
+
}
|
35
|
+
|
36
|
+
public static cwd(this: void, cwd: string) {
|
37
|
+
return new FsCache({ cwd });
|
38
|
+
}
|
39
|
+
|
40
|
+
public key<TFnArgs extends any[], TKey extends FsCache.Key<TFnArgs>>(
|
41
|
+
key: TKey,
|
42
|
+
) {
|
43
|
+
return new FsCache({ ...this._config, key });
|
44
|
+
}
|
45
|
+
|
46
|
+
public pretty(pretty: boolean) {
|
47
|
+
return new FsCache({ ...this._config, pretty });
|
48
|
+
}
|
49
|
+
|
50
|
+
private static keyToRelativePath(key: FsCache.Key, args: any[]) {
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
52
|
+
return `./${key.map((k) => (k instanceof Function ? k(...args) : k)).join("/")}.json`;
|
53
|
+
}
|
54
|
+
|
55
|
+
public async get<TFnArgs extends any[], TValue = any>(
|
56
|
+
this: FsCache<FsCache.Options<TFnArgs>>,
|
57
|
+
args: TFnArgs,
|
58
|
+
): Promise<["miss", null] | ["hit", TValue]> {
|
59
|
+
const relativePath = FsCache.keyToRelativePath(this._config.key, args);
|
60
|
+
const cacheDir = dir(this._config.cwd);
|
61
|
+
|
62
|
+
if (!(await cacheDir.file(relativePath).exists())) return ["miss", null];
|
63
|
+
|
64
|
+
const value = await cacheDir
|
65
|
+
.file(relativePath)
|
66
|
+
.read.json<CacheValue<TValue>>();
|
67
|
+
|
68
|
+
if (value === null) return ["miss", null];
|
69
|
+
|
70
|
+
// Todo: FIX CACHE HIT
|
71
|
+
if (!deepEqual(value.input, args)) return ["miss", null];
|
72
|
+
|
73
|
+
return ["hit", value.output];
|
74
|
+
}
|
75
|
+
|
76
|
+
public async setDefault<TFnArgs extends any[], TValue = any>(
|
77
|
+
this: FsCache<FsCache.Options<TFnArgs>>,
|
78
|
+
args: TFnArgs,
|
79
|
+
defaultValue: TValue,
|
80
|
+
) {
|
81
|
+
const [status] = await this.get<TFnArgs, TValue>(args);
|
82
|
+
if (status === "hit") return false;
|
83
|
+
return this.set(args, defaultValue);
|
84
|
+
}
|
85
|
+
|
86
|
+
public async set<TFnArgs extends any[]>(
|
87
|
+
this: FsCache<FsCache.Options<TFnArgs>>,
|
88
|
+
args: TFnArgs,
|
89
|
+
value: any,
|
90
|
+
) {
|
91
|
+
const relativePath = FsCache.keyToRelativePath(this._config.key, args);
|
92
|
+
const file = dir(this._config.cwd).file(relativePath);
|
93
|
+
return file.write.text(
|
94
|
+
JSON.stringify(
|
95
|
+
{ input: args, output: value } satisfies CacheValue,
|
96
|
+
null,
|
97
|
+
this._config.pretty ? 2 : undefined,
|
98
|
+
),
|
99
|
+
);
|
100
|
+
}
|
101
|
+
|
102
|
+
public fn<TFnArgs extends any[], TFnOutput>(
|
103
|
+
this: FsCache<FsCache.Options<TFnArgs>>,
|
104
|
+
fn: (...args: TFnArgs) => Promise<TFnOutput> | TFnOutput,
|
105
|
+
): (...args: TFnArgs) => Promise<TFnOutput> {
|
106
|
+
return async (...args: TFnArgs): Promise<TFnOutput> => {
|
107
|
+
const [status, value] = await this.get<TFnArgs, TFnOutput>(args);
|
108
|
+
if (status === "hit") return value;
|
109
|
+
|
110
|
+
const output = await Promise.resolve(fn(...args));
|
111
|
+
await this.set(args, output);
|
112
|
+
return output;
|
113
|
+
};
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
export const fsCache = FsCache.cwd;
|