@happyvertical/cache 0.85.0 → 0.85.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.
|
@@ -22,6 +22,7 @@ var FileProvider = class {
|
|
|
22
22
|
fileExtension;
|
|
23
23
|
checkPeriod;
|
|
24
24
|
checkInterval;
|
|
25
|
+
cacheDirReady;
|
|
25
26
|
stats;
|
|
26
27
|
constructor(options) {
|
|
27
28
|
this.cacheDir = resolve(options.cacheDir);
|
|
@@ -68,6 +69,7 @@ var FileProvider = class {
|
|
|
68
69
|
}
|
|
69
70
|
async set(key, value, ttl) {
|
|
70
71
|
if (!isValidKey(key)) throw new CacheKeyError(key, "file");
|
|
72
|
+
await this.ensureCacheDir();
|
|
71
73
|
const fullKey = formatKey(this.namespace, key);
|
|
72
74
|
const filePath = this.getFilePath(fullKey);
|
|
73
75
|
const expiresAt = calculateExpiration(ttl ?? this.defaultTTL);
|
|
@@ -126,6 +128,7 @@ var FileProvider = class {
|
|
|
126
128
|
recursive: true,
|
|
127
129
|
force: true
|
|
128
130
|
});
|
|
131
|
+
this.cacheDirReady = void 0;
|
|
129
132
|
await this.ensureCacheDir();
|
|
130
133
|
this.stats.hits = 0;
|
|
131
134
|
this.stats.misses = 0;
|
|
@@ -229,9 +232,24 @@ var FileProvider = class {
|
|
|
229
232
|
}
|
|
230
233
|
}
|
|
231
234
|
/**
|
|
232
|
-
* Ensures cache directory exists
|
|
235
|
+
* Ensures cache directory exists.
|
|
236
|
+
*
|
|
237
|
+
* Creation is started once and memoized, so concurrent operations share a
|
|
238
|
+
* single `mkdir` and every caller awaits the same result. The constructor
|
|
239
|
+
* cannot await, so operations must: otherwise a write issued right after
|
|
240
|
+
* construction can reach `writeFile` before the directory exists and fail
|
|
241
|
+
* with ENOENT.
|
|
233
242
|
*/
|
|
234
|
-
|
|
243
|
+
ensureCacheDir() {
|
|
244
|
+
if (!this.cacheDirReady) {
|
|
245
|
+
this.cacheDirReady = this.createCacheDir();
|
|
246
|
+
this.cacheDirReady.catch(() => {
|
|
247
|
+
this.cacheDirReady = void 0;
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return this.cacheDirReady;
|
|
251
|
+
}
|
|
252
|
+
async createCacheDir() {
|
|
235
253
|
try {
|
|
236
254
|
await mkdir(this.cacheDir, { recursive: true });
|
|
237
255
|
} catch (error) {
|
|
@@ -323,7 +341,7 @@ var FileProvider = class {
|
|
|
323
341
|
*/
|
|
324
342
|
startCleanup() {
|
|
325
343
|
this.checkInterval = setInterval(() => {
|
|
326
|
-
this.removeExpiredFiles();
|
|
344
|
+
this.removeExpiredFiles().catch(() => {});
|
|
327
345
|
}, this.checkPeriod);
|
|
328
346
|
if (this.checkInterval.unref) this.checkInterval.unref();
|
|
329
347
|
}
|
|
@@ -347,4 +365,4 @@ var FileProvider = class {
|
|
|
347
365
|
//#endregion
|
|
348
366
|
export { FileProvider };
|
|
349
367
|
|
|
350
|
-
//# sourceMappingURL=file-
|
|
368
|
+
//# sourceMappingURL=file-DE86gaox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-DE86gaox.js","names":[],"sources":["../../src/providers/file.ts"],"sourcesContent":["/**\n * File cache provider implementation with compression\n */\n\nimport {\n mkdir,\n readdir,\n readFile,\n rm,\n stat,\n writeFile,\n} from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport { promisify } from 'node:util';\nimport { gunzip, gzip } from 'node:zlib';\nimport type {\n CacheEntry,\n CacheProvider,\n CacheStats,\n FileOptions,\n} from '../shared/types';\nimport {\n CacheError,\n CacheKeyError,\n CacheSerializationError,\n CacheSizeError,\n} from '../shared/types';\nimport {\n calculateExpiration,\n calculateSize,\n deserialize,\n extractKey,\n formatKey,\n isExpired,\n isValidKey,\n matchesPattern,\n serialize,\n} from '../shared/utils';\n\nconst gzipAsync = promisify(gzip);\nconst gunzipAsync = promisify(gunzip);\n\n/**\n * File cache provider implementation\n * Stores cache entries as files with optional compression\n */\nexport class FileProvider implements CacheProvider {\n private cacheDir: string;\n private namespace?: string;\n private defaultTTL?: number;\n private maxSize: number;\n private compression: boolean;\n private fileExtension: string;\n private checkPeriod: number;\n private checkInterval?: NodeJS.Timeout;\n private cacheDirReady?: Promise<void>;\n private stats: {\n hits: number;\n misses: number;\n evictions: number;\n };\n\n constructor(options: FileOptions) {\n this.cacheDir = resolve(options.cacheDir);\n this.namespace = options.namespace;\n this.defaultTTL = options.defaultTTL;\n this.maxSize = options.maxSize || 500 * 1024 * 1024; // 500MB default\n this.compression = options.compression ?? false;\n this.fileExtension = options.fileExtension || '.cache';\n this.checkPeriod = options.checkPeriod || 300000; // 5 minutes default\n this.stats = {\n hits: 0,\n misses: 0,\n evictions: 0,\n };\n\n // Start creating the cache directory now so the first operation rarely has\n // to wait. Every filesystem operation still awaits it, because the\n // constructor cannot.\n this.ensureCacheDir();\n\n // Start background cleanup\n this.startCleanup();\n }\n\n async get<T = any>(key: string): Promise<T | undefined> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 'file');\n }\n\n const fullKey = formatKey(this.namespace, key);\n const filePath = this.getFilePath(fullKey);\n\n try {\n const fileContent = await readFile(filePath);\n let data: Buffer;\n\n // Decompress if needed\n if (this.compression) {\n data = await gunzipAsync(fileContent);\n } else {\n data = fileContent;\n }\n\n const entry: CacheEntry<T> = deserialize(data.toString('utf-8'));\n\n // Check if expired\n if (isExpired(entry.expiresAt)) {\n await rm(filePath, { force: true });\n this.stats.misses++;\n return undefined;\n }\n\n // Update hit count\n entry.hits++;\n this.stats.hits++;\n\n // Write back updated entry (for hit tracking)\n await this.writeEntry(filePath, entry);\n\n return entry.value;\n } catch (error: any) {\n if (error.code === 'ENOENT') {\n this.stats.misses++;\n return undefined;\n }\n throw new CacheError(\n `Failed to read cache entry: ${error.message}`,\n 'READ_ERROR',\n 'file',\n );\n }\n }\n\n async set<T = any>(key: string, value: T, ttl?: number): Promise<void> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 'file');\n }\n\n // The only operation that creates a file from nothing, so the only one\n // that can outrun directory creation. Reads tolerate a missing directory,\n // and the write-backs in get()/touch() only run after a successful read,\n // which already proves the directory exists. Any new operation that writes\n // without reading first must await this too.\n //\n // This settles initialization, not the directory's continued existence:\n // once creation has succeeded the memo is fulfilled, so a directory\n // deleted externally afterwards is not recreated here.\n await this.ensureCacheDir();\n\n const fullKey = formatKey(this.namespace, key);\n const filePath = this.getFilePath(fullKey);\n const expiresAt = calculateExpiration(ttl ?? this.defaultTTL);\n\n const entry: CacheEntry<T> = {\n value,\n createdAt: Date.now(),\n expiresAt,\n size: calculateSize(value),\n hits: 0,\n metadata: {\n compressed: this.compression,\n namespace: this.namespace,\n },\n };\n\n // Check if we need to evict files\n await this.evictIfNeeded(entry.size);\n\n // Write entry to file\n await this.writeEntry(filePath, entry);\n }\n\n async has(key: string): Promise<boolean> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 'file');\n }\n\n const fullKey = formatKey(this.namespace, key);\n const filePath = this.getFilePath(fullKey);\n\n try {\n const fileContent = await readFile(filePath);\n let data: Buffer;\n\n if (this.compression) {\n data = await gunzipAsync(fileContent);\n } else {\n data = fileContent;\n }\n\n const entry: CacheEntry = deserialize(data.toString('utf-8'));\n\n // Check if expired\n if (isExpired(entry.expiresAt)) {\n await rm(filePath, { force: true });\n return false;\n }\n\n return true;\n } catch (error: any) {\n if (error.code === 'ENOENT') {\n return false;\n }\n throw new CacheError(\n `Failed to check cache entry: ${error.message}`,\n 'CHECK_ERROR',\n 'file',\n );\n }\n }\n\n async delete(key: string): Promise<boolean> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 'file');\n }\n\n const fullKey = formatKey(this.namespace, key);\n const filePath = this.getFilePath(fullKey);\n\n try {\n await rm(filePath);\n return true;\n } catch (error: any) {\n if (error.code === 'ENOENT') {\n return false;\n }\n throw new CacheError(\n `Failed to delete cache entry: ${error.message}`,\n 'DELETE_ERROR',\n 'file',\n );\n }\n }\n\n async clear(namespace?: string): Promise<void> {\n if (namespace) {\n // Clear specific namespace\n const prefix = this.sanitizeKey(`${namespace}:`);\n const files = await this.getAllFiles();\n\n for (const file of files) {\n if (file.startsWith(prefix)) {\n await rm(join(this.cacheDir, file), { force: true });\n }\n }\n } else {\n // Clear all cache files\n try {\n await rm(this.cacheDir, { recursive: true, force: true });\n\n // The directory this provider memoized no longer exists, so drop the\n // memo and create it again rather than awaiting the settled promise.\n this.cacheDirReady = undefined;\n await this.ensureCacheDir();\n this.stats.hits = 0;\n this.stats.misses = 0;\n this.stats.evictions = 0;\n } catch (error: any) {\n throw new CacheError(\n `Failed to clear cache: ${error.message}`,\n 'CLEAR_ERROR',\n 'file',\n );\n }\n }\n }\n\n async keys(pattern?: string): Promise<string[]> {\n const files = await this.getAllFiles();\n const keys: string[] = [];\n\n for (const file of files) {\n // Remove file extension\n const key = file.replace(this.fileExtension, '');\n\n // Check if file is expired\n const filePath = join(this.cacheDir, file);\n try {\n const fileContent = await readFile(filePath);\n let data: Buffer;\n\n if (this.compression) {\n data = await gunzipAsync(fileContent);\n } else {\n data = fileContent;\n }\n\n const entry: CacheEntry = deserialize(data.toString('utf-8'));\n\n if (!isExpired(entry.expiresAt)) {\n const desanitized = this.desanitizeKey(key);\n keys.push(extractKey(this.namespace, desanitized));\n }\n } catch {}\n }\n\n // Apply pattern filter if provided\n if (pattern) {\n return keys.filter((key) => matchesPattern(pattern, key));\n }\n\n return keys;\n }\n\n async getMany<T = any>(keys: string[]): Promise<Map<string, T>> {\n const result = new Map<string, T>();\n\n for (const key of keys) {\n const value = await this.get<T>(key);\n if (value !== undefined) {\n result.set(key, value);\n }\n }\n\n return result;\n }\n\n async setMany<T = any>(\n entries: Array<{ key: string; value: T; ttl?: number }>,\n ): Promise<void> {\n for (const entry of entries) {\n await this.set(entry.key, entry.value, entry.ttl);\n }\n }\n\n async deleteMany(keys: string[]): Promise<number> {\n let deleted = 0;\n\n for (const key of keys) {\n const wasDeleted = await this.delete(key);\n if (wasDeleted) {\n deleted++;\n }\n }\n\n return deleted;\n }\n\n async getStats(): Promise<CacheStats> {\n const files = await this.getAllFiles();\n let totalSize = 0;\n let entries = 0;\n\n for (const file of files) {\n const filePath = join(this.cacheDir, file);\n try {\n const stats = await stat(filePath);\n totalSize += stats.size;\n\n // Check if expired\n const fileContent = await readFile(filePath);\n let data: Buffer;\n\n if (this.compression) {\n data = await gunzipAsync(fileContent);\n } else {\n data = fileContent;\n }\n\n const entry: CacheEntry = deserialize(data.toString('utf-8'));\n\n if (!isExpired(entry.expiresAt)) {\n entries++;\n }\n } catch {}\n }\n\n const totalAccesses = this.stats.hits + this.stats.misses;\n const hitRate = totalAccesses > 0 ? this.stats.hits / totalAccesses : 0;\n\n return {\n entries,\n totalSize,\n hits: this.stats.hits,\n misses: this.stats.misses,\n hitRate,\n evictions: this.stats.evictions,\n backend: {\n type: 'file',\n cacheDir: this.cacheDir,\n compression: this.compression,\n maxSize: this.maxSize,\n },\n };\n }\n\n async touch(key: string, ttl: number): Promise<boolean> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 'file');\n }\n\n const fullKey = formatKey(this.namespace, key);\n const filePath = this.getFilePath(fullKey);\n\n try {\n const fileContent = await readFile(filePath);\n let data: Buffer;\n\n if (this.compression) {\n data = await gunzipAsync(fileContent);\n } else {\n data = fileContent;\n }\n\n const entry: CacheEntry = deserialize(data.toString('utf-8'));\n\n if (isExpired(entry.expiresAt)) {\n return false;\n }\n\n entry.expiresAt = calculateExpiration(ttl);\n await this.writeEntry(filePath, entry);\n\n return true;\n } catch (error: any) {\n if (error.code === 'ENOENT') {\n return false;\n }\n throw new CacheError(\n `Failed to touch cache entry: ${error.message}`,\n 'TOUCH_ERROR',\n 'file',\n );\n }\n }\n\n async close(): Promise<void> {\n // Stop cleanup interval\n if (this.checkInterval) {\n clearInterval(this.checkInterval);\n this.checkInterval = undefined;\n }\n }\n\n /**\n * Ensures cache directory exists.\n *\n * Creation is started once and memoized, so concurrent operations share a\n * single `mkdir` and every caller awaits the same result. The constructor\n * cannot await, so operations must: otherwise a write issued right after\n * construction can reach `writeFile` before the directory exists and fail\n * with ENOENT.\n */\n private ensureCacheDir(): Promise<void> {\n if (!this.cacheDirReady) {\n this.cacheDirReady = this.createCacheDir();\n\n // Keep a failed creation from surfacing as an unhandled rejection when\n // the constructor kicks it off, and let a later operation retry it.\n // Callers awaiting this promise still observe the error.\n this.cacheDirReady.catch(() => {\n this.cacheDirReady = undefined;\n });\n }\n\n return this.cacheDirReady;\n }\n\n private async createCacheDir(): Promise<void> {\n try {\n await mkdir(this.cacheDir, { recursive: true });\n } catch (error: any) {\n throw new CacheError(\n `Failed to create cache directory: ${error.message}`,\n 'INIT_ERROR',\n 'file',\n );\n }\n }\n\n /**\n * Gets the file path for a cache key\n */\n private getFilePath(key: string): string {\n const sanitizedKey = this.sanitizeKey(key);\n return join(this.cacheDir, `${sanitizedKey}${this.fileExtension}`);\n }\n\n /**\n * Sanitizes a key for use as a filename\n */\n private sanitizeKey(key: string): string {\n return key.replace(/[^a-zA-Z0-9_:-]/g, '_');\n }\n\n /**\n * Desanitizes a filename back to the original key\n */\n private desanitizeKey(sanitized: string): string {\n // This is a simple implementation - in practice, you might need\n // a more sophisticated mapping\n return sanitized;\n }\n\n /**\n * Gets all cache file names\n */\n private async getAllFiles(): Promise<string[]> {\n try {\n const files = await readdir(this.cacheDir);\n return files.filter((file) => file.endsWith(this.fileExtension));\n } catch (error: any) {\n if (error.code === 'ENOENT') {\n return [];\n }\n throw new CacheError(\n `Failed to list cache files: ${error.message}`,\n 'LIST_ERROR',\n 'file',\n );\n }\n }\n\n /**\n * Writes an entry to a file\n */\n private async writeEntry(filePath: string, entry: CacheEntry): Promise<void> {\n try {\n let data: Buffer;\n const json = serialize(entry);\n data = Buffer.from(json, 'utf-8');\n\n // Compress if enabled\n if (this.compression) {\n data = await gzipAsync(data);\n }\n\n await writeFile(filePath, data);\n } catch (error: any) {\n throw new CacheSerializationError(\n `Failed to write cache entry: ${error.message}`,\n 'file',\n );\n }\n }\n\n /**\n * Evicts files if size limit is exceeded\n */\n private async evictIfNeeded(newEntrySize: number): Promise<void> {\n const stats = await this.getStats();\n\n // Check if we need to evict\n if (stats.totalSize + newEntrySize > this.maxSize) {\n await this.evict();\n\n // Check again after eviction\n const updatedStats = await this.getStats();\n if (updatedStats.totalSize + newEntrySize > this.maxSize) {\n throw new CacheSizeError(\n `Cannot cache entry: would exceed max size of ${this.maxSize} bytes`,\n 'file',\n );\n }\n }\n }\n\n /**\n * Evicts oldest files based on creation time\n */\n private async evict(): Promise<void> {\n const files = await this.getAllFiles();\n const filesWithStats: Array<{ file: string; createdAt: number }> = [];\n\n for (const file of files) {\n const filePath = join(this.cacheDir, file);\n try {\n const fileContent = await readFile(filePath);\n let data: Buffer;\n\n if (this.compression) {\n data = await gunzipAsync(fileContent);\n } else {\n data = fileContent;\n }\n\n const entry: CacheEntry = deserialize(data.toString('utf-8'));\n filesWithStats.push({ file, createdAt: entry.createdAt });\n } catch {}\n }\n\n // Sort by creation time (oldest first)\n filesWithStats.sort((a, b) => a.createdAt - b.createdAt);\n\n // Remove oldest 10% of files\n const toRemove = Math.max(1, Math.floor(filesWithStats.length * 0.1));\n for (let i = 0; i < toRemove; i++) {\n const filePath = join(this.cacheDir, filesWithStats[i].file);\n await rm(filePath, { force: true });\n this.stats.evictions++;\n }\n }\n\n /**\n * Starts background cleanup of expired files\n */\n private startCleanup(): void {\n this.checkInterval = setInterval(() => {\n // Background sweep: nothing awaits this, so swallow failures rather than\n // letting them surface as an unhandled rejection in an unrelated caller.\n this.removeExpiredFiles().catch(() => {});\n }, this.checkPeriod);\n\n // Don't block process exit\n if (this.checkInterval.unref) {\n this.checkInterval.unref();\n }\n }\n\n /**\n * Removes expired files\n */\n private async removeExpiredFiles(): Promise<void> {\n const files = await this.getAllFiles();\n\n for (const file of files) {\n const filePath = join(this.cacheDir, file);\n try {\n const fileContent = await readFile(filePath);\n let data: Buffer;\n\n if (this.compression) {\n data = await gunzipAsync(fileContent);\n } else {\n data = fileContent;\n }\n\n const entry: CacheEntry = deserialize(data.toString('utf-8'));\n\n if (isExpired(entry.expiresAt)) {\n await rm(filePath, { force: true });\n }\n } catch {}\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -280,7 +280,7 @@ async function getCache(options) {
|
|
|
280
280
|
return new MemoryProvider(config);
|
|
281
281
|
}
|
|
282
282
|
if (isFileOptions(config)) {
|
|
283
|
-
const { FileProvider } = await import("./chunks/file-
|
|
283
|
+
const { FileProvider } = await import("./chunks/file-DE86gaox.js");
|
|
284
284
|
return new FileProvider(config);
|
|
285
285
|
}
|
|
286
286
|
if (isRedisOptions(config)) {
|
package/dist/providers/file.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare class FileProvider implements CacheProvider {
|
|
|
12
12
|
private fileExtension;
|
|
13
13
|
private checkPeriod;
|
|
14
14
|
private checkInterval?;
|
|
15
|
+
private cacheDirReady?;
|
|
15
16
|
private stats;
|
|
16
17
|
constructor(options: FileOptions);
|
|
17
18
|
get<T = any>(key: string): Promise<T | undefined>;
|
|
@@ -31,9 +32,16 @@ export declare class FileProvider implements CacheProvider {
|
|
|
31
32
|
touch(key: string, ttl: number): Promise<boolean>;
|
|
32
33
|
close(): Promise<void>;
|
|
33
34
|
/**
|
|
34
|
-
* Ensures cache directory exists
|
|
35
|
+
* Ensures cache directory exists.
|
|
36
|
+
*
|
|
37
|
+
* Creation is started once and memoized, so concurrent operations share a
|
|
38
|
+
* single `mkdir` and every caller awaits the same result. The constructor
|
|
39
|
+
* cannot await, so operations must: otherwise a write issued right after
|
|
40
|
+
* construction can reach `writeFile` before the directory exists and fail
|
|
41
|
+
* with ENOENT.
|
|
35
42
|
*/
|
|
36
43
|
private ensureCacheDir;
|
|
44
|
+
private createCacheDir;
|
|
37
45
|
/**
|
|
38
46
|
* Gets the file path for a cache key
|
|
39
47
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/providers/file.ts"],"names":[],"mappings":"AAAA;;GAEG;AAaH,OAAO,KAAK,EAEV,aAAa,EACb,UAAU,EACV,WAAW,EACZ,MAAM,iBAAiB,CAAC;AAsBzB;;;GAGG;AACH,qBAAa,YAAa,YAAW,aAAa;IAChD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAU;IAC7B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,aAAa,CAAC,CAAiB;IACvC,OAAO,CAAC,KAAK,CAIX;gBAEU,OAAO,EAAE,WAAW;
|
|
1
|
+
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/providers/file.ts"],"names":[],"mappings":"AAAA;;GAEG;AAaH,OAAO,KAAK,EAEV,aAAa,EACb,UAAU,EACV,WAAW,EACZ,MAAM,iBAAiB,CAAC;AAsBzB;;;GAGG;AACH,qBAAa,YAAa,YAAW,aAAa;IAChD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAU;IAC7B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,aAAa,CAAC,CAAiB;IACvC,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC,OAAO,CAAC,KAAK,CAIX;gBAEU,OAAO,EAAE,WAAW;IAuB1B,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAiDjD,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuChE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAuClC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAuBrC,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiCxC,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAqCzC,OAAO,CAAC,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAazD,OAAO,CAAC,CAAC,GAAG,GAAG,EACnB,OAAO,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,CAAC,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GACtD,OAAO,CAAC,IAAI,CAAC;IAMV,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAa3C,QAAQ,IAAI,OAAO,CAAC,UAAU,CAAC;IAgD/B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAwCjD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B;;;;;;;;OAQG;IACH,OAAO,CAAC,cAAc;YAeR,cAAc;IAY5B;;OAEG;IACH,OAAO,CAAC,WAAW;IAKnB;;OAEG;IACH,OAAO,CAAC,WAAW;IAInB;;OAEG;IACH,OAAO,CAAC,aAAa;IAMrB;;OAEG;YACW,WAAW;IAgBzB;;OAEG;YACW,UAAU;IAoBxB;;OAEG;YACW,aAAa;IAkB3B;;OAEG;YACW,KAAK;IAiCnB;;OAEG;IACH,OAAO,CAAC,YAAY;IAapB;;OAEG;YACW,kBAAkB;CAuBjC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/cache",
|
|
3
|
-
"version": "0.85.
|
|
3
|
+
"version": "0.85.2",
|
|
4
4
|
"description": "Standardized caching interface supporting Memory, File, and Redis backends",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@aws-sdk/client-s3": "3.1034.0",
|
|
51
51
|
"@aws-sdk/credential-providers": "3.1034.0",
|
|
52
52
|
"redis": "^5.12.1",
|
|
53
|
-
"@happyvertical/utils": "0.85.
|
|
53
|
+
"@happyvertical/utils": "0.85.2"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/node": "25.0.10",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"file-BiNbZgFQ.js","names":[],"sources":["../../src/providers/file.ts"],"sourcesContent":["/**\n * File cache provider implementation with compression\n */\n\nimport {\n mkdir,\n readdir,\n readFile,\n rm,\n stat,\n writeFile,\n} from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport { promisify } from 'node:util';\nimport { gunzip, gzip } from 'node:zlib';\nimport type {\n CacheEntry,\n CacheProvider,\n CacheStats,\n FileOptions,\n} from '../shared/types';\nimport {\n CacheError,\n CacheKeyError,\n CacheSerializationError,\n CacheSizeError,\n} from '../shared/types';\nimport {\n calculateExpiration,\n calculateSize,\n deserialize,\n extractKey,\n formatKey,\n isExpired,\n isValidKey,\n matchesPattern,\n serialize,\n} from '../shared/utils';\n\nconst gzipAsync = promisify(gzip);\nconst gunzipAsync = promisify(gunzip);\n\n/**\n * File cache provider implementation\n * Stores cache entries as files with optional compression\n */\nexport class FileProvider implements CacheProvider {\n private cacheDir: string;\n private namespace?: string;\n private defaultTTL?: number;\n private maxSize: number;\n private compression: boolean;\n private fileExtension: string;\n private checkPeriod: number;\n private checkInterval?: NodeJS.Timeout;\n private stats: {\n hits: number;\n misses: number;\n evictions: number;\n };\n\n constructor(options: FileOptions) {\n this.cacheDir = resolve(options.cacheDir);\n this.namespace = options.namespace;\n this.defaultTTL = options.defaultTTL;\n this.maxSize = options.maxSize || 500 * 1024 * 1024; // 500MB default\n this.compression = options.compression ?? false;\n this.fileExtension = options.fileExtension || '.cache';\n this.checkPeriod = options.checkPeriod || 300000; // 5 minutes default\n this.stats = {\n hits: 0,\n misses: 0,\n evictions: 0,\n };\n\n // Ensure cache directory exists\n this.ensureCacheDir();\n\n // Start background cleanup\n this.startCleanup();\n }\n\n async get<T = any>(key: string): Promise<T | undefined> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 'file');\n }\n\n const fullKey = formatKey(this.namespace, key);\n const filePath = this.getFilePath(fullKey);\n\n try {\n const fileContent = await readFile(filePath);\n let data: Buffer;\n\n // Decompress if needed\n if (this.compression) {\n data = await gunzipAsync(fileContent);\n } else {\n data = fileContent;\n }\n\n const entry: CacheEntry<T> = deserialize(data.toString('utf-8'));\n\n // Check if expired\n if (isExpired(entry.expiresAt)) {\n await rm(filePath, { force: true });\n this.stats.misses++;\n return undefined;\n }\n\n // Update hit count\n entry.hits++;\n this.stats.hits++;\n\n // Write back updated entry (for hit tracking)\n await this.writeEntry(filePath, entry);\n\n return entry.value;\n } catch (error: any) {\n if (error.code === 'ENOENT') {\n this.stats.misses++;\n return undefined;\n }\n throw new CacheError(\n `Failed to read cache entry: ${error.message}`,\n 'READ_ERROR',\n 'file',\n );\n }\n }\n\n async set<T = any>(key: string, value: T, ttl?: number): Promise<void> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 'file');\n }\n\n const fullKey = formatKey(this.namespace, key);\n const filePath = this.getFilePath(fullKey);\n const expiresAt = calculateExpiration(ttl ?? this.defaultTTL);\n\n const entry: CacheEntry<T> = {\n value,\n createdAt: Date.now(),\n expiresAt,\n size: calculateSize(value),\n hits: 0,\n metadata: {\n compressed: this.compression,\n namespace: this.namespace,\n },\n };\n\n // Check if we need to evict files\n await this.evictIfNeeded(entry.size);\n\n // Write entry to file\n await this.writeEntry(filePath, entry);\n }\n\n async has(key: string): Promise<boolean> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 'file');\n }\n\n const fullKey = formatKey(this.namespace, key);\n const filePath = this.getFilePath(fullKey);\n\n try {\n const fileContent = await readFile(filePath);\n let data: Buffer;\n\n if (this.compression) {\n data = await gunzipAsync(fileContent);\n } else {\n data = fileContent;\n }\n\n const entry: CacheEntry = deserialize(data.toString('utf-8'));\n\n // Check if expired\n if (isExpired(entry.expiresAt)) {\n await rm(filePath, { force: true });\n return false;\n }\n\n return true;\n } catch (error: any) {\n if (error.code === 'ENOENT') {\n return false;\n }\n throw new CacheError(\n `Failed to check cache entry: ${error.message}`,\n 'CHECK_ERROR',\n 'file',\n );\n }\n }\n\n async delete(key: string): Promise<boolean> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 'file');\n }\n\n const fullKey = formatKey(this.namespace, key);\n const filePath = this.getFilePath(fullKey);\n\n try {\n await rm(filePath);\n return true;\n } catch (error: any) {\n if (error.code === 'ENOENT') {\n return false;\n }\n throw new CacheError(\n `Failed to delete cache entry: ${error.message}`,\n 'DELETE_ERROR',\n 'file',\n );\n }\n }\n\n async clear(namespace?: string): Promise<void> {\n if (namespace) {\n // Clear specific namespace\n const prefix = this.sanitizeKey(`${namespace}:`);\n const files = await this.getAllFiles();\n\n for (const file of files) {\n if (file.startsWith(prefix)) {\n await rm(join(this.cacheDir, file), { force: true });\n }\n }\n } else {\n // Clear all cache files\n try {\n await rm(this.cacheDir, { recursive: true, force: true });\n await this.ensureCacheDir();\n this.stats.hits = 0;\n this.stats.misses = 0;\n this.stats.evictions = 0;\n } catch (error: any) {\n throw new CacheError(\n `Failed to clear cache: ${error.message}`,\n 'CLEAR_ERROR',\n 'file',\n );\n }\n }\n }\n\n async keys(pattern?: string): Promise<string[]> {\n const files = await this.getAllFiles();\n const keys: string[] = [];\n\n for (const file of files) {\n // Remove file extension\n const key = file.replace(this.fileExtension, '');\n\n // Check if file is expired\n const filePath = join(this.cacheDir, file);\n try {\n const fileContent = await readFile(filePath);\n let data: Buffer;\n\n if (this.compression) {\n data = await gunzipAsync(fileContent);\n } else {\n data = fileContent;\n }\n\n const entry: CacheEntry = deserialize(data.toString('utf-8'));\n\n if (!isExpired(entry.expiresAt)) {\n const desanitized = this.desanitizeKey(key);\n keys.push(extractKey(this.namespace, desanitized));\n }\n } catch {}\n }\n\n // Apply pattern filter if provided\n if (pattern) {\n return keys.filter((key) => matchesPattern(pattern, key));\n }\n\n return keys;\n }\n\n async getMany<T = any>(keys: string[]): Promise<Map<string, T>> {\n const result = new Map<string, T>();\n\n for (const key of keys) {\n const value = await this.get<T>(key);\n if (value !== undefined) {\n result.set(key, value);\n }\n }\n\n return result;\n }\n\n async setMany<T = any>(\n entries: Array<{ key: string; value: T; ttl?: number }>,\n ): Promise<void> {\n for (const entry of entries) {\n await this.set(entry.key, entry.value, entry.ttl);\n }\n }\n\n async deleteMany(keys: string[]): Promise<number> {\n let deleted = 0;\n\n for (const key of keys) {\n const wasDeleted = await this.delete(key);\n if (wasDeleted) {\n deleted++;\n }\n }\n\n return deleted;\n }\n\n async getStats(): Promise<CacheStats> {\n const files = await this.getAllFiles();\n let totalSize = 0;\n let entries = 0;\n\n for (const file of files) {\n const filePath = join(this.cacheDir, file);\n try {\n const stats = await stat(filePath);\n totalSize += stats.size;\n\n // Check if expired\n const fileContent = await readFile(filePath);\n let data: Buffer;\n\n if (this.compression) {\n data = await gunzipAsync(fileContent);\n } else {\n data = fileContent;\n }\n\n const entry: CacheEntry = deserialize(data.toString('utf-8'));\n\n if (!isExpired(entry.expiresAt)) {\n entries++;\n }\n } catch {}\n }\n\n const totalAccesses = this.stats.hits + this.stats.misses;\n const hitRate = totalAccesses > 0 ? this.stats.hits / totalAccesses : 0;\n\n return {\n entries,\n totalSize,\n hits: this.stats.hits,\n misses: this.stats.misses,\n hitRate,\n evictions: this.stats.evictions,\n backend: {\n type: 'file',\n cacheDir: this.cacheDir,\n compression: this.compression,\n maxSize: this.maxSize,\n },\n };\n }\n\n async touch(key: string, ttl: number): Promise<boolean> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 'file');\n }\n\n const fullKey = formatKey(this.namespace, key);\n const filePath = this.getFilePath(fullKey);\n\n try {\n const fileContent = await readFile(filePath);\n let data: Buffer;\n\n if (this.compression) {\n data = await gunzipAsync(fileContent);\n } else {\n data = fileContent;\n }\n\n const entry: CacheEntry = deserialize(data.toString('utf-8'));\n\n if (isExpired(entry.expiresAt)) {\n return false;\n }\n\n entry.expiresAt = calculateExpiration(ttl);\n await this.writeEntry(filePath, entry);\n\n return true;\n } catch (error: any) {\n if (error.code === 'ENOENT') {\n return false;\n }\n throw new CacheError(\n `Failed to touch cache entry: ${error.message}`,\n 'TOUCH_ERROR',\n 'file',\n );\n }\n }\n\n async close(): Promise<void> {\n // Stop cleanup interval\n if (this.checkInterval) {\n clearInterval(this.checkInterval);\n this.checkInterval = undefined;\n }\n }\n\n /**\n * Ensures cache directory exists\n */\n private async ensureCacheDir(): Promise<void> {\n try {\n await mkdir(this.cacheDir, { recursive: true });\n } catch (error: any) {\n throw new CacheError(\n `Failed to create cache directory: ${error.message}`,\n 'INIT_ERROR',\n 'file',\n );\n }\n }\n\n /**\n * Gets the file path for a cache key\n */\n private getFilePath(key: string): string {\n const sanitizedKey = this.sanitizeKey(key);\n return join(this.cacheDir, `${sanitizedKey}${this.fileExtension}`);\n }\n\n /**\n * Sanitizes a key for use as a filename\n */\n private sanitizeKey(key: string): string {\n return key.replace(/[^a-zA-Z0-9_:-]/g, '_');\n }\n\n /**\n * Desanitizes a filename back to the original key\n */\n private desanitizeKey(sanitized: string): string {\n // This is a simple implementation - in practice, you might need\n // a more sophisticated mapping\n return sanitized;\n }\n\n /**\n * Gets all cache file names\n */\n private async getAllFiles(): Promise<string[]> {\n try {\n const files = await readdir(this.cacheDir);\n return files.filter((file) => file.endsWith(this.fileExtension));\n } catch (error: any) {\n if (error.code === 'ENOENT') {\n return [];\n }\n throw new CacheError(\n `Failed to list cache files: ${error.message}`,\n 'LIST_ERROR',\n 'file',\n );\n }\n }\n\n /**\n * Writes an entry to a file\n */\n private async writeEntry(filePath: string, entry: CacheEntry): Promise<void> {\n try {\n let data: Buffer;\n const json = serialize(entry);\n data = Buffer.from(json, 'utf-8');\n\n // Compress if enabled\n if (this.compression) {\n data = await gzipAsync(data);\n }\n\n await writeFile(filePath, data);\n } catch (error: any) {\n throw new CacheSerializationError(\n `Failed to write cache entry: ${error.message}`,\n 'file',\n );\n }\n }\n\n /**\n * Evicts files if size limit is exceeded\n */\n private async evictIfNeeded(newEntrySize: number): Promise<void> {\n const stats = await this.getStats();\n\n // Check if we need to evict\n if (stats.totalSize + newEntrySize > this.maxSize) {\n await this.evict();\n\n // Check again after eviction\n const updatedStats = await this.getStats();\n if (updatedStats.totalSize + newEntrySize > this.maxSize) {\n throw new CacheSizeError(\n `Cannot cache entry: would exceed max size of ${this.maxSize} bytes`,\n 'file',\n );\n }\n }\n }\n\n /**\n * Evicts oldest files based on creation time\n */\n private async evict(): Promise<void> {\n const files = await this.getAllFiles();\n const filesWithStats: Array<{ file: string; createdAt: number }> = [];\n\n for (const file of files) {\n const filePath = join(this.cacheDir, file);\n try {\n const fileContent = await readFile(filePath);\n let data: Buffer;\n\n if (this.compression) {\n data = await gunzipAsync(fileContent);\n } else {\n data = fileContent;\n }\n\n const entry: CacheEntry = deserialize(data.toString('utf-8'));\n filesWithStats.push({ file, createdAt: entry.createdAt });\n } catch {}\n }\n\n // Sort by creation time (oldest first)\n filesWithStats.sort((a, b) => a.createdAt - b.createdAt);\n\n // Remove oldest 10% of files\n const toRemove = Math.max(1, Math.floor(filesWithStats.length * 0.1));\n for (let i = 0; i < toRemove; i++) {\n const filePath = join(this.cacheDir, filesWithStats[i].file);\n await rm(filePath, { force: true });\n this.stats.evictions++;\n }\n }\n\n /**\n * Starts background cleanup of expired files\n */\n private startCleanup(): void {\n this.checkInterval = setInterval(() => {\n this.removeExpiredFiles();\n }, this.checkPeriod);\n\n // Don't block process exit\n if (this.checkInterval.unref) {\n this.checkInterval.unref();\n }\n }\n\n /**\n * Removes expired files\n */\n private async removeExpiredFiles(): Promise<void> {\n const files = await this.getAllFiles();\n\n for (const file of files) {\n const filePath = join(this.cacheDir, file);\n try {\n const fileContent = await readFile(filePath);\n let data: Buffer;\n\n if (this.compression) {\n data = await gunzipAsync(fileContent);\n } else {\n data = fileContent;\n }\n\n const entry: CacheEntry = deserialize(data.toString('utf-8'));\n\n if (isExpired(entry.expiresAt)) {\n await rm(filePath, { force: true });\n }\n } catch {}\n }\n }\n}\n"],"mappings":"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"}
|