@happyvertical/cache 0.80.0 → 0.80.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/file-BiNbZgFQ.js +350 -0
- package/dist/chunks/file-BiNbZgFQ.js.map +1 -0
- package/dist/chunks/memory-7jBOw6kZ.js +224 -0
- package/dist/chunks/memory-7jBOw6kZ.js.map +1 -0
- package/dist/chunks/redis-BMACvZJq.js +268 -0
- package/dist/chunks/redis-BMACvZJq.js.map +1 -0
- package/dist/chunks/s3-DG191QH0.js +348 -0
- package/dist/chunks/s3-DG191QH0.js.map +1 -0
- package/dist/cli/claude-context.js +17 -17
- package/dist/cli/claude-context.js.map +1 -1
- package/dist/index.js +272 -141
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/dist/chunks/file-DyC_7WDS.js +0 -450
- package/dist/chunks/file-DyC_7WDS.js.map +0 -1
- package/dist/chunks/memory-C6vfNZYg.js +0 -274
- package/dist/chunks/memory-C6vfNZYg.js.map +0 -1
- package/dist/chunks/redis-D-SNLXE_.js +0 -365
- package/dist/chunks/redis-D-SNLXE_.js.map +0 -1
- package/dist/chunks/s3-ByokNFv_.js +0 -427
- package/dist/chunks/s3-ByokNFv_.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-DG191QH0.js","names":[],"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"],"mappings":"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"}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, mkdirSync
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
if (existsSync(metaSrc)) {
|
|
18
|
-
copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));
|
|
19
|
-
}
|
|
5
|
+
//#region src/cli/claude-context.ts
|
|
6
|
+
/**
|
|
7
|
+
* CLI script to install agent context for @happyvertical/cache
|
|
8
|
+
* Run the published context installer binary for this package.
|
|
9
|
+
*/
|
|
10
|
+
var pkgRoot = join(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
11
|
+
var targetDir = join(process.cwd(), ".claude");
|
|
12
|
+
if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });
|
|
13
|
+
var pkgName = "cache";
|
|
14
|
+
var agentMdSrc = existsSync(join(pkgRoot, "AGENT.md")) ? join(pkgRoot, "AGENT.md") : join(pkgRoot, "CLAUDE.md");
|
|
15
|
+
var metaSrc = existsSync(join(pkgRoot, "metadata.json")) ? join(pkgRoot, "metadata.json") : join(pkgRoot, ".claude-meta.json");
|
|
16
|
+
if (existsSync(agentMdSrc)) copyFileSync(agentMdSrc, join(targetDir, `have-${pkgName}.md`));
|
|
17
|
+
if (existsSync(metaSrc)) copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));
|
|
20
18
|
console.log(`✓ Installed @happyvertical/${pkgName} context to .claude/`);
|
|
21
|
-
//#
|
|
19
|
+
//#endregion
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=claude-context.js.map
|
|
@@ -1 +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"],"
|
|
1
|
+
{"version":3,"file":"claude-context.js","names":[],"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"],"mappings":";;;;;;;;;AAUA,IAAM,UAAU,KADA,QAAQ,cAAc,OAAO,KAAK,GAAG,CAChC,GAAS,OAAO;AACrC,IAAM,YAAY,KAAK,QAAQ,IAAI,GAAG,SAAS;AAE/C,IAAI,CAAC,WAAW,SAAS,GACvB,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAG1C,IAAM,UAAU;AAChB,IAAM,aAAa,WAAW,KAAK,SAAS,UAAU,CAAC,IACnD,KAAK,SAAS,UAAU,IACxB,KAAK,SAAS,WAAW;AAC7B,IAAM,UAAU,WAAW,KAAK,SAAS,eAAe,CAAC,IACrD,KAAK,SAAS,eAAe,IAC7B,KAAK,SAAS,mBAAmB;AAErC,IAAI,WAAW,UAAU,GACvB,aAAa,YAAY,KAAK,WAAW,QAAQ,QAAQ,IAAI,CAAC;AAGhE,IAAI,WAAW,OAAO,GACpB,aAAa,SAAS,KAAK,WAAW,QAAQ,QAAQ,WAAW,CAAC;AAGpE,QAAQ,IAAI,8BAA8B,QAAQ,qBAAqB"}
|
package/dist/index.js
CHANGED
|
@@ -1,170 +1,301 @@
|
|
|
1
1
|
import { loadEnvConfig } from "@happyvertical/utils";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
2
|
+
//#region src/shared/types.ts
|
|
3
|
+
/**
|
|
4
|
+
* Base cache error class
|
|
5
|
+
*/
|
|
6
|
+
var CacheError = class extends Error {
|
|
7
|
+
code;
|
|
8
|
+
provider;
|
|
9
|
+
constructor(message, code, provider) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.provider = provider;
|
|
13
|
+
this.name = "CacheError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Invalid cache key error
|
|
18
|
+
*/
|
|
19
|
+
var CacheKeyError = class extends CacheError {
|
|
20
|
+
key;
|
|
21
|
+
constructor(key, provider) {
|
|
22
|
+
super(`Invalid cache key: ${key}`, "INVALID_KEY", provider);
|
|
23
|
+
this.key = key;
|
|
24
|
+
this.name = "CacheKeyError";
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Cache connection error
|
|
29
|
+
*/
|
|
30
|
+
var CacheConnectionError = class extends CacheError {
|
|
31
|
+
constructor(message, provider) {
|
|
32
|
+
super(message, "CONNECTION_ERROR", provider);
|
|
33
|
+
this.name = "CacheConnectionError";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Cache size limit exceeded error
|
|
38
|
+
*/
|
|
39
|
+
var CacheSizeError = class extends CacheError {
|
|
40
|
+
constructor(message, provider) {
|
|
41
|
+
super(message, "SIZE_EXCEEDED", provider);
|
|
42
|
+
this.name = "CacheSizeError";
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Cache serialization error
|
|
47
|
+
*/
|
|
48
|
+
var CacheSerializationError = class extends CacheError {
|
|
49
|
+
constructor(message, provider) {
|
|
50
|
+
super(message, "SERIALIZATION_ERROR", provider);
|
|
51
|
+
this.name = "CacheSerializationError";
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/shared/utils.ts
|
|
56
|
+
/**
|
|
57
|
+
* Utility functions for cache operations
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* Validates a cache key
|
|
61
|
+
* @param key - The cache key to validate
|
|
62
|
+
* @returns True if the key is valid
|
|
63
|
+
*/
|
|
35
64
|
function isValidKey(key) {
|
|
36
|
-
|
|
65
|
+
return typeof key === "string" && key.length > 0 && key.length <= 250;
|
|
37
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Calculates the size of a value in bytes (approximate)
|
|
69
|
+
* @param value - The value to measure
|
|
70
|
+
* @returns Size in bytes
|
|
71
|
+
*/
|
|
38
72
|
function calculateSize(value) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
73
|
+
try {
|
|
74
|
+
const json = JSON.stringify(value);
|
|
75
|
+
return new Blob([json]).size;
|
|
76
|
+
} catch {
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
45
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Checks if a pattern matches a string (glob-style)
|
|
82
|
+
* @param pattern - The glob pattern (supports * wildcard)
|
|
83
|
+
* @param str - The string to test
|
|
84
|
+
* @returns True if the pattern matches
|
|
85
|
+
*/
|
|
46
86
|
function matchesPattern(pattern, str) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return regex.test(str);
|
|
87
|
+
const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
88
|
+
return new RegExp(`^${regexPattern}$`).test(str);
|
|
50
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Formats a namespace and key into a full key
|
|
92
|
+
* @param namespace - Optional namespace
|
|
93
|
+
* @param key - The cache key
|
|
94
|
+
* @returns Formatted key with namespace prefix if provided
|
|
95
|
+
*/
|
|
51
96
|
function formatKey(namespace, key) {
|
|
52
|
-
|
|
97
|
+
return namespace ? `${namespace}:${key}` : key;
|
|
53
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Extracts the original key from a namespaced key
|
|
101
|
+
* @param namespace - Optional namespace
|
|
102
|
+
* @param fullKey - The full key with namespace
|
|
103
|
+
* @returns Original key without namespace
|
|
104
|
+
*/
|
|
54
105
|
function extractKey(namespace, fullKey) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const prefix = `${namespace}:`;
|
|
59
|
-
return fullKey.startsWith(prefix) ? fullKey.slice(prefix.length) : fullKey;
|
|
106
|
+
if (!namespace) return fullKey;
|
|
107
|
+
const prefix = `${namespace}:`;
|
|
108
|
+
return fullKey.startsWith(prefix) ? fullKey.slice(prefix.length) : fullKey;
|
|
60
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Checks if an entry has expired
|
|
112
|
+
* @param expiresAt - Expiration timestamp (undefined means no expiration)
|
|
113
|
+
* @returns True if the entry has expired
|
|
114
|
+
*/
|
|
61
115
|
function isExpired(expiresAt) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
return Date.now() >= expiresAt;
|
|
116
|
+
if (expiresAt === void 0) return false;
|
|
117
|
+
return Date.now() >= expiresAt;
|
|
66
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Calculates expiration timestamp from TTL
|
|
121
|
+
* @param ttl - Time-to-live in seconds (undefined means no expiration)
|
|
122
|
+
* @returns Expiration timestamp in milliseconds, or undefined
|
|
123
|
+
*/
|
|
67
124
|
function calculateExpiration(ttl) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
return Date.now() + ttl * 1e3;
|
|
125
|
+
if (ttl === void 0 || ttl <= 0) return;
|
|
126
|
+
return Date.now() + ttl * 1e3;
|
|
72
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Serializes a value to JSON string
|
|
130
|
+
* @param value - The value to serialize
|
|
131
|
+
* @returns JSON string
|
|
132
|
+
* @throws Error if serialization fails
|
|
133
|
+
*/
|
|
73
134
|
function serialize(value) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
);
|
|
80
|
-
}
|
|
135
|
+
try {
|
|
136
|
+
return JSON.stringify(value);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
throw new Error(`Failed to serialize value: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
139
|
+
}
|
|
81
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Deserializes a JSON string to a value
|
|
143
|
+
* @param json - The JSON string
|
|
144
|
+
* @returns Deserialized value
|
|
145
|
+
* @throws Error if deserialization fails
|
|
146
|
+
*/
|
|
82
147
|
function deserialize(json) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
);
|
|
89
|
-
}
|
|
148
|
+
try {
|
|
149
|
+
return JSON.parse(json);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
throw new Error(`Failed to deserialize value: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
152
|
+
}
|
|
90
153
|
}
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/index.ts
|
|
156
|
+
/**
|
|
157
|
+
* Cache package entry point
|
|
158
|
+
* Provides standardized caching interface
|
|
159
|
+
*/
|
|
160
|
+
/**
|
|
161
|
+
* Type guard for Memory cache options
|
|
162
|
+
*/
|
|
91
163
|
function isMemoryOptions(options) {
|
|
92
|
-
|
|
164
|
+
return options.provider === "memory";
|
|
93
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Type guard for File cache options
|
|
168
|
+
*/
|
|
94
169
|
function isFileOptions(options) {
|
|
95
|
-
|
|
170
|
+
return options.provider === "file";
|
|
96
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Type guard for Redis cache options
|
|
174
|
+
*/
|
|
97
175
|
function isRedisOptions(options) {
|
|
98
|
-
|
|
176
|
+
return options.provider === "redis";
|
|
99
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Type guard for S3 cache options
|
|
180
|
+
*/
|
|
100
181
|
function isS3Options(options) {
|
|
101
|
-
|
|
182
|
+
return options.provider === "s3";
|
|
102
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Factory function to create a cache adapter instance
|
|
186
|
+
*
|
|
187
|
+
* Supports environment variable configuration using the HAVE_CACHE_* pattern:
|
|
188
|
+
* - HAVE_CACHE_PROVIDER → provider ('memory'|'file'|'redis'|'s3')
|
|
189
|
+
* - HAVE_CACHE_NAMESPACE → namespace (string)
|
|
190
|
+
* - HAVE_CACHE_DEFAULT_TTL → defaultTTL (number: seconds)
|
|
191
|
+
* - HAVE_CACHE_MAX_SIZE → maxSize (number: bytes)
|
|
192
|
+
* - HAVE_CACHE_MAX_ENTRIES → maxEntries (number, memory only)
|
|
193
|
+
* - HAVE_CACHE_EVICTION_POLICY → evictionPolicy ('lru'|'lfu'|'fifo', memory only)
|
|
194
|
+
* - HAVE_CACHE_CACHE_DIR → cacheDir (string, file only)
|
|
195
|
+
* - HAVE_CACHE_COMPRESSION → compression (boolean, file/s3)
|
|
196
|
+
* - HAVE_CACHE_HOST → host (string, redis only)
|
|
197
|
+
* - HAVE_CACHE_PORT → port (number, redis only)
|
|
198
|
+
* - HAVE_CACHE_BUCKET → bucket (string, s3 only)
|
|
199
|
+
* - HAVE_CACHE_PREFIX → prefix (string, s3 only)
|
|
200
|
+
* - HAVE_CACHE_REGION → region (string, s3 only)
|
|
201
|
+
*
|
|
202
|
+
* User-provided options always take precedence over environment variables.
|
|
203
|
+
*
|
|
204
|
+
* @param options - Configuration options for the cache provider
|
|
205
|
+
* @returns Promise resolving to a cache adapter that implements CacheAdapter
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```typescript
|
|
209
|
+
* // Create memory cache with explicit options
|
|
210
|
+
* const memoryCache = await getCache({
|
|
211
|
+
* provider: 'memory',
|
|
212
|
+
* maxSize: 100 * 1024 * 1024,
|
|
213
|
+
* evictionPolicy: 'lru'
|
|
214
|
+
* });
|
|
215
|
+
*
|
|
216
|
+
* // Create memory cache with environment variables
|
|
217
|
+
* // HAVE_CACHE_PROVIDER=memory
|
|
218
|
+
* // HAVE_CACHE_MAX_SIZE=104857600
|
|
219
|
+
* // HAVE_CACHE_EVICTION_POLICY=lru
|
|
220
|
+
* const envCache = await getCache({ provider: 'memory' });
|
|
221
|
+
*
|
|
222
|
+
* // Create file cache
|
|
223
|
+
* const fileCache = await getCache({
|
|
224
|
+
* provider: 'file',
|
|
225
|
+
* cacheDir: './cache',
|
|
226
|
+
* compression: true
|
|
227
|
+
* });
|
|
228
|
+
*
|
|
229
|
+
* // Create Redis cache
|
|
230
|
+
* const redisCache = await getCache({
|
|
231
|
+
* provider: 'redis',
|
|
232
|
+
* host: 'localhost',
|
|
233
|
+
* port: 6379
|
|
234
|
+
* });
|
|
235
|
+
*
|
|
236
|
+
* // Create S3 cache (for CI persistence)
|
|
237
|
+
* const s3Cache = await getCache({
|
|
238
|
+
* provider: 's3',
|
|
239
|
+
* bucket: 'my-cache-bucket',
|
|
240
|
+
* prefix: 'cache/',
|
|
241
|
+
* region: 'us-east-1'
|
|
242
|
+
* });
|
|
243
|
+
*
|
|
244
|
+
* // Use the cache
|
|
245
|
+
* await memoryCache.set('user:123', { name: 'John' });
|
|
246
|
+
* const user = await memoryCache.get('user:123');
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
103
249
|
async function getCache(options) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
throw new Error(`Unsupported provider: ${config.provider}`);
|
|
250
|
+
const config = loadEnvConfig(options, {
|
|
251
|
+
packageName: "cache",
|
|
252
|
+
schema: {
|
|
253
|
+
provider: "string",
|
|
254
|
+
namespace: "string",
|
|
255
|
+
defaultTTL: "number",
|
|
256
|
+
maxSize: "number",
|
|
257
|
+
maxEntries: "number",
|
|
258
|
+
evictionPolicy: "string",
|
|
259
|
+
checkPeriod: "number",
|
|
260
|
+
cacheDir: "string",
|
|
261
|
+
compression: "boolean",
|
|
262
|
+
fileExtension: "string",
|
|
263
|
+
host: "string",
|
|
264
|
+
port: "number",
|
|
265
|
+
password: "string",
|
|
266
|
+
db: "number",
|
|
267
|
+
keyPrefix: "string",
|
|
268
|
+
enableCompression: "boolean",
|
|
269
|
+
compressionThreshold: "number",
|
|
270
|
+
connectTimeout: "number",
|
|
271
|
+
commandTimeout: "number",
|
|
272
|
+
bucket: "string",
|
|
273
|
+
prefix: "string",
|
|
274
|
+
region: "string"
|
|
275
|
+
},
|
|
276
|
+
allowUnknown: false
|
|
277
|
+
});
|
|
278
|
+
if (isMemoryOptions(config)) {
|
|
279
|
+
const { MemoryProvider } = await import("./chunks/memory-7jBOw6kZ.js");
|
|
280
|
+
return new MemoryProvider(config);
|
|
281
|
+
}
|
|
282
|
+
if (isFileOptions(config)) {
|
|
283
|
+
const { FileProvider } = await import("./chunks/file-BiNbZgFQ.js");
|
|
284
|
+
return new FileProvider(config);
|
|
285
|
+
}
|
|
286
|
+
if (isRedisOptions(config)) {
|
|
287
|
+
const { RedisProvider } = await import("./chunks/redis-BMACvZJq.js");
|
|
288
|
+
return new RedisProvider(config);
|
|
289
|
+
}
|
|
290
|
+
if (isS3Options(config)) {
|
|
291
|
+
const { S3Provider } = await import("./chunks/s3-DG191QH0.js");
|
|
292
|
+
return new S3Provider(config);
|
|
293
|
+
}
|
|
294
|
+
throw new Error(`Unsupported provider: ${config.provider}`);
|
|
150
295
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
CacheSizeError,
|
|
158
|
-
PACKAGE_VERSION_INITIALIZED,
|
|
159
|
-
calculateExpiration,
|
|
160
|
-
calculateSize,
|
|
161
|
-
deserialize,
|
|
162
|
-
extractKey,
|
|
163
|
-
formatKey,
|
|
164
|
-
getCache,
|
|
165
|
-
isExpired,
|
|
166
|
-
isValidKey,
|
|
167
|
-
matchesPattern,
|
|
168
|
-
serialize
|
|
169
|
-
};
|
|
170
|
-
//# sourceMappingURL=index.js.map
|
|
296
|
+
/** @internal */
|
|
297
|
+
var PACKAGE_VERSION_INITIALIZED = true;
|
|
298
|
+
//#endregion
|
|
299
|
+
export { CacheConnectionError, CacheError, CacheKeyError, CacheSerializationError, CacheSizeError, PACKAGE_VERSION_INITIALIZED, calculateExpiration, calculateSize, deserialize, extractKey, formatKey, getCache, isExpired, isValidKey, matchesPattern, serialize };
|
|
300
|
+
|
|
301
|
+
//# sourceMappingURL=index.js.map
|