@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 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;