@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.
@@ -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, copyFileSync } from "node:fs";
2
+ import { copyFileSync, existsSync, mkdirSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
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
- }
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
- //# sourceMappingURL=claude-context.js.map
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"],"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;"}
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
- class CacheError extends Error {
3
- constructor(message, code, provider) {
4
- super(message);
5
- this.code = code;
6
- this.provider = provider;
7
- this.name = "CacheError";
8
- }
9
- }
10
- class CacheKeyError extends CacheError {
11
- constructor(key, provider) {
12
- super(`Invalid cache key: ${key}`, "INVALID_KEY", provider);
13
- this.key = key;
14
- this.name = "CacheKeyError";
15
- }
16
- }
17
- class CacheConnectionError extends CacheError {
18
- constructor(message, provider) {
19
- super(message, "CONNECTION_ERROR", provider);
20
- this.name = "CacheConnectionError";
21
- }
22
- }
23
- class CacheSizeError extends CacheError {
24
- constructor(message, provider) {
25
- super(message, "SIZE_EXCEEDED", provider);
26
- this.name = "CacheSizeError";
27
- }
28
- }
29
- class CacheSerializationError extends CacheError {
30
- constructor(message, provider) {
31
- super(message, "SERIALIZATION_ERROR", provider);
32
- this.name = "CacheSerializationError";
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
- return typeof key === "string" && key.length > 0 && key.length <= 250;
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
- try {
40
- const json = JSON.stringify(value);
41
- return new Blob([json]).size;
42
- } catch {
43
- return 0;
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
- const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
48
- const regex = new RegExp(`^${regexPattern}$`);
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
- return namespace ? `${namespace}:${key}` : key;
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
- if (!namespace) {
56
- return fullKey;
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
- if (expiresAt === void 0) {
63
- return false;
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
- if (ttl === void 0 || ttl <= 0) {
69
- return void 0;
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
- try {
75
- return JSON.stringify(value);
76
- } catch (error) {
77
- throw new Error(
78
- `Failed to serialize value: ${error instanceof Error ? error.message : "Unknown error"}`
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
- try {
84
- return JSON.parse(json);
85
- } catch (error) {
86
- throw new Error(
87
- `Failed to deserialize value: ${error instanceof Error ? error.message : "Unknown error"}`
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
- return options.provider === "memory";
164
+ return options.provider === "memory";
93
165
  }
166
+ /**
167
+ * Type guard for File cache options
168
+ */
94
169
  function isFileOptions(options) {
95
- return options.provider === "file";
170
+ return options.provider === "file";
96
171
  }
172
+ /**
173
+ * Type guard for Redis cache options
174
+ */
97
175
  function isRedisOptions(options) {
98
- return options.provider === "redis";
176
+ return options.provider === "redis";
99
177
  }
178
+ /**
179
+ * Type guard for S3 cache options
180
+ */
100
181
  function isS3Options(options) {
101
- return options.provider === "s3";
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
- const config = loadEnvConfig(options, {
105
- packageName: "cache",
106
- schema: {
107
- provider: "string",
108
- namespace: "string",
109
- defaultTTL: "number",
110
- maxSize: "number",
111
- maxEntries: "number",
112
- evictionPolicy: "string",
113
- checkPeriod: "number",
114
- cacheDir: "string",
115
- compression: "boolean",
116
- fileExtension: "string",
117
- host: "string",
118
- port: "number",
119
- password: "string",
120
- db: "number",
121
- keyPrefix: "string",
122
- enableCompression: "boolean",
123
- compressionThreshold: "number",
124
- connectTimeout: "number",
125
- commandTimeout: "number",
126
- // S3 options
127
- bucket: "string",
128
- prefix: "string",
129
- region: "string"
130
- },
131
- allowUnknown: false
132
- });
133
- if (isMemoryOptions(config)) {
134
- const { MemoryProvider } = await import("./chunks/memory-C6vfNZYg.js");
135
- return new MemoryProvider(config);
136
- }
137
- if (isFileOptions(config)) {
138
- const { FileProvider } = await import("./chunks/file-DyC_7WDS.js");
139
- return new FileProvider(config);
140
- }
141
- if (isRedisOptions(config)) {
142
- const { RedisProvider } = await import("./chunks/redis-D-SNLXE_.js");
143
- return new RedisProvider(config);
144
- }
145
- if (isS3Options(config)) {
146
- const { S3Provider } = await import("./chunks/s3-ByokNFv_.js");
147
- return new S3Provider(config);
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
- const PACKAGE_VERSION_INITIALIZED = true;
152
- export {
153
- CacheConnectionError,
154
- CacheError,
155
- CacheKeyError,
156
- CacheSerializationError,
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