@trieb.work/nextjs-turbo-redis-cache 1.2.0 → 1.3.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.
Files changed (60) hide show
  1. package/.github/workflows/ci.yml +30 -6
  2. package/.github/workflows/release.yml +6 -3
  3. package/.next/trace +11 -0
  4. package/.vscode/settings.json +10 -0
  5. package/CHANGELOG.md +61 -0
  6. package/README.md +149 -34
  7. package/dist/index.d.mts +92 -20
  8. package/dist/index.d.ts +92 -20
  9. package/dist/index.js +319 -60
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +315 -60
  12. package/dist/index.mjs.map +1 -1
  13. package/package.json +14 -9
  14. package/scripts/vitest-run-staged.cjs +1 -1
  15. package/src/CachedHandler.ts +23 -9
  16. package/src/DeduplicatedRequestHandler.ts +50 -1
  17. package/src/RedisStringsHandler.ts +330 -89
  18. package/src/SyncedMap.ts +74 -4
  19. package/src/index.ts +4 -2
  20. package/src/utils/debug.ts +30 -0
  21. package/src/utils/json.ts +26 -0
  22. package/test/integration/next-app/README.md +36 -0
  23. package/test/integration/next-app/eslint.config.mjs +16 -0
  24. package/test/integration/next-app/next.config.js +6 -0
  25. package/test/integration/next-app/package-lock.json +5833 -0
  26. package/test/integration/next-app/package.json +29 -0
  27. package/test/integration/next-app/pnpm-lock.yaml +3679 -0
  28. package/test/integration/next-app/postcss.config.mjs +5 -0
  29. package/test/integration/next-app/public/file.svg +1 -0
  30. package/test/integration/next-app/public/globe.svg +1 -0
  31. package/test/integration/next-app/public/next.svg +1 -0
  32. package/test/integration/next-app/public/vercel.svg +1 -0
  33. package/test/integration/next-app/public/window.svg +1 -0
  34. package/test/integration/next-app/src/app/api/cached-static-fetch/route.ts +18 -0
  35. package/test/integration/next-app/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +27 -0
  36. package/test/integration/next-app/src/app/api/revalidatePath/route.ts +15 -0
  37. package/test/integration/next-app/src/app/api/revalidateTag/route.ts +15 -0
  38. package/test/integration/next-app/src/app/api/revalidated-fetch/route.ts +17 -0
  39. package/test/integration/next-app/src/app/api/uncached-fetch/route.ts +15 -0
  40. package/test/integration/next-app/src/app/globals.css +26 -0
  41. package/test/integration/next-app/src/app/layout.tsx +59 -0
  42. package/test/integration/next-app/src/app/page.tsx +755 -0
  43. package/test/integration/next-app/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +19 -0
  44. package/test/integration/next-app/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +34 -0
  45. package/test/integration/next-app/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +25 -0
  46. package/test/integration/next-app/src/app/pages/no-fetch/default-page/page.tsx +55 -0
  47. package/test/integration/next-app/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +19 -0
  48. package/test/integration/next-app/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +35 -0
  49. package/test/integration/next-app/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +25 -0
  50. package/test/integration/next-app/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +19 -0
  51. package/test/integration/next-app/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +32 -0
  52. package/test/integration/next-app/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +25 -0
  53. package/test/integration/next-app/src/app/revalidation-interface.tsx +267 -0
  54. package/test/integration/next-app/tsconfig.json +27 -0
  55. package/test/integration/next-app-customized/README.md +36 -0
  56. package/test/integration/next-app-customized/customized-cache-handler.js +34 -0
  57. package/test/integration/next-app-customized/eslint.config.mjs +16 -0
  58. package/test/integration/next-app-customized/next.config.js +6 -0
  59. package/test/integration/nextjs-cache-handler.integration.test.ts +840 -0
  60. package/vite.config.ts +23 -8
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ RedisStringsHandler: () => RedisStringsHandler,
23
24
  default: () => index_default
24
25
  });
25
26
  module.exports = __toCommonJS(index_exports);
@@ -27,6 +28,27 @@ module.exports = __toCommonJS(index_exports);
27
28
  // src/RedisStringsHandler.ts
28
29
  var import_redis = require("redis");
29
30
 
31
+ // src/utils/debug.ts
32
+ function debug(color = "none", ...args) {
33
+ const colorCode = {
34
+ red: "\x1B[31m",
35
+ blue: "\x1B[34m",
36
+ green: "\x1B[32m",
37
+ yellow: "\x1B[33m",
38
+ cyan: "\x1B[36m",
39
+ white: "\x1B[37m",
40
+ none: ""
41
+ };
42
+ if (process.env.DEBUG_CACHE_HANDLER) {
43
+ console.log(colorCode[color], "DEBUG CACHE HANDLER: ", ...args);
44
+ }
45
+ }
46
+ function debugVerbose(color, ...args) {
47
+ if (process.env.DEBUG_CACHE_HANDLER_VERBOSE_VERBOSE) {
48
+ console.log("\x1B[35m", "DEBUG SYNCED MAP: ", ...args);
49
+ }
50
+ }
51
+
30
52
  // src/SyncedMap.ts
31
53
  var SYNC_CHANNEL_SUFFIX = ":sync-channel:";
32
54
  var SyncedMap = class {
@@ -142,24 +164,56 @@ var SyncedMap = class {
142
164
  }
143
165
  }
144
166
  };
145
- const keyEventHandler = async (_channel, message) => {
146
- const key = message;
167
+ const keyEventHandler = async (key, message) => {
168
+ debug(
169
+ "yellow",
170
+ "SyncedMap.keyEventHandler() called with message",
171
+ this.redisKey,
172
+ message,
173
+ key
174
+ );
147
175
  if (key.startsWith(this.keyPrefix)) {
148
176
  const keyInMap = key.substring(this.keyPrefix.length);
149
177
  if (this.filterKeys(keyInMap)) {
178
+ debugVerbose(
179
+ "SyncedMap.keyEventHandler() key matches filter and will be deleted",
180
+ this.redisKey,
181
+ message,
182
+ key
183
+ );
150
184
  await this.delete(keyInMap, true);
151
185
  }
186
+ } else {
187
+ debugVerbose(
188
+ "SyncedMap.keyEventHandler() key does not have prefix",
189
+ this.redisKey,
190
+ message,
191
+ key
192
+ );
152
193
  }
153
194
  };
154
195
  try {
155
- await this.subscriberClient.connect();
196
+ await this.subscriberClient.connect().catch(async () => {
197
+ await this.subscriberClient.connect();
198
+ });
199
+ const keyspaceEventConfig = (await this.subscriberClient.configGet("notify-keyspace-events"))?.["notify-keyspace-events"];
200
+ if (!keyspaceEventConfig.includes("E")) {
201
+ throw new Error(
202
+ "Keyspace event configuration has to include 'E' for Keyevent events, published with __keyevent@<db>__ prefix. We recommend to set it to 'Exe' like so `redis-cli -h localhost config set notify-keyspace-events Exe`"
203
+ );
204
+ }
205
+ if (!keyspaceEventConfig.includes("A") && !(keyspaceEventConfig.includes("x") && keyspaceEventConfig.includes("e"))) {
206
+ throw new Error(
207
+ "Keyspace event configuration has to include 'A' or 'x' and 'e' for expired and evicted events. We recommend to set it to 'Exe' like so `redis-cli -h localhost config set notify-keyspace-events Exe`"
208
+ );
209
+ }
156
210
  await Promise.all([
157
211
  // We use a custom channel for insert/delete For the following reason:
158
212
  // With custom channel we can delete multiple entries in one message. If we would listen to unlink / del we
159
213
  // could get thousands of messages for one revalidateTag (For example revalidateTag("algolia") would send an enormous amount of network packages)
160
214
  // Also we can send the value in the message for insert
161
215
  this.subscriberClient.subscribe(this.syncChannel, syncHandler),
162
- // Subscribe to Redis keyspace notifications for evicted and expired keys
216
+ // Subscribe to Redis keyevent notifications for evicted and expired keys
163
217
  this.subscriberClient.subscribe(
164
218
  `__keyevent@${this.database}__:evicted`,
165
219
  keyEventHandler
@@ -191,9 +245,19 @@ var SyncedMap = class {
191
245
  await this.setupLock;
192
246
  }
193
247
  get(key) {
248
+ debugVerbose(
249
+ "SyncedMap.get() called with key",
250
+ key,
251
+ JSON.stringify(this.map.get(key))?.substring(0, 100)
252
+ );
194
253
  return this.map.get(key);
195
254
  }
196
255
  async set(key, value) {
256
+ debugVerbose(
257
+ "SyncedMap.set() called with key",
258
+ key,
259
+ JSON.stringify(value)?.substring(0, 100)
260
+ );
197
261
  this.map.set(key, value);
198
262
  const operations = [];
199
263
  if (this.customizedSync?.withoutSetSync) {
@@ -220,7 +284,15 @@ var SyncedMap = class {
220
284
  );
221
285
  await Promise.all(operations);
222
286
  }
287
+ // /api/revalidated-fetch
288
+ // true
223
289
  async delete(keys, withoutSyncMessage = false) {
290
+ debugVerbose(
291
+ "SyncedMap.delete() called with keys",
292
+ this.redisKey,
293
+ keys,
294
+ withoutSyncMessage
295
+ );
224
296
  const keysArray = Array.isArray(keys) ? keys : [keys];
225
297
  const operations = [];
226
298
  for (const key of keysArray) {
@@ -242,6 +314,12 @@ var SyncedMap = class {
242
314
  );
243
315
  }
244
316
  await Promise.all(operations);
317
+ debugVerbose(
318
+ "SyncedMap.delete() finished operations",
319
+ this.redisKey,
320
+ keys,
321
+ operations.length
322
+ );
245
323
  }
246
324
  has(key) {
247
325
  return this.map.has(key);
@@ -256,19 +334,59 @@ var DeduplicatedRequestHandler = class {
256
334
  constructor(fn, cachingTimeMs, inMemoryDeduplicationCache) {
257
335
  // Method to handle deduplicated requests
258
336
  this.deduplicatedFunction = (key) => {
337
+ debugVerbose(
338
+ "DeduplicatedRequestHandler.deduplicatedFunction() called with",
339
+ key
340
+ );
259
341
  const self = this;
260
342
  const dedupedFn = async (...args) => {
343
+ debugVerbose(
344
+ "DeduplicatedRequestHandler.deduplicatedFunction().dedupedFn called with",
345
+ key
346
+ );
261
347
  if (self.inMemoryDeduplicationCache && self.inMemoryDeduplicationCache.has(key)) {
348
+ debugVerbose(
349
+ "DeduplicatedRequestHandler.deduplicatedFunction().dedupedFn ",
350
+ key,
351
+ "found key in inMemoryDeduplicationCache"
352
+ );
262
353
  const res = await self.inMemoryDeduplicationCache.get(key).then((v) => structuredClone(v));
354
+ debugVerbose(
355
+ "DeduplicatedRequestHandler.deduplicatedFunction().dedupedFn ",
356
+ key,
357
+ "found key in inMemoryDeduplicationCache and served result from there",
358
+ JSON.stringify(res).substring(0, 200)
359
+ );
263
360
  return res;
264
361
  }
265
362
  const promise = self.fn(...args);
266
363
  self.inMemoryDeduplicationCache.set(key, promise);
364
+ debugVerbose(
365
+ "DeduplicatedRequestHandler.deduplicatedFunction().dedupedFn ",
366
+ key,
367
+ "did not found key in inMemoryDeduplicationCache. Setting it now and waiting for promise to resolve"
368
+ );
267
369
  try {
370
+ const ts = performance.now();
268
371
  const result = await promise;
372
+ debugVerbose(
373
+ "DeduplicatedRequestHandler.deduplicatedFunction().dedupedFn ",
374
+ key,
375
+ "promise resolved (in ",
376
+ performance.now() - ts,
377
+ "ms). Returning result",
378
+ JSON.stringify(result).substring(0, 200)
379
+ );
269
380
  return structuredClone(result);
270
381
  } finally {
271
382
  setTimeout(() => {
383
+ debugVerbose(
384
+ "DeduplicatedRequestHandler.deduplicatedFunction().dedupedFn ",
385
+ key,
386
+ "deleting key from inMemoryDeduplicationCache after ",
387
+ self.cachingTimeMs,
388
+ "ms"
389
+ );
272
390
  self.inMemoryDeduplicationCache.delete(key);
273
391
  }, self.cachingTimeMs);
274
392
  }
@@ -283,18 +401,41 @@ var DeduplicatedRequestHandler = class {
283
401
  seedRequestReturn(key, value) {
284
402
  const resultPromise = new Promise((res) => res(value));
285
403
  this.inMemoryDeduplicationCache.set(key, resultPromise);
404
+ debugVerbose(
405
+ "DeduplicatedRequestHandler.seedRequestReturn() seeded result ",
406
+ key,
407
+ value.substring(0, 200)
408
+ );
286
409
  setTimeout(() => {
287
410
  this.inMemoryDeduplicationCache.delete(key);
288
411
  }, this.cachingTimeMs);
289
412
  }
290
413
  };
291
414
 
415
+ // src/utils/json.ts
416
+ function bufferReviver(_, value) {
417
+ if (value && typeof value === "object" && typeof value.$binary === "string") {
418
+ return Buffer.from(value.$binary, "base64");
419
+ }
420
+ return value;
421
+ }
422
+ function bufferReplacer(_, value) {
423
+ if (Buffer.isBuffer(value)) {
424
+ return {
425
+ $binary: value.toString("base64")
426
+ };
427
+ }
428
+ if (value && typeof value === "object" && value?.type === "Buffer" && Array.isArray(value.data)) {
429
+ return {
430
+ $binary: Buffer.from(value.data).toString("base64")
431
+ };
432
+ }
433
+ return value;
434
+ }
435
+
292
436
  // src/RedisStringsHandler.ts
293
437
  var NEXT_CACHE_IMPLICIT_TAG_ID = "_N_T_";
294
438
  var REVALIDATED_TAGS_KEY = "__revalidated_tags__";
295
- function isImplicitTag(tag) {
296
- return tag.startsWith(NEXT_CACHE_IMPLICIT_TAG_ID);
297
- }
298
439
  function getTimeoutRedisCommandOptions(timeoutMs) {
299
440
  return (0, import_redis.commandOptions)({ signal: AbortSignal.timeout(timeoutMs) });
300
441
  }
@@ -309,7 +450,7 @@ var RedisStringsHandler = class {
309
450
  redisGetDeduplication = true,
310
451
  inMemoryCachingTime = 1e4,
311
452
  defaultStaleAge = 60 * 60 * 24 * 14,
312
- estimateExpireAge = (staleAge) => process.env.VERCEL_ENV === "preview" ? staleAge * 1.2 : staleAge * 2
453
+ estimateExpireAge = (staleAge) => process.env.VERCEL_ENV === "production" ? staleAge * 2 : staleAge * 1.2
313
454
  }) {
314
455
  this.keyPrefix = keyPrefix;
315
456
  this.timeoutMs = timeoutMs;
@@ -327,9 +468,12 @@ var RedisStringsHandler = class {
327
468
  });
328
469
  this.client.connect().then(() => {
329
470
  console.info("Redis client connected.");
330
- }).catch((error) => {
331
- console.error("Failed to connect Redis client:", error);
332
- this.client.disconnect();
471
+ }).catch(() => {
472
+ this.client.connect().catch((error) => {
473
+ console.error("Failed to connect Redis client:", error);
474
+ this.client.disconnect();
475
+ throw error;
476
+ });
333
477
  });
334
478
  } catch (error) {
335
479
  console.error("Failed to initialize Redis client");
@@ -378,8 +522,7 @@ var RedisStringsHandler = class {
378
522
  this.redisGet = redisGet;
379
523
  this.deduplicatedRedisGet = this.redisDeduplicationHandler.deduplicatedFunction;
380
524
  }
381
- resetRequestCache(...args) {
382
- console.warn("WARNING resetRequestCache() was called", args);
525
+ resetRequestCache() {
383
526
  }
384
527
  async assertClientIsReady() {
385
528
  await Promise.all([
@@ -391,75 +534,153 @@ var RedisStringsHandler = class {
391
534
  }
392
535
  }
393
536
  async get(key, ctx) {
537
+ if (ctx.kind !== "APP_ROUTE" && ctx.kind !== "APP_PAGE" && ctx.kind !== "FETCH") {
538
+ console.warn(
539
+ "RedisStringsHandler.get() called with",
540
+ key,
541
+ ctx,
542
+ " this cache handler is only designed and tested for kind APP_ROUTE and APP_PAGE and not for kind ",
543
+ ctx?.kind
544
+ );
545
+ }
546
+ debug("green", "RedisStringsHandler.get() called with", key, ctx);
394
547
  await this.assertClientIsReady();
395
548
  const clientGet = this.redisGetDeduplication ? this.deduplicatedRedisGet(key) : this.redisGet;
396
- const result = await clientGet(
549
+ const serializedCacheEntry = await clientGet(
397
550
  getTimeoutRedisCommandOptions(this.timeoutMs),
398
551
  this.keyPrefix + key
399
552
  );
400
- if (!result) {
553
+ debug(
554
+ "green",
555
+ "RedisStringsHandler.get() finished with result (serializedCacheEntry)",
556
+ serializedCacheEntry?.substring(0, 200)
557
+ );
558
+ if (!serializedCacheEntry) {
401
559
  return null;
402
560
  }
403
- const cacheValue = JSON.parse(result);
404
- if (!cacheValue) {
561
+ const cacheEntry = JSON.parse(
562
+ serializedCacheEntry,
563
+ bufferReviver
564
+ );
565
+ debug(
566
+ "green",
567
+ "RedisStringsHandler.get() finished with result (cacheEntry)",
568
+ JSON.stringify(cacheEntry).substring(0, 200)
569
+ );
570
+ if (!cacheEntry) {
405
571
  return null;
406
572
  }
407
- if (cacheValue.value?.kind === "FETCH") {
408
- cacheValue.value.data.body = Buffer.from(
409
- cacheValue.value.data.body
410
- ).toString("base64");
573
+ if (!cacheEntry?.tags) {
574
+ console.warn(
575
+ "RedisStringsHandler.get() called with",
576
+ key,
577
+ ctx,
578
+ "cacheEntry is mall formed (missing tags)"
579
+ );
411
580
  }
412
- const combinedTags = /* @__PURE__ */ new Set([
413
- ...ctx?.softTags || [],
414
- ...ctx?.tags || []
415
- ]);
416
- if (combinedTags.size === 0) {
417
- return cacheValue;
581
+ if (!cacheEntry?.value) {
582
+ console.warn(
583
+ "RedisStringsHandler.get() called with",
584
+ key,
585
+ ctx,
586
+ "cacheEntry is mall formed (missing value)"
587
+ );
418
588
  }
419
- for (const tag of combinedTags) {
420
- const revalidationTime = this.revalidatedTagsMap.get(tag);
421
- if (revalidationTime && revalidationTime > cacheValue.lastModified) {
422
- const redisKey = this.keyPrefix + key;
423
- this.client.unlink(getTimeoutRedisCommandOptions(this.timeoutMs), redisKey).catch((err) => {
424
- console.error(
425
- "Error occurred while unlinking stale data. Retrying now. Error was:",
426
- err
427
- );
428
- this.client.unlink(
429
- getTimeoutRedisCommandOptions(this.timeoutMs),
430
- redisKey
589
+ if (!cacheEntry?.lastModified) {
590
+ console.warn(
591
+ "RedisStringsHandler.get() called with",
592
+ key,
593
+ ctx,
594
+ "cacheEntry is mall formed (missing lastModified)"
595
+ );
596
+ }
597
+ if (ctx.kind === "FETCH") {
598
+ const combinedTags = /* @__PURE__ */ new Set([
599
+ ...ctx?.softTags || [],
600
+ ...ctx?.tags || []
601
+ ]);
602
+ if (combinedTags.size === 0) {
603
+ return cacheEntry;
604
+ }
605
+ for (const tag of combinedTags) {
606
+ const revalidationTime = this.revalidatedTagsMap.get(tag);
607
+ if (revalidationTime && revalidationTime > cacheEntry.lastModified) {
608
+ const redisKey = this.keyPrefix + key;
609
+ this.client.unlink(getTimeoutRedisCommandOptions(this.timeoutMs), redisKey).catch((err) => {
610
+ console.error(
611
+ "Error occurred while unlinking stale data. Retrying now. Error was:",
612
+ err
613
+ );
614
+ this.client.unlink(
615
+ getTimeoutRedisCommandOptions(this.timeoutMs),
616
+ redisKey
617
+ );
618
+ }).finally(async () => {
619
+ await this.sharedTagsMap.delete(key);
620
+ await this.revalidatedTagsMap.delete(tag);
621
+ });
622
+ debug(
623
+ "green",
624
+ 'RedisStringsHandler.get() found revalidation time for tag. Cache entry is stale and will be deleted and "null" will be returned.',
625
+ tag,
626
+ redisKey,
627
+ revalidationTime,
628
+ cacheEntry
431
629
  );
432
- }).finally(async () => {
433
- await this.sharedTagsMap.delete(key);
434
- await this.revalidatedTagsMap.delete(tag);
435
- });
436
- return null;
630
+ return null;
631
+ }
437
632
  }
438
633
  }
439
- return cacheValue;
634
+ return cacheEntry;
440
635
  }
441
636
  async set(key, data, ctx) {
442
- if (data.kind === "FETCH") {
443
- console.time("encoding" + key);
444
- data.data.body = Buffer.from(data.data.body, "base64").toString();
445
- console.timeEnd("encoding" + key);
637
+ if (data.kind !== "APP_ROUTE" && data.kind !== "APP_PAGE" && data.kind !== "FETCH") {
638
+ console.warn(
639
+ "RedisStringsHandler.set() called with",
640
+ key,
641
+ ctx,
642
+ data,
643
+ " this cache handler is only designed and tested for kind APP_ROUTE and APP_PAGE and not for kind ",
644
+ data?.kind
645
+ );
446
646
  }
447
647
  await this.assertClientIsReady();
448
- data.lastModified = Date.now();
449
- const value = JSON.stringify(data);
648
+ if (data.kind === "APP_PAGE" || data.kind === "APP_ROUTE") {
649
+ const tags = data.headers["x-next-cache-tags"]?.split(",");
650
+ ctx.tags = [...ctx.tags || [], ...tags || []];
651
+ }
652
+ const cacheEntry = {
653
+ lastModified: Date.now(),
654
+ tags: ctx?.tags || [],
655
+ value: data
656
+ };
657
+ const serializedCacheEntry = JSON.stringify(cacheEntry, bufferReplacer);
450
658
  if (this.redisGetDeduplication) {
451
- this.redisDeduplicationHandler.seedRequestReturn(key, value);
659
+ this.redisDeduplicationHandler.seedRequestReturn(
660
+ key,
661
+ serializedCacheEntry
662
+ );
452
663
  }
453
664
  const expireAt = ctx.revalidate && Number.isSafeInteger(ctx.revalidate) && ctx.revalidate > 0 ? this.estimateExpireAge(ctx.revalidate) : this.estimateExpireAge(this.defaultStaleAge);
454
665
  const options = getTimeoutRedisCommandOptions(this.timeoutMs);
455
666
  const setOperation = this.client.set(
456
667
  options,
457
668
  this.keyPrefix + key,
458
- value,
669
+ serializedCacheEntry,
459
670
  {
460
671
  EX: expireAt
461
672
  }
462
673
  );
674
+ debug(
675
+ "blue",
676
+ "RedisStringsHandler.set() will set the following serializedCacheEntry",
677
+ this.keyPrefix,
678
+ key,
679
+ data,
680
+ ctx,
681
+ serializedCacheEntry?.substring(0, 200),
682
+ expireAt
683
+ );
463
684
  let setTagsOperation;
464
685
  if (ctx.tags && ctx.tags.length > 0) {
465
686
  const currentTags = this.sharedTagsMap.get(key);
@@ -471,27 +692,54 @@ var RedisStringsHandler = class {
471
692
  );
472
693
  }
473
694
  }
695
+ debug(
696
+ "blue",
697
+ "RedisStringsHandler.set() will set the following sharedTagsMap",
698
+ key,
699
+ ctx.tags
700
+ );
474
701
  await Promise.all([setOperation, setTagsOperation]);
475
702
  }
476
- async revalidateTag(tagOrTags) {
703
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
704
+ async revalidateTag(tagOrTags, ...rest) {
705
+ debug(
706
+ "red",
707
+ "RedisStringsHandler.revalidateTag() called with",
708
+ tagOrTags,
709
+ rest
710
+ );
477
711
  const tags = new Set([tagOrTags || []].flat());
478
712
  await this.assertClientIsReady();
713
+ const keysToDelete = /* @__PURE__ */ new Set();
479
714
  for (const tag of tags) {
480
- if (isImplicitTag(tag)) {
715
+ if (tag.startsWith(NEXT_CACHE_IMPLICIT_TAG_ID)) {
481
716
  const now = Date.now();
717
+ debug(
718
+ "red",
719
+ "RedisStringsHandler.revalidateTag() set revalidation time for tag",
720
+ tag,
721
+ "to",
722
+ now
723
+ );
482
724
  await this.revalidatedTagsMap.set(tag, now);
483
725
  }
484
726
  }
485
- const keysToDelete = [];
486
727
  for (const [key, sharedTags] of this.sharedTagsMap.entries()) {
487
728
  if (sharedTags.some((tag) => tags.has(tag))) {
488
- keysToDelete.push(key);
729
+ keysToDelete.add(key);
489
730
  }
490
731
  }
491
- if (keysToDelete.length === 0) {
732
+ debug(
733
+ "red",
734
+ "RedisStringsHandler.revalidateTag() found",
735
+ keysToDelete,
736
+ "keys to delete"
737
+ );
738
+ if (keysToDelete.size === 0) {
492
739
  return;
493
740
  }
494
- const fullRedisKeys = keysToDelete.map((key) => this.keyPrefix + key);
741
+ const redisKeys = Array.from(keysToDelete);
742
+ const fullRedisKeys = redisKeys.map((key) => this.keyPrefix + key);
495
743
  const options = getTimeoutRedisCommandOptions(this.timeoutMs);
496
744
  const deleteKeysOperation = this.client.unlink(options, fullRedisKeys);
497
745
  if (this.redisGetDeduplication && this.inMemoryCachingTime > 0) {
@@ -499,8 +747,12 @@ var RedisStringsHandler = class {
499
747
  this.inMemoryDeduplicationCache.delete(key);
500
748
  }
501
749
  }
502
- const deleteTagsOperation = this.sharedTagsMap.delete(keysToDelete);
750
+ const deleteTagsOperation = this.sharedTagsMap.delete(redisKeys);
503
751
  await Promise.all([deleteKeysOperation, deleteTagsOperation]);
752
+ debug(
753
+ "red",
754
+ "RedisStringsHandler.revalidateTag() finished delete operations"
755
+ );
504
756
  }
505
757
  };
506
758
 
@@ -514,12 +766,15 @@ var CachedHandler = class {
514
766
  }
515
767
  }
516
768
  get(...args) {
769
+ debugVerbose("CachedHandler.get called with", args);
517
770
  return cachedHandler.get(...args);
518
771
  }
519
772
  set(...args) {
773
+ debugVerbose("CachedHandler.set called with", args);
520
774
  return cachedHandler.set(...args);
521
775
  }
522
776
  revalidateTag(...args) {
777
+ debugVerbose("CachedHandler.revalidateTag called with", args);
523
778
  return cachedHandler.revalidateTag(...args);
524
779
  }
525
780
  resetRequestCache(...args) {
@@ -529,4 +784,8 @@ var CachedHandler = class {
529
784
 
530
785
  // src/index.ts
531
786
  var index_default = CachedHandler;
787
+ // Annotate the CommonJS export names for ESM import in node:
788
+ 0 && (module.exports = {
789
+ RedisStringsHandler
790
+ });
532
791
  //# sourceMappingURL=index.js.map