@synstack/fs-cache 1.0.11 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -72,14 +72,32 @@ var FsCache = class _FsCache {
72
72
  static keyToRelativePath(key, args) {
73
73
  return `./${key.map((k) => k instanceof Function ? k(...args) : k).join("/")}.json`;
74
74
  }
75
+ async lock(isLocked, args) {
76
+ const relativePath = _FsCache.keyToRelativePath(this._config.key, args);
77
+ const cacheDir = (0, import_fs.dir)(this._config.cwd);
78
+ const file = cacheDir.file(relativePath);
79
+ if (!await file.exists()) {
80
+ console.warn(
81
+ "[fs-cache] Failed to update lock on non-existing file",
82
+ file.path
83
+ );
84
+ return;
85
+ }
86
+ const value = await file.read.json();
87
+ await file.write.json({
88
+ isLocked,
89
+ ...value
90
+ });
91
+ }
75
92
  async get(args) {
76
93
  const relativePath = _FsCache.keyToRelativePath(this._config.key, args);
77
94
  const cacheDir = (0, import_fs.dir)(this._config.cwd);
78
95
  if (!await cacheDir.file(relativePath).exists()) return ["miss", null];
79
96
  const value = await cacheDir.file(relativePath).read.json();
80
97
  if (value === null) return ["miss", null];
81
- if (!deepEqual(value.input, args)) return ["miss", null];
82
- return ["hit", value.output];
98
+ if (value.isLocked || deepEqual(value.input, args))
99
+ return ["hit", value.output];
100
+ return ["miss", null];
83
101
  }
84
102
  async setDefault(args, defaultValue) {
85
103
  const [status] = await this.get(args);
@@ -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\";\n","import { OneToN } from \"@shared/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":[]}
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/ts.utils\";\nimport { dir } from \"@synstack/fs\";\nimport { deepEqual } from \"./deepEqual.lib\";\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 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 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 isLocked,\n ...value,\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 (value.isLocked || deepEqual(value.input, args))\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 { 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":[]}
@@ -29,6 +29,7 @@ declare class FsCache<TConfig extends FsCache.Options.Partial> {
29
29
  pretty: boolean;
30
30
  }>;
31
31
  private static keyToRelativePath;
32
+ lock<TFnArgs extends any[]>(this: FsCache<FsCache.Options<TFnArgs>>, isLocked: boolean, args: TFnArgs): Promise<void>;
32
33
  get<TFnArgs extends any[], TValue = any>(this: FsCache<FsCache.Options<TFnArgs>>, args: TFnArgs): Promise<["miss", null] | ["hit", TValue]>;
33
34
  setDefault<TFnArgs extends any[], TValue = any>(this: FsCache<FsCache.Options<TFnArgs>>, args: TFnArgs, defaultValue: TValue): Promise<false | void>;
34
35
  set<TFnArgs extends any[]>(this: FsCache<FsCache.Options<TFnArgs>>, args: TFnArgs, value: any): Promise<void>;
@@ -29,6 +29,7 @@ declare class FsCache<TConfig extends FsCache.Options.Partial> {
29
29
  pretty: boolean;
30
30
  }>;
31
31
  private static keyToRelativePath;
32
+ lock<TFnArgs extends any[]>(this: FsCache<FsCache.Options<TFnArgs>>, isLocked: boolean, args: TFnArgs): Promise<void>;
32
33
  get<TFnArgs extends any[], TValue = any>(this: FsCache<FsCache.Options<TFnArgs>>, args: TFnArgs): Promise<["miss", null] | ["hit", TValue]>;
33
34
  setDefault<TFnArgs extends any[], TValue = any>(this: FsCache<FsCache.Options<TFnArgs>>, args: TFnArgs, defaultValue: TValue): Promise<false | void>;
34
35
  set<TFnArgs extends any[]>(this: FsCache<FsCache.Options<TFnArgs>>, args: TFnArgs, value: any): Promise<void>;
@@ -45,14 +45,32 @@ var FsCache = class _FsCache {
45
45
  static keyToRelativePath(key, args) {
46
46
  return `./${key.map((k) => k instanceof Function ? k(...args) : k).join("/")}.json`;
47
47
  }
48
+ async lock(isLocked, args) {
49
+ const relativePath = _FsCache.keyToRelativePath(this._config.key, args);
50
+ const cacheDir = dir(this._config.cwd);
51
+ const file = cacheDir.file(relativePath);
52
+ if (!await file.exists()) {
53
+ console.warn(
54
+ "[fs-cache] Failed to update lock on non-existing file",
55
+ file.path
56
+ );
57
+ return;
58
+ }
59
+ const value = await file.read.json();
60
+ await file.write.json({
61
+ isLocked,
62
+ ...value
63
+ });
64
+ }
48
65
  async get(args) {
49
66
  const relativePath = _FsCache.keyToRelativePath(this._config.key, args);
50
67
  const cacheDir = dir(this._config.cwd);
51
68
  if (!await cacheDir.file(relativePath).exists()) return ["miss", null];
52
69
  const value = await cacheDir.file(relativePath).read.json();
53
70
  if (value === null) return ["miss", null];
54
- if (!deepEqual(value.input, args)) return ["miss", null];
55
- return ["hit", value.output];
71
+ if (value.isLocked || deepEqual(value.input, args))
72
+ return ["hit", value.output];
73
+ return ["miss", null];
56
74
  }
57
75
  async setDefault(args, defaultValue) {
58
76
  const [status] = await this.get(args);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/fs-cache.lib.ts","../src/deepEqual.lib.ts"],"sourcesContent":["import { OneToN } from \"@shared/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":[]}
1
+ {"version":3,"sources":["../src/fs-cache.lib.ts","../src/deepEqual.lib.ts"],"sourcesContent":["import { OneToN } from \"@shared/ts.utils\";\nimport { dir } from \"@synstack/fs\";\nimport { deepEqual } from \"./deepEqual.lib\";\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 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 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 isLocked,\n ...value,\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 (value.isLocked || deepEqual(value.input, args))\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 { 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 CHANGED
@@ -5,7 +5,7 @@
5
5
  "access": "public"
6
6
  },
7
7
  "packageManager": "yarn@4.4.0",
8
- "version": "1.0.11",
8
+ "version": "1.1.0",
9
9
  "description": "Human-friendly file system caching",
10
10
  "keywords": [
11
11
  "cache",
@@ -44,7 +44,7 @@
44
44
  }
45
45
  },
46
46
  "dependencies": {
47
- "@synstack/fs": "1.2.1"
47
+ "@synstack/fs": "1.2.2"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/node": "^22.7.0",
@@ -57,5 +57,5 @@
57
57
  "!src/**/*.test.ts",
58
58
  "dist/**/*"
59
59
  ],
60
- "gitHead": "16734c29ef679fd828501b5656be1359308027bb"
60
+ "gitHead": "236e77a607139bd17a3956d2e84ffda121b5681f"
61
61
  }
@@ -5,6 +5,7 @@ import { deepEqual } from "./deepEqual.lib";
5
5
  type $Partial<T> = Partial<T>;
6
6
 
7
7
  interface CacheValue<TValue = any> {
8
+ isLocked?: boolean;
8
9
  input: any;
9
10
  output: TValue;
10
11
  }
@@ -52,6 +53,30 @@ export class FsCache<TConfig extends FsCache.Options.Partial> {
52
53
  return `./${key.map((k) => (k instanceof Function ? k(...args) : k)).join("/")}.json`;
53
54
  }
54
55
 
56
+ public async lock<TFnArgs extends any[]>(
57
+ this: FsCache<FsCache.Options<TFnArgs>>,
58
+ isLocked: boolean,
59
+ args: TFnArgs,
60
+ ) {
61
+ const relativePath = FsCache.keyToRelativePath(this._config.key, args);
62
+ const cacheDir = dir(this._config.cwd);
63
+ const file = cacheDir.file(relativePath);
64
+
65
+ if (!(await file.exists())) {
66
+ console.warn(
67
+ "[fs-cache] Failed to update lock on non-existing file",
68
+ file.path,
69
+ );
70
+ return;
71
+ }
72
+
73
+ const value = await file.read.json<CacheValue>();
74
+ await file.write.json({
75
+ isLocked,
76
+ ...value,
77
+ });
78
+ }
79
+
55
80
  public async get<TFnArgs extends any[], TValue = any>(
56
81
  this: FsCache<FsCache.Options<TFnArgs>>,
57
82
  args: TFnArgs,
@@ -67,10 +92,10 @@ export class FsCache<TConfig extends FsCache.Options.Partial> {
67
92
 
68
93
  if (value === null) return ["miss", null];
69
94
 
70
- // Todo: FIX CACHE HIT
71
- if (!deepEqual(value.input, args)) return ["miss", null];
95
+ if (value.isLocked || deepEqual(value.input, args))
96
+ return ["hit", value.output];
72
97
 
73
- return ["hit", value.output];
98
+ return ["miss", null];
74
99
  }
75
100
 
76
101
  public async setDefault<TFnArgs extends any[], TValue = any>(