@happyvertical/cache 0.79.0 → 0.80.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/AGENT.md CHANGED
@@ -7,7 +7,7 @@ Standardized caching interface supporting Memory, File, and Redis backends
7
7
  ## Package Map
8
8
  - Package: `@happyvertical/cache`
9
9
  - Hierarchy path: `@happyvertical/sdk > packages > cache`
10
- - Workspace position: `5 of 31` local packages
10
+ - Workspace position: `5 of 32` local packages
11
11
  - Internal dependencies: `@happyvertical/utils`
12
12
  - Internal dependents: `@happyvertical/geo`, `@happyvertical/translator`
13
13
  - Knowledge graph files: `AGENT.md`, `metadata.json`, `ecosystem-manifest.json`
@@ -0,0 +1,350 @@
1
+ import { CacheError, CacheKeyError, CacheSerializationError, CacheSizeError, calculateExpiration, calculateSize, deserialize, extractKey, formatKey, isExpired, isValidKey, matchesPattern, serialize } from "../index.js";
2
+ import { join, resolve } from "node:path";
3
+ import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
4
+ import { promisify } from "node:util";
5
+ import { gunzip, gzip } from "node:zlib";
6
+ //#region src/providers/file.ts
7
+ /**
8
+ * File cache provider implementation with compression
9
+ */
10
+ var gzipAsync = promisify(gzip);
11
+ var gunzipAsync = promisify(gunzip);
12
+ /**
13
+ * File cache provider implementation
14
+ * Stores cache entries as files with optional compression
15
+ */
16
+ var FileProvider = class {
17
+ cacheDir;
18
+ namespace;
19
+ defaultTTL;
20
+ maxSize;
21
+ compression;
22
+ fileExtension;
23
+ checkPeriod;
24
+ checkInterval;
25
+ stats;
26
+ constructor(options) {
27
+ this.cacheDir = resolve(options.cacheDir);
28
+ this.namespace = options.namespace;
29
+ this.defaultTTL = options.defaultTTL;
30
+ this.maxSize = options.maxSize || 500 * 1024 * 1024;
31
+ this.compression = options.compression ?? false;
32
+ this.fileExtension = options.fileExtension || ".cache";
33
+ this.checkPeriod = options.checkPeriod || 3e5;
34
+ this.stats = {
35
+ hits: 0,
36
+ misses: 0,
37
+ evictions: 0
38
+ };
39
+ this.ensureCacheDir();
40
+ this.startCleanup();
41
+ }
42
+ async get(key) {
43
+ if (!isValidKey(key)) throw new CacheKeyError(key, "file");
44
+ const fullKey = formatKey(this.namespace, key);
45
+ const filePath = this.getFilePath(fullKey);
46
+ try {
47
+ const fileContent = await readFile(filePath);
48
+ let data;
49
+ if (this.compression) data = await gunzipAsync(fileContent);
50
+ else data = fileContent;
51
+ const entry = deserialize(data.toString("utf-8"));
52
+ if (isExpired(entry.expiresAt)) {
53
+ await rm(filePath, { force: true });
54
+ this.stats.misses++;
55
+ return;
56
+ }
57
+ entry.hits++;
58
+ this.stats.hits++;
59
+ await this.writeEntry(filePath, entry);
60
+ return entry.value;
61
+ } catch (error) {
62
+ if (error.code === "ENOENT") {
63
+ this.stats.misses++;
64
+ return;
65
+ }
66
+ throw new CacheError(`Failed to read cache entry: ${error.message}`, "READ_ERROR", "file");
67
+ }
68
+ }
69
+ async set(key, value, ttl) {
70
+ if (!isValidKey(key)) throw new CacheKeyError(key, "file");
71
+ const fullKey = formatKey(this.namespace, key);
72
+ const filePath = this.getFilePath(fullKey);
73
+ const expiresAt = calculateExpiration(ttl ?? this.defaultTTL);
74
+ const entry = {
75
+ value,
76
+ createdAt: Date.now(),
77
+ expiresAt,
78
+ size: calculateSize(value),
79
+ hits: 0,
80
+ metadata: {
81
+ compressed: this.compression,
82
+ namespace: this.namespace
83
+ }
84
+ };
85
+ await this.evictIfNeeded(entry.size);
86
+ await this.writeEntry(filePath, entry);
87
+ }
88
+ async has(key) {
89
+ if (!isValidKey(key)) throw new CacheKeyError(key, "file");
90
+ const fullKey = formatKey(this.namespace, key);
91
+ const filePath = this.getFilePath(fullKey);
92
+ try {
93
+ const fileContent = await readFile(filePath);
94
+ let data;
95
+ if (this.compression) data = await gunzipAsync(fileContent);
96
+ else data = fileContent;
97
+ if (isExpired(deserialize(data.toString("utf-8")).expiresAt)) {
98
+ await rm(filePath, { force: true });
99
+ return false;
100
+ }
101
+ return true;
102
+ } catch (error) {
103
+ if (error.code === "ENOENT") return false;
104
+ throw new CacheError(`Failed to check cache entry: ${error.message}`, "CHECK_ERROR", "file");
105
+ }
106
+ }
107
+ async delete(key) {
108
+ if (!isValidKey(key)) throw new CacheKeyError(key, "file");
109
+ const fullKey = formatKey(this.namespace, key);
110
+ const filePath = this.getFilePath(fullKey);
111
+ try {
112
+ await rm(filePath);
113
+ return true;
114
+ } catch (error) {
115
+ if (error.code === "ENOENT") return false;
116
+ throw new CacheError(`Failed to delete cache entry: ${error.message}`, "DELETE_ERROR", "file");
117
+ }
118
+ }
119
+ async clear(namespace) {
120
+ if (namespace) {
121
+ const prefix = this.sanitizeKey(`${namespace}:`);
122
+ const files = await this.getAllFiles();
123
+ for (const file of files) if (file.startsWith(prefix)) await rm(join(this.cacheDir, file), { force: true });
124
+ } else try {
125
+ await rm(this.cacheDir, {
126
+ recursive: true,
127
+ force: true
128
+ });
129
+ await this.ensureCacheDir();
130
+ this.stats.hits = 0;
131
+ this.stats.misses = 0;
132
+ this.stats.evictions = 0;
133
+ } catch (error) {
134
+ throw new CacheError(`Failed to clear cache: ${error.message}`, "CLEAR_ERROR", "file");
135
+ }
136
+ }
137
+ async keys(pattern) {
138
+ const files = await this.getAllFiles();
139
+ const keys = [];
140
+ for (const file of files) {
141
+ const key = file.replace(this.fileExtension, "");
142
+ const filePath = join(this.cacheDir, file);
143
+ try {
144
+ const fileContent = await readFile(filePath);
145
+ let data;
146
+ if (this.compression) data = await gunzipAsync(fileContent);
147
+ else data = fileContent;
148
+ if (!isExpired(deserialize(data.toString("utf-8")).expiresAt)) {
149
+ const desanitized = this.desanitizeKey(key);
150
+ keys.push(extractKey(this.namespace, desanitized));
151
+ }
152
+ } catch {}
153
+ }
154
+ if (pattern) return keys.filter((key) => matchesPattern(pattern, key));
155
+ return keys;
156
+ }
157
+ async getMany(keys) {
158
+ const result = /* @__PURE__ */ new Map();
159
+ for (const key of keys) {
160
+ const value = await this.get(key);
161
+ if (value !== void 0) result.set(key, value);
162
+ }
163
+ return result;
164
+ }
165
+ async setMany(entries) {
166
+ for (const entry of entries) await this.set(entry.key, entry.value, entry.ttl);
167
+ }
168
+ async deleteMany(keys) {
169
+ let deleted = 0;
170
+ for (const key of keys) if (await this.delete(key)) deleted++;
171
+ return deleted;
172
+ }
173
+ async getStats() {
174
+ const files = await this.getAllFiles();
175
+ let totalSize = 0;
176
+ let entries = 0;
177
+ for (const file of files) {
178
+ const filePath = join(this.cacheDir, file);
179
+ try {
180
+ const stats = await stat(filePath);
181
+ totalSize += stats.size;
182
+ const fileContent = await readFile(filePath);
183
+ let data;
184
+ if (this.compression) data = await gunzipAsync(fileContent);
185
+ else data = fileContent;
186
+ if (!isExpired(deserialize(data.toString("utf-8")).expiresAt)) entries++;
187
+ } catch {}
188
+ }
189
+ const totalAccesses = this.stats.hits + this.stats.misses;
190
+ const hitRate = totalAccesses > 0 ? this.stats.hits / totalAccesses : 0;
191
+ return {
192
+ entries,
193
+ totalSize,
194
+ hits: this.stats.hits,
195
+ misses: this.stats.misses,
196
+ hitRate,
197
+ evictions: this.stats.evictions,
198
+ backend: {
199
+ type: "file",
200
+ cacheDir: this.cacheDir,
201
+ compression: this.compression,
202
+ maxSize: this.maxSize
203
+ }
204
+ };
205
+ }
206
+ async touch(key, ttl) {
207
+ if (!isValidKey(key)) throw new CacheKeyError(key, "file");
208
+ const fullKey = formatKey(this.namespace, key);
209
+ const filePath = this.getFilePath(fullKey);
210
+ try {
211
+ const fileContent = await readFile(filePath);
212
+ let data;
213
+ if (this.compression) data = await gunzipAsync(fileContent);
214
+ else data = fileContent;
215
+ const entry = deserialize(data.toString("utf-8"));
216
+ if (isExpired(entry.expiresAt)) return false;
217
+ entry.expiresAt = calculateExpiration(ttl);
218
+ await this.writeEntry(filePath, entry);
219
+ return true;
220
+ } catch (error) {
221
+ if (error.code === "ENOENT") return false;
222
+ throw new CacheError(`Failed to touch cache entry: ${error.message}`, "TOUCH_ERROR", "file");
223
+ }
224
+ }
225
+ async close() {
226
+ if (this.checkInterval) {
227
+ clearInterval(this.checkInterval);
228
+ this.checkInterval = void 0;
229
+ }
230
+ }
231
+ /**
232
+ * Ensures cache directory exists
233
+ */
234
+ async ensureCacheDir() {
235
+ try {
236
+ await mkdir(this.cacheDir, { recursive: true });
237
+ } catch (error) {
238
+ throw new CacheError(`Failed to create cache directory: ${error.message}`, "INIT_ERROR", "file");
239
+ }
240
+ }
241
+ /**
242
+ * Gets the file path for a cache key
243
+ */
244
+ getFilePath(key) {
245
+ const sanitizedKey = this.sanitizeKey(key);
246
+ return join(this.cacheDir, `${sanitizedKey}${this.fileExtension}`);
247
+ }
248
+ /**
249
+ * Sanitizes a key for use as a filename
250
+ */
251
+ sanitizeKey(key) {
252
+ return key.replace(/[^a-zA-Z0-9_:-]/g, "_");
253
+ }
254
+ /**
255
+ * Desanitizes a filename back to the original key
256
+ */
257
+ desanitizeKey(sanitized) {
258
+ return sanitized;
259
+ }
260
+ /**
261
+ * Gets all cache file names
262
+ */
263
+ async getAllFiles() {
264
+ try {
265
+ return (await readdir(this.cacheDir)).filter((file) => file.endsWith(this.fileExtension));
266
+ } catch (error) {
267
+ if (error.code === "ENOENT") return [];
268
+ throw new CacheError(`Failed to list cache files: ${error.message}`, "LIST_ERROR", "file");
269
+ }
270
+ }
271
+ /**
272
+ * Writes an entry to a file
273
+ */
274
+ async writeEntry(filePath, entry) {
275
+ try {
276
+ let data;
277
+ const json = serialize(entry);
278
+ data = Buffer.from(json, "utf-8");
279
+ if (this.compression) data = await gzipAsync(data);
280
+ await writeFile(filePath, data);
281
+ } catch (error) {
282
+ throw new CacheSerializationError(`Failed to write cache entry: ${error.message}`, "file");
283
+ }
284
+ }
285
+ /**
286
+ * Evicts files if size limit is exceeded
287
+ */
288
+ async evictIfNeeded(newEntrySize) {
289
+ if ((await this.getStats()).totalSize + newEntrySize > this.maxSize) {
290
+ await this.evict();
291
+ if ((await this.getStats()).totalSize + newEntrySize > this.maxSize) throw new CacheSizeError(`Cannot cache entry: would exceed max size of ${this.maxSize} bytes`, "file");
292
+ }
293
+ }
294
+ /**
295
+ * Evicts oldest files based on creation time
296
+ */
297
+ async evict() {
298
+ const files = await this.getAllFiles();
299
+ const filesWithStats = [];
300
+ for (const file of files) {
301
+ const filePath = join(this.cacheDir, file);
302
+ try {
303
+ const fileContent = await readFile(filePath);
304
+ let data;
305
+ if (this.compression) data = await gunzipAsync(fileContent);
306
+ else data = fileContent;
307
+ const entry = deserialize(data.toString("utf-8"));
308
+ filesWithStats.push({
309
+ file,
310
+ createdAt: entry.createdAt
311
+ });
312
+ } catch {}
313
+ }
314
+ filesWithStats.sort((a, b) => a.createdAt - b.createdAt);
315
+ const toRemove = Math.max(1, Math.floor(filesWithStats.length * .1));
316
+ for (let i = 0; i < toRemove; i++) {
317
+ await rm(join(this.cacheDir, filesWithStats[i].file), { force: true });
318
+ this.stats.evictions++;
319
+ }
320
+ }
321
+ /**
322
+ * Starts background cleanup of expired files
323
+ */
324
+ startCleanup() {
325
+ this.checkInterval = setInterval(() => {
326
+ this.removeExpiredFiles();
327
+ }, this.checkPeriod);
328
+ if (this.checkInterval.unref) this.checkInterval.unref();
329
+ }
330
+ /**
331
+ * Removes expired files
332
+ */
333
+ async removeExpiredFiles() {
334
+ const files = await this.getAllFiles();
335
+ for (const file of files) {
336
+ const filePath = join(this.cacheDir, file);
337
+ try {
338
+ const fileContent = await readFile(filePath);
339
+ let data;
340
+ if (this.compression) data = await gunzipAsync(fileContent);
341
+ else data = fileContent;
342
+ if (isExpired(deserialize(data.toString("utf-8")).expiresAt)) await rm(filePath, { force: true });
343
+ } catch {}
344
+ }
345
+ }
346
+ };
347
+ //#endregion
348
+ export { FileProvider };
349
+
350
+ //# sourceMappingURL=file-BiNbZgFQ.js.map
@@ -0,0 +1 @@
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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"}
@@ -0,0 +1,224 @@
1
+ import { CacheKeyError, CacheSizeError, calculateExpiration, calculateSize, extractKey, formatKey, isExpired, isValidKey, matchesPattern } from "../index.js";
2
+ //#region src/providers/memory.ts
3
+ /**
4
+ * Memory cache provider implementation
5
+ * Stores cache entries in memory with LRU eviction
6
+ */
7
+ var MemoryProvider = class {
8
+ cache;
9
+ namespace;
10
+ defaultTTL;
11
+ maxSize;
12
+ maxEntries;
13
+ evictionPolicy;
14
+ checkPeriod;
15
+ checkInterval;
16
+ stats;
17
+ constructor(options) {
18
+ this.cache = /* @__PURE__ */ new Map();
19
+ this.namespace = options.namespace;
20
+ this.defaultTTL = options.defaultTTL;
21
+ this.maxSize = options.maxSize || 100 * 1024 * 1024;
22
+ this.maxEntries = options.maxEntries || 1e4;
23
+ this.evictionPolicy = options.evictionPolicy || "lru";
24
+ this.checkPeriod = options.checkPeriod || 6e4;
25
+ this.stats = {
26
+ hits: 0,
27
+ misses: 0,
28
+ evictions: 0
29
+ };
30
+ this.startExpirationCheck();
31
+ }
32
+ async get(key) {
33
+ if (!isValidKey(key)) throw new CacheKeyError(key, "memory");
34
+ const fullKey = formatKey(this.namespace, key);
35
+ const entry = this.cache.get(fullKey);
36
+ if (!entry) {
37
+ this.stats.misses++;
38
+ return;
39
+ }
40
+ if (isExpired(entry.expiresAt)) {
41
+ this.cache.delete(fullKey);
42
+ this.stats.misses++;
43
+ return;
44
+ }
45
+ entry.hits++;
46
+ if (this.evictionPolicy === "lru") {
47
+ this.cache.delete(fullKey);
48
+ this.cache.set(fullKey, entry);
49
+ }
50
+ this.stats.hits++;
51
+ return entry.value;
52
+ }
53
+ async set(key, value, ttl) {
54
+ if (!isValidKey(key)) throw new CacheKeyError(key, "memory");
55
+ const fullKey = formatKey(this.namespace, key);
56
+ const size = calculateSize(value);
57
+ const expiresAt = calculateExpiration(ttl ?? this.defaultTTL);
58
+ const entry = {
59
+ value,
60
+ createdAt: Date.now(),
61
+ expiresAt,
62
+ size,
63
+ hits: 0,
64
+ metadata: { namespace: this.namespace }
65
+ };
66
+ await this.evictIfNeeded(size);
67
+ this.cache.set(fullKey, entry);
68
+ }
69
+ async has(key) {
70
+ if (!isValidKey(key)) throw new CacheKeyError(key, "memory");
71
+ const fullKey = formatKey(this.namespace, key);
72
+ const entry = this.cache.get(fullKey);
73
+ if (!entry) return false;
74
+ if (isExpired(entry.expiresAt)) {
75
+ this.cache.delete(fullKey);
76
+ return false;
77
+ }
78
+ return true;
79
+ }
80
+ async delete(key) {
81
+ if (!isValidKey(key)) throw new CacheKeyError(key, "memory");
82
+ const fullKey = formatKey(this.namespace, key);
83
+ return this.cache.delete(fullKey);
84
+ }
85
+ async clear(namespace) {
86
+ if (namespace) {
87
+ const prefix = `${namespace}:`;
88
+ for (const key of this.cache.keys()) if (key.startsWith(prefix)) this.cache.delete(key);
89
+ } else {
90
+ this.cache.clear();
91
+ this.stats.hits = 0;
92
+ this.stats.misses = 0;
93
+ this.stats.evictions = 0;
94
+ }
95
+ }
96
+ async keys(pattern) {
97
+ const allKeys = Array.from(this.cache.keys());
98
+ const validKeys = [];
99
+ for (const key of allKeys) {
100
+ const entry = this.cache.get(key);
101
+ if (entry && !isExpired(entry.expiresAt)) validKeys.push(extractKey(this.namespace, key));
102
+ }
103
+ if (pattern) return validKeys.filter((key) => matchesPattern(pattern, key));
104
+ return validKeys;
105
+ }
106
+ async getMany(keys) {
107
+ const result = /* @__PURE__ */ new Map();
108
+ for (const key of keys) {
109
+ const value = await this.get(key);
110
+ if (value !== void 0) result.set(key, value);
111
+ }
112
+ return result;
113
+ }
114
+ async setMany(entries) {
115
+ for (const entry of entries) await this.set(entry.key, entry.value, entry.ttl);
116
+ }
117
+ async deleteMany(keys) {
118
+ let deleted = 0;
119
+ for (const key of keys) if (await this.delete(key)) deleted++;
120
+ return deleted;
121
+ }
122
+ async getStats() {
123
+ let totalSize = 0;
124
+ let entries = 0;
125
+ for (const entry of this.cache.values()) if (!isExpired(entry.expiresAt)) {
126
+ totalSize += entry.size;
127
+ entries++;
128
+ }
129
+ const totalAccesses = this.stats.hits + this.stats.misses;
130
+ const hitRate = totalAccesses > 0 ? this.stats.hits / totalAccesses : 0;
131
+ return {
132
+ entries,
133
+ totalSize,
134
+ hits: this.stats.hits,
135
+ misses: this.stats.misses,
136
+ hitRate,
137
+ evictions: this.stats.evictions,
138
+ backend: {
139
+ type: "memory",
140
+ evictionPolicy: this.evictionPolicy,
141
+ maxSize: this.maxSize,
142
+ maxEntries: this.maxEntries
143
+ }
144
+ };
145
+ }
146
+ async touch(key, ttl) {
147
+ if (!isValidKey(key)) throw new CacheKeyError(key, "memory");
148
+ const fullKey = formatKey(this.namespace, key);
149
+ const entry = this.cache.get(fullKey);
150
+ if (!entry || isExpired(entry.expiresAt)) return false;
151
+ entry.expiresAt = calculateExpiration(ttl);
152
+ return true;
153
+ }
154
+ async close() {
155
+ if (this.checkInterval) {
156
+ clearInterval(this.checkInterval);
157
+ this.checkInterval = void 0;
158
+ }
159
+ this.cache.clear();
160
+ }
161
+ /**
162
+ * Evicts entries if size or count limits are exceeded
163
+ */
164
+ async evictIfNeeded(newEntrySize) {
165
+ const stats = await this.getStats();
166
+ if (stats.entries >= this.maxEntries) await this.evict(1);
167
+ while (stats.totalSize + newEntrySize > this.maxSize && this.cache.size > 0) {
168
+ await this.evict(1);
169
+ if ((await this.getStats()).totalSize + newEntrySize <= this.maxSize) break;
170
+ }
171
+ if ((await this.getStats()).totalSize + newEntrySize > this.maxSize) throw new CacheSizeError(`Cannot cache entry: would exceed max size of ${this.maxSize} bytes`, "memory");
172
+ }
173
+ /**
174
+ * Evicts entries based on eviction policy
175
+ */
176
+ async evict(count) {
177
+ if (this.cache.size === 0) return;
178
+ const entries = Array.from(this.cache.entries());
179
+ switch (this.evictionPolicy) {
180
+ case "lru":
181
+ for (let i = 0; i < count && i < entries.length; i++) {
182
+ this.cache.delete(entries[i][0]);
183
+ this.stats.evictions++;
184
+ }
185
+ break;
186
+ case "lfu": {
187
+ const sorted = entries.sort((a, b) => a[1].hits - b[1].hits);
188
+ for (let i = 0; i < count && i < sorted.length; i++) {
189
+ this.cache.delete(sorted[i][0]);
190
+ this.stats.evictions++;
191
+ }
192
+ break;
193
+ }
194
+ case "fifo": {
195
+ const sorted = entries.sort((a, b) => a[1].createdAt - b[1].createdAt);
196
+ for (let i = 0; i < count && i < sorted.length; i++) {
197
+ this.cache.delete(sorted[i][0]);
198
+ this.stats.evictions++;
199
+ }
200
+ break;
201
+ }
202
+ }
203
+ }
204
+ /**
205
+ * Starts background task to remove expired entries
206
+ */
207
+ startExpirationCheck() {
208
+ this.checkInterval = setInterval(() => {
209
+ this.removeExpiredEntries();
210
+ }, this.checkPeriod);
211
+ if (this.checkInterval.unref) this.checkInterval.unref();
212
+ }
213
+ /**
214
+ * Removes all expired entries from the cache
215
+ */
216
+ removeExpiredEntries() {
217
+ const now = Date.now();
218
+ for (const [key, entry] of this.cache.entries()) if (entry.expiresAt && now >= entry.expiresAt) this.cache.delete(key);
219
+ }
220
+ };
221
+ //#endregion
222
+ export { MemoryProvider };
223
+
224
+ //# sourceMappingURL=memory-7jBOw6kZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-7jBOw6kZ.js","names":[],"sources":["../../src/providers/memory.ts"],"sourcesContent":["/**\n * Memory cache provider implementation with LRU eviction\n */\n\nimport type {\n CacheEntry,\n CacheProvider,\n CacheStats,\n MemoryOptions,\n} from '../shared/types';\nimport { CacheKeyError, CacheSizeError } from '../shared/types';\nimport {\n calculateExpiration,\n calculateSize,\n extractKey,\n formatKey,\n isExpired,\n isValidKey,\n matchesPattern,\n} from '../shared/utils';\n\n/**\n * Memory cache provider implementation\n * Stores cache entries in memory with LRU eviction\n */\nexport class MemoryProvider implements CacheProvider {\n private cache: Map<string, CacheEntry>;\n private namespace?: string;\n private defaultTTL?: number;\n private maxSize: number;\n private maxEntries: number;\n private evictionPolicy: 'lru' | 'lfu' | 'fifo';\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: MemoryOptions) {\n this.cache = new Map();\n this.namespace = options.namespace;\n this.defaultTTL = options.defaultTTL;\n this.maxSize = options.maxSize || 100 * 1024 * 1024; // 100MB default\n this.maxEntries = options.maxEntries || 10000; // 10k entries default\n this.evictionPolicy = options.evictionPolicy || 'lru';\n this.checkPeriod = options.checkPeriod || 60000; // 1 minute default\n this.stats = {\n hits: 0,\n misses: 0,\n evictions: 0,\n };\n\n // Start background expiration check\n this.startExpirationCheck();\n }\n\n async get<T = any>(key: string): Promise<T | undefined> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 'memory');\n }\n\n const fullKey = formatKey(this.namespace, key);\n const entry = this.cache.get(fullKey);\n\n if (!entry) {\n this.stats.misses++;\n return undefined;\n }\n\n // Check if expired\n if (isExpired(entry.expiresAt)) {\n this.cache.delete(fullKey);\n this.stats.misses++;\n return undefined;\n }\n\n // Update access statistics for LRU/LFU\n entry.hits++;\n\n // For LRU, move to end of map (most recently used)\n if (this.evictionPolicy === 'lru') {\n this.cache.delete(fullKey);\n this.cache.set(fullKey, entry);\n }\n\n this.stats.hits++;\n return entry.value as T;\n }\n\n async set<T = any>(key: string, value: T, ttl?: number): Promise<void> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 'memory');\n }\n\n const fullKey = formatKey(this.namespace, key);\n const size = calculateSize(value);\n const expiresAt = calculateExpiration(ttl ?? this.defaultTTL);\n\n const entry: CacheEntry<T> = {\n value,\n createdAt: Date.now(),\n expiresAt,\n size,\n hits: 0,\n metadata: {\n namespace: this.namespace,\n },\n };\n\n // Check if we need to evict entries\n await this.evictIfNeeded(size);\n\n this.cache.set(fullKey, entry);\n }\n\n async has(key: string): Promise<boolean> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 'memory');\n }\n\n const fullKey = formatKey(this.namespace, key);\n const entry = this.cache.get(fullKey);\n\n if (!entry) {\n return false;\n }\n\n // Check if expired\n if (isExpired(entry.expiresAt)) {\n this.cache.delete(fullKey);\n return false;\n }\n\n return true;\n }\n\n async delete(key: string): Promise<boolean> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 'memory');\n }\n\n const fullKey = formatKey(this.namespace, key);\n return this.cache.delete(fullKey);\n }\n\n async clear(namespace?: string): Promise<void> {\n if (namespace) {\n // Clear specific namespace\n const prefix = `${namespace}:`;\n for (const key of this.cache.keys()) {\n if (key.startsWith(prefix)) {\n this.cache.delete(key);\n }\n }\n } else {\n // Clear all entries\n this.cache.clear();\n this.stats.hits = 0;\n this.stats.misses = 0;\n this.stats.evictions = 0;\n }\n }\n\n async keys(pattern?: string): Promise<string[]> {\n const allKeys = Array.from(this.cache.keys());\n\n // Filter out expired entries and extract keys without namespace\n const validKeys: string[] = [];\n for (const key of allKeys) {\n const entry = this.cache.get(key);\n if (entry && !isExpired(entry.expiresAt)) {\n validKeys.push(extractKey(this.namespace, key));\n }\n }\n\n // Apply pattern filter if provided (pattern is applied to the extracted key, not the full key)\n if (pattern) {\n return validKeys.filter((key) => matchesPattern(pattern, key));\n }\n\n return validKeys;\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 // Calculate current cache size\n let totalSize = 0;\n let entries = 0;\n\n for (const entry of this.cache.values()) {\n if (!isExpired(entry.expiresAt)) {\n totalSize += entry.size;\n entries++;\n }\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: 'memory',\n evictionPolicy: this.evictionPolicy,\n maxSize: this.maxSize,\n maxEntries: this.maxEntries,\n },\n };\n }\n\n async touch(key: string, ttl: number): Promise<boolean> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 'memory');\n }\n\n const fullKey = formatKey(this.namespace, key);\n const entry = this.cache.get(fullKey);\n\n if (!entry || isExpired(entry.expiresAt)) {\n return false;\n }\n\n entry.expiresAt = calculateExpiration(ttl);\n return true;\n }\n\n async close(): Promise<void> {\n // Stop expiration check interval\n if (this.checkInterval) {\n clearInterval(this.checkInterval);\n this.checkInterval = undefined;\n }\n\n // Clear cache\n this.cache.clear();\n }\n\n /**\n * Evicts entries if size or count limits are exceeded\n */\n private async evictIfNeeded(newEntrySize: number): Promise<void> {\n const stats = await this.getStats();\n\n // Check if we need to evict based on entry count\n if (stats.entries >= this.maxEntries) {\n await this.evict(1);\n }\n\n // Check if we need to evict based on size\n while (\n stats.totalSize + newEntrySize > this.maxSize &&\n this.cache.size > 0\n ) {\n await this.evict(1);\n const updatedStats = await this.getStats();\n if (updatedStats.totalSize + newEntrySize <= this.maxSize) {\n break;\n }\n }\n\n // Final check - if still too large, throw error\n const finalStats = await this.getStats();\n if (finalStats.totalSize + newEntrySize > this.maxSize) {\n throw new CacheSizeError(\n `Cannot cache entry: would exceed max size of ${this.maxSize} bytes`,\n 'memory',\n );\n }\n }\n\n /**\n * Evicts entries based on eviction policy\n */\n private async evict(count: number): Promise<void> {\n if (this.cache.size === 0) {\n return;\n }\n\n const entries = Array.from(this.cache.entries());\n\n switch (this.evictionPolicy) {\n case 'lru': {\n // LRU: Remove oldest (first in map, since we move accessed items to end)\n for (let i = 0; i < count && i < entries.length; i++) {\n this.cache.delete(entries[i][0]);\n this.stats.evictions++;\n }\n break;\n }\n\n case 'lfu': {\n // LFU: Remove least frequently used\n const sorted = entries.sort((a, b) => a[1].hits - b[1].hits);\n for (let i = 0; i < count && i < sorted.length; i++) {\n this.cache.delete(sorted[i][0]);\n this.stats.evictions++;\n }\n break;\n }\n\n case 'fifo': {\n // FIFO: Remove oldest by creation time\n const sorted = entries.sort((a, b) => a[1].createdAt - b[1].createdAt);\n for (let i = 0; i < count && i < sorted.length; i++) {\n this.cache.delete(sorted[i][0]);\n this.stats.evictions++;\n }\n break;\n }\n }\n }\n\n /**\n * Starts background task to remove expired entries\n */\n private startExpirationCheck(): void {\n this.checkInterval = setInterval(() => {\n this.removeExpiredEntries();\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 all expired entries from the cache\n */\n private removeExpiredEntries(): void {\n const now = Date.now();\n\n for (const [key, entry] of this.cache.entries()) {\n if (entry.expiresAt && now >= entry.expiresAt) {\n this.cache.delete(key);\n }\n }\n }\n}\n"],"mappings":"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"}