@parsrun/cache 0.1.0

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,242 @@
1
+ // src/types.ts
2
+ import {
3
+ type,
4
+ cacheSetOptions,
5
+ cacheGetResult,
6
+ cacheStats,
7
+ memoryCacheConfig,
8
+ redisCacheConfig,
9
+ upstashCacheConfig,
10
+ cloudflareKvConfig,
11
+ multiTierCacheConfig,
12
+ cacheConfig
13
+ } from "@parsrun/types";
14
+ var CacheError = class extends Error {
15
+ constructor(message, code, cause) {
16
+ super(message);
17
+ this.code = code;
18
+ this.cause = cause;
19
+ this.name = "CacheError";
20
+ }
21
+ };
22
+ var CacheErrorCodes = {
23
+ CONNECTION_FAILED: "CONNECTION_FAILED",
24
+ OPERATION_FAILED: "OPERATION_FAILED",
25
+ SERIALIZATION_ERROR: "SERIALIZATION_ERROR",
26
+ INVALID_CONFIG: "INVALID_CONFIG",
27
+ NOT_IMPLEMENTED: "NOT_IMPLEMENTED"
28
+ };
29
+
30
+ // src/adapters/redis.ts
31
+ var RedisCacheAdapter = class {
32
+ type = "redis";
33
+ client;
34
+ keyPrefix;
35
+ isExternalClient;
36
+ constructor(config) {
37
+ this.client = config.client;
38
+ this.keyPrefix = config.keyPrefix ?? "";
39
+ this.isExternalClient = true;
40
+ }
41
+ prefixKey(key) {
42
+ return this.keyPrefix ? `${this.keyPrefix}:${key}` : key;
43
+ }
44
+ async get(key, _options) {
45
+ try {
46
+ const data = await this.client.get(this.prefixKey(key));
47
+ if (!data) {
48
+ return null;
49
+ }
50
+ const parsed = JSON.parse(data);
51
+ return parsed.value;
52
+ } catch (err) {
53
+ throw new CacheError(
54
+ `Redis get failed: ${err instanceof Error ? err.message : "Unknown error"}`,
55
+ CacheErrorCodes.OPERATION_FAILED,
56
+ err
57
+ );
58
+ }
59
+ }
60
+ async set(key, value, options) {
61
+ try {
62
+ const data = JSON.stringify({
63
+ value,
64
+ tags: options?.tags,
65
+ metadata: options?.metadata
66
+ });
67
+ const prefixedKey = this.prefixKey(key);
68
+ if (options?.ttl) {
69
+ await this.client.set(prefixedKey, data, "EX", options.ttl);
70
+ } else {
71
+ await this.client.set(prefixedKey, data);
72
+ }
73
+ if (options?.tags && options.tags.length > 0) {
74
+ const pipeline = this.client.pipeline();
75
+ for (const tag of options.tags) {
76
+ const tagKey = this.prefixKey(`__tag:${tag}`);
77
+ pipeline.set(tagKey, JSON.stringify([...await this.getTagKeys(tag), key]));
78
+ }
79
+ await pipeline.exec();
80
+ }
81
+ } catch (err) {
82
+ throw new CacheError(
83
+ `Redis set failed: ${err instanceof Error ? err.message : "Unknown error"}`,
84
+ CacheErrorCodes.OPERATION_FAILED,
85
+ err
86
+ );
87
+ }
88
+ }
89
+ async delete(key) {
90
+ try {
91
+ await this.client.del(this.prefixKey(key));
92
+ } catch (err) {
93
+ throw new CacheError(
94
+ `Redis delete failed: ${err instanceof Error ? err.message : "Unknown error"}`,
95
+ CacheErrorCodes.OPERATION_FAILED,
96
+ err
97
+ );
98
+ }
99
+ }
100
+ async has(key) {
101
+ try {
102
+ const exists = await this.client.exists(this.prefixKey(key));
103
+ return exists > 0;
104
+ } catch (err) {
105
+ throw new CacheError(
106
+ `Redis exists failed: ${err instanceof Error ? err.message : "Unknown error"}`,
107
+ CacheErrorCodes.OPERATION_FAILED,
108
+ err
109
+ );
110
+ }
111
+ }
112
+ async clear() {
113
+ try {
114
+ if (this.keyPrefix) {
115
+ const keys = await this.client.keys(`${this.keyPrefix}:*`);
116
+ if (keys.length > 0) {
117
+ await this.client.del(...keys);
118
+ }
119
+ } else {
120
+ await this.client.flushdb();
121
+ }
122
+ } catch (err) {
123
+ throw new CacheError(
124
+ `Redis clear failed: ${err instanceof Error ? err.message : "Unknown error"}`,
125
+ CacheErrorCodes.OPERATION_FAILED,
126
+ err
127
+ );
128
+ }
129
+ }
130
+ async getMany(keys) {
131
+ try {
132
+ const prefixedKeys = keys.map((k) => this.prefixKey(k));
133
+ const results = await this.client.mget(...prefixedKeys);
134
+ const map = /* @__PURE__ */ new Map();
135
+ for (let i = 0; i < keys.length; i++) {
136
+ const data = results[i];
137
+ if (data) {
138
+ const parsed = JSON.parse(data);
139
+ map.set(keys[i] ?? "", parsed.value);
140
+ } else {
141
+ map.set(keys[i] ?? "", null);
142
+ }
143
+ }
144
+ return map;
145
+ } catch (err) {
146
+ throw new CacheError(
147
+ `Redis mget failed: ${err instanceof Error ? err.message : "Unknown error"}`,
148
+ CacheErrorCodes.OPERATION_FAILED,
149
+ err
150
+ );
151
+ }
152
+ }
153
+ async setMany(entries, options) {
154
+ try {
155
+ const pipeline = this.client.pipeline();
156
+ for (const [key, value] of entries) {
157
+ const data = JSON.stringify({
158
+ value,
159
+ tags: options?.tags,
160
+ metadata: options?.metadata
161
+ });
162
+ const prefixedKey = this.prefixKey(key);
163
+ if (options?.ttl) {
164
+ pipeline.set(prefixedKey, data, "EX", options.ttl);
165
+ } else {
166
+ pipeline.set(prefixedKey, data);
167
+ }
168
+ }
169
+ await pipeline.exec();
170
+ } catch (err) {
171
+ throw new CacheError(
172
+ `Redis mset failed: ${err instanceof Error ? err.message : "Unknown error"}`,
173
+ CacheErrorCodes.OPERATION_FAILED,
174
+ err
175
+ );
176
+ }
177
+ }
178
+ async deleteMany(keys) {
179
+ try {
180
+ const prefixedKeys = keys.map((k) => this.prefixKey(k));
181
+ await this.client.del(...prefixedKeys);
182
+ } catch (err) {
183
+ throw new CacheError(
184
+ `Redis mdel failed: ${err instanceof Error ? err.message : "Unknown error"}`,
185
+ CacheErrorCodes.OPERATION_FAILED,
186
+ err
187
+ );
188
+ }
189
+ }
190
+ async invalidateByTags(tags) {
191
+ try {
192
+ const keysToDelete = [];
193
+ for (const tag of tags) {
194
+ const keys = await this.getTagKeys(tag);
195
+ keysToDelete.push(...keys);
196
+ }
197
+ if (keysToDelete.length > 0) {
198
+ await this.deleteMany(keysToDelete);
199
+ }
200
+ const tagKeys = tags.map((tag) => this.prefixKey(`__tag:${tag}`));
201
+ await this.client.del(...tagKeys);
202
+ } catch (err) {
203
+ throw new CacheError(
204
+ `Redis invalidate by tags failed: ${err instanceof Error ? err.message : "Unknown error"}`,
205
+ CacheErrorCodes.OPERATION_FAILED,
206
+ err
207
+ );
208
+ }
209
+ }
210
+ async ttl(key) {
211
+ try {
212
+ return await this.client.ttl(this.prefixKey(key));
213
+ } catch (err) {
214
+ throw new CacheError(
215
+ `Redis ttl failed: ${err instanceof Error ? err.message : "Unknown error"}`,
216
+ CacheErrorCodes.OPERATION_FAILED,
217
+ err
218
+ );
219
+ }
220
+ }
221
+ async close() {
222
+ if (!this.isExternalClient) {
223
+ await this.client.quit();
224
+ }
225
+ }
226
+ async getTagKeys(tag) {
227
+ const tagKey = this.prefixKey(`__tag:${tag}`);
228
+ const data = await this.client.get(tagKey);
229
+ if (data) {
230
+ return JSON.parse(data);
231
+ }
232
+ return [];
233
+ }
234
+ };
235
+ function createRedisCacheAdapter(config) {
236
+ return new RedisCacheAdapter(config);
237
+ }
238
+ export {
239
+ RedisCacheAdapter,
240
+ createRedisCacheAdapter
241
+ };
242
+ //# sourceMappingURL=redis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/types.ts","../../src/adapters/redis.ts"],"sourcesContent":["/**\n * @parsrun/cache - Type Definitions\n * Cache types and interfaces\n */\n\n// Re-export types from @parsrun/types for convenience\nexport {\n type,\n cacheSetOptions as parsCacheSetOptions,\n cacheGetResult,\n cacheStats,\n memoryCacheConfig,\n redisCacheConfig,\n upstashCacheConfig,\n cloudflareKvConfig,\n multiTierCacheConfig,\n cacheConfig,\n type CacheSetOptions as ParsCacheSetOptions,\n type CacheGetResult,\n type CacheStats,\n type MemoryCacheConfig as ParsMemoryCacheConfig,\n type RedisCacheConfig as ParsRedisCacheConfig,\n type UpstashCacheConfig as ParsUpstashCacheConfig,\n type CloudflareKvConfig as ParsCloudflareKvConfig,\n type MultiTierCacheConfig,\n type CacheConfig,\n} from \"@parsrun/types\";\n\n/**\n * Cache adapter type\n */\nexport type CacheAdapterType = \"memory\" | \"redis\" | \"upstash\" | \"cloudflare-kv\";\n\n/**\n * Cache entry with metadata\n */\nexport interface CacheEntry<T = unknown> {\n /** Cached value */\n value: T;\n /** Expiration timestamp (ms) */\n expiresAt?: number | undefined;\n /** Original TTL in seconds (for refresh/sliding expiration) */\n ttlSeconds?: number | undefined;\n /** Cache entry tags for invalidation */\n tags?: string[] | undefined;\n /** Cache entry metadata */\n metadata?: Record<string, unknown> | undefined;\n}\n\n/**\n * Cache get options\n */\nexport interface CacheGetOptions {\n /** Whether to update TTL on access (sliding expiration) */\n refresh?: boolean | undefined;\n}\n\n/**\n * Cache set options\n */\nexport interface CacheSetOptions {\n /** Time to live in seconds */\n ttl?: number | undefined;\n /** Tags for cache invalidation */\n tags?: string[] | undefined;\n /** Additional metadata */\n metadata?: Record<string, unknown> | undefined;\n}\n\n/**\n * Cache delete options\n */\nexport interface CacheDeleteOptions {\n /** Delete all entries with matching tags */\n tags?: string[] | undefined;\n}\n\n/**\n * Cache adapter interface\n */\nexport interface CacheAdapter {\n /** Adapter type */\n readonly type: CacheAdapterType;\n\n /**\n * Get a value from cache\n * @param key Cache key\n * @param options Get options\n * @returns Cached value or null if not found/expired\n */\n get<T = unknown>(key: string, options?: CacheGetOptions): Promise<T | null>;\n\n /**\n * Set a value in cache\n * @param key Cache key\n * @param value Value to cache\n * @param options Set options\n */\n set<T = unknown>(key: string, value: T, options?: CacheSetOptions): Promise<void>;\n\n /**\n * Delete a value from cache\n * @param key Cache key\n */\n delete(key: string): Promise<void>;\n\n /**\n * Check if a key exists in cache\n * @param key Cache key\n */\n has(key: string): Promise<boolean>;\n\n /**\n * Clear all entries from cache\n */\n clear?(): Promise<void>;\n\n /**\n * Get multiple values at once\n * @param keys Cache keys\n */\n getMany?<T = unknown>(keys: string[]): Promise<Map<string, T | null>>;\n\n /**\n * Set multiple values at once\n * @param entries Key-value pairs\n * @param options Set options (applied to all)\n */\n setMany?<T = unknown>(entries: Map<string, T>, options?: CacheSetOptions): Promise<void>;\n\n /**\n * Delete multiple keys\n * @param keys Cache keys\n */\n deleteMany?(keys: string[]): Promise<void>;\n\n /**\n * Invalidate entries by tags\n * @param tags Tags to invalidate\n */\n invalidateByTags?(tags: string[]): Promise<void>;\n\n /**\n * Get TTL for a key (in seconds)\n * @param key Cache key\n * @returns TTL in seconds, -1 if no expiry, -2 if key doesn't exist\n */\n ttl?(key: string): Promise<number>;\n\n /**\n * Close/cleanup adapter resources\n */\n close?(): Promise<void>;\n}\n\n/**\n * Cache service configuration\n */\nexport interface CacheServiceConfig {\n /** Cache adapter to use */\n adapter: CacheAdapter;\n /** Default TTL in seconds */\n defaultTtl?: number | undefined;\n /** Key prefix for namespacing */\n keyPrefix?: string | undefined;\n /** Enable debug logging */\n debug?: boolean | undefined;\n}\n\n/**\n * Memory adapter configuration\n */\nexport interface MemoryCacheConfig {\n /** Maximum number of entries */\n maxEntries?: number | undefined;\n /** Cleanup interval in ms (default: 60000) */\n cleanupInterval?: number | undefined;\n}\n\n/**\n * Redis adapter configuration\n */\nexport interface RedisCacheConfig {\n /** Redis connection URL */\n url?: string | undefined;\n /** Redis host */\n host?: string | undefined;\n /** Redis port */\n port?: number | undefined;\n /** Redis password */\n password?: string | undefined;\n /** Redis database number */\n db?: number | undefined;\n /** Key prefix */\n keyPrefix?: string | undefined;\n /** Use TLS */\n tls?: boolean | undefined;\n}\n\n/**\n * Upstash adapter configuration\n */\nexport interface UpstashCacheConfig {\n /** Upstash Redis URL */\n url: string;\n /** Upstash Redis token */\n token: string;\n /** Key prefix */\n keyPrefix?: string | undefined;\n}\n\n/**\n * Cloudflare KV adapter configuration\n */\nexport interface CloudflareKVCacheConfig {\n /** KV namespace binding */\n namespace: KVNamespace;\n /** Key prefix */\n keyPrefix?: string | undefined;\n}\n\n/**\n * KVNamespace interface (Cloudflare Workers)\n */\nexport interface KVNamespace {\n get(key: string, options?: { type?: \"text\" | \"json\" | \"arrayBuffer\" | \"stream\" }): Promise<string | null>;\n get(key: string, options: { type: \"json\" }): Promise<unknown>;\n put(key: string, value: string | ArrayBuffer | ReadableStream, options?: { expiration?: number; expirationTtl?: number; metadata?: unknown }): Promise<void>;\n delete(key: string): Promise<void>;\n list(options?: { prefix?: string | undefined; limit?: number | undefined; cursor?: string | undefined }): Promise<{ keys: Array<{ name: string; expiration?: number; metadata?: unknown }>; list_complete: boolean; cursor?: string }>;\n getWithMetadata<T = unknown>(key: string, options?: { type?: \"text\" | \"json\" | \"arrayBuffer\" | \"stream\" }): Promise<{ value: string | null; metadata: T | null }>;\n}\n\n/**\n * Cache error\n */\nexport class CacheError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly cause?: unknown\n ) {\n super(message);\n this.name = \"CacheError\";\n }\n}\n\n/**\n * Common cache error codes\n */\nexport const CacheErrorCodes = {\n CONNECTION_FAILED: \"CONNECTION_FAILED\",\n OPERATION_FAILED: \"OPERATION_FAILED\",\n SERIALIZATION_ERROR: \"SERIALIZATION_ERROR\",\n INVALID_CONFIG: \"INVALID_CONFIG\",\n NOT_IMPLEMENTED: \"NOT_IMPLEMENTED\",\n} as const;\n","/**\n * @parsrun/cache - Redis Adapter\n * Redis cache adapter using ioredis (Node.js)\n */\n\nimport type {\n CacheAdapter,\n CacheGetOptions,\n CacheSetOptions,\n RedisCacheConfig,\n} from \"../types.js\";\nimport { CacheError, CacheErrorCodes } from \"../types.js\";\n\n/**\n * Redis client interface (ioredis compatible)\n */\ninterface RedisClient {\n get(key: string): Promise<string | null>;\n set(key: string, value: string, ...args: (string | number)[]): Promise<\"OK\" | null>;\n del(...keys: string[]): Promise<number>;\n exists(...keys: string[]): Promise<number>;\n ttl(key: string): Promise<number>;\n mget(...keys: string[]): Promise<(string | null)[]>;\n keys(pattern: string): Promise<string[]>;\n flushdb(): Promise<\"OK\">;\n quit(): Promise<\"OK\">;\n pipeline(): RedisPipeline;\n}\n\ninterface RedisPipeline {\n set(key: string, value: string, ...args: (string | number)[]): this;\n exec(): Promise<Array<[Error | null, unknown]>>;\n}\n\n/**\n * Redis Cache Adapter\n * Uses ioredis for Node.js environments\n *\n * @example\n * ```typescript\n * import Redis from 'ioredis';\n *\n * const redis = new Redis(process.env.REDIS_URL);\n * const cache = new RedisCacheAdapter({ client: redis });\n *\n * await cache.set('user:123', { name: 'John' }, { ttl: 3600 });\n * const user = await cache.get('user:123');\n * ```\n */\nexport class RedisCacheAdapter implements CacheAdapter {\n readonly type = \"redis\" as const;\n\n private client: RedisClient;\n private keyPrefix: string;\n private isExternalClient: boolean;\n\n constructor(config: RedisCacheConfig & { client: RedisClient }) {\n this.client = config.client;\n this.keyPrefix = config.keyPrefix ?? \"\";\n this.isExternalClient = true;\n }\n\n private prefixKey(key: string): string {\n return this.keyPrefix ? `${this.keyPrefix}:${key}` : key;\n }\n\n async get<T = unknown>(key: string, _options?: CacheGetOptions): Promise<T | null> {\n try {\n const data = await this.client.get(this.prefixKey(key));\n\n if (!data) {\n return null;\n }\n\n const parsed = JSON.parse(data) as { value: T; tags?: string[] };\n return parsed.value;\n } catch (err) {\n throw new CacheError(\n `Redis get failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n CacheErrorCodes.OPERATION_FAILED,\n err\n );\n }\n }\n\n async set<T = unknown>(key: string, value: T, options?: CacheSetOptions): Promise<void> {\n try {\n const data = JSON.stringify({\n value,\n tags: options?.tags,\n metadata: options?.metadata,\n });\n\n const prefixedKey = this.prefixKey(key);\n\n if (options?.ttl) {\n await this.client.set(prefixedKey, data, \"EX\", options.ttl);\n } else {\n await this.client.set(prefixedKey, data);\n }\n\n // Store tag-to-key mappings\n if (options?.tags && options.tags.length > 0) {\n const pipeline = this.client.pipeline();\n for (const tag of options.tags) {\n const tagKey = this.prefixKey(`__tag:${tag}`);\n pipeline.set(tagKey, JSON.stringify([...(await this.getTagKeys(tag)), key]));\n }\n await pipeline.exec();\n }\n } catch (err) {\n throw new CacheError(\n `Redis set failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n CacheErrorCodes.OPERATION_FAILED,\n err\n );\n }\n }\n\n async delete(key: string): Promise<void> {\n try {\n await this.client.del(this.prefixKey(key));\n } catch (err) {\n throw new CacheError(\n `Redis delete failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n CacheErrorCodes.OPERATION_FAILED,\n err\n );\n }\n }\n\n async has(key: string): Promise<boolean> {\n try {\n const exists = await this.client.exists(this.prefixKey(key));\n return exists > 0;\n } catch (err) {\n throw new CacheError(\n `Redis exists failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n CacheErrorCodes.OPERATION_FAILED,\n err\n );\n }\n }\n\n async clear(): Promise<void> {\n try {\n if (this.keyPrefix) {\n // Only delete keys with our prefix\n const keys = await this.client.keys(`${this.keyPrefix}:*`);\n if (keys.length > 0) {\n await this.client.del(...keys);\n }\n } else {\n await this.client.flushdb();\n }\n } catch (err) {\n throw new CacheError(\n `Redis clear failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n CacheErrorCodes.OPERATION_FAILED,\n err\n );\n }\n }\n\n async getMany<T = unknown>(keys: string[]): Promise<Map<string, T | null>> {\n try {\n const prefixedKeys = keys.map((k) => this.prefixKey(k));\n const results = await this.client.mget(...prefixedKeys);\n\n const map = new Map<string, T | null>();\n for (let i = 0; i < keys.length; i++) {\n const data = results[i];\n if (data) {\n const parsed = JSON.parse(data) as { value: T };\n map.set(keys[i] ?? \"\", parsed.value);\n } else {\n map.set(keys[i] ?? \"\", null);\n }\n }\n\n return map;\n } catch (err) {\n throw new CacheError(\n `Redis mget failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n CacheErrorCodes.OPERATION_FAILED,\n err\n );\n }\n }\n\n async setMany<T = unknown>(entries: Map<string, T>, options?: CacheSetOptions): Promise<void> {\n try {\n const pipeline = this.client.pipeline();\n\n for (const [key, value] of entries) {\n const data = JSON.stringify({\n value,\n tags: options?.tags,\n metadata: options?.metadata,\n });\n\n const prefixedKey = this.prefixKey(key);\n\n if (options?.ttl) {\n pipeline.set(prefixedKey, data, \"EX\", options.ttl);\n } else {\n pipeline.set(prefixedKey, data);\n }\n }\n\n await pipeline.exec();\n } catch (err) {\n throw new CacheError(\n `Redis mset failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n CacheErrorCodes.OPERATION_FAILED,\n err\n );\n }\n }\n\n async deleteMany(keys: string[]): Promise<void> {\n try {\n const prefixedKeys = keys.map((k) => this.prefixKey(k));\n await this.client.del(...prefixedKeys);\n } catch (err) {\n throw new CacheError(\n `Redis mdel failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n CacheErrorCodes.OPERATION_FAILED,\n err\n );\n }\n }\n\n async invalidateByTags(tags: string[]): Promise<void> {\n try {\n const keysToDelete: string[] = [];\n\n for (const tag of tags) {\n const keys = await this.getTagKeys(tag);\n keysToDelete.push(...keys);\n }\n\n if (keysToDelete.length > 0) {\n await this.deleteMany(keysToDelete);\n }\n\n // Clean up tag mappings\n const tagKeys = tags.map((tag) => this.prefixKey(`__tag:${tag}`));\n await this.client.del(...tagKeys);\n } catch (err) {\n throw new CacheError(\n `Redis invalidate by tags failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n CacheErrorCodes.OPERATION_FAILED,\n err\n );\n }\n }\n\n async ttl(key: string): Promise<number> {\n try {\n return await this.client.ttl(this.prefixKey(key));\n } catch (err) {\n throw new CacheError(\n `Redis ttl failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n CacheErrorCodes.OPERATION_FAILED,\n err\n );\n }\n }\n\n async close(): Promise<void> {\n if (!this.isExternalClient) {\n await this.client.quit();\n }\n }\n\n private async getTagKeys(tag: string): Promise<string[]> {\n const tagKey = this.prefixKey(`__tag:${tag}`);\n const data = await this.client.get(tagKey);\n if (data) {\n return JSON.parse(data) as string[];\n }\n return [];\n }\n}\n\n/**\n * Create a Redis cache adapter\n */\nexport function createRedisCacheAdapter(\n config: RedisCacheConfig & { client: RedisClient }\n): RedisCacheAdapter {\n return new RedisCacheAdapter(config);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,50 @@
1
+ import { CacheAdapter, UpstashCacheConfig, CacheGetOptions, CacheSetOptions } from '../types.js';
2
+ import '@parsrun/types';
3
+
4
+ /**
5
+ * @parsrun/cache - Upstash Adapter
6
+ * Edge-compatible Upstash Redis adapter using fetch API
7
+ */
8
+
9
+ /**
10
+ * Upstash Cache Adapter
11
+ * Edge-compatible using HTTP API
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const cache = new UpstashCacheAdapter({
16
+ * url: process.env.UPSTASH_REDIS_REST_URL,
17
+ * token: process.env.UPSTASH_REDIS_REST_TOKEN,
18
+ * });
19
+ *
20
+ * await cache.set('user:123', { name: 'John' }, { ttl: 3600 });
21
+ * const user = await cache.get('user:123');
22
+ * ```
23
+ */
24
+ declare class UpstashCacheAdapter implements CacheAdapter {
25
+ readonly type: "upstash";
26
+ private url;
27
+ private token;
28
+ private keyPrefix;
29
+ constructor(config: UpstashCacheConfig);
30
+ private prefixKey;
31
+ private command;
32
+ private pipeline;
33
+ get<T = unknown>(key: string, _options?: CacheGetOptions): Promise<T | null>;
34
+ set<T = unknown>(key: string, value: T, options?: CacheSetOptions): Promise<void>;
35
+ delete(key: string): Promise<void>;
36
+ has(key: string): Promise<boolean>;
37
+ clear(): Promise<void>;
38
+ getMany<T = unknown>(keys: string[]): Promise<Map<string, T | null>>;
39
+ setMany<T = unknown>(entries: Map<string, T>, options?: CacheSetOptions): Promise<void>;
40
+ deleteMany(keys: string[]): Promise<void>;
41
+ invalidateByTags(tags: string[]): Promise<void>;
42
+ ttl(key: string): Promise<number>;
43
+ close(): Promise<void>;
44
+ }
45
+ /**
46
+ * Create an Upstash cache adapter
47
+ */
48
+ declare function createUpstashCacheAdapter(config: UpstashCacheConfig): UpstashCacheAdapter;
49
+
50
+ export { UpstashCacheAdapter, createUpstashCacheAdapter };
@@ -0,0 +1,229 @@
1
+ // src/types.ts
2
+ import {
3
+ type,
4
+ cacheSetOptions,
5
+ cacheGetResult,
6
+ cacheStats,
7
+ memoryCacheConfig,
8
+ redisCacheConfig,
9
+ upstashCacheConfig,
10
+ cloudflareKvConfig,
11
+ multiTierCacheConfig,
12
+ cacheConfig
13
+ } from "@parsrun/types";
14
+ var CacheError = class extends Error {
15
+ constructor(message, code, cause) {
16
+ super(message);
17
+ this.code = code;
18
+ this.cause = cause;
19
+ this.name = "CacheError";
20
+ }
21
+ };
22
+ var CacheErrorCodes = {
23
+ CONNECTION_FAILED: "CONNECTION_FAILED",
24
+ OPERATION_FAILED: "OPERATION_FAILED",
25
+ SERIALIZATION_ERROR: "SERIALIZATION_ERROR",
26
+ INVALID_CONFIG: "INVALID_CONFIG",
27
+ NOT_IMPLEMENTED: "NOT_IMPLEMENTED"
28
+ };
29
+
30
+ // src/adapters/upstash.ts
31
+ var UpstashCacheAdapter = class {
32
+ type = "upstash";
33
+ url;
34
+ token;
35
+ keyPrefix;
36
+ constructor(config) {
37
+ this.url = config.url.replace(/\/$/, "");
38
+ this.token = config.token;
39
+ this.keyPrefix = config.keyPrefix ?? "";
40
+ }
41
+ prefixKey(key) {
42
+ return this.keyPrefix ? `${this.keyPrefix}:${key}` : key;
43
+ }
44
+ async command(...args) {
45
+ try {
46
+ const response = await fetch(this.url, {
47
+ method: "POST",
48
+ headers: {
49
+ Authorization: `Bearer ${this.token}`,
50
+ "Content-Type": "application/json"
51
+ },
52
+ body: JSON.stringify(args)
53
+ });
54
+ if (!response.ok) {
55
+ const error = await response.text();
56
+ throw new Error(`Upstash API error: ${error}`);
57
+ }
58
+ const data = await response.json();
59
+ if (data.error) {
60
+ throw new Error(data.error);
61
+ }
62
+ return data.result;
63
+ } catch (err) {
64
+ throw new CacheError(
65
+ `Upstash command failed: ${err instanceof Error ? err.message : "Unknown error"}`,
66
+ CacheErrorCodes.OPERATION_FAILED,
67
+ err
68
+ );
69
+ }
70
+ }
71
+ async pipeline(commands) {
72
+ try {
73
+ const response = await fetch(`${this.url}/pipeline`, {
74
+ method: "POST",
75
+ headers: {
76
+ Authorization: `Bearer ${this.token}`,
77
+ "Content-Type": "application/json"
78
+ },
79
+ body: JSON.stringify(commands)
80
+ });
81
+ if (!response.ok) {
82
+ const error = await response.text();
83
+ throw new Error(`Upstash API error: ${error}`);
84
+ }
85
+ const data = await response.json();
86
+ return data.map((item) => {
87
+ if (item.error) {
88
+ throw new Error(item.error);
89
+ }
90
+ return item.result;
91
+ });
92
+ } catch (err) {
93
+ throw new CacheError(
94
+ `Upstash pipeline failed: ${err instanceof Error ? err.message : "Unknown error"}`,
95
+ CacheErrorCodes.OPERATION_FAILED,
96
+ err
97
+ );
98
+ }
99
+ }
100
+ async get(key, _options) {
101
+ const data = await this.command("GET", this.prefixKey(key));
102
+ if (!data) {
103
+ return null;
104
+ }
105
+ try {
106
+ const parsed = JSON.parse(data);
107
+ return parsed.value;
108
+ } catch {
109
+ return data;
110
+ }
111
+ }
112
+ async set(key, value, options) {
113
+ const data = JSON.stringify({
114
+ value,
115
+ tags: options?.tags,
116
+ metadata: options?.metadata
117
+ });
118
+ const prefixedKey = this.prefixKey(key);
119
+ if (options?.ttl) {
120
+ await this.command("SET", prefixedKey, data, "EX", options.ttl);
121
+ } else {
122
+ await this.command("SET", prefixedKey, data);
123
+ }
124
+ if (options?.tags && options.tags.length > 0) {
125
+ const commands = [];
126
+ for (const tag of options.tags) {
127
+ commands.push(["SADD", this.prefixKey(`__tag:${tag}`), key]);
128
+ }
129
+ await this.pipeline(commands);
130
+ }
131
+ }
132
+ async delete(key) {
133
+ await this.command("DEL", this.prefixKey(key));
134
+ }
135
+ async has(key) {
136
+ const exists = await this.command("EXISTS", this.prefixKey(key));
137
+ return exists > 0;
138
+ }
139
+ async clear() {
140
+ if (this.keyPrefix) {
141
+ let cursor = "0";
142
+ do {
143
+ const result = await this.command(
144
+ "SCAN",
145
+ cursor,
146
+ "MATCH",
147
+ `${this.keyPrefix}:*`,
148
+ "COUNT",
149
+ 100
150
+ );
151
+ cursor = result[0];
152
+ const keys = result[1];
153
+ if (keys.length > 0) {
154
+ await this.command("DEL", ...keys);
155
+ }
156
+ } while (cursor !== "0");
157
+ } else {
158
+ await this.command("FLUSHDB");
159
+ }
160
+ }
161
+ async getMany(keys) {
162
+ const prefixedKeys = keys.map((k) => this.prefixKey(k));
163
+ const results = await this.command("MGET", ...prefixedKeys);
164
+ const map = /* @__PURE__ */ new Map();
165
+ for (let i = 0; i < keys.length; i++) {
166
+ const data = results[i];
167
+ const key = keys[i];
168
+ if (key !== void 0) {
169
+ if (data) {
170
+ try {
171
+ const parsed = JSON.parse(data);
172
+ map.set(key, parsed.value);
173
+ } catch {
174
+ map.set(key, data);
175
+ }
176
+ } else {
177
+ map.set(key, null);
178
+ }
179
+ }
180
+ }
181
+ return map;
182
+ }
183
+ async setMany(entries, options) {
184
+ const commands = [];
185
+ for (const [key, value] of entries) {
186
+ const data = JSON.stringify({
187
+ value,
188
+ tags: options?.tags,
189
+ metadata: options?.metadata
190
+ });
191
+ const prefixedKey = this.prefixKey(key);
192
+ if (options?.ttl) {
193
+ commands.push(["SET", prefixedKey, data, "EX", options.ttl]);
194
+ } else {
195
+ commands.push(["SET", prefixedKey, data]);
196
+ }
197
+ }
198
+ await this.pipeline(commands);
199
+ }
200
+ async deleteMany(keys) {
201
+ const prefixedKeys = keys.map((k) => this.prefixKey(k));
202
+ await this.command("DEL", ...prefixedKeys);
203
+ }
204
+ async invalidateByTags(tags) {
205
+ const keysToDelete = [];
206
+ for (const tag of tags) {
207
+ const keys = await this.command("SMEMBERS", this.prefixKey(`__tag:${tag}`));
208
+ keysToDelete.push(...keys);
209
+ }
210
+ if (keysToDelete.length > 0) {
211
+ await this.deleteMany(keysToDelete);
212
+ }
213
+ const tagKeys = tags.map((tag) => this.prefixKey(`__tag:${tag}`));
214
+ await this.command("DEL", ...tagKeys);
215
+ }
216
+ async ttl(key) {
217
+ return await this.command("TTL", this.prefixKey(key));
218
+ }
219
+ async close() {
220
+ }
221
+ };
222
+ function createUpstashCacheAdapter(config) {
223
+ return new UpstashCacheAdapter(config);
224
+ }
225
+ export {
226
+ UpstashCacheAdapter,
227
+ createUpstashCacheAdapter
228
+ };
229
+ //# sourceMappingURL=upstash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/types.ts","../../src/adapters/upstash.ts"],"sourcesContent":["/**\n * @parsrun/cache - Type Definitions\n * Cache types and interfaces\n */\n\n// Re-export types from @parsrun/types for convenience\nexport {\n type,\n cacheSetOptions as parsCacheSetOptions,\n cacheGetResult,\n cacheStats,\n memoryCacheConfig,\n redisCacheConfig,\n upstashCacheConfig,\n cloudflareKvConfig,\n multiTierCacheConfig,\n cacheConfig,\n type CacheSetOptions as ParsCacheSetOptions,\n type CacheGetResult,\n type CacheStats,\n type MemoryCacheConfig as ParsMemoryCacheConfig,\n type RedisCacheConfig as ParsRedisCacheConfig,\n type UpstashCacheConfig as ParsUpstashCacheConfig,\n type CloudflareKvConfig as ParsCloudflareKvConfig,\n type MultiTierCacheConfig,\n type CacheConfig,\n} from \"@parsrun/types\";\n\n/**\n * Cache adapter type\n */\nexport type CacheAdapterType = \"memory\" | \"redis\" | \"upstash\" | \"cloudflare-kv\";\n\n/**\n * Cache entry with metadata\n */\nexport interface CacheEntry<T = unknown> {\n /** Cached value */\n value: T;\n /** Expiration timestamp (ms) */\n expiresAt?: number | undefined;\n /** Original TTL in seconds (for refresh/sliding expiration) */\n ttlSeconds?: number | undefined;\n /** Cache entry tags for invalidation */\n tags?: string[] | undefined;\n /** Cache entry metadata */\n metadata?: Record<string, unknown> | undefined;\n}\n\n/**\n * Cache get options\n */\nexport interface CacheGetOptions {\n /** Whether to update TTL on access (sliding expiration) */\n refresh?: boolean | undefined;\n}\n\n/**\n * Cache set options\n */\nexport interface CacheSetOptions {\n /** Time to live in seconds */\n ttl?: number | undefined;\n /** Tags for cache invalidation */\n tags?: string[] | undefined;\n /** Additional metadata */\n metadata?: Record<string, unknown> | undefined;\n}\n\n/**\n * Cache delete options\n */\nexport interface CacheDeleteOptions {\n /** Delete all entries with matching tags */\n tags?: string[] | undefined;\n}\n\n/**\n * Cache adapter interface\n */\nexport interface CacheAdapter {\n /** Adapter type */\n readonly type: CacheAdapterType;\n\n /**\n * Get a value from cache\n * @param key Cache key\n * @param options Get options\n * @returns Cached value or null if not found/expired\n */\n get<T = unknown>(key: string, options?: CacheGetOptions): Promise<T | null>;\n\n /**\n * Set a value in cache\n * @param key Cache key\n * @param value Value to cache\n * @param options Set options\n */\n set<T = unknown>(key: string, value: T, options?: CacheSetOptions): Promise<void>;\n\n /**\n * Delete a value from cache\n * @param key Cache key\n */\n delete(key: string): Promise<void>;\n\n /**\n * Check if a key exists in cache\n * @param key Cache key\n */\n has(key: string): Promise<boolean>;\n\n /**\n * Clear all entries from cache\n */\n clear?(): Promise<void>;\n\n /**\n * Get multiple values at once\n * @param keys Cache keys\n */\n getMany?<T = unknown>(keys: string[]): Promise<Map<string, T | null>>;\n\n /**\n * Set multiple values at once\n * @param entries Key-value pairs\n * @param options Set options (applied to all)\n */\n setMany?<T = unknown>(entries: Map<string, T>, options?: CacheSetOptions): Promise<void>;\n\n /**\n * Delete multiple keys\n * @param keys Cache keys\n */\n deleteMany?(keys: string[]): Promise<void>;\n\n /**\n * Invalidate entries by tags\n * @param tags Tags to invalidate\n */\n invalidateByTags?(tags: string[]): Promise<void>;\n\n /**\n * Get TTL for a key (in seconds)\n * @param key Cache key\n * @returns TTL in seconds, -1 if no expiry, -2 if key doesn't exist\n */\n ttl?(key: string): Promise<number>;\n\n /**\n * Close/cleanup adapter resources\n */\n close?(): Promise<void>;\n}\n\n/**\n * Cache service configuration\n */\nexport interface CacheServiceConfig {\n /** Cache adapter to use */\n adapter: CacheAdapter;\n /** Default TTL in seconds */\n defaultTtl?: number | undefined;\n /** Key prefix for namespacing */\n keyPrefix?: string | undefined;\n /** Enable debug logging */\n debug?: boolean | undefined;\n}\n\n/**\n * Memory adapter configuration\n */\nexport interface MemoryCacheConfig {\n /** Maximum number of entries */\n maxEntries?: number | undefined;\n /** Cleanup interval in ms (default: 60000) */\n cleanupInterval?: number | undefined;\n}\n\n/**\n * Redis adapter configuration\n */\nexport interface RedisCacheConfig {\n /** Redis connection URL */\n url?: string | undefined;\n /** Redis host */\n host?: string | undefined;\n /** Redis port */\n port?: number | undefined;\n /** Redis password */\n password?: string | undefined;\n /** Redis database number */\n db?: number | undefined;\n /** Key prefix */\n keyPrefix?: string | undefined;\n /** Use TLS */\n tls?: boolean | undefined;\n}\n\n/**\n * Upstash adapter configuration\n */\nexport interface UpstashCacheConfig {\n /** Upstash Redis URL */\n url: string;\n /** Upstash Redis token */\n token: string;\n /** Key prefix */\n keyPrefix?: string | undefined;\n}\n\n/**\n * Cloudflare KV adapter configuration\n */\nexport interface CloudflareKVCacheConfig {\n /** KV namespace binding */\n namespace: KVNamespace;\n /** Key prefix */\n keyPrefix?: string | undefined;\n}\n\n/**\n * KVNamespace interface (Cloudflare Workers)\n */\nexport interface KVNamespace {\n get(key: string, options?: { type?: \"text\" | \"json\" | \"arrayBuffer\" | \"stream\" }): Promise<string | null>;\n get(key: string, options: { type: \"json\" }): Promise<unknown>;\n put(key: string, value: string | ArrayBuffer | ReadableStream, options?: { expiration?: number; expirationTtl?: number; metadata?: unknown }): Promise<void>;\n delete(key: string): Promise<void>;\n list(options?: { prefix?: string | undefined; limit?: number | undefined; cursor?: string | undefined }): Promise<{ keys: Array<{ name: string; expiration?: number; metadata?: unknown }>; list_complete: boolean; cursor?: string }>;\n getWithMetadata<T = unknown>(key: string, options?: { type?: \"text\" | \"json\" | \"arrayBuffer\" | \"stream\" }): Promise<{ value: string | null; metadata: T | null }>;\n}\n\n/**\n * Cache error\n */\nexport class CacheError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly cause?: unknown\n ) {\n super(message);\n this.name = \"CacheError\";\n }\n}\n\n/**\n * Common cache error codes\n */\nexport const CacheErrorCodes = {\n CONNECTION_FAILED: \"CONNECTION_FAILED\",\n OPERATION_FAILED: \"OPERATION_FAILED\",\n SERIALIZATION_ERROR: \"SERIALIZATION_ERROR\",\n INVALID_CONFIG: \"INVALID_CONFIG\",\n NOT_IMPLEMENTED: \"NOT_IMPLEMENTED\",\n} as const;\n","/**\n * @parsrun/cache - Upstash Adapter\n * Edge-compatible Upstash Redis adapter using fetch API\n */\n\nimport type {\n CacheAdapter,\n CacheGetOptions,\n CacheSetOptions,\n UpstashCacheConfig,\n} from \"../types.js\";\nimport { CacheError, CacheErrorCodes } from \"../types.js\";\n\n/**\n * Upstash Cache Adapter\n * Edge-compatible using HTTP API\n *\n * @example\n * ```typescript\n * const cache = new UpstashCacheAdapter({\n * url: process.env.UPSTASH_REDIS_REST_URL,\n * token: process.env.UPSTASH_REDIS_REST_TOKEN,\n * });\n *\n * await cache.set('user:123', { name: 'John' }, { ttl: 3600 });\n * const user = await cache.get('user:123');\n * ```\n */\nexport class UpstashCacheAdapter implements CacheAdapter {\n readonly type = \"upstash\" as const;\n\n private url: string;\n private token: string;\n private keyPrefix: string;\n\n constructor(config: UpstashCacheConfig) {\n this.url = config.url.replace(/\\/$/, \"\"); // Remove trailing slash\n this.token = config.token;\n this.keyPrefix = config.keyPrefix ?? \"\";\n }\n\n private prefixKey(key: string): string {\n return this.keyPrefix ? `${this.keyPrefix}:${key}` : key;\n }\n\n private async command<T = unknown>(...args: (string | number)[]): Promise<T> {\n try {\n const response = await fetch(this.url, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(args),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Upstash API error: ${error}`);\n }\n\n const data = await response.json() as { result: T; error?: string };\n\n if (data.error) {\n throw new Error(data.error);\n }\n\n return data.result;\n } catch (err) {\n throw new CacheError(\n `Upstash command failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n CacheErrorCodes.OPERATION_FAILED,\n err\n );\n }\n }\n\n private async pipeline<T = unknown>(commands: (string | number)[][]): Promise<T[]> {\n try {\n const response = await fetch(`${this.url}/pipeline`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(commands),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Upstash API error: ${error}`);\n }\n\n const data = await response.json() as Array<{ result: T; error?: string }>;\n return data.map((item) => {\n if (item.error) {\n throw new Error(item.error);\n }\n return item.result;\n });\n } catch (err) {\n throw new CacheError(\n `Upstash pipeline failed: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n CacheErrorCodes.OPERATION_FAILED,\n err\n );\n }\n }\n\n async get<T = unknown>(key: string, _options?: CacheGetOptions): Promise<T | null> {\n const data = await this.command<string | null>(\"GET\", this.prefixKey(key));\n\n if (!data) {\n return null;\n }\n\n try {\n const parsed = JSON.parse(data) as { value: T };\n return parsed.value;\n } catch {\n // Return raw string if not JSON\n return data as T;\n }\n }\n\n async set<T = unknown>(key: string, value: T, options?: CacheSetOptions): Promise<void> {\n const data = JSON.stringify({\n value,\n tags: options?.tags,\n metadata: options?.metadata,\n });\n\n const prefixedKey = this.prefixKey(key);\n\n if (options?.ttl) {\n await this.command(\"SET\", prefixedKey, data, \"EX\", options.ttl);\n } else {\n await this.command(\"SET\", prefixedKey, data);\n }\n\n // Store tag-to-key mappings\n if (options?.tags && options.tags.length > 0) {\n const commands: (string | number)[][] = [];\n for (const tag of options.tags) {\n commands.push([\"SADD\", this.prefixKey(`__tag:${tag}`), key]);\n }\n await this.pipeline(commands);\n }\n }\n\n async delete(key: string): Promise<void> {\n await this.command(\"DEL\", this.prefixKey(key));\n }\n\n async has(key: string): Promise<boolean> {\n const exists = await this.command<number>(\"EXISTS\", this.prefixKey(key));\n return exists > 0;\n }\n\n async clear(): Promise<void> {\n if (this.keyPrefix) {\n // Scan and delete keys with our prefix\n let cursor = \"0\";\n do {\n const result = await this.command<[string, string[]]>(\n \"SCAN\",\n cursor,\n \"MATCH\",\n `${this.keyPrefix}:*`,\n \"COUNT\",\n 100\n );\n cursor = result[0];\n const keys = result[1];\n\n if (keys.length > 0) {\n await this.command(\"DEL\", ...keys);\n }\n } while (cursor !== \"0\");\n } else {\n await this.command(\"FLUSHDB\");\n }\n }\n\n async getMany<T = unknown>(keys: string[]): Promise<Map<string, T | null>> {\n const prefixedKeys = keys.map((k) => this.prefixKey(k));\n const results = await this.command<(string | null)[]>(\"MGET\", ...prefixedKeys);\n\n const map = new Map<string, T | null>();\n for (let i = 0; i < keys.length; i++) {\n const data = results[i];\n const key = keys[i];\n if (key !== undefined) {\n if (data) {\n try {\n const parsed = JSON.parse(data) as { value: T };\n map.set(key, parsed.value);\n } catch {\n map.set(key, data as T);\n }\n } else {\n map.set(key, null);\n }\n }\n }\n\n return map;\n }\n\n async setMany<T = unknown>(entries: Map<string, T>, options?: CacheSetOptions): Promise<void> {\n const commands: (string | number)[][] = [];\n\n for (const [key, value] of entries) {\n const data = JSON.stringify({\n value,\n tags: options?.tags,\n metadata: options?.metadata,\n });\n\n const prefixedKey = this.prefixKey(key);\n\n if (options?.ttl) {\n commands.push([\"SET\", prefixedKey, data, \"EX\", options.ttl]);\n } else {\n commands.push([\"SET\", prefixedKey, data]);\n }\n }\n\n await this.pipeline(commands);\n }\n\n async deleteMany(keys: string[]): Promise<void> {\n const prefixedKeys = keys.map((k) => this.prefixKey(k));\n await this.command(\"DEL\", ...prefixedKeys);\n }\n\n async invalidateByTags(tags: string[]): Promise<void> {\n const keysToDelete: string[] = [];\n\n // Get all keys for each tag\n for (const tag of tags) {\n const keys = await this.command<string[]>(\"SMEMBERS\", this.prefixKey(`__tag:${tag}`));\n keysToDelete.push(...keys);\n }\n\n if (keysToDelete.length > 0) {\n await this.deleteMany(keysToDelete);\n }\n\n // Clean up tag sets\n const tagKeys = tags.map((tag) => this.prefixKey(`__tag:${tag}`));\n await this.command(\"DEL\", ...tagKeys);\n }\n\n async ttl(key: string): Promise<number> {\n return await this.command<number>(\"TTL\", this.prefixKey(key));\n }\n\n async close(): Promise<void> {\n // No-op for HTTP client\n }\n}\n\n/**\n * Create an Upstash cache adapter\n */\nexport function createUpstashCacheAdapter(config: UpstashCacheConfig): UpstashCacheAdapter {\n return new UpstashCacheAdapter(config);\n}\n"],"mappings":"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","names":[]}