@happyvertical/cache 0.74.8

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.
@@ -0,0 +1,365 @@
1
+ import { promisify } from "node:util";
2
+ import { gunzip, gzip } from "node:zlib";
3
+ import { createClient } from "redis";
4
+ import { CacheConnectionError, isValidKey, CacheKeyError, formatKey, deserialize, CacheError, serialize, CacheSerializationError } from "../index.js";
5
+ const gzipAsync = promisify(gzip);
6
+ const gunzipAsync = promisify(gunzip);
7
+ class RedisProvider {
8
+ constructor(options) {
9
+ this.options = options;
10
+ this.namespace = options.namespace || options.keyPrefix;
11
+ this.defaultTTL = options.defaultTTL;
12
+ this.enableCompression = options.enableCompression ?? false;
13
+ this.compressionThreshold = options.compressionThreshold || 1024;
14
+ this.stats = {
15
+ hits: 0,
16
+ misses: 0
17
+ };
18
+ this.client = createClient({
19
+ socket: {
20
+ host: options.host || "localhost",
21
+ port: options.port || 6379,
22
+ connectTimeout: options.connectTimeout || 5e3
23
+ },
24
+ password: options.password,
25
+ database: options.db || 0,
26
+ commandsQueueMaxLength: 1e3
27
+ });
28
+ this.client.on("error", (error) => {
29
+ console.error("Redis connection error:", error);
30
+ });
31
+ this.client.on("connect", () => {
32
+ this.connected = true;
33
+ });
34
+ this.client.on("end", () => {
35
+ this.connected = false;
36
+ });
37
+ }
38
+ client;
39
+ namespace;
40
+ defaultTTL;
41
+ enableCompression;
42
+ compressionThreshold;
43
+ stats;
44
+ connected = false;
45
+ /**
46
+ * Ensures the client is connected
47
+ */
48
+ async ensureConnected() {
49
+ if (!this.connected) {
50
+ try {
51
+ await this.client.connect();
52
+ this.connected = true;
53
+ } catch (error) {
54
+ throw new CacheConnectionError(
55
+ `Failed to connect to Redis: ${error.message}`,
56
+ "redis"
57
+ );
58
+ }
59
+ }
60
+ }
61
+ async get(key) {
62
+ if (!isValidKey(key)) {
63
+ throw new CacheKeyError(key, "redis");
64
+ }
65
+ await this.ensureConnected();
66
+ const fullKey = formatKey(this.namespace, key);
67
+ try {
68
+ const value = await this.client.get(fullKey);
69
+ if (value === null) {
70
+ this.stats.misses++;
71
+ return void 0;
72
+ }
73
+ this.stats.hits++;
74
+ let data = value;
75
+ if (this.enableCompression && value.startsWith("gzip:")) {
76
+ const compressed = Buffer.from(value.slice(5), "base64");
77
+ const decompressed = await gunzipAsync(compressed);
78
+ data = decompressed.toString("utf-8");
79
+ }
80
+ return deserialize(data);
81
+ } catch (error) {
82
+ throw new CacheError(
83
+ `Failed to get cache entry: ${error.message}`,
84
+ "GET_ERROR",
85
+ "redis"
86
+ );
87
+ }
88
+ }
89
+ async set(key, value, ttl) {
90
+ if (!isValidKey(key)) {
91
+ throw new CacheKeyError(key, "redis");
92
+ }
93
+ await this.ensureConnected();
94
+ const fullKey = formatKey(this.namespace, key);
95
+ try {
96
+ let data = serialize(value);
97
+ if (this.enableCompression && data.length > this.compressionThreshold) {
98
+ const compressed = await gzipAsync(Buffer.from(data, "utf-8"));
99
+ data = `gzip:${compressed.toString("base64")}`;
100
+ }
101
+ const effectiveTTL = ttl ?? this.defaultTTL;
102
+ if (effectiveTTL !== void 0 && effectiveTTL > 0) {
103
+ await this.client.setEx(fullKey, effectiveTTL, data);
104
+ } else {
105
+ await this.client.set(fullKey, data);
106
+ }
107
+ } catch (error) {
108
+ if (error.message?.includes("serialize")) {
109
+ throw new CacheSerializationError(
110
+ `Failed to serialize value: ${error.message}`,
111
+ "redis"
112
+ );
113
+ }
114
+ throw new CacheError(
115
+ `Failed to set cache entry: ${error.message}`,
116
+ "SET_ERROR",
117
+ "redis"
118
+ );
119
+ }
120
+ }
121
+ async has(key) {
122
+ if (!isValidKey(key)) {
123
+ throw new CacheKeyError(key, "redis");
124
+ }
125
+ await this.ensureConnected();
126
+ const fullKey = formatKey(this.namespace, key);
127
+ try {
128
+ const exists = await this.client.exists(fullKey);
129
+ return exists === 1;
130
+ } catch (error) {
131
+ throw new CacheError(
132
+ `Failed to check cache entry: ${error.message}`,
133
+ "EXISTS_ERROR",
134
+ "redis"
135
+ );
136
+ }
137
+ }
138
+ async delete(key) {
139
+ if (!isValidKey(key)) {
140
+ throw new CacheKeyError(key, "redis");
141
+ }
142
+ await this.ensureConnected();
143
+ const fullKey = formatKey(this.namespace, key);
144
+ try {
145
+ const deleted = await this.client.del(fullKey);
146
+ return deleted === 1;
147
+ } catch (error) {
148
+ throw new CacheError(
149
+ `Failed to delete cache entry: ${error.message}`,
150
+ "DELETE_ERROR",
151
+ "redis"
152
+ );
153
+ }
154
+ }
155
+ async clear(namespace) {
156
+ await this.ensureConnected();
157
+ try {
158
+ if (namespace) {
159
+ const pattern = `${namespace}:*`;
160
+ let cursor = "0";
161
+ do {
162
+ const reply = await this.client.scan(cursor, {
163
+ MATCH: pattern,
164
+ COUNT: 100
165
+ });
166
+ cursor = reply.cursor;
167
+ if (reply.keys.length > 0) {
168
+ await this.client.del(reply.keys);
169
+ }
170
+ } while (cursor !== "0");
171
+ } else {
172
+ await this.client.flushDb();
173
+ this.stats.hits = 0;
174
+ this.stats.misses = 0;
175
+ }
176
+ } catch (error) {
177
+ throw new CacheError(
178
+ `Failed to clear cache: ${error.message}`,
179
+ "CLEAR_ERROR",
180
+ "redis"
181
+ );
182
+ }
183
+ }
184
+ async keys(pattern) {
185
+ await this.ensureConnected();
186
+ try {
187
+ const searchPattern = pattern ? formatKey(this.namespace, pattern) : formatKey(this.namespace, "*");
188
+ const keys = [];
189
+ let cursor = "0";
190
+ do {
191
+ const reply = await this.client.scan(cursor, {
192
+ MATCH: searchPattern,
193
+ COUNT: 100
194
+ });
195
+ cursor = reply.cursor;
196
+ keys.push(...reply.keys);
197
+ } while (cursor !== "0");
198
+ if (this.namespace) {
199
+ const prefix = `${this.namespace}:`;
200
+ return keys.map(
201
+ (key) => key.startsWith(prefix) ? key.slice(prefix.length) : key
202
+ );
203
+ }
204
+ return keys;
205
+ } catch (error) {
206
+ throw new CacheError(
207
+ `Failed to get cache keys: ${error.message}`,
208
+ "KEYS_ERROR",
209
+ "redis"
210
+ );
211
+ }
212
+ }
213
+ async getMany(keys) {
214
+ if (keys.length === 0) {
215
+ return /* @__PURE__ */ new Map();
216
+ }
217
+ await this.ensureConnected();
218
+ const fullKeys = keys.map((key) => formatKey(this.namespace, key));
219
+ try {
220
+ const values = await this.client.mGet(fullKeys);
221
+ const result = /* @__PURE__ */ new Map();
222
+ for (let i = 0; i < keys.length; i++) {
223
+ const value = values[i];
224
+ if (value !== null) {
225
+ let data = value;
226
+ if (this.enableCompression && value.startsWith("gzip:")) {
227
+ const compressed = Buffer.from(value.slice(5), "base64");
228
+ const decompressed = await gunzipAsync(compressed);
229
+ data = decompressed.toString("utf-8");
230
+ }
231
+ result.set(keys[i], deserialize(data));
232
+ this.stats.hits++;
233
+ } else {
234
+ this.stats.misses++;
235
+ }
236
+ }
237
+ return result;
238
+ } catch (error) {
239
+ throw new CacheError(
240
+ `Failed to get multiple cache entries: ${error.message}`,
241
+ "MGET_ERROR",
242
+ "redis"
243
+ );
244
+ }
245
+ }
246
+ async setMany(entries) {
247
+ if (entries.length === 0) {
248
+ return;
249
+ }
250
+ await this.ensureConnected();
251
+ try {
252
+ const pipeline = this.client.multi();
253
+ for (const entry of entries) {
254
+ const fullKey = formatKey(this.namespace, entry.key);
255
+ let data = serialize(entry.value);
256
+ if (this.enableCompression && data.length > this.compressionThreshold) {
257
+ const compressed = await gzipAsync(Buffer.from(data, "utf-8"));
258
+ data = `gzip:${compressed.toString("base64")}`;
259
+ }
260
+ const effectiveTTL = entry.ttl ?? this.defaultTTL;
261
+ if (effectiveTTL !== void 0 && effectiveTTL > 0) {
262
+ pipeline.setEx(fullKey, effectiveTTL, data);
263
+ } else {
264
+ pipeline.set(fullKey, data);
265
+ }
266
+ }
267
+ await pipeline.exec();
268
+ } catch (error) {
269
+ throw new CacheError(
270
+ `Failed to set multiple cache entries: ${error.message}`,
271
+ "MSET_ERROR",
272
+ "redis"
273
+ );
274
+ }
275
+ }
276
+ async deleteMany(keys) {
277
+ if (keys.length === 0) {
278
+ return 0;
279
+ }
280
+ await this.ensureConnected();
281
+ const fullKeys = keys.map((key) => formatKey(this.namespace, key));
282
+ try {
283
+ const deleted = await this.client.del(fullKeys);
284
+ return deleted;
285
+ } catch (error) {
286
+ throw new CacheError(
287
+ `Failed to delete multiple cache entries: ${error.message}`,
288
+ "MDEL_ERROR",
289
+ "redis"
290
+ );
291
+ }
292
+ }
293
+ async getStats() {
294
+ await this.ensureConnected();
295
+ try {
296
+ const info = await this.client.info("stats");
297
+ const dbInfo = await this.client.info("keyspace");
298
+ const dbMatch = dbInfo.match(/db\d+:keys=(\d+)/);
299
+ const entries = dbMatch ? parseInt(dbMatch[1], 10) : 0;
300
+ const hitsMatch = info.match(/keyspace_hits:(\d+)/);
301
+ const missesMatch = info.match(/keyspace_misses:(\d+)/);
302
+ const redisHits = hitsMatch ? parseInt(hitsMatch[1], 10) : 0;
303
+ const redisMisses = missesMatch ? parseInt(missesMatch[1], 10) : 0;
304
+ const totalHits = this.stats.hits + redisHits;
305
+ const totalMisses = this.stats.misses + redisMisses;
306
+ const totalAccesses = totalHits + totalMisses;
307
+ const hitRate = totalAccesses > 0 ? totalHits / totalAccesses : 0;
308
+ return {
309
+ entries,
310
+ totalSize: 0,
311
+ // Redis doesn't easily expose total memory per keyspace
312
+ hits: totalHits,
313
+ misses: totalMisses,
314
+ hitRate,
315
+ evictions: 0,
316
+ // Would need to parse evicted_keys from stats
317
+ backend: {
318
+ type: "redis",
319
+ host: this.options.host || "localhost",
320
+ port: this.options.port || 6379,
321
+ db: this.options.db || 0,
322
+ compression: this.enableCompression
323
+ }
324
+ };
325
+ } catch (error) {
326
+ throw new CacheError(
327
+ `Failed to get cache stats: ${error.message}`,
328
+ "STATS_ERROR",
329
+ "redis"
330
+ );
331
+ }
332
+ }
333
+ async touch(key, ttl) {
334
+ if (!isValidKey(key)) {
335
+ throw new CacheKeyError(key, "redis");
336
+ }
337
+ await this.ensureConnected();
338
+ const fullKey = formatKey(this.namespace, key);
339
+ try {
340
+ const result = await this.client.expire(fullKey, ttl);
341
+ return result === 1;
342
+ } catch (error) {
343
+ throw new CacheError(
344
+ `Failed to touch cache entry: ${error.message}`,
345
+ "TOUCH_ERROR",
346
+ "redis"
347
+ );
348
+ }
349
+ }
350
+ async close() {
351
+ if (this.connected) {
352
+ try {
353
+ await this.client.quit();
354
+ this.connected = false;
355
+ } catch (_error) {
356
+ await this.client.disconnect();
357
+ this.connected = false;
358
+ }
359
+ }
360
+ }
361
+ }
362
+ export {
363
+ RedisProvider
364
+ };
365
+ //# sourceMappingURL=redis-D-SNLXE_.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-D-SNLXE_.js","sources":["../../src/providers/redis.ts"],"sourcesContent":["/**\n * Redis cache provider implementation\n */\n\nimport { promisify } from 'node:util';\nimport { gunzip, gzip } from 'node:zlib';\nimport { createClient, type RedisClientType } from 'redis';\nimport type { CacheProvider, CacheStats, RedisOptions } from '../shared/types';\nimport {\n CacheConnectionError,\n CacheError,\n CacheKeyError,\n CacheSerializationError,\n} from '../shared/types';\nimport { deserialize, formatKey, isValidKey, serialize } from '../shared/utils';\n\nconst gzipAsync = promisify(gzip);\nconst gunzipAsync = promisify(gunzip);\n\n/**\n * Redis cache provider implementation\n * Uses official redis client with optional compression\n */\nexport class RedisProvider implements CacheProvider {\n private client: RedisClientType;\n private namespace?: string;\n private defaultTTL?: number;\n private enableCompression: boolean;\n private compressionThreshold: number;\n private stats: {\n hits: number;\n misses: number;\n };\n private connected: boolean = false;\n\n constructor(private options: RedisOptions) {\n this.namespace = options.namespace || options.keyPrefix;\n this.defaultTTL = options.defaultTTL;\n this.enableCompression = options.enableCompression ?? false;\n this.compressionThreshold = options.compressionThreshold || 1024;\n this.stats = {\n hits: 0,\n misses: 0,\n };\n\n // Create Redis client\n this.client = createClient({\n socket: {\n host: options.host || 'localhost',\n port: options.port || 6379,\n connectTimeout: options.connectTimeout || 5000,\n },\n password: options.password,\n database: options.db || 0,\n commandsQueueMaxLength: 1000,\n }) as RedisClientType;\n\n // Setup error handling\n this.client.on('error', (error) => {\n console.error('Redis connection error:', error);\n });\n\n this.client.on('connect', () => {\n this.connected = true;\n });\n\n this.client.on('end', () => {\n this.connected = false;\n });\n }\n\n /**\n * Ensures the client is connected\n */\n private async ensureConnected(): Promise<void> {\n if (!this.connected) {\n try {\n await this.client.connect();\n this.connected = true;\n } catch (error: any) {\n throw new CacheConnectionError(\n `Failed to connect to Redis: ${error.message}`,\n 'redis',\n );\n }\n }\n }\n\n async get<T = any>(key: string): Promise<T | undefined> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 'redis');\n }\n\n await this.ensureConnected();\n\n const fullKey = formatKey(this.namespace, key);\n\n try {\n const value = await this.client.get(fullKey);\n\n if (value === null) {\n this.stats.misses++;\n return undefined;\n }\n\n this.stats.hits++;\n\n // Decompress if needed (check for compression marker)\n let data = value;\n if (this.enableCompression && value.startsWith('gzip:')) {\n const compressed = Buffer.from(value.slice(5), 'base64');\n const decompressed = await gunzipAsync(compressed);\n data = decompressed.toString('utf-8');\n }\n\n return deserialize<T>(data);\n } catch (error: any) {\n throw new CacheError(\n `Failed to get cache entry: ${error.message}`,\n 'GET_ERROR',\n 'redis',\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, 'redis');\n }\n\n await this.ensureConnected();\n\n const fullKey = formatKey(this.namespace, key);\n\n try {\n let data = serialize(value);\n\n // Compress if enabled and value is large enough\n if (this.enableCompression && data.length > this.compressionThreshold) {\n const compressed = await gzipAsync(Buffer.from(data, 'utf-8'));\n data = `gzip:${compressed.toString('base64')}`;\n }\n\n const effectiveTTL = ttl ?? this.defaultTTL;\n\n if (effectiveTTL !== undefined && effectiveTTL > 0) {\n await this.client.setEx(fullKey, effectiveTTL, data);\n } else {\n await this.client.set(fullKey, data);\n }\n } catch (error: any) {\n if (error.message?.includes('serialize')) {\n throw new CacheSerializationError(\n `Failed to serialize value: ${error.message}`,\n 'redis',\n );\n }\n throw new CacheError(\n `Failed to set cache entry: ${error.message}`,\n 'SET_ERROR',\n 'redis',\n );\n }\n }\n\n async has(key: string): Promise<boolean> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 'redis');\n }\n\n await this.ensureConnected();\n\n const fullKey = formatKey(this.namespace, key);\n\n try {\n const exists = await this.client.exists(fullKey);\n return exists === 1;\n } catch (error: any) {\n throw new CacheError(\n `Failed to check cache entry: ${error.message}`,\n 'EXISTS_ERROR',\n 'redis',\n );\n }\n }\n\n async delete(key: string): Promise<boolean> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 'redis');\n }\n\n await this.ensureConnected();\n\n const fullKey = formatKey(this.namespace, key);\n\n try {\n const deleted = await this.client.del(fullKey);\n return deleted === 1;\n } catch (error: any) {\n throw new CacheError(\n `Failed to delete cache entry: ${error.message}`,\n 'DELETE_ERROR',\n 'redis',\n );\n }\n }\n\n async clear(namespace?: string): Promise<void> {\n await this.ensureConnected();\n\n try {\n if (namespace) {\n // Clear specific namespace\n const pattern = `${namespace}:*`;\n let cursor = '0';\n\n do {\n const reply = await this.client.scan(cursor, {\n MATCH: pattern,\n COUNT: 100,\n });\n\n cursor = reply.cursor;\n\n if (reply.keys.length > 0) {\n await this.client.del(reply.keys);\n }\n } while (cursor !== '0');\n } else {\n // Clear all keys (use with caution!)\n await this.client.flushDb();\n this.stats.hits = 0;\n this.stats.misses = 0;\n }\n } catch (error: any) {\n throw new CacheError(\n `Failed to clear cache: ${error.message}`,\n 'CLEAR_ERROR',\n 'redis',\n );\n }\n }\n\n async keys(pattern?: string): Promise<string[]> {\n await this.ensureConnected();\n\n try {\n const searchPattern = pattern\n ? formatKey(this.namespace, pattern)\n : formatKey(this.namespace, '*');\n\n const keys: string[] = [];\n let cursor = '0';\n\n do {\n const reply = await this.client.scan(cursor, {\n MATCH: searchPattern,\n COUNT: 100,\n });\n\n cursor = reply.cursor;\n keys.push(...reply.keys);\n } while (cursor !== '0');\n\n // Remove namespace prefix if present\n if (this.namespace) {\n const prefix = `${this.namespace}:`;\n return keys.map((key) =>\n key.startsWith(prefix) ? key.slice(prefix.length) : key,\n );\n }\n\n return keys;\n } catch (error: any) {\n throw new CacheError(\n `Failed to get cache keys: ${error.message}`,\n 'KEYS_ERROR',\n 'redis',\n );\n }\n }\n\n async getMany<T = any>(keys: string[]): Promise<Map<string, T>> {\n if (keys.length === 0) {\n return new Map();\n }\n\n await this.ensureConnected();\n\n const fullKeys = keys.map((key) => formatKey(this.namespace, key));\n\n try {\n const values = await this.client.mGet(fullKeys);\n const result = new Map<string, T>();\n\n for (let i = 0; i < keys.length; i++) {\n const value = values[i];\n if (value !== null) {\n // Decompress if needed\n let data = value;\n if (this.enableCompression && value.startsWith('gzip:')) {\n const compressed = Buffer.from(value.slice(5), 'base64');\n const decompressed = await gunzipAsync(compressed);\n data = decompressed.toString('utf-8');\n }\n\n result.set(keys[i], deserialize<T>(data));\n this.stats.hits++;\n } else {\n this.stats.misses++;\n }\n }\n\n return result;\n } catch (error: any) {\n throw new CacheError(\n `Failed to get multiple cache entries: ${error.message}`,\n 'MGET_ERROR',\n 'redis',\n );\n }\n }\n\n async setMany<T = any>(\n entries: Array<{ key: string; value: T; ttl?: number }>,\n ): Promise<void> {\n if (entries.length === 0) {\n return;\n }\n\n await this.ensureConnected();\n\n try {\n // Use pipeline for efficiency\n const pipeline = this.client.multi();\n\n for (const entry of entries) {\n const fullKey = formatKey(this.namespace, entry.key);\n let data = serialize(entry.value);\n\n // Compress if enabled and value is large enough\n if (this.enableCompression && data.length > this.compressionThreshold) {\n const compressed = await gzipAsync(Buffer.from(data, 'utf-8'));\n data = `gzip:${compressed.toString('base64')}`;\n }\n\n const effectiveTTL = entry.ttl ?? this.defaultTTL;\n\n if (effectiveTTL !== undefined && effectiveTTL > 0) {\n pipeline.setEx(fullKey, effectiveTTL, data);\n } else {\n pipeline.set(fullKey, data);\n }\n }\n\n await pipeline.exec();\n } catch (error: any) {\n throw new CacheError(\n `Failed to set multiple cache entries: ${error.message}`,\n 'MSET_ERROR',\n 'redis',\n );\n }\n }\n\n async deleteMany(keys: string[]): Promise<number> {\n if (keys.length === 0) {\n return 0;\n }\n\n await this.ensureConnected();\n\n const fullKeys = keys.map((key) => formatKey(this.namespace, key));\n\n try {\n const deleted = await this.client.del(fullKeys);\n return deleted;\n } catch (error: any) {\n throw new CacheError(\n `Failed to delete multiple cache entries: ${error.message}`,\n 'MDEL_ERROR',\n 'redis',\n );\n }\n }\n\n async getStats(): Promise<CacheStats> {\n await this.ensureConnected();\n\n try {\n // Get Redis INFO stats\n const info = await this.client.info('stats');\n const dbInfo = await this.client.info('keyspace');\n\n // Parse keyspace info to get entry count\n const dbMatch = dbInfo.match(/db\\d+:keys=(\\d+)/);\n const entries = dbMatch ? parseInt(dbMatch[1], 10) : 0;\n\n // Parse stats info\n const hitsMatch = info.match(/keyspace_hits:(\\d+)/);\n const missesMatch = info.match(/keyspace_misses:(\\d+)/);\n const redisHits = hitsMatch ? parseInt(hitsMatch[1], 10) : 0;\n const redisMisses = missesMatch ? parseInt(missesMatch[1], 10) : 0;\n\n const totalHits = this.stats.hits + redisHits;\n const totalMisses = this.stats.misses + redisMisses;\n const totalAccesses = totalHits + totalMisses;\n const hitRate = totalAccesses > 0 ? totalHits / totalAccesses : 0;\n\n return {\n entries,\n totalSize: 0, // Redis doesn't easily expose total memory per keyspace\n hits: totalHits,\n misses: totalMisses,\n hitRate,\n evictions: 0, // Would need to parse evicted_keys from stats\n backend: {\n type: 'redis',\n host: this.options.host || 'localhost',\n port: this.options.port || 6379,\n db: this.options.db || 0,\n compression: this.enableCompression,\n },\n };\n } catch (error: any) {\n throw new CacheError(\n `Failed to get cache stats: ${error.message}`,\n 'STATS_ERROR',\n 'redis',\n );\n }\n }\n\n async touch(key: string, ttl: number): Promise<boolean> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 'redis');\n }\n\n await this.ensureConnected();\n\n const fullKey = formatKey(this.namespace, key);\n\n try {\n const result = await this.client.expire(fullKey, ttl);\n return result === 1;\n } catch (error: any) {\n throw new CacheError(\n `Failed to touch cache entry: ${error.message}`,\n 'TOUCH_ERROR',\n 'redis',\n );\n }\n }\n\n async close(): Promise<void> {\n if (this.connected) {\n try {\n await this.client.quit();\n this.connected = false;\n } catch (_error: any) {\n // Force disconnect if graceful quit fails\n await this.client.disconnect();\n this.connected = false;\n }\n }\n }\n}\n"],"names":[],"mappings":";;;;AAgBA,MAAM,YAAY,UAAU,IAAI;AAChC,MAAM,cAAc,UAAU,MAAM;AAM7B,MAAM,cAAuC;AAAA,EAYlD,YAAoB,SAAuB;AAAvB,SAAA,UAAA;AAClB,SAAK,YAAY,QAAQ,aAAa,QAAQ;AAC9C,SAAK,aAAa,QAAQ;AAC1B,SAAK,oBAAoB,QAAQ,qBAAqB;AACtD,SAAK,uBAAuB,QAAQ,wBAAwB;AAC5D,SAAK,QAAQ;AAAA,MACX,MAAM;AAAA,MACN,QAAQ;AAAA,IAAA;AAIV,SAAK,SAAS,aAAa;AAAA,MACzB,QAAQ;AAAA,QACN,MAAM,QAAQ,QAAQ;AAAA,QACtB,MAAM,QAAQ,QAAQ;AAAA,QACtB,gBAAgB,QAAQ,kBAAkB;AAAA,MAAA;AAAA,MAE5C,UAAU,QAAQ;AAAA,MAClB,UAAU,QAAQ,MAAM;AAAA,MACxB,wBAAwB;AAAA,IAAA,CACzB;AAGD,SAAK,OAAO,GAAG,SAAS,CAAC,UAAU;AACjC,cAAQ,MAAM,2BAA2B,KAAK;AAAA,IAChD,CAAC;AAED,SAAK,OAAO,GAAG,WAAW,MAAM;AAC9B,WAAK,YAAY;AAAA,IACnB,CAAC;AAED,SAAK,OAAO,GAAG,OAAO,MAAM;AAC1B,WAAK,YAAY;AAAA,IACnB,CAAC;AAAA,EACH;AAAA,EA7CQ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAIA,YAAqB;AAAA;AAAA;AAAA;AAAA,EAyC7B,MAAc,kBAAiC;AAC7C,QAAI,CAAC,KAAK,WAAW;AACnB,UAAI;AACF,cAAM,KAAK,OAAO,QAAA;AAClB,aAAK,YAAY;AAAA,MACnB,SAAS,OAAY;AACnB,cAAM,IAAI;AAAA,UACR,+BAA+B,MAAM,OAAO;AAAA,UAC5C;AAAA,QAAA;AAAA,MAEJ;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,IAAa,KAAqC;AACtD,QAAI,CAAC,WAAW,GAAG,GAAG;AACpB,YAAM,IAAI,cAAc,KAAK,OAAO;AAAA,IACtC;AAEA,UAAM,KAAK,gBAAA;AAEX,UAAM,UAAU,UAAU,KAAK,WAAW,GAAG;AAE7C,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,OAAO,IAAI,OAAO;AAE3C,UAAI,UAAU,MAAM;AAClB,aAAK,MAAM;AACX,eAAO;AAAA,MACT;AAEA,WAAK,MAAM;AAGX,UAAI,OAAO;AACX,UAAI,KAAK,qBAAqB,MAAM,WAAW,OAAO,GAAG;AACvD,cAAM,aAAa,OAAO,KAAK,MAAM,MAAM,CAAC,GAAG,QAAQ;AACvD,cAAM,eAAe,MAAM,YAAY,UAAU;AACjD,eAAO,aAAa,SAAS,OAAO;AAAA,MACtC;AAEA,aAAO,YAAe,IAAI;AAAA,IAC5B,SAAS,OAAY;AACnB,YAAM,IAAI;AAAA,QACR,8BAA8B,MAAM,OAAO;AAAA,QAC3C;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AAAA,EAEA,MAAM,IAAa,KAAa,OAAU,KAA6B;AACrE,QAAI,CAAC,WAAW,GAAG,GAAG;AACpB,YAAM,IAAI,cAAc,KAAK,OAAO;AAAA,IACtC;AAEA,UAAM,KAAK,gBAAA;AAEX,UAAM,UAAU,UAAU,KAAK,WAAW,GAAG;AAE7C,QAAI;AACF,UAAI,OAAO,UAAU,KAAK;AAG1B,UAAI,KAAK,qBAAqB,KAAK,SAAS,KAAK,sBAAsB;AACrE,cAAM,aAAa,MAAM,UAAU,OAAO,KAAK,MAAM,OAAO,CAAC;AAC7D,eAAO,QAAQ,WAAW,SAAS,QAAQ,CAAC;AAAA,MAC9C;AAEA,YAAM,eAAe,OAAO,KAAK;AAEjC,UAAI,iBAAiB,UAAa,eAAe,GAAG;AAClD,cAAM,KAAK,OAAO,MAAM,SAAS,cAAc,IAAI;AAAA,MACrD,OAAO;AACL,cAAM,KAAK,OAAO,IAAI,SAAS,IAAI;AAAA,MACrC;AAAA,IACF,SAAS,OAAY;AACnB,UAAI,MAAM,SAAS,SAAS,WAAW,GAAG;AACxC,cAAM,IAAI;AAAA,UACR,8BAA8B,MAAM,OAAO;AAAA,UAC3C;AAAA,QAAA;AAAA,MAEJ;AACA,YAAM,IAAI;AAAA,QACR,8BAA8B,MAAM,OAAO;AAAA,QAC3C;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AAAA,EAEA,MAAM,IAAI,KAA+B;AACvC,QAAI,CAAC,WAAW,GAAG,GAAG;AACpB,YAAM,IAAI,cAAc,KAAK,OAAO;AAAA,IACtC;AAEA,UAAM,KAAK,gBAAA;AAEX,UAAM,UAAU,UAAU,KAAK,WAAW,GAAG;AAE7C,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,OAAO,OAAO,OAAO;AAC/C,aAAO,WAAW;AAAA,IACpB,SAAS,OAAY;AACnB,YAAM,IAAI;AAAA,QACR,gCAAgC,MAAM,OAAO;AAAA,QAC7C;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,KAA+B;AAC1C,QAAI,CAAC,WAAW,GAAG,GAAG;AACpB,YAAM,IAAI,cAAc,KAAK,OAAO;AAAA,IACtC;AAEA,UAAM,KAAK,gBAAA;AAEX,UAAM,UAAU,UAAU,KAAK,WAAW,GAAG;AAE7C,QAAI;AACF,YAAM,UAAU,MAAM,KAAK,OAAO,IAAI,OAAO;AAC7C,aAAO,YAAY;AAAA,IACrB,SAAS,OAAY;AACnB,YAAM,IAAI;AAAA,QACR,iCAAiC,MAAM,OAAO;AAAA,QAC9C;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AAAA,EAEA,MAAM,MAAM,WAAmC;AAC7C,UAAM,KAAK,gBAAA;AAEX,QAAI;AACF,UAAI,WAAW;AAEb,cAAM,UAAU,GAAG,SAAS;AAC5B,YAAI,SAAS;AAEb,WAAG;AACD,gBAAM,QAAQ,MAAM,KAAK,OAAO,KAAK,QAAQ;AAAA,YAC3C,OAAO;AAAA,YACP,OAAO;AAAA,UAAA,CACR;AAED,mBAAS,MAAM;AAEf,cAAI,MAAM,KAAK,SAAS,GAAG;AACzB,kBAAM,KAAK,OAAO,IAAI,MAAM,IAAI;AAAA,UAClC;AAAA,QACF,SAAS,WAAW;AAAA,MACtB,OAAO;AAEL,cAAM,KAAK,OAAO,QAAA;AAClB,aAAK,MAAM,OAAO;AAClB,aAAK,MAAM,SAAS;AAAA,MACtB;AAAA,IACF,SAAS,OAAY;AACnB,YAAM,IAAI;AAAA,QACR,0BAA0B,MAAM,OAAO;AAAA,QACvC;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,SAAqC;AAC9C,UAAM,KAAK,gBAAA;AAEX,QAAI;AACF,YAAM,gBAAgB,UAClB,UAAU,KAAK,WAAW,OAAO,IACjC,UAAU,KAAK,WAAW,GAAG;AAEjC,YAAM,OAAiB,CAAA;AACvB,UAAI,SAAS;AAEb,SAAG;AACD,cAAM,QAAQ,MAAM,KAAK,OAAO,KAAK,QAAQ;AAAA,UAC3C,OAAO;AAAA,UACP,OAAO;AAAA,QAAA,CACR;AAED,iBAAS,MAAM;AACf,aAAK,KAAK,GAAG,MAAM,IAAI;AAAA,MACzB,SAAS,WAAW;AAGpB,UAAI,KAAK,WAAW;AAClB,cAAM,SAAS,GAAG,KAAK,SAAS;AAChC,eAAO,KAAK;AAAA,UAAI,CAAC,QACf,IAAI,WAAW,MAAM,IAAI,IAAI,MAAM,OAAO,MAAM,IAAI;AAAA,QAAA;AAAA,MAExD;AAEA,aAAO;AAAA,IACT,SAAS,OAAY;AACnB,YAAM,IAAI;AAAA,QACR,6BAA6B,MAAM,OAAO;AAAA,QAC1C;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AAAA,EAEA,MAAM,QAAiB,MAAyC;AAC9D,QAAI,KAAK,WAAW,GAAG;AACrB,iCAAW,IAAA;AAAA,IACb;AAEA,UAAM,KAAK,gBAAA;AAEX,UAAM,WAAW,KAAK,IAAI,CAAC,QAAQ,UAAU,KAAK,WAAW,GAAG,CAAC;AAEjE,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,OAAO,KAAK,QAAQ;AAC9C,YAAM,6BAAa,IAAA;AAEnB,eAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,cAAM,QAAQ,OAAO,CAAC;AACtB,YAAI,UAAU,MAAM;AAElB,cAAI,OAAO;AACX,cAAI,KAAK,qBAAqB,MAAM,WAAW,OAAO,GAAG;AACvD,kBAAM,aAAa,OAAO,KAAK,MAAM,MAAM,CAAC,GAAG,QAAQ;AACvD,kBAAM,eAAe,MAAM,YAAY,UAAU;AACjD,mBAAO,aAAa,SAAS,OAAO;AAAA,UACtC;AAEA,iBAAO,IAAI,KAAK,CAAC,GAAG,YAAe,IAAI,CAAC;AACxC,eAAK,MAAM;AAAA,QACb,OAAO;AACL,eAAK,MAAM;AAAA,QACb;AAAA,MACF;AAEA,aAAO;AAAA,IACT,SAAS,OAAY;AACnB,YAAM,IAAI;AAAA,QACR,yCAAyC,MAAM,OAAO;AAAA,QACtD;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AAAA,EAEA,MAAM,QACJ,SACe;AACf,QAAI,QAAQ,WAAW,GAAG;AACxB;AAAA,IACF;AAEA,UAAM,KAAK,gBAAA;AAEX,QAAI;AAEF,YAAM,WAAW,KAAK,OAAO,MAAA;AAE7B,iBAAW,SAAS,SAAS;AAC3B,cAAM,UAAU,UAAU,KAAK,WAAW,MAAM,GAAG;AACnD,YAAI,OAAO,UAAU,MAAM,KAAK;AAGhC,YAAI,KAAK,qBAAqB,KAAK,SAAS,KAAK,sBAAsB;AACrE,gBAAM,aAAa,MAAM,UAAU,OAAO,KAAK,MAAM,OAAO,CAAC;AAC7D,iBAAO,QAAQ,WAAW,SAAS,QAAQ,CAAC;AAAA,QAC9C;AAEA,cAAM,eAAe,MAAM,OAAO,KAAK;AAEvC,YAAI,iBAAiB,UAAa,eAAe,GAAG;AAClD,mBAAS,MAAM,SAAS,cAAc,IAAI;AAAA,QAC5C,OAAO;AACL,mBAAS,IAAI,SAAS,IAAI;AAAA,QAC5B;AAAA,MACF;AAEA,YAAM,SAAS,KAAA;AAAA,IACjB,SAAS,OAAY;AACnB,YAAM,IAAI;AAAA,QACR,yCAAyC,MAAM,OAAO;AAAA,QACtD;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,MAAiC;AAChD,QAAI,KAAK,WAAW,GAAG;AACrB,aAAO;AAAA,IACT;AAEA,UAAM,KAAK,gBAAA;AAEX,UAAM,WAAW,KAAK,IAAI,CAAC,QAAQ,UAAU,KAAK,WAAW,GAAG,CAAC;AAEjE,QAAI;AACF,YAAM,UAAU,MAAM,KAAK,OAAO,IAAI,QAAQ;AAC9C,aAAO;AAAA,IACT,SAAS,OAAY;AACnB,YAAM,IAAI;AAAA,QACR,4CAA4C,MAAM,OAAO;AAAA,QACzD;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AAAA,EAEA,MAAM,WAAgC;AACpC,UAAM,KAAK,gBAAA;AAEX,QAAI;AAEF,YAAM,OAAO,MAAM,KAAK,OAAO,KAAK,OAAO;AAC3C,YAAM,SAAS,MAAM,KAAK,OAAO,KAAK,UAAU;AAGhD,YAAM,UAAU,OAAO,MAAM,kBAAkB;AAC/C,YAAM,UAAU,UAAU,SAAS,QAAQ,CAAC,GAAG,EAAE,IAAI;AAGrD,YAAM,YAAY,KAAK,MAAM,qBAAqB;AAClD,YAAM,cAAc,KAAK,MAAM,uBAAuB;AACtD,YAAM,YAAY,YAAY,SAAS,UAAU,CAAC,GAAG,EAAE,IAAI;AAC3D,YAAM,cAAc,cAAc,SAAS,YAAY,CAAC,GAAG,EAAE,IAAI;AAEjE,YAAM,YAAY,KAAK,MAAM,OAAO;AACpC,YAAM,cAAc,KAAK,MAAM,SAAS;AACxC,YAAM,gBAAgB,YAAY;AAClC,YAAM,UAAU,gBAAgB,IAAI,YAAY,gBAAgB;AAEhE,aAAO;AAAA,QACL;AAAA,QACA,WAAW;AAAA;AAAA,QACX,MAAM;AAAA,QACN,QAAQ;AAAA,QACR;AAAA,QACA,WAAW;AAAA;AAAA,QACX,SAAS;AAAA,UACP,MAAM;AAAA,UACN,MAAM,KAAK,QAAQ,QAAQ;AAAA,UAC3B,MAAM,KAAK,QAAQ,QAAQ;AAAA,UAC3B,IAAI,KAAK,QAAQ,MAAM;AAAA,UACvB,aAAa,KAAK;AAAA,QAAA;AAAA,MACpB;AAAA,IAEJ,SAAS,OAAY;AACnB,YAAM,IAAI;AAAA,QACR,8BAA8B,MAAM,OAAO;AAAA,QAC3C;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AAAA,EAEA,MAAM,MAAM,KAAa,KAA+B;AACtD,QAAI,CAAC,WAAW,GAAG,GAAG;AACpB,YAAM,IAAI,cAAc,KAAK,OAAO;AAAA,IACtC;AAEA,UAAM,KAAK,gBAAA;AAEX,UAAM,UAAU,UAAU,KAAK,WAAW,GAAG;AAE7C,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,OAAO,OAAO,SAAS,GAAG;AACpD,aAAO,WAAW;AAAA,IACpB,SAAS,OAAY;AACnB,YAAM,IAAI;AAAA,QACR,gCAAgC,MAAM,OAAO;AAAA,QAC7C;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,WAAW;AAClB,UAAI;AACF,cAAM,KAAK,OAAO,KAAA;AAClB,aAAK,YAAY;AAAA,MACnB,SAAS,QAAa;AAEpB,cAAM,KAAK,OAAO,WAAA;AAClB,aAAK,YAAY;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACF;"}