@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.
package/dist/index.js ADDED
@@ -0,0 +1,978 @@
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/memory.ts
31
+ var MemoryCacheAdapter = class {
32
+ type = "memory";
33
+ cache = /* @__PURE__ */ new Map();
34
+ tagIndex = /* @__PURE__ */ new Map();
35
+ maxEntries;
36
+ cleanupInterval;
37
+ cleanupTimer = null;
38
+ constructor(config) {
39
+ this.maxEntries = config?.maxEntries ?? 1e4;
40
+ this.cleanupInterval = config?.cleanupInterval ?? 6e4;
41
+ if (this.cleanupInterval > 0) {
42
+ this.cleanupTimer = setInterval(() => this.cleanup(), this.cleanupInterval);
43
+ }
44
+ }
45
+ async get(key, options) {
46
+ const entry = this.cache.get(key);
47
+ if (!entry) {
48
+ return null;
49
+ }
50
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
51
+ this.cache.delete(key);
52
+ this.removeFromTagIndex(key, entry.tags);
53
+ return null;
54
+ }
55
+ if (options?.refresh && entry.expiresAt && entry.ttlSeconds) {
56
+ entry.expiresAt = Date.now() + entry.ttlSeconds * 1e3;
57
+ }
58
+ return entry.value;
59
+ }
60
+ async set(key, value, options) {
61
+ if (this.cache.size >= this.maxEntries && !this.cache.has(key)) {
62
+ const oldestKey = this.cache.keys().next().value;
63
+ if (oldestKey) {
64
+ const oldEntry = this.cache.get(oldestKey);
65
+ this.cache.delete(oldestKey);
66
+ if (oldEntry?.tags) {
67
+ this.removeFromTagIndex(oldestKey, oldEntry.tags);
68
+ }
69
+ }
70
+ }
71
+ const entry = {
72
+ value,
73
+ metadata: options?.metadata
74
+ };
75
+ if (options?.ttl) {
76
+ entry.expiresAt = Date.now() + options.ttl * 1e3;
77
+ entry.ttlSeconds = options.ttl;
78
+ }
79
+ if (options?.tags && options.tags.length > 0) {
80
+ entry.tags = options.tags;
81
+ this.addToTagIndex(key, options.tags);
82
+ }
83
+ this.cache.set(key, entry);
84
+ }
85
+ async delete(key) {
86
+ const entry = this.cache.get(key);
87
+ if (entry?.tags) {
88
+ this.removeFromTagIndex(key, entry.tags);
89
+ }
90
+ this.cache.delete(key);
91
+ }
92
+ async has(key) {
93
+ const entry = this.cache.get(key);
94
+ if (!entry) {
95
+ return false;
96
+ }
97
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
98
+ this.cache.delete(key);
99
+ this.removeFromTagIndex(key, entry.tags);
100
+ return false;
101
+ }
102
+ return true;
103
+ }
104
+ async clear() {
105
+ this.cache.clear();
106
+ this.tagIndex.clear();
107
+ }
108
+ async getMany(keys) {
109
+ const result = /* @__PURE__ */ new Map();
110
+ for (const key of keys) {
111
+ result.set(key, await this.get(key));
112
+ }
113
+ return result;
114
+ }
115
+ async setMany(entries, options) {
116
+ for (const [key, value] of entries) {
117
+ await this.set(key, value, options);
118
+ }
119
+ }
120
+ async deleteMany(keys) {
121
+ for (const key of keys) {
122
+ await this.delete(key);
123
+ }
124
+ }
125
+ async invalidateByTags(tags) {
126
+ const keysToDelete = /* @__PURE__ */ new Set();
127
+ for (const tag of tags) {
128
+ const keys = this.tagIndex.get(tag);
129
+ if (keys) {
130
+ for (const key of keys) {
131
+ keysToDelete.add(key);
132
+ }
133
+ }
134
+ }
135
+ for (const key of keysToDelete) {
136
+ await this.delete(key);
137
+ }
138
+ }
139
+ async ttl(key) {
140
+ const entry = this.cache.get(key);
141
+ if (!entry) {
142
+ return -2;
143
+ }
144
+ if (!entry.expiresAt) {
145
+ return -1;
146
+ }
147
+ const remaining = entry.expiresAt - Date.now();
148
+ if (remaining <= 0) {
149
+ return -2;
150
+ }
151
+ return Math.ceil(remaining / 1e3);
152
+ }
153
+ async close() {
154
+ if (this.cleanupTimer) {
155
+ clearInterval(this.cleanupTimer);
156
+ this.cleanupTimer = null;
157
+ }
158
+ await this.clear();
159
+ }
160
+ /**
161
+ * Get cache statistics
162
+ */
163
+ getStats() {
164
+ return {
165
+ size: this.cache.size,
166
+ maxEntries: this.maxEntries
167
+ };
168
+ }
169
+ addToTagIndex(key, tags) {
170
+ for (const tag of tags) {
171
+ let keys = this.tagIndex.get(tag);
172
+ if (!keys) {
173
+ keys = /* @__PURE__ */ new Set();
174
+ this.tagIndex.set(tag, keys);
175
+ }
176
+ keys.add(key);
177
+ }
178
+ }
179
+ removeFromTagIndex(key, tags) {
180
+ if (!tags) return;
181
+ for (const tag of tags) {
182
+ const keys = this.tagIndex.get(tag);
183
+ if (keys) {
184
+ keys.delete(key);
185
+ if (keys.size === 0) {
186
+ this.tagIndex.delete(tag);
187
+ }
188
+ }
189
+ }
190
+ }
191
+ cleanup() {
192
+ const now = Date.now();
193
+ for (const [key, entry] of this.cache) {
194
+ if (entry.expiresAt && now > entry.expiresAt) {
195
+ this.removeFromTagIndex(key, entry.tags);
196
+ this.cache.delete(key);
197
+ }
198
+ }
199
+ }
200
+ };
201
+ function createMemoryCacheAdapter(config) {
202
+ return new MemoryCacheAdapter(config);
203
+ }
204
+
205
+ // src/adapters/redis.ts
206
+ var RedisCacheAdapter = class {
207
+ type = "redis";
208
+ client;
209
+ keyPrefix;
210
+ isExternalClient;
211
+ constructor(config) {
212
+ this.client = config.client;
213
+ this.keyPrefix = config.keyPrefix ?? "";
214
+ this.isExternalClient = true;
215
+ }
216
+ prefixKey(key) {
217
+ return this.keyPrefix ? `${this.keyPrefix}:${key}` : key;
218
+ }
219
+ async get(key, _options) {
220
+ try {
221
+ const data = await this.client.get(this.prefixKey(key));
222
+ if (!data) {
223
+ return null;
224
+ }
225
+ const parsed = JSON.parse(data);
226
+ return parsed.value;
227
+ } catch (err) {
228
+ throw new CacheError(
229
+ `Redis get failed: ${err instanceof Error ? err.message : "Unknown error"}`,
230
+ CacheErrorCodes.OPERATION_FAILED,
231
+ err
232
+ );
233
+ }
234
+ }
235
+ async set(key, value, options) {
236
+ try {
237
+ const data = JSON.stringify({
238
+ value,
239
+ tags: options?.tags,
240
+ metadata: options?.metadata
241
+ });
242
+ const prefixedKey = this.prefixKey(key);
243
+ if (options?.ttl) {
244
+ await this.client.set(prefixedKey, data, "EX", options.ttl);
245
+ } else {
246
+ await this.client.set(prefixedKey, data);
247
+ }
248
+ if (options?.tags && options.tags.length > 0) {
249
+ const pipeline = this.client.pipeline();
250
+ for (const tag of options.tags) {
251
+ const tagKey = this.prefixKey(`__tag:${tag}`);
252
+ pipeline.set(tagKey, JSON.stringify([...await this.getTagKeys(tag), key]));
253
+ }
254
+ await pipeline.exec();
255
+ }
256
+ } catch (err) {
257
+ throw new CacheError(
258
+ `Redis set failed: ${err instanceof Error ? err.message : "Unknown error"}`,
259
+ CacheErrorCodes.OPERATION_FAILED,
260
+ err
261
+ );
262
+ }
263
+ }
264
+ async delete(key) {
265
+ try {
266
+ await this.client.del(this.prefixKey(key));
267
+ } catch (err) {
268
+ throw new CacheError(
269
+ `Redis delete failed: ${err instanceof Error ? err.message : "Unknown error"}`,
270
+ CacheErrorCodes.OPERATION_FAILED,
271
+ err
272
+ );
273
+ }
274
+ }
275
+ async has(key) {
276
+ try {
277
+ const exists = await this.client.exists(this.prefixKey(key));
278
+ return exists > 0;
279
+ } catch (err) {
280
+ throw new CacheError(
281
+ `Redis exists failed: ${err instanceof Error ? err.message : "Unknown error"}`,
282
+ CacheErrorCodes.OPERATION_FAILED,
283
+ err
284
+ );
285
+ }
286
+ }
287
+ async clear() {
288
+ try {
289
+ if (this.keyPrefix) {
290
+ const keys = await this.client.keys(`${this.keyPrefix}:*`);
291
+ if (keys.length > 0) {
292
+ await this.client.del(...keys);
293
+ }
294
+ } else {
295
+ await this.client.flushdb();
296
+ }
297
+ } catch (err) {
298
+ throw new CacheError(
299
+ `Redis clear failed: ${err instanceof Error ? err.message : "Unknown error"}`,
300
+ CacheErrorCodes.OPERATION_FAILED,
301
+ err
302
+ );
303
+ }
304
+ }
305
+ async getMany(keys) {
306
+ try {
307
+ const prefixedKeys = keys.map((k) => this.prefixKey(k));
308
+ const results = await this.client.mget(...prefixedKeys);
309
+ const map = /* @__PURE__ */ new Map();
310
+ for (let i = 0; i < keys.length; i++) {
311
+ const data = results[i];
312
+ if (data) {
313
+ const parsed = JSON.parse(data);
314
+ map.set(keys[i] ?? "", parsed.value);
315
+ } else {
316
+ map.set(keys[i] ?? "", null);
317
+ }
318
+ }
319
+ return map;
320
+ } catch (err) {
321
+ throw new CacheError(
322
+ `Redis mget failed: ${err instanceof Error ? err.message : "Unknown error"}`,
323
+ CacheErrorCodes.OPERATION_FAILED,
324
+ err
325
+ );
326
+ }
327
+ }
328
+ async setMany(entries, options) {
329
+ try {
330
+ const pipeline = this.client.pipeline();
331
+ for (const [key, value] of entries) {
332
+ const data = JSON.stringify({
333
+ value,
334
+ tags: options?.tags,
335
+ metadata: options?.metadata
336
+ });
337
+ const prefixedKey = this.prefixKey(key);
338
+ if (options?.ttl) {
339
+ pipeline.set(prefixedKey, data, "EX", options.ttl);
340
+ } else {
341
+ pipeline.set(prefixedKey, data);
342
+ }
343
+ }
344
+ await pipeline.exec();
345
+ } catch (err) {
346
+ throw new CacheError(
347
+ `Redis mset failed: ${err instanceof Error ? err.message : "Unknown error"}`,
348
+ CacheErrorCodes.OPERATION_FAILED,
349
+ err
350
+ );
351
+ }
352
+ }
353
+ async deleteMany(keys) {
354
+ try {
355
+ const prefixedKeys = keys.map((k) => this.prefixKey(k));
356
+ await this.client.del(...prefixedKeys);
357
+ } catch (err) {
358
+ throw new CacheError(
359
+ `Redis mdel failed: ${err instanceof Error ? err.message : "Unknown error"}`,
360
+ CacheErrorCodes.OPERATION_FAILED,
361
+ err
362
+ );
363
+ }
364
+ }
365
+ async invalidateByTags(tags) {
366
+ try {
367
+ const keysToDelete = [];
368
+ for (const tag of tags) {
369
+ const keys = await this.getTagKeys(tag);
370
+ keysToDelete.push(...keys);
371
+ }
372
+ if (keysToDelete.length > 0) {
373
+ await this.deleteMany(keysToDelete);
374
+ }
375
+ const tagKeys = tags.map((tag) => this.prefixKey(`__tag:${tag}`));
376
+ await this.client.del(...tagKeys);
377
+ } catch (err) {
378
+ throw new CacheError(
379
+ `Redis invalidate by tags failed: ${err instanceof Error ? err.message : "Unknown error"}`,
380
+ CacheErrorCodes.OPERATION_FAILED,
381
+ err
382
+ );
383
+ }
384
+ }
385
+ async ttl(key) {
386
+ try {
387
+ return await this.client.ttl(this.prefixKey(key));
388
+ } catch (err) {
389
+ throw new CacheError(
390
+ `Redis ttl failed: ${err instanceof Error ? err.message : "Unknown error"}`,
391
+ CacheErrorCodes.OPERATION_FAILED,
392
+ err
393
+ );
394
+ }
395
+ }
396
+ async close() {
397
+ if (!this.isExternalClient) {
398
+ await this.client.quit();
399
+ }
400
+ }
401
+ async getTagKeys(tag) {
402
+ const tagKey = this.prefixKey(`__tag:${tag}`);
403
+ const data = await this.client.get(tagKey);
404
+ if (data) {
405
+ return JSON.parse(data);
406
+ }
407
+ return [];
408
+ }
409
+ };
410
+ function createRedisCacheAdapter(config) {
411
+ return new RedisCacheAdapter(config);
412
+ }
413
+
414
+ // src/adapters/upstash.ts
415
+ var UpstashCacheAdapter = class {
416
+ type = "upstash";
417
+ url;
418
+ token;
419
+ keyPrefix;
420
+ constructor(config) {
421
+ this.url = config.url.replace(/\/$/, "");
422
+ this.token = config.token;
423
+ this.keyPrefix = config.keyPrefix ?? "";
424
+ }
425
+ prefixKey(key) {
426
+ return this.keyPrefix ? `${this.keyPrefix}:${key}` : key;
427
+ }
428
+ async command(...args) {
429
+ try {
430
+ const response = await fetch(this.url, {
431
+ method: "POST",
432
+ headers: {
433
+ Authorization: `Bearer ${this.token}`,
434
+ "Content-Type": "application/json"
435
+ },
436
+ body: JSON.stringify(args)
437
+ });
438
+ if (!response.ok) {
439
+ const error = await response.text();
440
+ throw new Error(`Upstash API error: ${error}`);
441
+ }
442
+ const data = await response.json();
443
+ if (data.error) {
444
+ throw new Error(data.error);
445
+ }
446
+ return data.result;
447
+ } catch (err) {
448
+ throw new CacheError(
449
+ `Upstash command failed: ${err instanceof Error ? err.message : "Unknown error"}`,
450
+ CacheErrorCodes.OPERATION_FAILED,
451
+ err
452
+ );
453
+ }
454
+ }
455
+ async pipeline(commands) {
456
+ try {
457
+ const response = await fetch(`${this.url}/pipeline`, {
458
+ method: "POST",
459
+ headers: {
460
+ Authorization: `Bearer ${this.token}`,
461
+ "Content-Type": "application/json"
462
+ },
463
+ body: JSON.stringify(commands)
464
+ });
465
+ if (!response.ok) {
466
+ const error = await response.text();
467
+ throw new Error(`Upstash API error: ${error}`);
468
+ }
469
+ const data = await response.json();
470
+ return data.map((item) => {
471
+ if (item.error) {
472
+ throw new Error(item.error);
473
+ }
474
+ return item.result;
475
+ });
476
+ } catch (err) {
477
+ throw new CacheError(
478
+ `Upstash pipeline failed: ${err instanceof Error ? err.message : "Unknown error"}`,
479
+ CacheErrorCodes.OPERATION_FAILED,
480
+ err
481
+ );
482
+ }
483
+ }
484
+ async get(key, _options) {
485
+ const data = await this.command("GET", this.prefixKey(key));
486
+ if (!data) {
487
+ return null;
488
+ }
489
+ try {
490
+ const parsed = JSON.parse(data);
491
+ return parsed.value;
492
+ } catch {
493
+ return data;
494
+ }
495
+ }
496
+ async set(key, value, options) {
497
+ const data = JSON.stringify({
498
+ value,
499
+ tags: options?.tags,
500
+ metadata: options?.metadata
501
+ });
502
+ const prefixedKey = this.prefixKey(key);
503
+ if (options?.ttl) {
504
+ await this.command("SET", prefixedKey, data, "EX", options.ttl);
505
+ } else {
506
+ await this.command("SET", prefixedKey, data);
507
+ }
508
+ if (options?.tags && options.tags.length > 0) {
509
+ const commands = [];
510
+ for (const tag of options.tags) {
511
+ commands.push(["SADD", this.prefixKey(`__tag:${tag}`), key]);
512
+ }
513
+ await this.pipeline(commands);
514
+ }
515
+ }
516
+ async delete(key) {
517
+ await this.command("DEL", this.prefixKey(key));
518
+ }
519
+ async has(key) {
520
+ const exists = await this.command("EXISTS", this.prefixKey(key));
521
+ return exists > 0;
522
+ }
523
+ async clear() {
524
+ if (this.keyPrefix) {
525
+ let cursor = "0";
526
+ do {
527
+ const result = await this.command(
528
+ "SCAN",
529
+ cursor,
530
+ "MATCH",
531
+ `${this.keyPrefix}:*`,
532
+ "COUNT",
533
+ 100
534
+ );
535
+ cursor = result[0];
536
+ const keys = result[1];
537
+ if (keys.length > 0) {
538
+ await this.command("DEL", ...keys);
539
+ }
540
+ } while (cursor !== "0");
541
+ } else {
542
+ await this.command("FLUSHDB");
543
+ }
544
+ }
545
+ async getMany(keys) {
546
+ const prefixedKeys = keys.map((k) => this.prefixKey(k));
547
+ const results = await this.command("MGET", ...prefixedKeys);
548
+ const map = /* @__PURE__ */ new Map();
549
+ for (let i = 0; i < keys.length; i++) {
550
+ const data = results[i];
551
+ const key = keys[i];
552
+ if (key !== void 0) {
553
+ if (data) {
554
+ try {
555
+ const parsed = JSON.parse(data);
556
+ map.set(key, parsed.value);
557
+ } catch {
558
+ map.set(key, data);
559
+ }
560
+ } else {
561
+ map.set(key, null);
562
+ }
563
+ }
564
+ }
565
+ return map;
566
+ }
567
+ async setMany(entries, options) {
568
+ const commands = [];
569
+ for (const [key, value] of entries) {
570
+ const data = JSON.stringify({
571
+ value,
572
+ tags: options?.tags,
573
+ metadata: options?.metadata
574
+ });
575
+ const prefixedKey = this.prefixKey(key);
576
+ if (options?.ttl) {
577
+ commands.push(["SET", prefixedKey, data, "EX", options.ttl]);
578
+ } else {
579
+ commands.push(["SET", prefixedKey, data]);
580
+ }
581
+ }
582
+ await this.pipeline(commands);
583
+ }
584
+ async deleteMany(keys) {
585
+ const prefixedKeys = keys.map((k) => this.prefixKey(k));
586
+ await this.command("DEL", ...prefixedKeys);
587
+ }
588
+ async invalidateByTags(tags) {
589
+ const keysToDelete = [];
590
+ for (const tag of tags) {
591
+ const keys = await this.command("SMEMBERS", this.prefixKey(`__tag:${tag}`));
592
+ keysToDelete.push(...keys);
593
+ }
594
+ if (keysToDelete.length > 0) {
595
+ await this.deleteMany(keysToDelete);
596
+ }
597
+ const tagKeys = tags.map((tag) => this.prefixKey(`__tag:${tag}`));
598
+ await this.command("DEL", ...tagKeys);
599
+ }
600
+ async ttl(key) {
601
+ return await this.command("TTL", this.prefixKey(key));
602
+ }
603
+ async close() {
604
+ }
605
+ };
606
+ function createUpstashCacheAdapter(config) {
607
+ return new UpstashCacheAdapter(config);
608
+ }
609
+
610
+ // src/adapters/cloudflare-kv.ts
611
+ var CloudflareKVCacheAdapter = class {
612
+ type = "cloudflare-kv";
613
+ kv;
614
+ keyPrefix;
615
+ constructor(config) {
616
+ this.kv = config.namespace;
617
+ this.keyPrefix = config.keyPrefix ?? "";
618
+ }
619
+ prefixKey(key) {
620
+ return this.keyPrefix ? `${this.keyPrefix}:${key}` : key;
621
+ }
622
+ async get(key, _options) {
623
+ try {
624
+ const data = await this.kv.get(this.prefixKey(key), { type: "json" });
625
+ if (!data) {
626
+ return null;
627
+ }
628
+ return data.value;
629
+ } catch (err) {
630
+ throw new CacheError(
631
+ `Cloudflare KV get failed: ${err instanceof Error ? err.message : "Unknown error"}`,
632
+ CacheErrorCodes.OPERATION_FAILED,
633
+ err
634
+ );
635
+ }
636
+ }
637
+ async set(key, value, options) {
638
+ try {
639
+ const data = {
640
+ value,
641
+ tags: options?.tags,
642
+ metadata: options?.metadata
643
+ };
644
+ const putOptions = {};
645
+ if (options?.ttl) {
646
+ putOptions.expirationTtl = options.ttl;
647
+ }
648
+ if (options?.tags || options?.metadata) {
649
+ putOptions.metadata = {
650
+ tags: options.tags,
651
+ ...options.metadata
652
+ };
653
+ }
654
+ await this.kv.put(this.prefixKey(key), JSON.stringify(data), putOptions);
655
+ if (options?.tags && options.tags.length > 0) {
656
+ for (const tag of options.tags) {
657
+ const tagKey = this.prefixKey(`__tag:${tag}`);
658
+ const existing = await this.kv.get(tagKey, { type: "json" });
659
+ const keys = existing ? [.../* @__PURE__ */ new Set([...existing, key])] : [key];
660
+ await this.kv.put(tagKey, JSON.stringify(keys));
661
+ }
662
+ }
663
+ } catch (err) {
664
+ throw new CacheError(
665
+ `Cloudflare KV set failed: ${err instanceof Error ? err.message : "Unknown error"}`,
666
+ CacheErrorCodes.OPERATION_FAILED,
667
+ err
668
+ );
669
+ }
670
+ }
671
+ async delete(key) {
672
+ try {
673
+ await this.kv.delete(this.prefixKey(key));
674
+ } catch (err) {
675
+ throw new CacheError(
676
+ `Cloudflare KV delete failed: ${err instanceof Error ? err.message : "Unknown error"}`,
677
+ CacheErrorCodes.OPERATION_FAILED,
678
+ err
679
+ );
680
+ }
681
+ }
682
+ async has(key) {
683
+ try {
684
+ const value = await this.kv.get(this.prefixKey(key));
685
+ return value !== null;
686
+ } catch (err) {
687
+ throw new CacheError(
688
+ `Cloudflare KV has failed: ${err instanceof Error ? err.message : "Unknown error"}`,
689
+ CacheErrorCodes.OPERATION_FAILED,
690
+ err
691
+ );
692
+ }
693
+ }
694
+ async clear() {
695
+ try {
696
+ const prefix = this.keyPrefix ? `${this.keyPrefix}:` : "";
697
+ let cursor;
698
+ do {
699
+ const result = await this.kv.list({
700
+ prefix,
701
+ limit: 1e3,
702
+ cursor
703
+ });
704
+ for (const key of result.keys) {
705
+ await this.kv.delete(key.name);
706
+ }
707
+ cursor = result.list_complete ? void 0 : result.cursor;
708
+ } while (cursor);
709
+ } catch (err) {
710
+ throw new CacheError(
711
+ `Cloudflare KV clear failed: ${err instanceof Error ? err.message : "Unknown error"}`,
712
+ CacheErrorCodes.OPERATION_FAILED,
713
+ err
714
+ );
715
+ }
716
+ }
717
+ async getMany(keys) {
718
+ const map = /* @__PURE__ */ new Map();
719
+ const promises = keys.map(async (key) => {
720
+ const value = await this.get(key);
721
+ return { key, value };
722
+ });
723
+ const results = await Promise.all(promises);
724
+ for (const { key, value } of results) {
725
+ map.set(key, value);
726
+ }
727
+ return map;
728
+ }
729
+ async setMany(entries, options) {
730
+ const promises = [];
731
+ for (const [key, value] of entries) {
732
+ promises.push(this.set(key, value, options));
733
+ }
734
+ await Promise.all(promises);
735
+ }
736
+ async deleteMany(keys) {
737
+ const promises = keys.map((key) => this.delete(key));
738
+ await Promise.all(promises);
739
+ }
740
+ async invalidateByTags(tags) {
741
+ try {
742
+ const keysToDelete = /* @__PURE__ */ new Set();
743
+ for (const tag of tags) {
744
+ const tagKey = this.prefixKey(`__tag:${tag}`);
745
+ const keys = await this.kv.get(tagKey, { type: "json" });
746
+ if (keys) {
747
+ for (const key of keys) {
748
+ keysToDelete.add(key);
749
+ }
750
+ await this.kv.delete(tagKey);
751
+ }
752
+ }
753
+ if (keysToDelete.size > 0) {
754
+ await this.deleteMany([...keysToDelete]);
755
+ }
756
+ } catch (err) {
757
+ throw new CacheError(
758
+ `Cloudflare KV invalidate by tags failed: ${err instanceof Error ? err.message : "Unknown error"}`,
759
+ CacheErrorCodes.OPERATION_FAILED,
760
+ err
761
+ );
762
+ }
763
+ }
764
+ async ttl(_key) {
765
+ return -1;
766
+ }
767
+ async close() {
768
+ }
769
+ };
770
+ function createCloudflareKVCacheAdapter(config) {
771
+ return new CloudflareKVCacheAdapter(config);
772
+ }
773
+
774
+ // src/index.ts
775
+ var CacheService = class {
776
+ adapter;
777
+ defaultTtl;
778
+ keyPrefix;
779
+ debug;
780
+ constructor(config) {
781
+ this.adapter = config.adapter;
782
+ this.defaultTtl = config.defaultTtl;
783
+ this.keyPrefix = config.keyPrefix ?? "";
784
+ this.debug = config.debug ?? false;
785
+ }
786
+ prefixKey(key) {
787
+ return this.keyPrefix ? `${this.keyPrefix}:${key}` : key;
788
+ }
789
+ /**
790
+ * Get adapter type
791
+ */
792
+ get adapterType() {
793
+ return this.adapter.type;
794
+ }
795
+ /**
796
+ * Get a value from cache
797
+ */
798
+ async get(key, options) {
799
+ const prefixedKey = this.prefixKey(key);
800
+ if (this.debug) {
801
+ console.log(`[Cache] GET ${prefixedKey}`);
802
+ }
803
+ return this.adapter.get(prefixedKey, options);
804
+ }
805
+ /**
806
+ * Set a value in cache
807
+ */
808
+ async set(key, value, options) {
809
+ const prefixedKey = this.prefixKey(key);
810
+ const ttl = options?.ttl ?? this.defaultTtl;
811
+ if (this.debug) {
812
+ console.log(`[Cache] SET ${prefixedKey} (ttl: ${ttl ?? "none"})`);
813
+ }
814
+ await this.adapter.set(prefixedKey, value, { ...options, ttl });
815
+ }
816
+ /**
817
+ * Delete a value from cache
818
+ */
819
+ async delete(key) {
820
+ const prefixedKey = this.prefixKey(key);
821
+ if (this.debug) {
822
+ console.log(`[Cache] DELETE ${prefixedKey}`);
823
+ }
824
+ await this.adapter.delete(prefixedKey);
825
+ }
826
+ /**
827
+ * Check if a key exists in cache
828
+ */
829
+ async has(key) {
830
+ return this.adapter.has(this.prefixKey(key));
831
+ }
832
+ /**
833
+ * Clear all entries from cache
834
+ */
835
+ async clear() {
836
+ if (this.adapter.clear) {
837
+ await this.adapter.clear();
838
+ }
839
+ }
840
+ /**
841
+ * Get multiple values at once
842
+ */
843
+ async getMany(keys) {
844
+ const prefixedKeys = keys.map((k) => this.prefixKey(k));
845
+ if (this.adapter.getMany) {
846
+ const results2 = await this.adapter.getMany(prefixedKeys);
847
+ const unprefixed = /* @__PURE__ */ new Map();
848
+ let i = 0;
849
+ for (const key of keys) {
850
+ const prefixedKey = prefixedKeys[i];
851
+ if (prefixedKey) {
852
+ unprefixed.set(key, results2.get(prefixedKey) ?? null);
853
+ }
854
+ i++;
855
+ }
856
+ return unprefixed;
857
+ }
858
+ const results = /* @__PURE__ */ new Map();
859
+ for (const key of keys) {
860
+ results.set(key, await this.get(key));
861
+ }
862
+ return results;
863
+ }
864
+ /**
865
+ * Set multiple values at once
866
+ */
867
+ async setMany(entries, options) {
868
+ const prefixedEntries = /* @__PURE__ */ new Map();
869
+ for (const [key, value] of entries) {
870
+ prefixedEntries.set(this.prefixKey(key), value);
871
+ }
872
+ const ttl = options?.ttl ?? this.defaultTtl;
873
+ const opts = { ...options, ttl };
874
+ if (this.adapter.setMany) {
875
+ await this.adapter.setMany(prefixedEntries, opts);
876
+ return;
877
+ }
878
+ for (const [key, value] of entries) {
879
+ await this.set(key, value, opts);
880
+ }
881
+ }
882
+ /**
883
+ * Delete multiple keys
884
+ */
885
+ async deleteMany(keys) {
886
+ const prefixedKeys = keys.map((k) => this.prefixKey(k));
887
+ if (this.adapter.deleteMany) {
888
+ await this.adapter.deleteMany(prefixedKeys);
889
+ return;
890
+ }
891
+ for (const key of keys) {
892
+ await this.delete(key);
893
+ }
894
+ }
895
+ /**
896
+ * Invalidate entries by tags
897
+ */
898
+ async invalidateByTags(tags) {
899
+ if (this.adapter.invalidateByTags) {
900
+ await this.adapter.invalidateByTags(tags);
901
+ }
902
+ }
903
+ /**
904
+ * Get TTL for a key (in seconds)
905
+ * @returns TTL in seconds, -1 if no expiry, -2 if key doesn't exist
906
+ */
907
+ async ttl(key) {
908
+ if (this.adapter.ttl) {
909
+ return this.adapter.ttl(this.prefixKey(key));
910
+ }
911
+ return -1;
912
+ }
913
+ /**
914
+ * Get or set a value with callback
915
+ * @param key Cache key
916
+ * @param fn Function to compute value if not cached
917
+ * @param options Set options (applied if value is computed)
918
+ */
919
+ async getOrSet(key, fn, options) {
920
+ const cached = await this.get(key);
921
+ if (cached !== null) {
922
+ return cached;
923
+ }
924
+ const value = await fn();
925
+ await this.set(key, value, options);
926
+ return value;
927
+ }
928
+ /**
929
+ * Wrap a function with caching
930
+ */
931
+ wrap(fn, keyFn, options) {
932
+ return async (...args) => {
933
+ const key = keyFn(...args);
934
+ return this.getOrSet(key, () => fn(...args), options);
935
+ };
936
+ }
937
+ /**
938
+ * Close/cleanup cache resources
939
+ */
940
+ async close() {
941
+ if (this.adapter.close) {
942
+ await this.adapter.close();
943
+ }
944
+ }
945
+ };
946
+ function createCacheService(config) {
947
+ return new CacheService(config);
948
+ }
949
+ var index_default = {
950
+ CacheService,
951
+ createCacheService
952
+ };
953
+ export {
954
+ CacheError,
955
+ CacheErrorCodes,
956
+ CacheService,
957
+ CloudflareKVCacheAdapter,
958
+ MemoryCacheAdapter,
959
+ RedisCacheAdapter,
960
+ UpstashCacheAdapter,
961
+ cacheConfig,
962
+ cacheGetResult,
963
+ cacheStats,
964
+ cloudflareKvConfig,
965
+ createCacheService,
966
+ createCloudflareKVCacheAdapter,
967
+ createMemoryCacheAdapter,
968
+ createRedisCacheAdapter,
969
+ createUpstashCacheAdapter,
970
+ index_default as default,
971
+ memoryCacheConfig,
972
+ multiTierCacheConfig,
973
+ cacheSetOptions as parsCacheSetOptions,
974
+ redisCacheConfig,
975
+ type,
976
+ upstashCacheConfig
977
+ };
978
+ //# sourceMappingURL=index.js.map