@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,427 @@
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
@@ -0,0 +1 @@
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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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=claude-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-context.d.ts","sourceRoot":"","sources":["../../src/cli/claude-context.ts"],"names":[],"mappings":""}
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync, copyFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ const Dirname = dirname(fileURLToPath(import.meta.url));
6
+ const pkgRoot = join(Dirname, "../..");
7
+ const targetDir = join(process.cwd(), ".claude");
8
+ if (!existsSync(targetDir)) {
9
+ mkdirSync(targetDir, { recursive: true });
10
+ }
11
+ const pkgName = "cache";
12
+ const agentMdSrc = existsSync(join(pkgRoot, "AGENT.md")) ? join(pkgRoot, "AGENT.md") : join(pkgRoot, "CLAUDE.md");
13
+ const metaSrc = existsSync(join(pkgRoot, "metadata.json")) ? join(pkgRoot, "metadata.json") : join(pkgRoot, ".claude-meta.json");
14
+ if (existsSync(agentMdSrc)) {
15
+ copyFileSync(agentMdSrc, join(targetDir, `have-${pkgName}.md`));
16
+ }
17
+ if (existsSync(metaSrc)) {
18
+ copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));
19
+ }
20
+ console.log(`✓ Installed @happyvertical/${pkgName} context to .claude/`);
21
+ //# sourceMappingURL=claude-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-context.js","sources":["../../src/cli/claude-context.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * CLI script to install agent context for @happyvertical/cache\n * Run the published context installer binary for this package.\n */\nimport { copyFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst Dirname = dirname(fileURLToPath(import.meta.url));\nconst pkgRoot = join(Dirname, '../..');\nconst targetDir = join(process.cwd(), '.claude');\n\nif (!existsSync(targetDir)) {\n mkdirSync(targetDir, { recursive: true });\n}\n\nconst pkgName = 'cache';\nconst agentMdSrc = existsSync(join(pkgRoot, 'AGENT.md'))\n ? join(pkgRoot, 'AGENT.md')\n : join(pkgRoot, 'CLAUDE.md');\nconst metaSrc = existsSync(join(pkgRoot, 'metadata.json'))\n ? join(pkgRoot, 'metadata.json')\n : join(pkgRoot, '.claude-meta.json');\n\nif (existsSync(agentMdSrc)) {\n copyFileSync(agentMdSrc, join(targetDir, `have-${pkgName}.md`));\n}\n\nif (existsSync(metaSrc)) {\n copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));\n}\n\nconsole.log(`✓ Installed @happyvertical/${pkgName} context to .claude/`);\n"],"names":[],"mappings":";;;;AASA,MAAM,UAAU,QAAQ,cAAc,YAAY,GAAG,CAAC;AACtD,MAAM,UAAU,KAAK,SAAS,OAAO;AACrC,MAAM,YAAY,KAAK,QAAQ,IAAA,GAAO,SAAS;AAE/C,IAAI,CAAC,WAAW,SAAS,GAAG;AAC1B,YAAU,WAAW,EAAE,WAAW,KAAA,CAAM;AAC1C;AAEA,MAAM,UAAU;AAChB,MAAM,aAAa,WAAW,KAAK,SAAS,UAAU,CAAC,IACnD,KAAK,SAAS,UAAU,IACxB,KAAK,SAAS,WAAW;AAC7B,MAAM,UAAU,WAAW,KAAK,SAAS,eAAe,CAAC,IACrD,KAAK,SAAS,eAAe,IAC7B,KAAK,SAAS,mBAAmB;AAErC,IAAI,WAAW,UAAU,GAAG;AAC1B,eAAa,YAAY,KAAK,WAAW,QAAQ,OAAO,KAAK,CAAC;AAChE;AAEA,IAAI,WAAW,OAAO,GAAG;AACvB,eAAa,SAAS,KAAK,WAAW,QAAQ,OAAO,YAAY,CAAC;AACpE;AAEA,QAAQ,IAAI,8BAA8B,OAAO,sBAAsB;"}
@@ -0,0 +1,72 @@
1
+ import { CacheAdapter, CacheAdapterOptions } from './shared/types';
2
+ export * from './shared/types';
3
+ export * from './shared/utils';
4
+ /**
5
+ * Factory function to create a cache adapter instance
6
+ *
7
+ * Supports environment variable configuration using the HAVE_CACHE_* pattern:
8
+ * - HAVE_CACHE_PROVIDER → provider ('memory'|'file'|'redis'|'s3')
9
+ * - HAVE_CACHE_NAMESPACE → namespace (string)
10
+ * - HAVE_CACHE_DEFAULT_TTL → defaultTTL (number: seconds)
11
+ * - HAVE_CACHE_MAX_SIZE → maxSize (number: bytes)
12
+ * - HAVE_CACHE_MAX_ENTRIES → maxEntries (number, memory only)
13
+ * - HAVE_CACHE_EVICTION_POLICY → evictionPolicy ('lru'|'lfu'|'fifo', memory only)
14
+ * - HAVE_CACHE_CACHE_DIR → cacheDir (string, file only)
15
+ * - HAVE_CACHE_COMPRESSION → compression (boolean, file/s3)
16
+ * - HAVE_CACHE_HOST → host (string, redis only)
17
+ * - HAVE_CACHE_PORT → port (number, redis only)
18
+ * - HAVE_CACHE_BUCKET → bucket (string, s3 only)
19
+ * - HAVE_CACHE_PREFIX → prefix (string, s3 only)
20
+ * - HAVE_CACHE_REGION → region (string, s3 only)
21
+ *
22
+ * User-provided options always take precedence over environment variables.
23
+ *
24
+ * @param options - Configuration options for the cache provider
25
+ * @returns Promise resolving to a cache adapter that implements CacheAdapter
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * // Create memory cache with explicit options
30
+ * const memoryCache = await getCache({
31
+ * provider: 'memory',
32
+ * maxSize: 100 * 1024 * 1024,
33
+ * evictionPolicy: 'lru'
34
+ * });
35
+ *
36
+ * // Create memory cache with environment variables
37
+ * // HAVE_CACHE_PROVIDER=memory
38
+ * // HAVE_CACHE_MAX_SIZE=104857600
39
+ * // HAVE_CACHE_EVICTION_POLICY=lru
40
+ * const envCache = await getCache({ provider: 'memory' });
41
+ *
42
+ * // Create file cache
43
+ * const fileCache = await getCache({
44
+ * provider: 'file',
45
+ * cacheDir: './cache',
46
+ * compression: true
47
+ * });
48
+ *
49
+ * // Create Redis cache
50
+ * const redisCache = await getCache({
51
+ * provider: 'redis',
52
+ * host: 'localhost',
53
+ * port: 6379
54
+ * });
55
+ *
56
+ * // Create S3 cache (for CI persistence)
57
+ * const s3Cache = await getCache({
58
+ * provider: 's3',
59
+ * bucket: 'my-cache-bucket',
60
+ * prefix: 'cache/',
61
+ * region: 'us-east-1'
62
+ * });
63
+ *
64
+ * // Use the cache
65
+ * await memoryCache.set('user:123', { name: 'John' });
66
+ * const user = await memoryCache.get('user:123');
67
+ * ```
68
+ */
69
+ export declare function getCache(options: CacheAdapterOptions): Promise<CacheAdapter>;
70
+ /** @internal */
71
+ export declare const PACKAGE_VERSION_INITIALIZED = true;
72
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EACV,YAAY,EACZ,mBAAmB,EAKpB,MAAM,gBAAgB,CAAC;AAGxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAgC/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AACH,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,YAAY,CAAC,CAwDvB;AAED,gBAAgB;AAChB,eAAO,MAAM,2BAA2B,OAAO,CAAC"}