@chriscdn/file-cache 0.0.1 → 0.0.2
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.ts +3 -4
- package/lib/index.js +17 -8
- package/lib/index.js.map +2 -2
- package/package.json +1 -1
- package/src/index.ts +20 -10
package/lib/index.d.ts
CHANGED
|
@@ -3,15 +3,14 @@ type FilePath = string;
|
|
|
3
3
|
type Milliseconds = number;
|
|
4
4
|
export type FileCacheOptions<T extends Record<string, any>> = {
|
|
5
5
|
cachePath: DirectoryPath;
|
|
6
|
+
autoCreateCachePath?: boolean;
|
|
6
7
|
cb: (filePath: FilePath, context: T, cache: FileCache<T>) => Promise<void>;
|
|
7
8
|
ext: string;
|
|
8
9
|
cleanupInterval?: Milliseconds;
|
|
9
10
|
ttl: Milliseconds;
|
|
10
|
-
resolveFileName?: (args: T) => FilePath;
|
|
11
11
|
};
|
|
12
12
|
declare class FileCache<T extends Record<string, any>> {
|
|
13
13
|
private _cachePath;
|
|
14
|
-
private _resolveFileName;
|
|
15
14
|
private _cb;
|
|
16
15
|
private _ext;
|
|
17
16
|
private _ttl;
|
|
@@ -19,7 +18,7 @@ declare class FileCache<T extends Record<string, any>> {
|
|
|
19
18
|
private _intervalId;
|
|
20
19
|
private _semaphore;
|
|
21
20
|
private _cleanupSemaphore;
|
|
22
|
-
constructor({ cachePath,
|
|
21
|
+
constructor({ cachePath, autoCreateCachePath, cb, ext, ttl, cleanupInterval }: FileCacheOptions<T>);
|
|
23
22
|
/**
|
|
24
23
|
* Runs a cleanup pass to remove files older than 1 day from the cache.
|
|
25
24
|
* This is called periodically, but can be triggered manually.
|
|
@@ -64,4 +63,4 @@ declare class FileCache<T extends Record<string, any>> {
|
|
|
64
63
|
resolveFilePath(args: T): FilePath;
|
|
65
64
|
resolveCreateFilePath(args: T): Promise<FilePath>;
|
|
66
65
|
}
|
|
67
|
-
export { FileCache, type FilePath };
|
|
66
|
+
export { type DirectoryPath, FileCache, type FilePath };
|
package/lib/index.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
-
import
|
|
3
|
-
import { pathExists } from "path-exists";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { pathExists, pathExistsSync } from "path-exists";
|
|
4
4
|
import sha1 from "sha1";
|
|
5
5
|
import Semaphore from "@chriscdn/promise-semaphore";
|
|
6
6
|
import touch from "touch";
|
|
7
7
|
import { findNuke } from "@chriscdn/find-nuke";
|
|
8
8
|
import { Duration } from "@chriscdn/duration";
|
|
9
9
|
import { rimraf } from "rimraf";
|
|
10
|
+
const fsp = fs.promises;
|
|
10
11
|
class FileCache {
|
|
11
12
|
_cachePath;
|
|
12
|
-
_resolveFileName;
|
|
13
|
+
// private _resolveFileName: FileCacheOptions<T>["resolveFileName"];
|
|
13
14
|
_cb;
|
|
14
15
|
_ext;
|
|
15
16
|
_ttl;
|
|
@@ -17,9 +18,9 @@ class FileCache {
|
|
|
17
18
|
_intervalId = null;
|
|
18
19
|
_semaphore = new Semaphore();
|
|
19
20
|
_cleanupSemaphore = new Semaphore();
|
|
20
|
-
constructor({ cachePath,
|
|
21
|
+
constructor({ cachePath, autoCreateCachePath, cb, ext, ttl, cleanupInterval }) {
|
|
21
22
|
this._cachePath = cachePath;
|
|
22
|
-
this._resolveFileName = resolveFileName;
|
|
23
|
+
// this._resolveFileName = resolveFileName;
|
|
23
24
|
this._cb = cb;
|
|
24
25
|
this._ext = ext;
|
|
25
26
|
this._ttl = ttl;
|
|
@@ -31,7 +32,15 @@ class FileCache {
|
|
|
31
32
|
// process.exit(1); // or throw new Error() if you want to let it bubble
|
|
32
33
|
}
|
|
33
34
|
})();
|
|
34
|
-
|
|
35
|
+
if ((pathExistsSync(cachePath))) {
|
|
36
|
+
}
|
|
37
|
+
else if (autoCreateCachePath) {
|
|
38
|
+
fs.mkdirSync(cachePath, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
throw new Error("💥 FileCache error: cachePath does not exist.");
|
|
42
|
+
// process.exit(1); // or throw new Error() if you want to let it bubble
|
|
43
|
+
}
|
|
35
44
|
// fire and forget
|
|
36
45
|
this.cleanup();
|
|
37
46
|
this._intervalId = setInterval(this.cleanup.bind(this), this._cleanupInterval);
|
|
@@ -75,7 +84,7 @@ class FileCache {
|
|
|
75
84
|
touch(filePath);
|
|
76
85
|
}
|
|
77
86
|
else {
|
|
78
|
-
await fs.
|
|
87
|
+
await fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
79
88
|
await this._cb(filePath, args, this);
|
|
80
89
|
}
|
|
81
90
|
}
|
|
@@ -145,7 +154,7 @@ class FileCache {
|
|
|
145
154
|
}
|
|
146
155
|
async resolveCreateFilePath(args) {
|
|
147
156
|
const filePath = this.resolveFilePath(args);
|
|
148
|
-
await
|
|
157
|
+
await fsp.mkdir(path.dirname(filePath), { recursive: true });
|
|
149
158
|
return filePath;
|
|
150
159
|
}
|
|
151
160
|
}
|
package/lib/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["import path from \"path\";\nimport
|
|
5
|
-
"mappings": ";AAAA,OAAO,UAAU;AACjB,
|
|
4
|
+
"sourcesContent": ["import path from \"path\";\nimport fs from \"fs\";\nimport { pathExists, pathExistsSync } from \"path-exists\";\nimport sha1 from \"sha1\";\nimport Semaphore from \"@chriscdn/promise-semaphore\";\nimport touch from \"touch\";\nimport { findNuke } from \"@chriscdn/find-nuke\";\nimport { Duration } from \"@chriscdn/duration\";\nimport { rimraf } from \"rimraf\";\n\nconst fsp = fs.promises;\n\ntype DirectoryPath = 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, context: T, cache: FileCache<T>) => Promise<void>;\n ext: string;\n cleanupInterval?: Milliseconds;\n ttl: Milliseconds;\n // resolveFileName?: (args: T) => FilePath;\n};\n\nclass FileCache<T extends Record<string, any>> {\n private _cachePath: FileCacheOptions<T>[\"cachePath\"];\n // private _resolveFileName: FileCacheOptions<T>[\"resolveFileName\"];\n private _cb: FileCacheOptions<T>[\"cb\"];\n private _ext: FileCacheOptions<T>[\"ext\"];\n private _ttl: FileCacheOptions<T>[\"ttl\"];\n private _cleanupInterval: FileCacheOptions<T>[\"cleanupInterval\"];\n\n private _intervalId: ReturnType<typeof setInterval> | null = null;\n\n private _semaphore: Semaphore = new Semaphore();\n private _cleanupSemaphore: Semaphore = new Semaphore();\n\n constructor(\n { cachePath, autoCreateCachePath, cb, ext, ttl, cleanupInterval }:\n FileCacheOptions<T>,\n ) {\n this._cachePath = cachePath;\n // this._resolveFileName = resolveFileName;\n this._cb = cb;\n this._ext = ext;\n this._ttl = ttl;\n this._cleanupInterval = cleanupInterval ??\n Duration.toMilliseconds({ days: 1 });\n\n (async () => {\n if (!(await pathExists(cachePath))) {\n throw new Error(\"\uD83D\uDCA5 FileCache error: cachePath does not exist.\");\n // process.exit(1); // or throw new Error() if you want to let it bubble\n }\n })();\n\n if ((pathExistsSync(cachePath))) {\n } else if (autoCreateCachePath) {\n fs.mkdirSync(cachePath, { recursive: true });\n } else {\n throw new Error(\"\uD83D\uDCA5 FileCache error: cachePath does not exist.\");\n // process.exit(1); // or throw new Error() if you want to let it bubble\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 * 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._cleanupSemaphore.acquire();\n await findNuke(this._cachePath, {\n olderThan: this._ttl,\n verbose: true,\n deleteEmptyDirectories: true,\n });\n } finally {\n this._cleanupSemaphore.release();\n }\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 = this.resolveFilePath(args);\n\n try {\n await Promise.all([\n this._semaphore.acquire(filePath),\n this._cleanupSemaphore.wait(),\n ]);\n\n if (await pathExists(filePath)) {\n // fire and forget\n touch(filePath);\n } else {\n await fs.mkdirSync(path.dirname(filePath), { recursive: true });\n await this._cb(filePath, args, this);\n }\n } finally {\n this._semaphore.release(filePath);\n }\n\n return filePath;\n }\n\n async has(args: T): Promise<boolean> {\n const filePath = this.resolveFilePath(args);\n return await pathExists(filePath);\n }\n\n /**\n * Deletes a file from the cache if it exists.\n *\n * @param args The arguments used to determine the file's identity\n * @returns True if the file was deleted, false if it didn't exist\n */\n async expire(args: T): Promise<boolean> {\n const filePath = this.resolveFilePath(args);\n\n if (await pathExists(filePath)) {\n await rimraf(filePath);\n return true;\n } else {\n return false;\n }\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 /**\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 resolveFileName(args: T): string {\n const baseName = sha1(JSON.stringify(args, Object.keys(args).sort()));\n\n return path.format({\n // dir: path.dirname(filePath),\n name: baseName,\n ext: this._ext,\n });\n\n // return this._resolveFileName\n // ? this._resolveFileName(args)\n // : `${sha1(JSON.stringify(args, Object.keys(args).sort()))}${this._ext}`;\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 resolveFilePath(args: T): FilePath {\n const fileName = this.resolveFileName(args);\n\n const filePath = path.resolve(\n this._cachePath,\n fileName[0],\n fileName[1],\n fileName[2],\n // fileName[3],\n fileName,\n );\n\n return filePath;\n }\n\n async resolveCreateFilePath(args: T): Promise<FilePath> {\n const filePath = this.resolveFilePath(args);\n\n await fsp.mkdir(path.dirname(filePath), { recursive: true });\n\n return filePath;\n }\n}\n\nexport { type DirectoryPath, FileCache, type FilePath };\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
-
import
|
|
3
|
-
import { pathExists } from "path-exists";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { pathExists, pathExistsSync } from "path-exists";
|
|
4
4
|
import sha1 from "sha1";
|
|
5
5
|
import Semaphore from "@chriscdn/promise-semaphore";
|
|
6
6
|
import touch from "touch";
|
|
@@ -8,22 +8,25 @@ import { findNuke } from "@chriscdn/find-nuke";
|
|
|
8
8
|
import { Duration } from "@chriscdn/duration";
|
|
9
9
|
import { rimraf } from "rimraf";
|
|
10
10
|
|
|
11
|
+
const fsp = fs.promises;
|
|
12
|
+
|
|
11
13
|
type DirectoryPath = string;
|
|
12
14
|
type FilePath = string;
|
|
13
15
|
type Milliseconds = number;
|
|
14
16
|
|
|
15
17
|
export type FileCacheOptions<T extends Record<string, any>> = {
|
|
16
18
|
cachePath: DirectoryPath;
|
|
19
|
+
autoCreateCachePath?: boolean;
|
|
17
20
|
cb: (filePath: FilePath, context: T, cache: FileCache<T>) => Promise<void>;
|
|
18
21
|
ext: string;
|
|
19
22
|
cleanupInterval?: Milliseconds;
|
|
20
23
|
ttl: Milliseconds;
|
|
21
|
-
resolveFileName?: (args: T) => FilePath;
|
|
24
|
+
// resolveFileName?: (args: T) => FilePath;
|
|
22
25
|
};
|
|
23
26
|
|
|
24
27
|
class FileCache<T extends Record<string, any>> {
|
|
25
28
|
private _cachePath: FileCacheOptions<T>["cachePath"];
|
|
26
|
-
private _resolveFileName: FileCacheOptions<T>["resolveFileName"];
|
|
29
|
+
// private _resolveFileName: FileCacheOptions<T>["resolveFileName"];
|
|
27
30
|
private _cb: FileCacheOptions<T>["cb"];
|
|
28
31
|
private _ext: FileCacheOptions<T>["ext"];
|
|
29
32
|
private _ttl: FileCacheOptions<T>["ttl"];
|
|
@@ -35,11 +38,11 @@ class FileCache<T extends Record<string, any>> {
|
|
|
35
38
|
private _cleanupSemaphore: Semaphore = new Semaphore();
|
|
36
39
|
|
|
37
40
|
constructor(
|
|
38
|
-
{ cachePath,
|
|
41
|
+
{ cachePath, autoCreateCachePath, cb, ext, ttl, cleanupInterval }:
|
|
39
42
|
FileCacheOptions<T>,
|
|
40
43
|
) {
|
|
41
44
|
this._cachePath = cachePath;
|
|
42
|
-
this._resolveFileName = resolveFileName;
|
|
45
|
+
// this._resolveFileName = resolveFileName;
|
|
43
46
|
this._cb = cb;
|
|
44
47
|
this._ext = ext;
|
|
45
48
|
this._ttl = ttl;
|
|
@@ -53,7 +56,14 @@ class FileCache<T extends Record<string, any>> {
|
|
|
53
56
|
}
|
|
54
57
|
})();
|
|
55
58
|
|
|
56
|
-
|
|
59
|
+
if ((pathExistsSync(cachePath))) {
|
|
60
|
+
} else if (autoCreateCachePath) {
|
|
61
|
+
fs.mkdirSync(cachePath, { recursive: true });
|
|
62
|
+
} else {
|
|
63
|
+
throw new Error("💥 FileCache error: cachePath does not exist.");
|
|
64
|
+
// process.exit(1); // or throw new Error() if you want to let it bubble
|
|
65
|
+
}
|
|
66
|
+
|
|
57
67
|
// fire and forget
|
|
58
68
|
this.cleanup();
|
|
59
69
|
|
|
@@ -103,7 +113,7 @@ class FileCache<T extends Record<string, any>> {
|
|
|
103
113
|
// fire and forget
|
|
104
114
|
touch(filePath);
|
|
105
115
|
} else {
|
|
106
|
-
await fs.
|
|
116
|
+
await fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
107
117
|
await this._cb(filePath, args, this);
|
|
108
118
|
}
|
|
109
119
|
} finally {
|
|
@@ -189,10 +199,10 @@ class FileCache<T extends Record<string, any>> {
|
|
|
189
199
|
async resolveCreateFilePath(args: T): Promise<FilePath> {
|
|
190
200
|
const filePath = this.resolveFilePath(args);
|
|
191
201
|
|
|
192
|
-
await
|
|
202
|
+
await fsp.mkdir(path.dirname(filePath), { recursive: true });
|
|
193
203
|
|
|
194
204
|
return filePath;
|
|
195
205
|
}
|
|
196
206
|
}
|
|
197
207
|
|
|
198
|
-
export { FileCache, type FilePath };
|
|
208
|
+
export { type DirectoryPath, FileCache, type FilePath };
|