@chriscdn/file-cache 0.0.14 → 2.0.1
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/lib/index.d.mts +64 -0
- package/lib/index.mjs +114 -0
- package/lib/index.mjs.map +1 -0
- package/package.json +25 -22
- package/lib/file-cache.cjs +0 -142
- package/lib/file-cache.cjs.map +0 -1
- package/lib/file-cache.modern.js +0 -135
- package/lib/file-cache.modern.js.map +0 -1
- package/lib/file-cache.module.js +0 -135
- package/lib/file-cache.module.js.map +0 -1
- package/lib/index.d.ts +0 -61
package/lib/index.d.mts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
type DirectoryPath = string;
|
|
3
|
+
type FileName = string;
|
|
4
|
+
type FilePath = string;
|
|
5
|
+
type Milliseconds = number;
|
|
6
|
+
export type FileCacheOptions<T extends Record<string, any>> = {
|
|
7
|
+
cachePath: DirectoryPath;
|
|
8
|
+
autoCreateCachePath?: boolean;
|
|
9
|
+
cb: (filePath: FilePath, args: T, cache: FileCache<T>) => Promise<void>;
|
|
10
|
+
ext: (args: T) => string | Promise<string>;
|
|
11
|
+
resolveCacheKey?: (args: T) => string | Promise<string>;
|
|
12
|
+
ttl: Milliseconds;
|
|
13
|
+
cleanupInterval?: Milliseconds;
|
|
14
|
+
};
|
|
15
|
+
declare class FileCache<T extends Record<string, any>> {
|
|
16
|
+
private _cachePath;
|
|
17
|
+
private _cb;
|
|
18
|
+
private _ttl;
|
|
19
|
+
private _cleanupInterval;
|
|
20
|
+
private _resolveCacheKey;
|
|
21
|
+
private _ext;
|
|
22
|
+
private _intervalId;
|
|
23
|
+
private _semaphore;
|
|
24
|
+
private _cleanupGroupSemaphore;
|
|
25
|
+
constructor({ cachePath, autoCreateCachePath, cb, ext, ttl, cleanupInterval, resolveCacheKey }: FileCacheOptions<T>);
|
|
26
|
+
/**
|
|
27
|
+
* Resolves the filename for the cache based on the provided arguments.
|
|
28
|
+
*
|
|
29
|
+
* Uses the provided `resolveFileName` function if available, or falls back to
|
|
30
|
+
* generating a hash from the arguments to create a unique file name.
|
|
31
|
+
*/
|
|
32
|
+
private resolveFileName;
|
|
33
|
+
/**
|
|
34
|
+
* Retrieves a cached file for the given arguments.
|
|
35
|
+
* If the file doesn't exist, it is generated by the callback and stored.
|
|
36
|
+
*
|
|
37
|
+
* Concurrency-safe, ensuring no conflicts when multiple requests are made
|
|
38
|
+
* for the same file.
|
|
39
|
+
*
|
|
40
|
+
* @param args The arguments used to determine the file's identity and generation
|
|
41
|
+
* @returns The path to the cached or newly created file
|
|
42
|
+
*/
|
|
43
|
+
getFile(args: T): Promise<FilePath>;
|
|
44
|
+
/**
|
|
45
|
+
* Resolves the full file path for the cached file based on the provided arguments.
|
|
46
|
+
*
|
|
47
|
+
* @param args The arguments used to resolve the file path
|
|
48
|
+
* @returns The full path to the cached file
|
|
49
|
+
*/
|
|
50
|
+
resolveFilePath(args: T): Promise<FilePath>;
|
|
51
|
+
/**
|
|
52
|
+
* Stops the background cleanup process, but does not delete any cached files.
|
|
53
|
+
*/
|
|
54
|
+
destroy(): void;
|
|
55
|
+
has(args: T): Promise<boolean>;
|
|
56
|
+
/**
|
|
57
|
+
* Runs a cleanup pass to remove files older than 1 day from the cache.
|
|
58
|
+
* This is called periodically, but can be triggered manually.
|
|
59
|
+
*/
|
|
60
|
+
cleanup(): Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
export { type DirectoryPath, FileCache, type FileName, type FilePath };
|
|
64
|
+
//# sourceMappingURL=index.d.mts.map
|
package/lib/index.mjs
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { pathExists, pathExistsSync } from "path-exists";
|
|
4
|
+
import sha1 from "sha1";
|
|
5
|
+
import { GroupSemaphore, Semaphore } from "@chriscdn/promise-semaphore";
|
|
6
|
+
import touch from "touch";
|
|
7
|
+
import { findNuke } from "@chriscdn/find-nuke";
|
|
8
|
+
import { Duration } from "@chriscdn/duration";
|
|
9
|
+
//#region src/index.ts
|
|
10
|
+
var FileCache = class {
|
|
11
|
+
_cachePath;
|
|
12
|
+
_cb;
|
|
13
|
+
_ttl;
|
|
14
|
+
_cleanupInterval;
|
|
15
|
+
_resolveCacheKey;
|
|
16
|
+
_ext;
|
|
17
|
+
_intervalId = null;
|
|
18
|
+
_semaphore = new Semaphore();
|
|
19
|
+
_cleanupGroupSemaphore = new GroupSemaphore();
|
|
20
|
+
constructor({ cachePath, autoCreateCachePath, cb, ext, ttl, cleanupInterval, resolveCacheKey }) {
|
|
21
|
+
this._cachePath = cachePath;
|
|
22
|
+
this._cb = cb;
|
|
23
|
+
this._ext = ext;
|
|
24
|
+
this._ttl = ttl;
|
|
25
|
+
this._cleanupInterval = cleanupInterval ?? Duration.toMilliseconds({ days: 1 });
|
|
26
|
+
this._resolveCacheKey = resolveCacheKey ?? ((args) => JSON.stringify(args));
|
|
27
|
+
if (pathExistsSync(cachePath)) {} else if (autoCreateCachePath) fs.mkdirSync(cachePath, { recursive: true });
|
|
28
|
+
else throw new Error("💥 FileCache error: cachePath does not exist.");
|
|
29
|
+
this.cleanup();
|
|
30
|
+
this._intervalId = setInterval(this.cleanup.bind(this), this._cleanupInterval);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolves the filename for the cache based on the provided arguments.
|
|
34
|
+
*
|
|
35
|
+
* Uses the provided `resolveFileName` function if available, or falls back to
|
|
36
|
+
* generating a hash from the arguments to create a unique file name.
|
|
37
|
+
*/
|
|
38
|
+
async resolveFileName(args) {
|
|
39
|
+
const [name, ext] = await Promise.all([this._resolveCacheKey(args), this._ext(args)]);
|
|
40
|
+
const resolvedFileName = sha1(JSON.stringify([name, ext]));
|
|
41
|
+
return path.format({
|
|
42
|
+
name: resolvedFileName,
|
|
43
|
+
ext
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Retrieves a cached file for the given arguments.
|
|
48
|
+
* If the file doesn't exist, it is generated by the callback and stored.
|
|
49
|
+
*
|
|
50
|
+
* Concurrency-safe, ensuring no conflicts when multiple requests are made
|
|
51
|
+
* for the same file.
|
|
52
|
+
*
|
|
53
|
+
* @param args The arguments used to determine the file's identity and generation
|
|
54
|
+
* @returns The path to the cached or newly created file
|
|
55
|
+
*/
|
|
56
|
+
async getFile(args) {
|
|
57
|
+
const filePath = await this.resolveFilePath(args);
|
|
58
|
+
try {
|
|
59
|
+
await Promise.all([this._cleanupGroupSemaphore.acquire("getFile"), this._semaphore.acquire(filePath)]);
|
|
60
|
+
if (await pathExists(filePath)) touch(filePath);
|
|
61
|
+
else {
|
|
62
|
+
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
63
|
+
await this._cb(filePath, args, this);
|
|
64
|
+
}
|
|
65
|
+
} finally {
|
|
66
|
+
this._semaphore.release(filePath);
|
|
67
|
+
this._cleanupGroupSemaphore.release("getFile");
|
|
68
|
+
}
|
|
69
|
+
return filePath;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Resolves the full file path for the cached file based on the provided arguments.
|
|
73
|
+
*
|
|
74
|
+
* @param args The arguments used to resolve the file path
|
|
75
|
+
* @returns The full path to the cached file
|
|
76
|
+
*/
|
|
77
|
+
async resolveFilePath(args) {
|
|
78
|
+
const fileName = await this.resolveFileName(args);
|
|
79
|
+
return path.resolve(this._cachePath, fileName[0], fileName[1], fileName[2], fileName);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Stops the background cleanup process, but does not delete any cached files.
|
|
83
|
+
*/
|
|
84
|
+
destroy() {
|
|
85
|
+
if (this._intervalId) {
|
|
86
|
+
clearInterval(this._intervalId);
|
|
87
|
+
this._intervalId = null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async has(args) {
|
|
91
|
+
const filePath = await this.resolveFilePath(args);
|
|
92
|
+
return await pathExists(filePath);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Runs a cleanup pass to remove files older than 1 day from the cache.
|
|
96
|
+
* This is called periodically, but can be triggered manually.
|
|
97
|
+
*/
|
|
98
|
+
async cleanup() {
|
|
99
|
+
try {
|
|
100
|
+
await this._cleanupGroupSemaphore.acquire("cleanup");
|
|
101
|
+
await findNuke(this._cachePath, {
|
|
102
|
+
olderThan: this._ttl,
|
|
103
|
+
verbose: true,
|
|
104
|
+
deleteEmptyDirectories: true
|
|
105
|
+
});
|
|
106
|
+
} finally {
|
|
107
|
+
this._cleanupGroupSemaphore.release("cleanup");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
//#endregion
|
|
112
|
+
export { FileCache };
|
|
113
|
+
|
|
114
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import path from \"path\";\nimport fs from \"fs\";\nimport { pathExists, pathExistsSync } from \"path-exists\";\nimport sha1 from \"sha1\";\nimport { GroupSemaphore, Semaphore } from \"@chriscdn/promise-semaphore\";\nimport touch from \"touch\";\nimport { findNuke } from \"@chriscdn/find-nuke\";\nimport { Duration } from \"@chriscdn/duration\";\n\ntype DirectoryPath = string;\ntype FileName = string;\ntype FilePath = string;\ntype Milliseconds = number;\n\nexport type FileCacheOptions<T extends Record<string, any>> = {\n cachePath: DirectoryPath;\n autoCreateCachePath?: boolean;\n cb: (filePath: FilePath, args: T, cache: FileCache<T>) => Promise<void>;\n ext: (args: T) => string | Promise<string>;\n resolveCacheKey?: (args: T) => string | Promise<string>;\n ttl: Milliseconds;\n cleanupInterval?: Milliseconds;\n};\n\nclass FileCache<T extends Record<string, any>> {\n private _cachePath: FileCacheOptions<T>[\"cachePath\"];\n private _cb: FileCacheOptions<T>[\"cb\"];\n private _ttl: FileCacheOptions<T>[\"ttl\"];\n private _cleanupInterval: FileCacheOptions<T>[\"cleanupInterval\"];\n private _resolveCacheKey: Required<FileCacheOptions<T>>[\"resolveCacheKey\"];\n private _ext: FileCacheOptions<T>[\"ext\"];\n private _intervalId: ReturnType<typeof setInterval> | null = null;\n private _semaphore: Semaphore = new Semaphore();\n private _cleanupGroupSemaphore: GroupSemaphore = new GroupSemaphore();\n\n constructor({\n cachePath,\n autoCreateCachePath,\n cb,\n ext,\n ttl,\n cleanupInterval,\n resolveCacheKey,\n }: FileCacheOptions<T>) {\n this._cachePath = cachePath;\n this._cb = cb;\n this._ext = ext;\n this._ttl = ttl;\n this._cleanupInterval =\n cleanupInterval ?? Duration.toMilliseconds({ days: 1 });\n\n this._resolveCacheKey =\n resolveCacheKey ?? ((args: T) => JSON.stringify(args));\n\n // Startup code\n if (pathExistsSync(cachePath)) {\n // we're good\n } else if (autoCreateCachePath) {\n fs.mkdirSync(cachePath, { recursive: true });\n } else {\n throw new Error(\"💥 FileCache error: cachePath does not exist.\");\n }\n\n // fire and forget\n this.cleanup();\n\n this._intervalId = setInterval(\n this.cleanup.bind(this),\n this._cleanupInterval,\n );\n }\n\n /**\n * Resolves the filename for the cache based on the provided arguments.\n *\n * Uses the provided `resolveFileName` function if available, or falls back to\n * generating a hash from the arguments to create a unique file name.\n */\n private async resolveFileName(args: T): Promise<FileName> {\n const [name, ext] = await Promise.all([\n this._resolveCacheKey(args),\n this._ext(args),\n ]);\n\n const resolvedFileName = sha1(JSON.stringify([name, ext]));\n\n return path.format({\n name: resolvedFileName,\n ext,\n }) as FileName;\n }\n\n /**\n * Retrieves a cached file for the given arguments.\n * If the file doesn't exist, it is generated by the callback and stored.\n *\n * Concurrency-safe, ensuring no conflicts when multiple requests are made\n * for the same file.\n *\n * @param args The arguments used to determine the file's identity and generation\n * @returns The path to the cached or newly created file\n */\n async getFile(args: T): Promise<FilePath> {\n const filePath = await this.resolveFilePath(args);\n\n try {\n await Promise.all([\n this._cleanupGroupSemaphore.acquire(\"getFile\"),\n this._semaphore.acquire(filePath),\n ]);\n\n if (await pathExists(filePath)) {\n // fire and forget\n touch(filePath);\n } else {\n await fs.promises.mkdir(path.dirname(filePath), { recursive: true });\n await this._cb(filePath, args, this);\n }\n } finally {\n this._semaphore.release(filePath);\n this._cleanupGroupSemaphore.release(\"getFile\");\n }\n\n return filePath;\n }\n\n /**\n * Resolves the full file path for the cached file based on the provided arguments.\n *\n * @param args The arguments used to resolve the file path\n * @returns The full path to the cached file\n */\n async resolveFilePath(args: T): Promise<FilePath> {\n const fileName = await this.resolveFileName(args);\n\n const filePath = path.resolve(\n this._cachePath,\n fileName[0]!,\n fileName[1]!,\n fileName[2]!,\n fileName,\n );\n\n return filePath;\n }\n\n /**\n * Stops the background cleanup process, but does not delete any cached files.\n */\n destroy() {\n if (this._intervalId) {\n clearInterval(this._intervalId);\n this._intervalId = null;\n }\n }\n\n async has(args: T): Promise<boolean> {\n const filePath = await this.resolveFilePath(args);\n return await pathExists(filePath);\n }\n\n /**\n * Runs a cleanup pass to remove files older than 1 day from the cache.\n * This is called periodically, but can be triggered manually.\n */\n async cleanup() {\n try {\n await this._cleanupGroupSemaphore.acquire(\"cleanup\");\n await findNuke(this._cachePath, {\n olderThan: this._ttl,\n verbose: true,\n deleteEmptyDirectories: true,\n });\n } finally {\n this._cleanupGroupSemaphore.release(\"cleanup\");\n }\n }\n}\n\nexport { type DirectoryPath, FileCache, type FileName, type FilePath };\n"],"mappings":";;;;;;;;;AAwBA,IAAM,YAAN,MAA+C;CAC7C;CACA;CACA;CACA;CACA;CACA;CACA,cAA6D;CAC7D,aAAgC,IAAI,UAAU;CAC9C,yBAAiD,IAAI,eAAe;CAEpE,YAAY,EACV,WACA,qBACA,IACA,KACA,KACA,iBACA,mBACsB;EACtB,KAAK,aAAa;EAClB,KAAK,MAAM;EACX,KAAK,OAAO;EACZ,KAAK,OAAO;EACZ,KAAK,mBACH,mBAAmB,SAAS,eAAe,EAAE,MAAM,EAAE,CAAC;EAExD,KAAK,mBACH,qBAAqB,SAAY,KAAK,UAAU,IAAI;EAGtD,IAAI,eAAe,SAAS,GAAG,CAE/B,OAAO,IAAI,qBACT,GAAG,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;OAE3C,MAAM,IAAI,MAAM,+CAA+C;EAIjE,KAAK,QAAQ;EAEb,KAAK,cAAc,YACjB,KAAK,QAAQ,KAAK,IAAI,GACtB,KAAK,gBACP;CACF;;;;;;;CAQA,MAAc,gBAAgB,MAA4B;EACxD,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ,IAAI,CACpC,KAAK,iBAAiB,IAAI,GAC1B,KAAK,KAAK,IAAI,CAChB,CAAC;EAED,MAAM,mBAAmB,KAAK,KAAK,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;EAEzD,OAAO,KAAK,OAAO;GACjB,MAAM;GACN;EACF,CAAC;CACH;;;;;;;;;;;CAYA,MAAM,QAAQ,MAA4B;EACxC,MAAM,WAAW,MAAM,KAAK,gBAAgB,IAAI;EAEhD,IAAI;GACF,MAAM,QAAQ,IAAI,CAChB,KAAK,uBAAuB,QAAQ,SAAS,GAC7C,KAAK,WAAW,QAAQ,QAAQ,CAClC,CAAC;GAED,IAAI,MAAM,WAAW,QAAQ,GAE3B,MAAM,QAAQ;QACT;IACL,MAAM,GAAG,SAAS,MAAM,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;IACnE,MAAM,KAAK,IAAI,UAAU,MAAM,IAAI;GACrC;EACF,UAAU;GACR,KAAK,WAAW,QAAQ,QAAQ;GAChC,KAAK,uBAAuB,QAAQ,SAAS;EAC/C;EAEA,OAAO;CACT;;;;;;;CAQA,MAAM,gBAAgB,MAA4B;EAChD,MAAM,WAAW,MAAM,KAAK,gBAAgB,IAAI;EAUhD,OARiB,KAAK,QACpB,KAAK,YACL,SAAS,IACT,SAAS,IACT,SAAS,IACT,QAGY;CAChB;;;;CAKA,UAAU;EACR,IAAI,KAAK,aAAa;GACpB,cAAc,KAAK,WAAW;GAC9B,KAAK,cAAc;EACrB;CACF;CAEA,MAAM,IAAI,MAA2B;EACnC,MAAM,WAAW,MAAM,KAAK,gBAAgB,IAAI;EAChD,OAAO,MAAM,WAAW,QAAQ;CAClC;;;;;CAMA,MAAM,UAAU;EACd,IAAI;GACF,MAAM,KAAK,uBAAuB,QAAQ,SAAS;GACnD,MAAM,SAAS,KAAK,YAAY;IAC9B,WAAW,KAAK;IAChB,SAAS;IACT,wBAAwB;GAC1B,CAAC;EACH,UAAU;GACR,KAAK,uBAAuB,QAAQ,SAAS;EAC/C;CACF;AACF"}
|
package/package.json
CHANGED
|
@@ -1,41 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chriscdn/file-cache",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "File cache",
|
|
5
|
-
"repository":
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/chriscdn/file-cache"
|
|
8
|
+
},
|
|
6
9
|
"author": "Christopher Meyer <chris@schwiiz.org>",
|
|
7
10
|
"license": "MIT",
|
|
8
11
|
"type": "module",
|
|
9
|
-
"
|
|
10
|
-
"main": "./lib/file-cache.cjs",
|
|
11
|
-
"module": "./lib/file-cache.module.js",
|
|
12
|
+
"types": "./lib/index.d.mts",
|
|
12
13
|
"exports": {
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"types": "./lib/index.d.ts",
|
|
18
|
-
"scripts": {
|
|
19
|
-
"build": "rm -rf ./lib/ && microbundle --target node --format modern,esm,cjs",
|
|
20
|
-
"test": "vitest"
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./lib/index.d.mts",
|
|
16
|
+
"import": "./lib/index.mjs"
|
|
17
|
+
}
|
|
21
18
|
},
|
|
22
19
|
"dependencies": {
|
|
23
|
-
"@chriscdn/duration": "^
|
|
24
|
-
"@chriscdn/find-nuke": "^
|
|
25
|
-
"@chriscdn/promise-semaphore": "^
|
|
20
|
+
"@chriscdn/duration": "^2.0.1",
|
|
21
|
+
"@chriscdn/find-nuke": "^2.0.1",
|
|
22
|
+
"@chriscdn/promise-semaphore": "^4.0.1",
|
|
26
23
|
"path-exists": "^5.0.0",
|
|
27
24
|
"sha1": "^1.1.1",
|
|
28
|
-
"temp": "^0.9.4",
|
|
29
25
|
"touch": "^3.1.1"
|
|
30
26
|
},
|
|
31
27
|
"devDependencies": {
|
|
28
|
+
"@tsconfig/strictest": "^2.0.7",
|
|
32
29
|
"@types/sha1": "^1.1.5",
|
|
30
|
+
"@types/temp": "^0.9.4",
|
|
33
31
|
"@types/touch": "^3.1.5",
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
32
|
+
"temp": "^0.9.4",
|
|
33
|
+
"tsdown": "0.23.0",
|
|
34
|
+
"typescript": "^7.0.2",
|
|
35
|
+
"vitest": "^5.0.0"
|
|
37
36
|
},
|
|
38
37
|
"files": [
|
|
39
38
|
"lib"
|
|
40
|
-
]
|
|
41
|
-
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsdown",
|
|
42
|
+
"test": "vitest"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/lib/file-cache.cjs
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
var path = require('path');
|
|
2
|
-
var fs = require('fs');
|
|
3
|
-
var pathExists = require('path-exists');
|
|
4
|
-
var sha1 = require('sha1');
|
|
5
|
-
var promiseSemaphore = require('@chriscdn/promise-semaphore');
|
|
6
|
-
var touch = require('touch');
|
|
7
|
-
var findNuke = require('@chriscdn/find-nuke');
|
|
8
|
-
var duration = require('@chriscdn/duration');
|
|
9
|
-
|
|
10
|
-
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
11
|
-
|
|
12
|
-
var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
|
|
13
|
-
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
|
|
14
|
-
var sha1__default = /*#__PURE__*/_interopDefaultLegacy(sha1);
|
|
15
|
-
var touch__default = /*#__PURE__*/_interopDefaultLegacy(touch);
|
|
16
|
-
|
|
17
|
-
class FileCache {
|
|
18
|
-
constructor({
|
|
19
|
-
cachePath,
|
|
20
|
-
autoCreateCachePath,
|
|
21
|
-
cb,
|
|
22
|
-
ext,
|
|
23
|
-
ttl,
|
|
24
|
-
cleanupInterval,
|
|
25
|
-
resolveCacheKey
|
|
26
|
-
}) {
|
|
27
|
-
this._cachePath = void 0;
|
|
28
|
-
this._cb = void 0;
|
|
29
|
-
this._ttl = void 0;
|
|
30
|
-
this._cleanupInterval = void 0;
|
|
31
|
-
this._resolveCacheKey = void 0;
|
|
32
|
-
this._ext = void 0;
|
|
33
|
-
this._intervalId = null;
|
|
34
|
-
this._semaphore = new promiseSemaphore.Semaphore();
|
|
35
|
-
this._cleanupGroupSemaphore = new promiseSemaphore.GroupSemaphore();
|
|
36
|
-
this._cachePath = cachePath;
|
|
37
|
-
this._cb = cb;
|
|
38
|
-
this._ext = ext;
|
|
39
|
-
this._ttl = ttl;
|
|
40
|
-
this._cleanupInterval = cleanupInterval != null ? cleanupInterval : duration.Duration.toMilliseconds({
|
|
41
|
-
days: 1
|
|
42
|
-
});
|
|
43
|
-
this._resolveCacheKey = resolveCacheKey != null ? resolveCacheKey : args => JSON.stringify(args);
|
|
44
|
-
// Startup code
|
|
45
|
-
if (pathExists.pathExistsSync(cachePath)) ; else if (autoCreateCachePath) {
|
|
46
|
-
fs__default["default"].mkdirSync(cachePath, {
|
|
47
|
-
recursive: true
|
|
48
|
-
});
|
|
49
|
-
} else {
|
|
50
|
-
throw new Error("💥 FileCache error: cachePath does not exist.");
|
|
51
|
-
}
|
|
52
|
-
// fire and forget
|
|
53
|
-
this.cleanup();
|
|
54
|
-
this._intervalId = setInterval(this.cleanup.bind(this), this._cleanupInterval);
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Resolves the filename for the cache based on the provided arguments.
|
|
58
|
-
*
|
|
59
|
-
* Uses the provided `resolveFileName` function if available, or falls back to
|
|
60
|
-
* generating a hash from the arguments to create a unique file name.
|
|
61
|
-
*/
|
|
62
|
-
async resolveFileName(args) {
|
|
63
|
-
const [name, ext] = await Promise.all([this._resolveCacheKey(args), this._ext(args)]);
|
|
64
|
-
const resolvedFileName = sha1__default["default"](JSON.stringify([name, ext]));
|
|
65
|
-
return path__default["default"].format({
|
|
66
|
-
name: resolvedFileName,
|
|
67
|
-
ext
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Retrieves a cached file for the given arguments.
|
|
72
|
-
* If the file doesn't exist, it is generated by the callback and stored.
|
|
73
|
-
*
|
|
74
|
-
* Concurrency-safe, ensuring no conflicts when multiple requests are made
|
|
75
|
-
* for the same file.
|
|
76
|
-
*
|
|
77
|
-
* @param args The arguments used to determine the file's identity and generation
|
|
78
|
-
* @returns The path to the cached or newly created file
|
|
79
|
-
*/
|
|
80
|
-
async getFile(args) {
|
|
81
|
-
const filePath = await this.resolveFilePath(args);
|
|
82
|
-
try {
|
|
83
|
-
await Promise.all([this._cleanupGroupSemaphore.acquire("getFile"), this._semaphore.acquire(filePath)]);
|
|
84
|
-
if (await pathExists.pathExists(filePath)) {
|
|
85
|
-
// fire and forget
|
|
86
|
-
touch__default["default"](filePath);
|
|
87
|
-
} else {
|
|
88
|
-
await fs__default["default"].promises.mkdir(path__default["default"].dirname(filePath), {
|
|
89
|
-
recursive: true
|
|
90
|
-
});
|
|
91
|
-
await this._cb(filePath, args, this);
|
|
92
|
-
}
|
|
93
|
-
} finally {
|
|
94
|
-
this._semaphore.release(filePath);
|
|
95
|
-
this._cleanupGroupSemaphore.release("getFile");
|
|
96
|
-
}
|
|
97
|
-
return filePath;
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Resolves the full file path for the cached file based on the provided arguments.
|
|
101
|
-
*
|
|
102
|
-
* @param args The arguments used to resolve the file path
|
|
103
|
-
* @returns The full path to the cached file
|
|
104
|
-
*/
|
|
105
|
-
async resolveFilePath(args) {
|
|
106
|
-
const fileName = await this.resolveFileName(args);
|
|
107
|
-
const filePath = path__default["default"].resolve(this._cachePath, fileName[0], fileName[1], fileName[2], fileName);
|
|
108
|
-
return filePath;
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Stops the background cleanup process, but does not delete any cached files.
|
|
112
|
-
*/
|
|
113
|
-
destroy() {
|
|
114
|
-
if (this._intervalId) {
|
|
115
|
-
clearInterval(this._intervalId);
|
|
116
|
-
this._intervalId = null;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
async has(args) {
|
|
120
|
-
const filePath = await this.resolveFilePath(args);
|
|
121
|
-
return await pathExists.pathExists(filePath);
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Runs a cleanup pass to remove files older than 1 day from the cache.
|
|
125
|
-
* This is called periodically, but can be triggered manually.
|
|
126
|
-
*/
|
|
127
|
-
async cleanup() {
|
|
128
|
-
try {
|
|
129
|
-
await this._cleanupGroupSemaphore.acquire("cleanup");
|
|
130
|
-
await findNuke.findNuke(this._cachePath, {
|
|
131
|
-
olderThan: this._ttl,
|
|
132
|
-
verbose: true,
|
|
133
|
-
deleteEmptyDirectories: true
|
|
134
|
-
});
|
|
135
|
-
} finally {
|
|
136
|
-
this._cleanupGroupSemaphore.release("cleanup");
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
exports.FileCache = FileCache;
|
|
142
|
-
//# sourceMappingURL=file-cache.cjs.map
|
package/lib/file-cache.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"file-cache.cjs","sources":["../src/index.ts"],"sourcesContent":["import path from \"path\";\nimport fs from \"fs\";\nimport { pathExists, pathExistsSync } from \"path-exists\";\nimport sha1 from \"sha1\";\nimport { GroupSemaphore, Semaphore } from \"@chriscdn/promise-semaphore\";\nimport touch from \"touch\";\nimport { findNuke } from \"@chriscdn/find-nuke\";\nimport { Duration } from \"@chriscdn/duration\";\n\ntype DirectoryPath = string;\ntype FileName = string;\ntype FilePath = string;\ntype Milliseconds = number;\n\nexport type FileCacheOptions<T extends Record<string, any>> = {\n cachePath: DirectoryPath;\n autoCreateCachePath?: boolean;\n cb: (filePath: FilePath, args: T, cache: FileCache<T>) => Promise<void>;\n ext: (args: T) => string | Promise<string>;\n resolveCacheKey?: (args: T) => string | Promise<string>;\n ttl: Milliseconds;\n cleanupInterval?: Milliseconds;\n};\n\nclass FileCache<T extends Record<string, any>> {\n private _cachePath: FileCacheOptions<T>[\"cachePath\"];\n private _cb: FileCacheOptions<T>[\"cb\"];\n private _ttl: FileCacheOptions<T>[\"ttl\"];\n private _cleanupInterval: FileCacheOptions<T>[\"cleanupInterval\"];\n private _resolveCacheKey: Required<\n FileCacheOptions<T>\n >[\"resolveCacheKey\"];\n private _ext: FileCacheOptions<T>[\"ext\"];\n private _intervalId: ReturnType<typeof setInterval> | null = null;\n private _semaphore: Semaphore = new Semaphore();\n private _cleanupGroupSemaphore: GroupSemaphore = new GroupSemaphore();\n\n constructor(\n {\n cachePath,\n autoCreateCachePath,\n cb,\n ext,\n ttl,\n cleanupInterval,\n resolveCacheKey,\n }: FileCacheOptions<T>,\n ) {\n this._cachePath = cachePath;\n this._cb = cb;\n this._ext = ext;\n this._ttl = ttl;\n this._cleanupInterval = cleanupInterval ??\n Duration.toMilliseconds({ days: 1 });\n\n this._resolveCacheKey = resolveCacheKey ??\n ((args: T) => (JSON.stringify(args)));\n\n // Startup code\n if ((pathExistsSync(cachePath))) {\n // we're good\n } else if (autoCreateCachePath) {\n fs.mkdirSync(cachePath, { recursive: true });\n } else {\n throw new Error(\"💥 FileCache error: cachePath does not exist.\");\n }\n\n // fire and forget\n this.cleanup();\n\n this._intervalId = setInterval(\n this.cleanup.bind(this),\n this._cleanupInterval,\n );\n }\n\n /**\n * Resolves the filename for the cache based on the provided arguments.\n *\n * Uses the provided `resolveFileName` function if available, or falls back to\n * generating a hash from the arguments to create a unique file name.\n */\n private async resolveFileName(args: T): Promise<FileName> {\n const [name, ext] = await Promise.all([\n this._resolveCacheKey(args),\n this._ext(args),\n ]);\n\n const resolvedFileName = sha1(JSON.stringify([name, ext]));\n\n return path.format({\n name: resolvedFileName,\n ext,\n }) as FileName;\n }\n\n /**\n * Retrieves a cached file for the given arguments.\n * If the file doesn't exist, it is generated by the callback and stored.\n *\n * Concurrency-safe, ensuring no conflicts when multiple requests are made\n * for the same file.\n *\n * @param args The arguments used to determine the file's identity and generation\n * @returns The path to the cached or newly created file\n */\n async getFile(args: T): Promise<FilePath> {\n const filePath = await this.resolveFilePath(args);\n\n try {\n await Promise.all([\n this._cleanupGroupSemaphore.acquire(\"getFile\"),\n this._semaphore.acquire(filePath),\n ]);\n\n if (await pathExists(filePath)) {\n // fire and forget\n touch(filePath);\n } else {\n await fs.promises.mkdir(path.dirname(filePath), { recursive: true });\n await this._cb(filePath, args, this);\n }\n } finally {\n this._semaphore.release(filePath);\n this._cleanupGroupSemaphore.release(\"getFile\");\n }\n\n return filePath;\n }\n\n /**\n * Resolves the full file path for the cached file based on the provided arguments.\n *\n * @param args The arguments used to resolve the file path\n * @returns The full path to the cached file\n */\n async resolveFilePath(args: T): Promise<FilePath> {\n const fileName = await this.resolveFileName(args);\n\n const filePath = path.resolve(\n this._cachePath,\n fileName[0],\n fileName[1],\n fileName[2],\n fileName,\n );\n\n return filePath;\n }\n\n /**\n * Stops the background cleanup process, but does not delete any cached files.\n */\n destroy() {\n if (this._intervalId) {\n clearInterval(this._intervalId);\n this._intervalId = null;\n }\n }\n\n async has(args: T): Promise<boolean> {\n const filePath = await this.resolveFilePath(args);\n return await pathExists(filePath);\n }\n\n /**\n * Runs a cleanup pass to remove files older than 1 day from the cache.\n * This is called periodically, but can be triggered manually.\n */\n async cleanup() {\n try {\n await this._cleanupGroupSemaphore.acquire(\"cleanup\");\n await findNuke(this._cachePath, {\n olderThan: this._ttl,\n verbose: true,\n deleteEmptyDirectories: true,\n });\n } finally {\n this._cleanupGroupSemaphore.release(\"cleanup\");\n }\n }\n}\n\nexport { type DirectoryPath, FileCache, type FileName, type FilePath };\n"],"names":["FileCache","constructor","cachePath","autoCreateCachePath","cb","ext","ttl","cleanupInterval","resolveCacheKey","_cachePath","_cb","_ttl","_cleanupInterval","_resolveCacheKey","_ext","_intervalId","_semaphore","Semaphore","_cleanupGroupSemaphore","GroupSemaphore","Duration","toMilliseconds","days","args","JSON","stringify","pathExistsSync","fs","mkdirSync","recursive","Error","cleanup","setInterval","bind","resolveFileName","name","Promise","all","resolvedFileName","sha1","path","format","getFile","filePath","resolveFilePath","acquire","pathExists","touch","promises","mkdir","dirname","release","fileName","resolve","destroy","clearInterval","has","findNuke","olderThan","verbose","deleteEmptyDirectories"],"mappings":";;;;;;;;;;;;;;;;AAwBA,MAAMA,SAAS,CAAA;AAabC,EAAAA,WAAAA,CACE;IACEC,SAAS;IACTC,mBAAmB;IACnBC,EAAE;IACFC,GAAG;IACHC,GAAG;IACHC,eAAe;AACfC,IAAAA,eAAAA;AACoB,GAAA,EAAA;AAAA,IAAA,IAAA,CArBhBC,UAAU,GAAA,KAAA,CAAA,CAAA;AAAA,IAAA,IAAA,CACVC,GAAG,GAAA,KAAA,CAAA,CAAA;AAAA,IAAA,IAAA,CACHC,IAAI,GAAA,KAAA,CAAA,CAAA;AAAA,IAAA,IAAA,CACJC,gBAAgB,GAAA,KAAA,CAAA,CAAA;AAAA,IAAA,IAAA,CAChBC,gBAAgB,GAAA,KAAA,CAAA,CAAA;AAAA,IAAA,IAAA,CAGhBC,IAAI,GAAA,KAAA,CAAA,CAAA;IAAA,IACJC,CAAAA,WAAW,GAA0C,IAAI,CAAA;AAAA,IAAA,IAAA,CACzDC,UAAU,GAAc,IAAIC,0BAAS,EAAE,CAAA;AAAA,IAAA,IAAA,CACvCC,sBAAsB,GAAmB,IAAIC,+BAAc,EAAE,CAAA;IAanE,IAAI,CAACV,UAAU,GAAGP,SAAS,CAAA;IAC3B,IAAI,CAACQ,GAAG,GAAGN,EAAE,CAAA;IACb,IAAI,CAACU,IAAI,GAAGT,GAAG,CAAA;IACf,IAAI,CAACM,IAAI,GAAGL,GAAG,CAAA;IACf,IAAI,CAACM,gBAAgB,GAAGL,eAAe,IAAA,IAAA,GAAfA,eAAe,GACrCa,iBAAQ,CAACC,cAAc,CAAC;AAAEC,MAAAA,IAAI,EAAE,CAAA;AAAG,KAAA,CAAC,CAAA;AAEtC,IAAA,IAAI,CAACT,gBAAgB,GAAGL,eAAe,WAAfA,eAAe,GACnCe,IAAO,IAAMC,IAAI,CAACC,SAAS,CAACF,IAAI,CAAG,CAAA;AAEvC;AACA,IAAA,IAAKG,yBAAc,CAACxB,SAAS,CAAC,EAAG,CAEhC,MAAM,IAAIC,mBAAmB,EAAE;AAC9BwB,MAAAA,sBAAE,CAACC,SAAS,CAAC1B,SAAS,EAAE;AAAE2B,QAAAA,SAAS,EAAE,IAAA;AAAM,OAAA,CAAC,CAAA;AAC9C,KAAC,MAAM;AACL,MAAA,MAAM,IAAIC,KAAK,CAAC,+CAA+C,CAAC,CAAA;AAClE,KAAA;AAEA;IACA,IAAI,CAACC,OAAO,EAAE,CAAA;AAEd,IAAA,IAAI,CAAChB,WAAW,GAAGiB,WAAW,CAC5B,IAAI,CAACD,OAAO,CAACE,IAAI,CAAC,IAAI,CAAC,EACvB,IAAI,CAACrB,gBAAgB,CACtB,CAAA;AACH,GAAA;AAEA;;;;;AAKG;EACK,MAAMsB,eAAeA,CAACX,IAAO,EAAA;IACnC,MAAM,CAACY,IAAI,EAAE9B,GAAG,CAAC,GAAG,MAAM+B,OAAO,CAACC,GAAG,CAAC,CACpC,IAAI,CAACxB,gBAAgB,CAACU,IAAI,CAAC,EAC3B,IAAI,CAACT,IAAI,CAACS,IAAI,CAAC,CAChB,CAAC,CAAA;AAEF,IAAA,MAAMe,gBAAgB,GAAGC,wBAAI,CAACf,IAAI,CAACC,SAAS,CAAC,CAACU,IAAI,EAAE9B,GAAG,CAAC,CAAC,CAAC,CAAA;IAE1D,OAAOmC,wBAAI,CAACC,MAAM,CAAC;AACjBN,MAAAA,IAAI,EAAEG,gBAAgB;AACtBjC,MAAAA,GAAAA;AACD,KAAA,CAAa,CAAA;AAChB,GAAA;AAEA;;;;;;;;;AASG;EACH,MAAMqC,OAAOA,CAACnB,IAAO,EAAA;IACnB,MAAMoB,QAAQ,GAAG,MAAM,IAAI,CAACC,eAAe,CAACrB,IAAI,CAAC,CAAA;IAEjD,IAAI;MACF,MAAMa,OAAO,CAACC,GAAG,CAAC,CAChB,IAAI,CAACnB,sBAAsB,CAAC2B,OAAO,CAAC,SAAS,CAAC,EAC9C,IAAI,CAAC7B,UAAU,CAAC6B,OAAO,CAACF,QAAQ,CAAC,CAClC,CAAC,CAAA;AAEF,MAAA,IAAI,MAAMG,qBAAU,CAACH,QAAQ,CAAC,EAAE;AAC9B;QACAI,yBAAK,CAACJ,QAAQ,CAAC,CAAA;AACjB,OAAC,MAAM;AACL,QAAA,MAAMhB,sBAAE,CAACqB,QAAQ,CAACC,KAAK,CAACT,wBAAI,CAACU,OAAO,CAACP,QAAQ,CAAC,EAAE;AAAEd,UAAAA,SAAS,EAAE,IAAA;AAAI,SAAE,CAAC,CAAA;QACpE,MAAM,IAAI,CAACnB,GAAG,CAACiC,QAAQ,EAAEpB,IAAI,EAAE,IAAI,CAAC,CAAA;AACtC,OAAA;AACF,KAAC,SAAS;AACR,MAAA,IAAI,CAACP,UAAU,CAACmC,OAAO,CAACR,QAAQ,CAAC,CAAA;AACjC,MAAA,IAAI,CAACzB,sBAAsB,CAACiC,OAAO,CAAC,SAAS,CAAC,CAAA;AAChD,KAAA;AAEA,IAAA,OAAOR,QAAQ,CAAA;AACjB,GAAA;AAEA;;;;;AAKG;EACH,MAAMC,eAAeA,CAACrB,IAAO,EAAA;IAC3B,MAAM6B,QAAQ,GAAG,MAAM,IAAI,CAAClB,eAAe,CAACX,IAAI,CAAC,CAAA;IAEjD,MAAMoB,QAAQ,GAAGH,wBAAI,CAACa,OAAO,CAC3B,IAAI,CAAC5C,UAAU,EACf2C,QAAQ,CAAC,CAAC,CAAC,EACXA,QAAQ,CAAC,CAAC,CAAC,EACXA,QAAQ,CAAC,CAAC,CAAC,EACXA,QAAQ,CACT,CAAA;AAED,IAAA,OAAOT,QAAQ,CAAA;AACjB,GAAA;AAEA;;AAEG;AACHW,EAAAA,OAAOA,GAAA;IACL,IAAI,IAAI,CAACvC,WAAW,EAAE;AACpBwC,MAAAA,aAAa,CAAC,IAAI,CAACxC,WAAW,CAAC,CAAA;MAC/B,IAAI,CAACA,WAAW,GAAG,IAAI,CAAA;AACzB,KAAA;AACF,GAAA;EAEA,MAAMyC,GAAGA,CAACjC,IAAO,EAAA;IACf,MAAMoB,QAAQ,GAAG,MAAM,IAAI,CAACC,eAAe,CAACrB,IAAI,CAAC,CAAA;AACjD,IAAA,OAAO,MAAMuB,qBAAU,CAACH,QAAQ,CAAC,CAAA;AACnC,GAAA;AAEA;;;AAGG;EACH,MAAMZ,OAAOA,GAAA;IACX,IAAI;AACF,MAAA,MAAM,IAAI,CAACb,sBAAsB,CAAC2B,OAAO,CAAC,SAAS,CAAC,CAAA;AACpD,MAAA,MAAMY,iBAAQ,CAAC,IAAI,CAAChD,UAAU,EAAE;QAC9BiD,SAAS,EAAE,IAAI,CAAC/C,IAAI;AACpBgD,QAAAA,OAAO,EAAE,IAAI;AACbC,QAAAA,sBAAsB,EAAE,IAAA;AACzB,OAAA,CAAC,CAAA;AACJ,KAAC,SAAS;AACR,MAAA,IAAI,CAAC1C,sBAAsB,CAACiC,OAAO,CAAC,SAAS,CAAC,CAAA;AAChD,KAAA;AACF,GAAA;AACD;;;;"}
|
package/lib/file-cache.modern.js
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import { pathExistsSync, pathExists } from 'path-exists';
|
|
4
|
-
import sha1 from 'sha1';
|
|
5
|
-
import { Semaphore, GroupSemaphore } from '@chriscdn/promise-semaphore';
|
|
6
|
-
import touch from 'touch';
|
|
7
|
-
import { findNuke } from '@chriscdn/find-nuke';
|
|
8
|
-
import { Duration } from '@chriscdn/duration';
|
|
9
|
-
|
|
10
|
-
class FileCache {
|
|
11
|
-
constructor({
|
|
12
|
-
cachePath,
|
|
13
|
-
autoCreateCachePath,
|
|
14
|
-
cb,
|
|
15
|
-
ext,
|
|
16
|
-
ttl,
|
|
17
|
-
cleanupInterval,
|
|
18
|
-
resolveCacheKey
|
|
19
|
-
}) {
|
|
20
|
-
this._cachePath = void 0;
|
|
21
|
-
this._cb = void 0;
|
|
22
|
-
this._ttl = void 0;
|
|
23
|
-
this._cleanupInterval = void 0;
|
|
24
|
-
this._resolveCacheKey = void 0;
|
|
25
|
-
this._ext = void 0;
|
|
26
|
-
this._intervalId = null;
|
|
27
|
-
this._semaphore = new Semaphore();
|
|
28
|
-
this._cleanupGroupSemaphore = new GroupSemaphore();
|
|
29
|
-
this._cachePath = cachePath;
|
|
30
|
-
this._cb = cb;
|
|
31
|
-
this._ext = ext;
|
|
32
|
-
this._ttl = ttl;
|
|
33
|
-
this._cleanupInterval = cleanupInterval != null ? cleanupInterval : Duration.toMilliseconds({
|
|
34
|
-
days: 1
|
|
35
|
-
});
|
|
36
|
-
this._resolveCacheKey = resolveCacheKey != null ? resolveCacheKey : args => JSON.stringify(args);
|
|
37
|
-
// Startup code
|
|
38
|
-
if (pathExistsSync(cachePath)) ; else if (autoCreateCachePath) {
|
|
39
|
-
fs.mkdirSync(cachePath, {
|
|
40
|
-
recursive: true
|
|
41
|
-
});
|
|
42
|
-
} else {
|
|
43
|
-
throw new Error("💥 FileCache error: cachePath does not exist.");
|
|
44
|
-
}
|
|
45
|
-
// fire and forget
|
|
46
|
-
this.cleanup();
|
|
47
|
-
this._intervalId = setInterval(this.cleanup.bind(this), this._cleanupInterval);
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Resolves the filename for the cache based on the provided arguments.
|
|
51
|
-
*
|
|
52
|
-
* Uses the provided `resolveFileName` function if available, or falls back to
|
|
53
|
-
* generating a hash from the arguments to create a unique file name.
|
|
54
|
-
*/
|
|
55
|
-
async resolveFileName(args) {
|
|
56
|
-
const [name, ext] = await Promise.all([this._resolveCacheKey(args), this._ext(args)]);
|
|
57
|
-
const resolvedFileName = sha1(JSON.stringify([name, ext]));
|
|
58
|
-
return path.format({
|
|
59
|
-
name: resolvedFileName,
|
|
60
|
-
ext
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Retrieves a cached file for the given arguments.
|
|
65
|
-
* If the file doesn't exist, it is generated by the callback and stored.
|
|
66
|
-
*
|
|
67
|
-
* Concurrency-safe, ensuring no conflicts when multiple requests are made
|
|
68
|
-
* for the same file.
|
|
69
|
-
*
|
|
70
|
-
* @param args The arguments used to determine the file's identity and generation
|
|
71
|
-
* @returns The path to the cached or newly created file
|
|
72
|
-
*/
|
|
73
|
-
async getFile(args) {
|
|
74
|
-
const filePath = await this.resolveFilePath(args);
|
|
75
|
-
try {
|
|
76
|
-
await Promise.all([this._cleanupGroupSemaphore.acquire("getFile"), this._semaphore.acquire(filePath)]);
|
|
77
|
-
if (await pathExists(filePath)) {
|
|
78
|
-
// fire and forget
|
|
79
|
-
touch(filePath);
|
|
80
|
-
} else {
|
|
81
|
-
await fs.promises.mkdir(path.dirname(filePath), {
|
|
82
|
-
recursive: true
|
|
83
|
-
});
|
|
84
|
-
await this._cb(filePath, args, this);
|
|
85
|
-
}
|
|
86
|
-
} finally {
|
|
87
|
-
this._semaphore.release(filePath);
|
|
88
|
-
this._cleanupGroupSemaphore.release("getFile");
|
|
89
|
-
}
|
|
90
|
-
return filePath;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Resolves the full file path for the cached file based on the provided arguments.
|
|
94
|
-
*
|
|
95
|
-
* @param args The arguments used to resolve the file path
|
|
96
|
-
* @returns The full path to the cached file
|
|
97
|
-
*/
|
|
98
|
-
async resolveFilePath(args) {
|
|
99
|
-
const fileName = await this.resolveFileName(args);
|
|
100
|
-
const filePath = path.resolve(this._cachePath, fileName[0], fileName[1], fileName[2], fileName);
|
|
101
|
-
return filePath;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Stops the background cleanup process, but does not delete any cached files.
|
|
105
|
-
*/
|
|
106
|
-
destroy() {
|
|
107
|
-
if (this._intervalId) {
|
|
108
|
-
clearInterval(this._intervalId);
|
|
109
|
-
this._intervalId = null;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
async has(args) {
|
|
113
|
-
const filePath = await this.resolveFilePath(args);
|
|
114
|
-
return await pathExists(filePath);
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Runs a cleanup pass to remove files older than 1 day from the cache.
|
|
118
|
-
* This is called periodically, but can be triggered manually.
|
|
119
|
-
*/
|
|
120
|
-
async cleanup() {
|
|
121
|
-
try {
|
|
122
|
-
await this._cleanupGroupSemaphore.acquire("cleanup");
|
|
123
|
-
await findNuke(this._cachePath, {
|
|
124
|
-
olderThan: this._ttl,
|
|
125
|
-
verbose: true,
|
|
126
|
-
deleteEmptyDirectories: true
|
|
127
|
-
});
|
|
128
|
-
} finally {
|
|
129
|
-
this._cleanupGroupSemaphore.release("cleanup");
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export { FileCache };
|
|
135
|
-
//# sourceMappingURL=file-cache.modern.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"file-cache.modern.js","sources":["../src/index.ts"],"sourcesContent":["import path from \"path\";\nimport fs from \"fs\";\nimport { pathExists, pathExistsSync } from \"path-exists\";\nimport sha1 from \"sha1\";\nimport { GroupSemaphore, Semaphore } from \"@chriscdn/promise-semaphore\";\nimport touch from \"touch\";\nimport { findNuke } from \"@chriscdn/find-nuke\";\nimport { Duration } from \"@chriscdn/duration\";\n\ntype DirectoryPath = string;\ntype FileName = string;\ntype FilePath = string;\ntype Milliseconds = number;\n\nexport type FileCacheOptions<T extends Record<string, any>> = {\n cachePath: DirectoryPath;\n autoCreateCachePath?: boolean;\n cb: (filePath: FilePath, args: T, cache: FileCache<T>) => Promise<void>;\n ext: (args: T) => string | Promise<string>;\n resolveCacheKey?: (args: T) => string | Promise<string>;\n ttl: Milliseconds;\n cleanupInterval?: Milliseconds;\n};\n\nclass FileCache<T extends Record<string, any>> {\n private _cachePath: FileCacheOptions<T>[\"cachePath\"];\n private _cb: FileCacheOptions<T>[\"cb\"];\n private _ttl: FileCacheOptions<T>[\"ttl\"];\n private _cleanupInterval: FileCacheOptions<T>[\"cleanupInterval\"];\n private _resolveCacheKey: Required<\n FileCacheOptions<T>\n >[\"resolveCacheKey\"];\n private _ext: FileCacheOptions<T>[\"ext\"];\n private _intervalId: ReturnType<typeof setInterval> | null = null;\n private _semaphore: Semaphore = new Semaphore();\n private _cleanupGroupSemaphore: GroupSemaphore = new GroupSemaphore();\n\n constructor(\n {\n cachePath,\n autoCreateCachePath,\n cb,\n ext,\n ttl,\n cleanupInterval,\n resolveCacheKey,\n }: FileCacheOptions<T>,\n ) {\n this._cachePath = cachePath;\n this._cb = cb;\n this._ext = ext;\n this._ttl = ttl;\n this._cleanupInterval = cleanupInterval ??\n Duration.toMilliseconds({ days: 1 });\n\n this._resolveCacheKey = resolveCacheKey ??\n ((args: T) => (JSON.stringify(args)));\n\n // Startup code\n if ((pathExistsSync(cachePath))) {\n // we're good\n } else if (autoCreateCachePath) {\n fs.mkdirSync(cachePath, { recursive: true });\n } else {\n throw new Error(\"💥 FileCache error: cachePath does not exist.\");\n }\n\n // fire and forget\n this.cleanup();\n\n this._intervalId = setInterval(\n this.cleanup.bind(this),\n this._cleanupInterval,\n );\n }\n\n /**\n * Resolves the filename for the cache based on the provided arguments.\n *\n * Uses the provided `resolveFileName` function if available, or falls back to\n * generating a hash from the arguments to create a unique file name.\n */\n private async resolveFileName(args: T): Promise<FileName> {\n const [name, ext] = await Promise.all([\n this._resolveCacheKey(args),\n this._ext(args),\n ]);\n\n const resolvedFileName = sha1(JSON.stringify([name, ext]));\n\n return path.format({\n name: resolvedFileName,\n ext,\n }) as FileName;\n }\n\n /**\n * Retrieves a cached file for the given arguments.\n * If the file doesn't exist, it is generated by the callback and stored.\n *\n * Concurrency-safe, ensuring no conflicts when multiple requests are made\n * for the same file.\n *\n * @param args The arguments used to determine the file's identity and generation\n * @returns The path to the cached or newly created file\n */\n async getFile(args: T): Promise<FilePath> {\n const filePath = await this.resolveFilePath(args);\n\n try {\n await Promise.all([\n this._cleanupGroupSemaphore.acquire(\"getFile\"),\n this._semaphore.acquire(filePath),\n ]);\n\n if (await pathExists(filePath)) {\n // fire and forget\n touch(filePath);\n } else {\n await fs.promises.mkdir(path.dirname(filePath), { recursive: true });\n await this._cb(filePath, args, this);\n }\n } finally {\n this._semaphore.release(filePath);\n this._cleanupGroupSemaphore.release(\"getFile\");\n }\n\n return filePath;\n }\n\n /**\n * Resolves the full file path for the cached file based on the provided arguments.\n *\n * @param args The arguments used to resolve the file path\n * @returns The full path to the cached file\n */\n async resolveFilePath(args: T): Promise<FilePath> {\n const fileName = await this.resolveFileName(args);\n\n const filePath = path.resolve(\n this._cachePath,\n fileName[0],\n fileName[1],\n fileName[2],\n fileName,\n );\n\n return filePath;\n }\n\n /**\n * Stops the background cleanup process, but does not delete any cached files.\n */\n destroy() {\n if (this._intervalId) {\n clearInterval(this._intervalId);\n this._intervalId = null;\n }\n }\n\n async has(args: T): Promise<boolean> {\n const filePath = await this.resolveFilePath(args);\n return await pathExists(filePath);\n }\n\n /**\n * Runs a cleanup pass to remove files older than 1 day from the cache.\n * This is called periodically, but can be triggered manually.\n */\n async cleanup() {\n try {\n await this._cleanupGroupSemaphore.acquire(\"cleanup\");\n await findNuke(this._cachePath, {\n olderThan: this._ttl,\n verbose: true,\n deleteEmptyDirectories: true,\n });\n } finally {\n this._cleanupGroupSemaphore.release(\"cleanup\");\n }\n }\n}\n\nexport { type DirectoryPath, FileCache, type FileName, type FilePath };\n"],"names":["FileCache","constructor","cachePath","autoCreateCachePath","cb","ext","ttl","cleanupInterval","resolveCacheKey","_cachePath","_cb","_ttl","_cleanupInterval","_resolveCacheKey","_ext","_intervalId","_semaphore","Semaphore","_cleanupGroupSemaphore","GroupSemaphore","Duration","toMilliseconds","days","args","JSON","stringify","pathExistsSync","fs","mkdirSync","recursive","Error","cleanup","setInterval","bind","resolveFileName","name","Promise","all","resolvedFileName","sha1","path","format","getFile","filePath","resolveFilePath","acquire","pathExists","touch","promises","mkdir","dirname","release","fileName","resolve","destroy","clearInterval","has","findNuke","olderThan","verbose","deleteEmptyDirectories"],"mappings":"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"}
|
package/lib/file-cache.module.js
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import { pathExistsSync, pathExists } from 'path-exists';
|
|
4
|
-
import sha1 from 'sha1';
|
|
5
|
-
import { Semaphore, GroupSemaphore } from '@chriscdn/promise-semaphore';
|
|
6
|
-
import touch from 'touch';
|
|
7
|
-
import { findNuke } from '@chriscdn/find-nuke';
|
|
8
|
-
import { Duration } from '@chriscdn/duration';
|
|
9
|
-
|
|
10
|
-
class FileCache {
|
|
11
|
-
constructor({
|
|
12
|
-
cachePath,
|
|
13
|
-
autoCreateCachePath,
|
|
14
|
-
cb,
|
|
15
|
-
ext,
|
|
16
|
-
ttl,
|
|
17
|
-
cleanupInterval,
|
|
18
|
-
resolveCacheKey
|
|
19
|
-
}) {
|
|
20
|
-
this._cachePath = void 0;
|
|
21
|
-
this._cb = void 0;
|
|
22
|
-
this._ttl = void 0;
|
|
23
|
-
this._cleanupInterval = void 0;
|
|
24
|
-
this._resolveCacheKey = void 0;
|
|
25
|
-
this._ext = void 0;
|
|
26
|
-
this._intervalId = null;
|
|
27
|
-
this._semaphore = new Semaphore();
|
|
28
|
-
this._cleanupGroupSemaphore = new GroupSemaphore();
|
|
29
|
-
this._cachePath = cachePath;
|
|
30
|
-
this._cb = cb;
|
|
31
|
-
this._ext = ext;
|
|
32
|
-
this._ttl = ttl;
|
|
33
|
-
this._cleanupInterval = cleanupInterval != null ? cleanupInterval : Duration.toMilliseconds({
|
|
34
|
-
days: 1
|
|
35
|
-
});
|
|
36
|
-
this._resolveCacheKey = resolveCacheKey != null ? resolveCacheKey : args => JSON.stringify(args);
|
|
37
|
-
// Startup code
|
|
38
|
-
if (pathExistsSync(cachePath)) ; else if (autoCreateCachePath) {
|
|
39
|
-
fs.mkdirSync(cachePath, {
|
|
40
|
-
recursive: true
|
|
41
|
-
});
|
|
42
|
-
} else {
|
|
43
|
-
throw new Error("💥 FileCache error: cachePath does not exist.");
|
|
44
|
-
}
|
|
45
|
-
// fire and forget
|
|
46
|
-
this.cleanup();
|
|
47
|
-
this._intervalId = setInterval(this.cleanup.bind(this), this._cleanupInterval);
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Resolves the filename for the cache based on the provided arguments.
|
|
51
|
-
*
|
|
52
|
-
* Uses the provided `resolveFileName` function if available, or falls back to
|
|
53
|
-
* generating a hash from the arguments to create a unique file name.
|
|
54
|
-
*/
|
|
55
|
-
async resolveFileName(args) {
|
|
56
|
-
const [name, ext] = await Promise.all([this._resolveCacheKey(args), this._ext(args)]);
|
|
57
|
-
const resolvedFileName = sha1(JSON.stringify([name, ext]));
|
|
58
|
-
return path.format({
|
|
59
|
-
name: resolvedFileName,
|
|
60
|
-
ext
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Retrieves a cached file for the given arguments.
|
|
65
|
-
* If the file doesn't exist, it is generated by the callback and stored.
|
|
66
|
-
*
|
|
67
|
-
* Concurrency-safe, ensuring no conflicts when multiple requests are made
|
|
68
|
-
* for the same file.
|
|
69
|
-
*
|
|
70
|
-
* @param args The arguments used to determine the file's identity and generation
|
|
71
|
-
* @returns The path to the cached or newly created file
|
|
72
|
-
*/
|
|
73
|
-
async getFile(args) {
|
|
74
|
-
const filePath = await this.resolveFilePath(args);
|
|
75
|
-
try {
|
|
76
|
-
await Promise.all([this._cleanupGroupSemaphore.acquire("getFile"), this._semaphore.acquire(filePath)]);
|
|
77
|
-
if (await pathExists(filePath)) {
|
|
78
|
-
// fire and forget
|
|
79
|
-
touch(filePath);
|
|
80
|
-
} else {
|
|
81
|
-
await fs.promises.mkdir(path.dirname(filePath), {
|
|
82
|
-
recursive: true
|
|
83
|
-
});
|
|
84
|
-
await this._cb(filePath, args, this);
|
|
85
|
-
}
|
|
86
|
-
} finally {
|
|
87
|
-
this._semaphore.release(filePath);
|
|
88
|
-
this._cleanupGroupSemaphore.release("getFile");
|
|
89
|
-
}
|
|
90
|
-
return filePath;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Resolves the full file path for the cached file based on the provided arguments.
|
|
94
|
-
*
|
|
95
|
-
* @param args The arguments used to resolve the file path
|
|
96
|
-
* @returns The full path to the cached file
|
|
97
|
-
*/
|
|
98
|
-
async resolveFilePath(args) {
|
|
99
|
-
const fileName = await this.resolveFileName(args);
|
|
100
|
-
const filePath = path.resolve(this._cachePath, fileName[0], fileName[1], fileName[2], fileName);
|
|
101
|
-
return filePath;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Stops the background cleanup process, but does not delete any cached files.
|
|
105
|
-
*/
|
|
106
|
-
destroy() {
|
|
107
|
-
if (this._intervalId) {
|
|
108
|
-
clearInterval(this._intervalId);
|
|
109
|
-
this._intervalId = null;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
async has(args) {
|
|
113
|
-
const filePath = await this.resolveFilePath(args);
|
|
114
|
-
return await pathExists(filePath);
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Runs a cleanup pass to remove files older than 1 day from the cache.
|
|
118
|
-
* This is called periodically, but can be triggered manually.
|
|
119
|
-
*/
|
|
120
|
-
async cleanup() {
|
|
121
|
-
try {
|
|
122
|
-
await this._cleanupGroupSemaphore.acquire("cleanup");
|
|
123
|
-
await findNuke(this._cachePath, {
|
|
124
|
-
olderThan: this._ttl,
|
|
125
|
-
verbose: true,
|
|
126
|
-
deleteEmptyDirectories: true
|
|
127
|
-
});
|
|
128
|
-
} finally {
|
|
129
|
-
this._cleanupGroupSemaphore.release("cleanup");
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export { FileCache };
|
|
135
|
-
//# sourceMappingURL=file-cache.module.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"file-cache.module.js","sources":["../src/index.ts"],"sourcesContent":["import path from \"path\";\nimport fs from \"fs\";\nimport { pathExists, pathExistsSync } from \"path-exists\";\nimport sha1 from \"sha1\";\nimport { GroupSemaphore, Semaphore } from \"@chriscdn/promise-semaphore\";\nimport touch from \"touch\";\nimport { findNuke } from \"@chriscdn/find-nuke\";\nimport { Duration } from \"@chriscdn/duration\";\n\ntype DirectoryPath = string;\ntype FileName = string;\ntype FilePath = string;\ntype Milliseconds = number;\n\nexport type FileCacheOptions<T extends Record<string, any>> = {\n cachePath: DirectoryPath;\n autoCreateCachePath?: boolean;\n cb: (filePath: FilePath, args: T, cache: FileCache<T>) => Promise<void>;\n ext: (args: T) => string | Promise<string>;\n resolveCacheKey?: (args: T) => string | Promise<string>;\n ttl: Milliseconds;\n cleanupInterval?: Milliseconds;\n};\n\nclass FileCache<T extends Record<string, any>> {\n private _cachePath: FileCacheOptions<T>[\"cachePath\"];\n private _cb: FileCacheOptions<T>[\"cb\"];\n private _ttl: FileCacheOptions<T>[\"ttl\"];\n private _cleanupInterval: FileCacheOptions<T>[\"cleanupInterval\"];\n private _resolveCacheKey: Required<\n FileCacheOptions<T>\n >[\"resolveCacheKey\"];\n private _ext: FileCacheOptions<T>[\"ext\"];\n private _intervalId: ReturnType<typeof setInterval> | null = null;\n private _semaphore: Semaphore = new Semaphore();\n private _cleanupGroupSemaphore: GroupSemaphore = new GroupSemaphore();\n\n constructor(\n {\n cachePath,\n autoCreateCachePath,\n cb,\n ext,\n ttl,\n cleanupInterval,\n resolveCacheKey,\n }: FileCacheOptions<T>,\n ) {\n this._cachePath = cachePath;\n this._cb = cb;\n this._ext = ext;\n this._ttl = ttl;\n this._cleanupInterval = cleanupInterval ??\n Duration.toMilliseconds({ days: 1 });\n\n this._resolveCacheKey = resolveCacheKey ??\n ((args: T) => (JSON.stringify(args)));\n\n // Startup code\n if ((pathExistsSync(cachePath))) {\n // we're good\n } else if (autoCreateCachePath) {\n fs.mkdirSync(cachePath, { recursive: true });\n } else {\n throw new Error(\"💥 FileCache error: cachePath does not exist.\");\n }\n\n // fire and forget\n this.cleanup();\n\n this._intervalId = setInterval(\n this.cleanup.bind(this),\n this._cleanupInterval,\n );\n }\n\n /**\n * Resolves the filename for the cache based on the provided arguments.\n *\n * Uses the provided `resolveFileName` function if available, or falls back to\n * generating a hash from the arguments to create a unique file name.\n */\n private async resolveFileName(args: T): Promise<FileName> {\n const [name, ext] = await Promise.all([\n this._resolveCacheKey(args),\n this._ext(args),\n ]);\n\n const resolvedFileName = sha1(JSON.stringify([name, ext]));\n\n return path.format({\n name: resolvedFileName,\n ext,\n }) as FileName;\n }\n\n /**\n * Retrieves a cached file for the given arguments.\n * If the file doesn't exist, it is generated by the callback and stored.\n *\n * Concurrency-safe, ensuring no conflicts when multiple requests are made\n * for the same file.\n *\n * @param args The arguments used to determine the file's identity and generation\n * @returns The path to the cached or newly created file\n */\n async getFile(args: T): Promise<FilePath> {\n const filePath = await this.resolveFilePath(args);\n\n try {\n await Promise.all([\n this._cleanupGroupSemaphore.acquire(\"getFile\"),\n this._semaphore.acquire(filePath),\n ]);\n\n if (await pathExists(filePath)) {\n // fire and forget\n touch(filePath);\n } else {\n await fs.promises.mkdir(path.dirname(filePath), { recursive: true });\n await this._cb(filePath, args, this);\n }\n } finally {\n this._semaphore.release(filePath);\n this._cleanupGroupSemaphore.release(\"getFile\");\n }\n\n return filePath;\n }\n\n /**\n * Resolves the full file path for the cached file based on the provided arguments.\n *\n * @param args The arguments used to resolve the file path\n * @returns The full path to the cached file\n */\n async resolveFilePath(args: T): Promise<FilePath> {\n const fileName = await this.resolveFileName(args);\n\n const filePath = path.resolve(\n this._cachePath,\n fileName[0],\n fileName[1],\n fileName[2],\n fileName,\n );\n\n return filePath;\n }\n\n /**\n * Stops the background cleanup process, but does not delete any cached files.\n */\n destroy() {\n if (this._intervalId) {\n clearInterval(this._intervalId);\n this._intervalId = null;\n }\n }\n\n async has(args: T): Promise<boolean> {\n const filePath = await this.resolveFilePath(args);\n return await pathExists(filePath);\n }\n\n /**\n * Runs a cleanup pass to remove files older than 1 day from the cache.\n * This is called periodically, but can be triggered manually.\n */\n async cleanup() {\n try {\n await this._cleanupGroupSemaphore.acquire(\"cleanup\");\n await findNuke(this._cachePath, {\n olderThan: this._ttl,\n verbose: true,\n deleteEmptyDirectories: true,\n });\n } finally {\n this._cleanupGroupSemaphore.release(\"cleanup\");\n }\n }\n}\n\nexport { type DirectoryPath, FileCache, type FileName, type FilePath };\n"],"names":["FileCache","constructor","cachePath","autoCreateCachePath","cb","ext","ttl","cleanupInterval","resolveCacheKey","_cachePath","_cb","_ttl","_cleanupInterval","_resolveCacheKey","_ext","_intervalId","_semaphore","Semaphore","_cleanupGroupSemaphore","GroupSemaphore","Duration","toMilliseconds","days","args","JSON","stringify","pathExistsSync","fs","mkdirSync","recursive","Error","cleanup","setInterval","bind","resolveFileName","name","Promise","all","resolvedFileName","sha1","path","format","getFile","filePath","resolveFilePath","acquire","pathExists","touch","promises","mkdir","dirname","release","fileName","resolve","destroy","clearInterval","has","findNuke","olderThan","verbose","deleteEmptyDirectories"],"mappings":"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"}
|
package/lib/index.d.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
type DirectoryPath = string;
|
|
2
|
-
type FileName = string;
|
|
3
|
-
type FilePath = string;
|
|
4
|
-
type Milliseconds = number;
|
|
5
|
-
export type FileCacheOptions<T extends Record<string, any>> = {
|
|
6
|
-
cachePath: DirectoryPath;
|
|
7
|
-
autoCreateCachePath?: boolean;
|
|
8
|
-
cb: (filePath: FilePath, args: T, cache: FileCache<T>) => Promise<void>;
|
|
9
|
-
ext: (args: T) => string | Promise<string>;
|
|
10
|
-
resolveCacheKey?: (args: T) => string | Promise<string>;
|
|
11
|
-
ttl: Milliseconds;
|
|
12
|
-
cleanupInterval?: Milliseconds;
|
|
13
|
-
};
|
|
14
|
-
declare class FileCache<T extends Record<string, any>> {
|
|
15
|
-
private _cachePath;
|
|
16
|
-
private _cb;
|
|
17
|
-
private _ttl;
|
|
18
|
-
private _cleanupInterval;
|
|
19
|
-
private _resolveCacheKey;
|
|
20
|
-
private _ext;
|
|
21
|
-
private _intervalId;
|
|
22
|
-
private _semaphore;
|
|
23
|
-
private _cleanupGroupSemaphore;
|
|
24
|
-
constructor({ cachePath, autoCreateCachePath, cb, ext, ttl, cleanupInterval, resolveCacheKey, }: FileCacheOptions<T>);
|
|
25
|
-
/**
|
|
26
|
-
* Resolves the filename for the cache based on the provided arguments.
|
|
27
|
-
*
|
|
28
|
-
* Uses the provided `resolveFileName` function if available, or falls back to
|
|
29
|
-
* generating a hash from the arguments to create a unique file name.
|
|
30
|
-
*/
|
|
31
|
-
private resolveFileName;
|
|
32
|
-
/**
|
|
33
|
-
* Retrieves a cached file for the given arguments.
|
|
34
|
-
* If the file doesn't exist, it is generated by the callback and stored.
|
|
35
|
-
*
|
|
36
|
-
* Concurrency-safe, ensuring no conflicts when multiple requests are made
|
|
37
|
-
* for the same file.
|
|
38
|
-
*
|
|
39
|
-
* @param args The arguments used to determine the file's identity and generation
|
|
40
|
-
* @returns The path to the cached or newly created file
|
|
41
|
-
*/
|
|
42
|
-
getFile(args: T): Promise<FilePath>;
|
|
43
|
-
/**
|
|
44
|
-
* Resolves the full file path for the cached file based on the provided arguments.
|
|
45
|
-
*
|
|
46
|
-
* @param args The arguments used to resolve the file path
|
|
47
|
-
* @returns The full path to the cached file
|
|
48
|
-
*/
|
|
49
|
-
resolveFilePath(args: T): Promise<FilePath>;
|
|
50
|
-
/**
|
|
51
|
-
* Stops the background cleanup process, but does not delete any cached files.
|
|
52
|
-
*/
|
|
53
|
-
destroy(): void;
|
|
54
|
-
has(args: T): Promise<boolean>;
|
|
55
|
-
/**
|
|
56
|
-
* Runs a cleanup pass to remove files older than 1 day from the cache.
|
|
57
|
-
* This is called periodically, but can be triggered manually.
|
|
58
|
-
*/
|
|
59
|
-
cleanup(): Promise<void>;
|
|
60
|
-
}
|
|
61
|
-
export { type DirectoryPath, FileCache, type FileName, type FilePath };
|