@synstack/fs-cache 1.0.1-alpha.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/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;