@happyvertical/cache 0.80.0 → 0.80.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.
@@ -1 +0,0 @@
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":"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"}
@@ -1,427 +0,0 @@
1
- import { promisify } from "node:util";
2
- import { gunzip, gzip } from "node:zlib";
3
- import { isValidKey, CacheKeyError, deserialize, isExpired, CacheError, calculateExpiration, calculateSize, serialize, CacheSerializationError, extractKey, matchesPattern, formatKey } from "../index.js";
4
- const gzipAsync = promisify(gzip);
5
- const gunzipAsync = promisify(gunzip);
6
- let S3Client;
7
- let GetObjectCommand;
8
- let PutObjectCommand;
9
- let DeleteObjectCommand;
10
- let DeleteObjectsCommand;
11
- let ListObjectsV2Command;
12
- let HeadObjectCommand;
13
- async function loadS3SDK() {
14
- if (!S3Client) {
15
- const sdk = await import("@aws-sdk/client-s3");
16
- S3Client = sdk.S3Client;
17
- GetObjectCommand = sdk.GetObjectCommand;
18
- PutObjectCommand = sdk.PutObjectCommand;
19
- DeleteObjectCommand = sdk.DeleteObjectCommand;
20
- DeleteObjectsCommand = sdk.DeleteObjectsCommand;
21
- ListObjectsV2Command = sdk.ListObjectsV2Command;
22
- HeadObjectCommand = sdk.HeadObjectCommand;
23
- }
24
- }
25
- async function streamToBuffer(stream) {
26
- const chunks = [];
27
- for await (const chunk of stream) {
28
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
29
- }
30
- return Buffer.concat(chunks);
31
- }
32
- class S3Provider {
33
- client;
34
- bucket;
35
- prefix;
36
- namespace;
37
- defaultTTL;
38
- compression;
39
- compressionThreshold;
40
- region;
41
- initialized = false;
42
- stats;
43
- constructor(options) {
44
- this.bucket = options.bucket;
45
- this.prefix = options.prefix || "cache/";
46
- this.namespace = options.namespace;
47
- this.defaultTTL = options.defaultTTL;
48
- this.compression = options.compression ?? true;
49
- this.compressionThreshold = options.compressionThreshold ?? 1024;
50
- this.region = options.region || process.env.AWS_REGION || "us-east-1";
51
- this.stats = {
52
- hits: 0,
53
- misses: 0,
54
- evictions: 0
55
- };
56
- }
57
- /**
58
- * Lazily initialize the S3 client
59
- */
60
- async ensureInitialized() {
61
- if (this.initialized) return;
62
- await loadS3SDK();
63
- const credentials = process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY ? {
64
- accessKeyId: process.env.AWS_ACCESS_KEY_ID,
65
- secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
66
- } : void 0;
67
- this.client = new S3Client({
68
- region: this.region,
69
- credentials
70
- });
71
- this.initialized = true;
72
- }
73
- async get(key) {
74
- if (!isValidKey(key)) {
75
- throw new CacheKeyError(key, "s3");
76
- }
77
- await this.ensureInitialized();
78
- const s3Key = this.getS3Key(key);
79
- try {
80
- const response = await this.client.send(
81
- new GetObjectCommand({
82
- Bucket: this.bucket,
83
- Key: s3Key
84
- })
85
- );
86
- const bodyBuffer = await streamToBuffer(response.Body);
87
- const isCompressed = response.Metadata?.["compressed"] === "true";
88
- let data;
89
- if (isCompressed) {
90
- data = await gunzipAsync(bodyBuffer);
91
- } else {
92
- data = bodyBuffer;
93
- }
94
- const entry = deserialize(data.toString("utf-8"));
95
- if (isExpired(entry.expiresAt)) {
96
- this.stats.misses++;
97
- this.delete(key).catch(() => {
98
- });
99
- return void 0;
100
- }
101
- this.stats.hits++;
102
- return entry.value;
103
- } catch (error) {
104
- if (error.name === "NoSuchKey" || error.$metadata?.httpStatusCode === 404) {
105
- this.stats.misses++;
106
- return void 0;
107
- }
108
- throw new CacheError(
109
- `Failed to read S3 cache entry: ${error.message}`,
110
- "READ_ERROR",
111
- "s3"
112
- );
113
- }
114
- }
115
- async set(key, value, ttl) {
116
- if (!isValidKey(key)) {
117
- throw new CacheKeyError(key, "s3");
118
- }
119
- await this.ensureInitialized();
120
- const s3Key = this.getS3Key(key);
121
- const expiresAt = calculateExpiration(ttl ?? this.defaultTTL);
122
- const entry = {
123
- value,
124
- createdAt: Date.now(),
125
- expiresAt,
126
- size: calculateSize(value),
127
- hits: 0,
128
- metadata: {
129
- compressed: false,
130
- // Will be set below if compressed
131
- namespace: this.namespace
132
- }
133
- };
134
- let data = Buffer.from(serialize(entry), "utf-8");
135
- let isCompressed = false;
136
- if (this.compression && data.length > this.compressionThreshold) {
137
- data = await gzipAsync(data);
138
- isCompressed = true;
139
- }
140
- const metadata = {
141
- "created-at": entry.createdAt.toString(),
142
- compressed: isCompressed.toString()
143
- };
144
- if (expiresAt) {
145
- metadata["expires-at"] = expiresAt.toString();
146
- }
147
- if (this.namespace) {
148
- metadata["namespace"] = this.namespace;
149
- }
150
- try {
151
- await this.client.send(
152
- new PutObjectCommand({
153
- Bucket: this.bucket,
154
- Key: s3Key,
155
- Body: data,
156
- Metadata: metadata,
157
- ContentType: "application/json"
158
- })
159
- );
160
- } catch (error) {
161
- throw new CacheSerializationError(
162
- `Failed to write S3 cache entry: ${error.message}`,
163
- "s3"
164
- );
165
- }
166
- }
167
- async has(key) {
168
- if (!isValidKey(key)) {
169
- throw new CacheKeyError(key, "s3");
170
- }
171
- await this.ensureInitialized();
172
- const s3Key = this.getS3Key(key);
173
- try {
174
- const response = await this.client.send(
175
- new HeadObjectCommand({
176
- Bucket: this.bucket,
177
- Key: s3Key
178
- })
179
- );
180
- const expiresAt = response.Metadata?.["expires-at"];
181
- if (expiresAt && Date.now() >= parseInt(expiresAt, 10)) {
182
- return false;
183
- }
184
- return true;
185
- } catch (error) {
186
- if (error.name === "NotFound" || error.$metadata?.httpStatusCode === 404) {
187
- return false;
188
- }
189
- throw new CacheError(
190
- `Failed to check S3 cache entry: ${error.message}`,
191
- "CHECK_ERROR",
192
- "s3"
193
- );
194
- }
195
- }
196
- async delete(key) {
197
- if (!isValidKey(key)) {
198
- throw new CacheKeyError(key, "s3");
199
- }
200
- await this.ensureInitialized();
201
- const s3Key = this.getS3Key(key);
202
- try {
203
- await this.client.send(
204
- new DeleteObjectCommand({
205
- Bucket: this.bucket,
206
- Key: s3Key
207
- })
208
- );
209
- return true;
210
- } catch (error) {
211
- return true;
212
- }
213
- }
214
- async clear(namespace) {
215
- await this.ensureInitialized();
216
- const targetNamespace = namespace || this.namespace;
217
- const prefix = targetNamespace ? `${this.prefix}${this.sanitizeKey(`${targetNamespace}:`)}` : this.prefix;
218
- try {
219
- let continuationToken;
220
- do {
221
- const listResponse = await this.client.send(
222
- new ListObjectsV2Command({
223
- Bucket: this.bucket,
224
- Prefix: prefix,
225
- ContinuationToken: continuationToken
226
- })
227
- );
228
- if (listResponse.Contents && listResponse.Contents.length > 0) {
229
- const objects = listResponse.Contents.map((obj) => ({
230
- Key: obj.Key
231
- }));
232
- await this.client.send(
233
- new DeleteObjectsCommand({
234
- Bucket: this.bucket,
235
- Delete: { Objects: objects }
236
- })
237
- );
238
- }
239
- continuationToken = listResponse.NextContinuationToken;
240
- } while (continuationToken);
241
- this.stats.hits = 0;
242
- this.stats.misses = 0;
243
- this.stats.evictions = 0;
244
- } catch (error) {
245
- throw new CacheError(
246
- `Failed to clear S3 cache: ${error.message}`,
247
- "CLEAR_ERROR",
248
- "s3"
249
- );
250
- }
251
- }
252
- async keys(pattern) {
253
- await this.ensureInitialized();
254
- const keys = [];
255
- let continuationToken;
256
- try {
257
- do {
258
- const listResponse = await this.client.send(
259
- new ListObjectsV2Command({
260
- Bucket: this.bucket,
261
- Prefix: this.prefix,
262
- ContinuationToken: continuationToken
263
- })
264
- );
265
- if (listResponse.Contents) {
266
- for (const obj of listResponse.Contents) {
267
- const s3Key = obj.Key;
268
- if (!s3Key.endsWith(".cache")) continue;
269
- const rawKey = s3Key.slice(this.prefix.length).replace(/\.cache$/, "");
270
- const desanitized = this.desanitizeKey(rawKey);
271
- const extractedKey = extractKey(this.namespace, desanitized);
272
- keys.push(extractedKey);
273
- }
274
- }
275
- continuationToken = listResponse.NextContinuationToken;
276
- } while (continuationToken);
277
- } catch (error) {
278
- throw new CacheError(
279
- `Failed to list S3 cache keys: ${error.message}`,
280
- "LIST_ERROR",
281
- "s3"
282
- );
283
- }
284
- if (pattern) {
285
- return keys.filter((key) => matchesPattern(pattern, key));
286
- }
287
- return keys;
288
- }
289
- async getMany(keys) {
290
- const result = /* @__PURE__ */ new Map();
291
- const results = await Promise.allSettled(
292
- keys.map(async (key) => {
293
- const value = await this.get(key);
294
- return { key, value };
295
- })
296
- );
297
- for (const result2 of results) {
298
- if (result2.status === "fulfilled" && result2.value.value !== void 0) {
299
- result2.value;
300
- }
301
- }
302
- for (const r of results) {
303
- if (r.status === "fulfilled" && r.value.value !== void 0) {
304
- result.set(r.value.key, r.value.value);
305
- }
306
- }
307
- return result;
308
- }
309
- async setMany(entries) {
310
- await Promise.all(
311
- entries.map((entry) => this.set(entry.key, entry.value, entry.ttl))
312
- );
313
- }
314
- async deleteMany(keys) {
315
- await this.ensureInitialized();
316
- if (keys.length === 0) return 0;
317
- const s3Keys = keys.map((key) => ({
318
- Key: this.getS3Key(key)
319
- }));
320
- try {
321
- let deleted = 0;
322
- for (let i = 0; i < s3Keys.length; i += 1e3) {
323
- const batch = s3Keys.slice(i, i + 1e3);
324
- const response = await this.client.send(
325
- new DeleteObjectsCommand({
326
- Bucket: this.bucket,
327
- Delete: { Objects: batch }
328
- })
329
- );
330
- deleted += response.Deleted?.length || 0;
331
- }
332
- return deleted;
333
- } catch (error) {
334
- throw new CacheError(
335
- `Failed to delete S3 cache entries: ${error.message}`,
336
- "DELETE_ERROR",
337
- "s3"
338
- );
339
- }
340
- }
341
- async getStats() {
342
- await this.ensureInitialized();
343
- let entries = 0;
344
- let totalSize = 0;
345
- let continuationToken;
346
- try {
347
- do {
348
- const listResponse = await this.client.send(
349
- new ListObjectsV2Command({
350
- Bucket: this.bucket,
351
- Prefix: this.prefix,
352
- ContinuationToken: continuationToken
353
- })
354
- );
355
- if (listResponse.Contents) {
356
- for (const obj of listResponse.Contents) {
357
- entries++;
358
- totalSize += obj.Size || 0;
359
- }
360
- }
361
- continuationToken = listResponse.NextContinuationToken;
362
- } while (continuationToken);
363
- } catch (error) {
364
- entries = 0;
365
- totalSize = 0;
366
- }
367
- const totalAccesses = this.stats.hits + this.stats.misses;
368
- const hitRate = totalAccesses > 0 ? this.stats.hits / totalAccesses : 0;
369
- return {
370
- entries,
371
- totalSize,
372
- hits: this.stats.hits,
373
- misses: this.stats.misses,
374
- hitRate,
375
- evictions: this.stats.evictions,
376
- backend: {
377
- type: "s3",
378
- bucket: this.bucket,
379
- prefix: this.prefix,
380
- region: this.region,
381
- compression: this.compression
382
- }
383
- };
384
- }
385
- async touch(key, ttl) {
386
- if (!isValidKey(key)) {
387
- throw new CacheKeyError(key, "s3");
388
- }
389
- const value = await this.get(key);
390
- if (value === void 0) {
391
- return false;
392
- }
393
- await this.set(key, value, ttl);
394
- return true;
395
- }
396
- async close() {
397
- if (this.client?.destroy) {
398
- this.client.destroy();
399
- }
400
- this.initialized = false;
401
- }
402
- /**
403
- * Gets the S3 key for a cache key
404
- */
405
- getS3Key(key) {
406
- const fullKey = formatKey(this.namespace, key);
407
- const sanitized = this.sanitizeKey(fullKey);
408
- return `${this.prefix}${sanitized}.cache`;
409
- }
410
- /**
411
- * Sanitizes a key for use as an S3 key
412
- * S3 keys can contain most characters, but we sanitize for consistency
413
- */
414
- sanitizeKey(key) {
415
- return key.replace(/[^a-zA-Z0-9_:-]/g, "_");
416
- }
417
- /**
418
- * Desanitizes an S3 key back to the original format
419
- */
420
- desanitizeKey(sanitized) {
421
- return sanitized;
422
- }
423
- }
424
- export {
425
- S3Provider
426
- };
427
- //# sourceMappingURL=s3-ByokNFv_.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"s3-ByokNFv_.js","sources":["../../src/providers/s3.ts"],"sourcesContent":["/**\n * S3 cache provider implementation\n * Stores cache entries in S3 for persistence across CI runs\n */\n\nimport { promisify } from 'node:util';\nimport { gunzip, gzip } from 'node:zlib';\nimport type {\n CacheEntry,\n CacheProvider,\n CacheStats,\n S3Options,\n} from '../shared/types';\nimport {\n CacheError,\n CacheKeyError,\n CacheSerializationError,\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// Dynamic imports for AWS SDK (lazy loaded)\nlet S3Client: any;\nlet GetObjectCommand: any;\nlet PutObjectCommand: any;\nlet DeleteObjectCommand: any;\nlet DeleteObjectsCommand: any;\nlet ListObjectsV2Command: any;\nlet HeadObjectCommand: any;\n\nasync function loadS3SDK() {\n if (!S3Client) {\n const sdk = await import('@aws-sdk/client-s3');\n S3Client = sdk.S3Client;\n GetObjectCommand = sdk.GetObjectCommand;\n PutObjectCommand = sdk.PutObjectCommand;\n DeleteObjectCommand = sdk.DeleteObjectCommand;\n DeleteObjectsCommand = sdk.DeleteObjectsCommand;\n ListObjectsV2Command = sdk.ListObjectsV2Command;\n HeadObjectCommand = sdk.HeadObjectCommand;\n }\n}\n\n/**\n * Converts a readable stream to a buffer\n */\nasync function streamToBuffer(stream: any): Promise<Buffer> {\n const chunks: Buffer[] = [];\n for await (const chunk of stream) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n }\n return Buffer.concat(chunks);\n}\n\n/**\n * S3 cache provider implementation\n * Stores cache entries as S3 objects with optional compression\n */\nexport class S3Provider implements CacheProvider {\n private client: any;\n private bucket: string;\n private prefix: string;\n private namespace?: string;\n private defaultTTL?: number;\n private compression: boolean;\n private compressionThreshold: number;\n private region: string;\n private initialized: boolean = false;\n private stats: {\n hits: number;\n misses: number;\n evictions: number;\n };\n\n constructor(options: S3Options) {\n this.bucket = options.bucket;\n this.prefix = options.prefix || 'cache/';\n this.namespace = options.namespace;\n this.defaultTTL = options.defaultTTL;\n this.compression = options.compression ?? true; // Default ON for S3 (reduces egress costs)\n this.compressionThreshold = options.compressionThreshold ?? 1024; // 1KB\n this.region = options.region || process.env.AWS_REGION || 'us-east-1';\n this.stats = {\n hits: 0,\n misses: 0,\n evictions: 0,\n };\n }\n\n /**\n * Lazily initialize the S3 client\n */\n private async ensureInitialized(): Promise<void> {\n if (this.initialized) return;\n\n await loadS3SDK();\n\n // Use explicit credentials from environment if available\n // This ensures credentials work even when bundled by Vite\n const credentials =\n process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY\n ? {\n accessKeyId: process.env.AWS_ACCESS_KEY_ID,\n secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,\n }\n : undefined;\n\n this.client = new S3Client({\n region: this.region,\n credentials,\n });\n this.initialized = true;\n }\n\n async get<T = any>(key: string): Promise<T | undefined> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 's3');\n }\n\n await this.ensureInitialized();\n\n const s3Key = this.getS3Key(key);\n\n try {\n const response = await this.client.send(\n new GetObjectCommand({\n Bucket: this.bucket,\n Key: s3Key,\n }),\n );\n\n // Get body as buffer\n const bodyBuffer = await streamToBuffer(response.Body);\n\n // Check if compressed from metadata\n const isCompressed = response.Metadata?.['compressed'] === 'true';\n\n let data: Buffer;\n if (isCompressed) {\n data = await gunzipAsync(bodyBuffer);\n } else {\n data = bodyBuffer;\n }\n\n const entry: CacheEntry<T> = deserialize(data.toString('utf-8'));\n\n // Check if expired\n if (isExpired(entry.expiresAt)) {\n this.stats.misses++;\n // Optionally delete expired entry (fire and forget)\n this.delete(key).catch(() => {});\n return undefined;\n }\n\n this.stats.hits++;\n return entry.value;\n } catch (error: any) {\n if (\n error.name === 'NoSuchKey' ||\n error.$metadata?.httpStatusCode === 404\n ) {\n this.stats.misses++;\n return undefined;\n }\n throw new CacheError(\n `Failed to read S3 cache entry: ${error.message}`,\n 'READ_ERROR',\n 's3',\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, 's3');\n }\n\n await this.ensureInitialized();\n\n const s3Key = this.getS3Key(key);\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: false, // Will be set below if compressed\n namespace: this.namespace,\n },\n };\n\n let data: Buffer = Buffer.from(serialize(entry), 'utf-8');\n let isCompressed = false;\n\n // Compress if enabled and data exceeds threshold\n if (this.compression && data.length > this.compressionThreshold) {\n data = (await gzipAsync(data)) as Buffer;\n isCompressed = true;\n }\n\n const metadata: Record<string, string> = {\n 'created-at': entry.createdAt.toString(),\n compressed: isCompressed.toString(),\n };\n\n if (expiresAt) {\n metadata['expires-at'] = expiresAt.toString();\n }\n\n if (this.namespace) {\n metadata['namespace'] = this.namespace;\n }\n\n try {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.bucket,\n Key: s3Key,\n Body: data,\n Metadata: metadata,\n ContentType: 'application/json',\n }),\n );\n } catch (error: any) {\n throw new CacheSerializationError(\n `Failed to write S3 cache entry: ${error.message}`,\n 's3',\n );\n }\n }\n\n async has(key: string): Promise<boolean> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 's3');\n }\n\n await this.ensureInitialized();\n\n const s3Key = this.getS3Key(key);\n\n try {\n const response = await this.client.send(\n new HeadObjectCommand({\n Bucket: this.bucket,\n Key: s3Key,\n }),\n );\n\n // Check expiration from metadata\n const expiresAt = response.Metadata?.['expires-at'];\n if (expiresAt && Date.now() >= parseInt(expiresAt, 10)) {\n return false;\n }\n\n return true;\n } catch (error: any) {\n if (\n error.name === 'NotFound' ||\n error.$metadata?.httpStatusCode === 404\n ) {\n return false;\n }\n throw new CacheError(\n `Failed to check S3 cache entry: ${error.message}`,\n 'CHECK_ERROR',\n 's3',\n );\n }\n }\n\n async delete(key: string): Promise<boolean> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 's3');\n }\n\n await this.ensureInitialized();\n\n const s3Key = this.getS3Key(key);\n\n try {\n await this.client.send(\n new DeleteObjectCommand({\n Bucket: this.bucket,\n Key: s3Key,\n }),\n );\n return true;\n } catch (error: any) {\n // S3 DeleteObject doesn't error if key doesn't exist\n // It just succeeds silently\n return true;\n }\n }\n\n async clear(namespace?: string): Promise<void> {\n await this.ensureInitialized();\n\n const targetNamespace = namespace || this.namespace;\n const prefix = targetNamespace\n ? `${this.prefix}${this.sanitizeKey(`${targetNamespace}:`)}`\n : this.prefix;\n\n try {\n // List all objects with prefix\n let continuationToken: string | undefined;\n do {\n const listResponse = await this.client.send(\n new ListObjectsV2Command({\n Bucket: this.bucket,\n Prefix: prefix,\n ContinuationToken: continuationToken,\n }),\n );\n\n if (listResponse.Contents && listResponse.Contents.length > 0) {\n // Delete in batches of 1000 (S3 limit)\n const objects = listResponse.Contents.map((obj: any) => ({\n Key: obj.Key,\n }));\n\n await this.client.send(\n new DeleteObjectsCommand({\n Bucket: this.bucket,\n Delete: { Objects: objects },\n }),\n );\n }\n\n continuationToken = listResponse.NextContinuationToken;\n } while (continuationToken);\n\n // Reset stats\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 S3 cache: ${error.message}`,\n 'CLEAR_ERROR',\n 's3',\n );\n }\n }\n\n async keys(pattern?: string): Promise<string[]> {\n await this.ensureInitialized();\n\n const keys: string[] = [];\n let continuationToken: string | undefined;\n\n try {\n do {\n const listResponse = await this.client.send(\n new ListObjectsV2Command({\n Bucket: this.bucket,\n Prefix: this.prefix,\n ContinuationToken: continuationToken,\n }),\n );\n\n if (listResponse.Contents) {\n for (const obj of listResponse.Contents) {\n // Extract key from S3 key\n const s3Key = obj.Key as string;\n if (!s3Key.endsWith('.cache')) continue;\n\n // Remove prefix and .cache extension\n const rawKey = s3Key\n .slice(this.prefix.length)\n .replace(/\\.cache$/, '');\n\n const desanitized = this.desanitizeKey(rawKey);\n const extractedKey = extractKey(this.namespace, desanitized);\n\n keys.push(extractedKey);\n }\n }\n\n continuationToken = listResponse.NextContinuationToken;\n } while (continuationToken);\n } catch (error: any) {\n throw new CacheError(\n `Failed to list S3 cache keys: ${error.message}`,\n 'LIST_ERROR',\n 's3',\n );\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 // S3 doesn't have batch get, so fetch in parallel\n const results = await Promise.allSettled(\n keys.map(async (key) => {\n const value = await this.get<T>(key);\n return { key, value };\n }),\n );\n\n for (const result of results) {\n if (result.status === 'fulfilled' && result.value.value !== undefined) {\n result.value;\n }\n }\n\n for (const r of results) {\n if (r.status === 'fulfilled' && r.value.value !== undefined) {\n result.set(r.value.key, r.value.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 // S3 doesn't have batch put, so write in parallel\n await Promise.all(\n entries.map((entry) => this.set(entry.key, entry.value, entry.ttl)),\n );\n }\n\n async deleteMany(keys: string[]): Promise<number> {\n await this.ensureInitialized();\n\n if (keys.length === 0) return 0;\n\n const s3Keys = keys.map((key) => ({\n Key: this.getS3Key(key),\n }));\n\n try {\n // Delete in batches of 1000\n let deleted = 0;\n for (let i = 0; i < s3Keys.length; i += 1000) {\n const batch = s3Keys.slice(i, i + 1000);\n const response = await this.client.send(\n new DeleteObjectsCommand({\n Bucket: this.bucket,\n Delete: { Objects: batch },\n }),\n );\n deleted += response.Deleted?.length || 0;\n }\n return deleted;\n } catch (error: any) {\n throw new CacheError(\n `Failed to delete S3 cache entries: ${error.message}`,\n 'DELETE_ERROR',\n 's3',\n );\n }\n }\n\n async getStats(): Promise<CacheStats> {\n await this.ensureInitialized();\n\n let entries = 0;\n let totalSize = 0;\n let continuationToken: string | undefined;\n\n try {\n do {\n const listResponse = await this.client.send(\n new ListObjectsV2Command({\n Bucket: this.bucket,\n Prefix: this.prefix,\n ContinuationToken: continuationToken,\n }),\n );\n\n if (listResponse.Contents) {\n for (const obj of listResponse.Contents) {\n entries++;\n totalSize += obj.Size || 0;\n }\n }\n\n continuationToken = listResponse.NextContinuationToken;\n } while (continuationToken);\n } catch (error: any) {\n // If we can't list, return empty stats\n entries = 0;\n totalSize = 0;\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: 's3',\n bucket: this.bucket,\n prefix: this.prefix,\n region: this.region,\n compression: this.compression,\n },\n };\n }\n\n async touch(key: string, ttl: number): Promise<boolean> {\n if (!isValidKey(key)) {\n throw new CacheKeyError(key, 's3');\n }\n\n // For S3, we need to read the object, update TTL, and write it back\n const value = await this.get(key);\n if (value === undefined) {\n return false;\n }\n\n await this.set(key, value, ttl);\n return true;\n }\n\n async close(): Promise<void> {\n // S3 client doesn't need explicit cleanup\n // Just destroy the client if it exists\n if (this.client?.destroy) {\n this.client.destroy();\n }\n this.initialized = false;\n }\n\n /**\n * Gets the S3 key for a cache key\n */\n private getS3Key(key: string): string {\n const fullKey = formatKey(this.namespace, key);\n const sanitized = this.sanitizeKey(fullKey);\n return `${this.prefix}${sanitized}.cache`;\n }\n\n /**\n * Sanitizes a key for use as an S3 key\n * S3 keys can contain most characters, but we sanitize for consistency\n */\n private sanitizeKey(key: string): string {\n return key.replace(/[^a-zA-Z0-9_:-]/g, '_');\n }\n\n /**\n * Desanitizes an S3 key back to the original format\n */\n private desanitizeKey(sanitized: string): string {\n // This is a simple implementation\n // In practice, we might need a more sophisticated mapping\n return sanitized;\n }\n}\n"],"names":["result"],"mappings":"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"}