@synstack/fs-cache 1.4.3 → 1.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1 +1,104 @@
1
1
  # @synstack/fs-cache
2
+
3
+ Human-friendly file system caching
4
+
5
+ > [!WARNING]
6
+ > This package is included in the [@synstack/synscript](https://github.com/pAIrprogio/synscript) package. It is not recommended to install both packages at the same time.
7
+
8
+ ## What is it for?
9
+
10
+ Sometimes you need to cache expensive function results between program runs. This package makes it easy to cache function outputs to disk with type safety while keeping the file system clean and cache entries readable.
11
+
12
+ ```typescript
13
+ import { fsCache } from "@synstack/fs-cache";
14
+
15
+ // Create a cache in the .cache/expensive/function.json file
16
+ const cache = fsCache(".cache").key(["expensive", "function"]).pretty(true);
17
+
18
+ // Cache an expensive function
19
+ const cachedExpensiveFunction = cache.fn(expensiveFunction);
20
+ ```
21
+
22
+ ## Caching philosophy
23
+
24
+ The idea is to generate cache entries without poluting the file system with expired entries. This requires a two-step matching process:
25
+
26
+ - First file is matched based on the key defined with `key()`
27
+ - If the file exists, a second check is done based on input parameters and can be customized with `signatureFn()` which defaults to [`deepEqual`](#Deep-Equality)
28
+ - If it matches, the output is returned from the cache
29
+ - If it doesn't match or the file doesn't exist, the function is called, the output is cached, and then returned
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ # Using npm
35
+ npm install @synstack/fs-cache
36
+
37
+ # Using yarn
38
+ yarn add @synstack/fs-cache
39
+
40
+ # Using pnpm
41
+ pnpm add @synstack/fs-cache
42
+ ```
43
+
44
+ ## Features
45
+
46
+ ### [@synstack/fs](../fs/README.md) interoperability
47
+
48
+ Cache directory can be initialized using an `FsDir` instance:
49
+
50
+ ```typescript
51
+ import { dir } from "@synstack/fs";
52
+
53
+ const cacheDir = dir(".cache");
54
+ const cache = fsCache(cacheDir);
55
+ ```
56
+
57
+ ### Function Caching
58
+
59
+ Cache expensive function results with type safety:
60
+
61
+ ```typescript
62
+ import { fsCache } from "@synstack/fs-cache";
63
+
64
+ const cache = fsCache(".cache");
65
+
66
+ // Cache with static key
67
+ const cachedFn = cache.key(["myFunction"]).fn((x: number) => x * x);
68
+ ```
69
+
70
+ ### Cache Control
71
+
72
+ Fine-grained control over cache behavior:
73
+
74
+ ```typescript
75
+ // Pretty-print cached JSON
76
+ const cache = fsCache(".cache").pretty(true);
77
+
78
+ // Custom cache signature generation
79
+ const cache2 = fsCache(".cache")
80
+ .signatureFn((arg: string) => arg.toLowerCase())
81
+ .key(["normalized"]);
82
+
83
+ // Lock cached values
84
+ await cache.lock(true, ["key"]); // Prevent updates
85
+ await cache.lock(false, ["key"]); // Allow updates
86
+
87
+ // Manual cache operations
88
+ const [status, value] = await cache.get(["key"]);
89
+ await cache.set(["key"], "value");
90
+
91
+ // Set a default cache value if the file doesn't exist
92
+ await cache.setDefault(["key"], "default");
93
+ ```
94
+
95
+ ## Deep Equality
96
+
97
+ A custom deep equality function is provided.
98
+ The only custom behavior implemented, is that objects with undefined values are considered equal to objects with matching missing values.
99
+
100
+ ```typescript
101
+ import { deepEqual } from "@synstack/fs-cache";
102
+
103
+ deepEqual({ a: undefined }, {}); // true
104
+ ```
@@ -21,13 +21,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var fs_cache_index_exports = {};
22
22
  __export(fs_cache_index_exports, {
23
23
  FsCache: () => FsCache,
24
+ deepEqual: () => deepEqual,
24
25
  fsCache: () => fsCache
25
26
  });
26
27
  module.exports = __toCommonJS(fs_cache_index_exports);
27
28
 
28
- // src/fs-cache.lib.ts
29
- var import_fs = require("@synstack/fs");
30
-
31
29
  // src/deepEqual.lib.ts
32
30
  function deepEqual(obj1, obj2) {
33
31
  if (obj1 === obj2) {
@@ -55,6 +53,7 @@ function isPrimitive(obj) {
55
53
  }
56
54
 
57
55
  // src/fs-cache.lib.ts
56
+ var import_fs = require("@synstack/fs");
58
57
  var FsCache = class _FsCache {
59
58
  _config;
60
59
  constructor(config) {
@@ -143,6 +142,7 @@ var fsCache = FsCache.cwd;
143
142
  // Annotate the CommonJS export names for ESM import in node:
144
143
  0 && (module.exports = {
145
144
  FsCache,
145
+ deepEqual,
146
146
  fsCache
147
147
  });
148
148
  //# sourceMappingURL=fs-cache.index.cjs.map
@@ -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 { 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<\n TConfig extends FsCache.Options.Partial = FsCache.Options.Partial,\n> {\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":[]}
1
+ {"version":3,"sources":["../src/fs-cache.index.ts","../src/deepEqual.lib.ts","../src/fs-cache.lib.ts"],"sourcesContent":["export * from \"./deepEqual.lib.ts\";\nexport * from \"./fs-cache.lib.ts\";\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","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<\n TConfig extends FsCache.Options.Partial = FsCache.Options.Partial,\n> {\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"],"mappings":"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","names":[]}
@@ -1,5 +1,7 @@
1
1
  import { FsDir } from '@synstack/fs';
2
2
 
3
+ declare function deepEqual(obj1: any, obj2: any): boolean;
4
+
3
5
  /**
4
6
  * One or more values
5
7
  */
@@ -45,4 +47,4 @@ declare class FsCache<TConfig extends FsCache.Options.Partial = FsCache.Options.
45
47
  }
46
48
  declare const fsCache: typeof FsCache.cwd;
47
49
 
48
- export { FsCache, fsCache };
50
+ export { FsCache, deepEqual, fsCache };
@@ -1,5 +1,7 @@
1
1
  import { FsDir } from '@synstack/fs';
2
2
 
3
+ declare function deepEqual(obj1: any, obj2: any): boolean;
4
+
3
5
  /**
4
6
  * One or more values
5
7
  */
@@ -45,4 +47,4 @@ declare class FsCache<TConfig extends FsCache.Options.Partial = FsCache.Options.
45
47
  }
46
48
  declare const fsCache: typeof FsCache.cwd;
47
49
 
48
- export { FsCache, fsCache };
50
+ export { FsCache, deepEqual, fsCache };
@@ -1,6 +1,3 @@
1
- // src/fs-cache.lib.ts
2
- import { dir, FsDir } from "@synstack/fs";
3
-
4
1
  // src/deepEqual.lib.ts
5
2
  function deepEqual(obj1, obj2) {
6
3
  if (obj1 === obj2) {
@@ -28,6 +25,7 @@ function isPrimitive(obj) {
28
25
  }
29
26
 
30
27
  // src/fs-cache.lib.ts
28
+ import { dir, FsDir } from "@synstack/fs";
31
29
  var FsCache = class _FsCache {
32
30
  _config;
33
31
  constructor(config) {
@@ -115,6 +113,7 @@ var FsCache = class _FsCache {
115
113
  var fsCache = FsCache.cwd;
116
114
  export {
117
115
  FsCache,
116
+ deepEqual,
118
117
  fsCache
119
118
  };
120
119
  //# sourceMappingURL=fs-cache.index.js.map
@@ -1 +1 @@
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<\n TConfig extends FsCache.Options.Partial = FsCache.Options.Partial,\n> {\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":[]}
1
+ {"version":3,"sources":["../src/deepEqual.lib.ts","../src/fs-cache.lib.ts"],"sourcesContent":["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","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<\n TConfig extends FsCache.Options.Partial = FsCache.Options.Partial,\n> {\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"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -4,11 +4,12 @@
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "version": "1.4.3",
7
+ "version": "1.4.5",
8
8
  "description": "Human-friendly file system caching",
9
9
  "keywords": [
10
10
  "cache",
11
- "memoize"
11
+ "memoize",
12
+ "file-system"
12
13
  ],
13
14
  "author": {
14
15
  "name": "pAIrprog",
@@ -17,7 +18,7 @@
17
18
  "homepage": "https://github.com/pAIrprogio/synscript/tree/main/packages/fs-cache",
18
19
  "repository": {
19
20
  "type": "git",
20
- "url": "https://github.com/pAIrprogio/syn-stack.git",
21
+ "url": "https://github.com/pAIrprogio/synscript.git",
21
22
  "directory": "packages/fs-cache"
22
23
  },
23
24
  "license": "Apache-2.0",
@@ -43,7 +44,7 @@
43
44
  }
44
45
  },
45
46
  "dependencies": {
46
- "@synstack/fs": "1.4.2"
47
+ "@synstack/fs": "1.4.4"
47
48
  },
48
49
  "devDependencies": {
49
50
  "@types/node": "^22.10.1",
@@ -55,5 +56,5 @@
55
56
  "!src/**/*.test.ts",
56
57
  "dist/**/*"
57
58
  ],
58
- "gitHead": "21afc6329d10fefdb7473db0e48c89c32a9c2f8c"
59
+ "gitHead": "886036553ab02c6c1b98289b1de64240de866521"
59
60
  }
@@ -1 +1,2 @@
1
+ export * from "./deepEqual.lib.ts";
1
2
  export * from "./fs-cache.lib.ts";