@trieb.work/nextjs-turbo-redis-cache 1.2.1 → 1.4.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/.github/workflows/ci.yml +31 -6
- package/.github/workflows/release.yml +7 -3
- package/.next/trace +11 -0
- package/.vscode/settings.json +10 -0
- package/CHANGELOG.md +71 -0
- package/README.md +154 -34
- package/dist/index.d.mts +96 -20
- package/dist/index.d.ts +96 -20
- package/dist/index.js +317 -61
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +313 -61
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -7
- package/scripts/vitest-run-staged.cjs +1 -1
- package/src/CachedHandler.ts +23 -9
- package/src/DeduplicatedRequestHandler.ts +50 -1
- package/src/RedisStringsHandler.ts +331 -91
- package/src/SyncedMap.ts +74 -4
- package/src/ZodHandler.ts +45 -0
- package/src/index.ts +4 -2
- package/src/utils/debug.ts +30 -0
- package/src/utils/json.ts +26 -0
- package/test/integration/next-app-15-0-3/README.md +36 -0
- package/test/integration/next-app-15-0-3/eslint.config.mjs +16 -0
- package/test/integration/next-app-15-0-3/next.config.js +6 -0
- package/test/integration/next-app-15-0-3/package-lock.json +5833 -0
- package/test/integration/next-app-15-0-3/package.json +29 -0
- package/test/integration/next-app-15-0-3/pnpm-lock.yaml +3679 -0
- package/test/integration/next-app-15-0-3/postcss.config.mjs +5 -0
- package/test/integration/next-app-15-0-3/public/file.svg +1 -0
- package/test/integration/next-app-15-0-3/public/globe.svg +1 -0
- package/test/integration/next-app-15-0-3/public/next.svg +1 -0
- package/test/integration/next-app-15-0-3/public/vercel.svg +1 -0
- package/test/integration/next-app-15-0-3/public/window.svg +1 -0
- package/test/integration/next-app-15-0-3/src/app/api/cached-static-fetch/route.ts +18 -0
- package/test/integration/next-app-15-0-3/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +27 -0
- package/test/integration/next-app-15-0-3/src/app/api/revalidatePath/route.ts +15 -0
- package/test/integration/next-app-15-0-3/src/app/api/revalidateTag/route.ts +15 -0
- package/test/integration/next-app-15-0-3/src/app/api/revalidated-fetch/route.ts +17 -0
- package/test/integration/next-app-15-0-3/src/app/api/uncached-fetch/route.ts +15 -0
- package/test/integration/next-app-15-0-3/src/app/globals.css +26 -0
- package/test/integration/next-app-15-0-3/src/app/layout.tsx +59 -0
- package/test/integration/next-app-15-0-3/src/app/page.tsx +755 -0
- package/test/integration/next-app-15-0-3/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +19 -0
- package/test/integration/next-app-15-0-3/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +34 -0
- package/test/integration/next-app-15-0-3/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +25 -0
- package/test/integration/next-app-15-0-3/src/app/pages/no-fetch/default-page/page.tsx +55 -0
- package/test/integration/next-app-15-0-3/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +19 -0
- package/test/integration/next-app-15-0-3/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +35 -0
- package/test/integration/next-app-15-0-3/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +25 -0
- package/test/integration/next-app-15-0-3/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +19 -0
- package/test/integration/next-app-15-0-3/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +32 -0
- package/test/integration/next-app-15-0-3/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +25 -0
- package/test/integration/next-app-15-0-3/src/app/revalidation-interface.tsx +267 -0
- package/test/integration/next-app-15-0-3/tsconfig.json +27 -0
- package/test/integration/next-app-15-3-2/README.md +36 -0
- package/test/integration/next-app-15-3-2/eslint.config.mjs +16 -0
- package/test/integration/next-app-15-3-2/next.config.js +6 -0
- package/test/integration/next-app-15-3-2/package-lock.json +5969 -0
- package/test/integration/next-app-15-3-2/package.json +33 -0
- package/test/integration/next-app-15-3-2/pnpm-lock.yaml +3688 -0
- package/test/integration/next-app-15-3-2/postcss.config.mjs +5 -0
- package/test/integration/next-app-15-3-2/public/file.svg +1 -0
- package/test/integration/next-app-15-3-2/public/globe.svg +1 -0
- package/test/integration/next-app-15-3-2/public/next.svg +1 -0
- package/test/integration/next-app-15-3-2/public/vercel.svg +1 -0
- package/test/integration/next-app-15-3-2/public/window.svg +1 -0
- package/test/integration/next-app-15-3-2/src/app/api/cached-static-fetch/route.ts +18 -0
- package/test/integration/next-app-15-3-2/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +27 -0
- package/test/integration/next-app-15-3-2/src/app/api/revalidatePath/route.ts +15 -0
- package/test/integration/next-app-15-3-2/src/app/api/revalidateTag/route.ts +15 -0
- package/test/integration/next-app-15-3-2/src/app/api/revalidated-fetch/route.ts +17 -0
- package/test/integration/next-app-15-3-2/src/app/api/uncached-fetch/route.ts +15 -0
- package/test/integration/next-app-15-3-2/src/app/globals.css +26 -0
- package/test/integration/next-app-15-3-2/src/app/layout.tsx +59 -0
- package/test/integration/next-app-15-3-2/src/app/page.tsx +755 -0
- package/test/integration/next-app-15-3-2/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +19 -0
- package/test/integration/next-app-15-3-2/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +34 -0
- package/test/integration/next-app-15-3-2/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +25 -0
- package/test/integration/next-app-15-3-2/src/app/pages/no-fetch/default-page/page.tsx +55 -0
- package/test/integration/next-app-15-3-2/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +19 -0
- package/test/integration/next-app-15-3-2/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +35 -0
- package/test/integration/next-app-15-3-2/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +25 -0
- package/test/integration/next-app-15-3-2/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +19 -0
- package/test/integration/next-app-15-3-2/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +32 -0
- package/test/integration/next-app-15-3-2/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +25 -0
- package/test/integration/next-app-15-3-2/src/app/revalidation-interface.tsx +267 -0
- package/test/integration/next-app-15-3-2/tsconfig.json +27 -0
- package/test/integration/next-app-customized/README.md +36 -0
- package/test/integration/next-app-customized/customized-cache-handler.js +34 -0
- package/test/integration/next-app-customized/eslint.config.mjs +16 -0
- package/test/integration/next-app-customized/next.config.js +6 -0
- package/test/integration/nextjs-cache-handler.integration.test.ts +859 -0
- 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 (
|
|
146
|
-
|
|
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
|
|
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 === "
|
|
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((
|
|
331
|
-
|
|
332
|
-
|
|
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(
|
|
382
|
-
console.warn("WARNING resetRequestCache() was called", args);
|
|
525
|
+
resetRequestCache() {
|
|
383
526
|
}
|
|
384
527
|
async assertClientIsReady() {
|
|
385
528
|
await Promise.all([
|
|
@@ -391,75 +534,150 @@ 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
|
|
549
|
+
const serializedCacheEntry = await clientGet(
|
|
397
550
|
getTimeoutRedisCommandOptions(this.timeoutMs),
|
|
398
551
|
this.keyPrefix + key
|
|
399
552
|
);
|
|
400
|
-
|
|
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
|
|
404
|
-
|
|
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 (
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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. Error was:",
|
|
612
|
+
err
|
|
613
|
+
);
|
|
614
|
+
}).finally(async () => {
|
|
615
|
+
await this.sharedTagsMap.delete(key);
|
|
616
|
+
await this.revalidatedTagsMap.delete(tag);
|
|
617
|
+
});
|
|
618
|
+
debug(
|
|
619
|
+
"green",
|
|
620
|
+
'RedisStringsHandler.get() found revalidation time for tag. Cache entry is stale and will be deleted and "null" will be returned.',
|
|
621
|
+
tag,
|
|
622
|
+
redisKey,
|
|
623
|
+
revalidationTime,
|
|
624
|
+
cacheEntry
|
|
431
625
|
);
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
await this.revalidatedTagsMap.delete(tag);
|
|
435
|
-
});
|
|
436
|
-
return null;
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
437
628
|
}
|
|
438
629
|
}
|
|
439
|
-
return
|
|
630
|
+
return cacheEntry;
|
|
440
631
|
}
|
|
441
632
|
async set(key, data, ctx) {
|
|
442
|
-
if (data.kind
|
|
443
|
-
console.
|
|
444
|
-
|
|
445
|
-
|
|
633
|
+
if (data.kind !== "APP_ROUTE" && data.kind !== "APP_PAGE" && data.kind !== "FETCH") {
|
|
634
|
+
console.warn(
|
|
635
|
+
"RedisStringsHandler.set() called with",
|
|
636
|
+
key,
|
|
637
|
+
ctx,
|
|
638
|
+
data,
|
|
639
|
+
" this cache handler is only designed and tested for kind APP_ROUTE and APP_PAGE and not for kind ",
|
|
640
|
+
data?.kind
|
|
641
|
+
);
|
|
446
642
|
}
|
|
447
643
|
await this.assertClientIsReady();
|
|
448
|
-
data.
|
|
449
|
-
|
|
644
|
+
if (data.kind === "APP_PAGE" || data.kind === "APP_ROUTE") {
|
|
645
|
+
const tags = data.headers["x-next-cache-tags"]?.split(",");
|
|
646
|
+
ctx.tags = [...ctx.tags || [], ...tags || []];
|
|
647
|
+
}
|
|
648
|
+
const cacheEntry = {
|
|
649
|
+
lastModified: Date.now(),
|
|
650
|
+
tags: ctx?.tags || [],
|
|
651
|
+
value: data
|
|
652
|
+
};
|
|
653
|
+
const serializedCacheEntry = JSON.stringify(cacheEntry, bufferReplacer);
|
|
450
654
|
if (this.redisGetDeduplication) {
|
|
451
|
-
this.redisDeduplicationHandler.seedRequestReturn(
|
|
655
|
+
this.redisDeduplicationHandler.seedRequestReturn(
|
|
656
|
+
key,
|
|
657
|
+
serializedCacheEntry
|
|
658
|
+
);
|
|
452
659
|
}
|
|
453
|
-
const
|
|
660
|
+
const revalidate = ctx.revalidate || ctx.cacheControl?.revalidate;
|
|
661
|
+
const expireAt = revalidate && Number.isSafeInteger(revalidate) && revalidate > 0 ? this.estimateExpireAge(revalidate) : this.estimateExpireAge(this.defaultStaleAge);
|
|
454
662
|
const options = getTimeoutRedisCommandOptions(this.timeoutMs);
|
|
455
663
|
const setOperation = this.client.set(
|
|
456
664
|
options,
|
|
457
665
|
this.keyPrefix + key,
|
|
458
|
-
|
|
666
|
+
serializedCacheEntry,
|
|
459
667
|
{
|
|
460
668
|
EX: expireAt
|
|
461
669
|
}
|
|
462
670
|
);
|
|
671
|
+
debug(
|
|
672
|
+
"blue",
|
|
673
|
+
"RedisStringsHandler.set() will set the following serializedCacheEntry",
|
|
674
|
+
this.keyPrefix,
|
|
675
|
+
key,
|
|
676
|
+
data,
|
|
677
|
+
ctx,
|
|
678
|
+
serializedCacheEntry?.substring(0, 200),
|
|
679
|
+
expireAt
|
|
680
|
+
);
|
|
463
681
|
let setTagsOperation;
|
|
464
682
|
if (ctx.tags && ctx.tags.length > 0) {
|
|
465
683
|
const currentTags = this.sharedTagsMap.get(key);
|
|
@@ -471,27 +689,54 @@ var RedisStringsHandler = class {
|
|
|
471
689
|
);
|
|
472
690
|
}
|
|
473
691
|
}
|
|
692
|
+
debug(
|
|
693
|
+
"blue",
|
|
694
|
+
"RedisStringsHandler.set() will set the following sharedTagsMap",
|
|
695
|
+
key,
|
|
696
|
+
ctx.tags
|
|
697
|
+
);
|
|
474
698
|
await Promise.all([setOperation, setTagsOperation]);
|
|
475
699
|
}
|
|
476
|
-
|
|
700
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
701
|
+
async revalidateTag(tagOrTags, ...rest) {
|
|
702
|
+
debug(
|
|
703
|
+
"red",
|
|
704
|
+
"RedisStringsHandler.revalidateTag() called with",
|
|
705
|
+
tagOrTags,
|
|
706
|
+
rest
|
|
707
|
+
);
|
|
477
708
|
const tags = new Set([tagOrTags || []].flat());
|
|
478
709
|
await this.assertClientIsReady();
|
|
710
|
+
const keysToDelete = /* @__PURE__ */ new Set();
|
|
479
711
|
for (const tag of tags) {
|
|
480
|
-
if (
|
|
712
|
+
if (tag.startsWith(NEXT_CACHE_IMPLICIT_TAG_ID)) {
|
|
481
713
|
const now = Date.now();
|
|
714
|
+
debug(
|
|
715
|
+
"red",
|
|
716
|
+
"RedisStringsHandler.revalidateTag() set revalidation time for tag",
|
|
717
|
+
tag,
|
|
718
|
+
"to",
|
|
719
|
+
now
|
|
720
|
+
);
|
|
482
721
|
await this.revalidatedTagsMap.set(tag, now);
|
|
483
722
|
}
|
|
484
723
|
}
|
|
485
|
-
const keysToDelete = [];
|
|
486
724
|
for (const [key, sharedTags] of this.sharedTagsMap.entries()) {
|
|
487
725
|
if (sharedTags.some((tag) => tags.has(tag))) {
|
|
488
|
-
keysToDelete.
|
|
726
|
+
keysToDelete.add(key);
|
|
489
727
|
}
|
|
490
728
|
}
|
|
491
|
-
|
|
729
|
+
debug(
|
|
730
|
+
"red",
|
|
731
|
+
"RedisStringsHandler.revalidateTag() found",
|
|
732
|
+
keysToDelete,
|
|
733
|
+
"keys to delete"
|
|
734
|
+
);
|
|
735
|
+
if (keysToDelete.size === 0) {
|
|
492
736
|
return;
|
|
493
737
|
}
|
|
494
|
-
const
|
|
738
|
+
const redisKeys = Array.from(keysToDelete);
|
|
739
|
+
const fullRedisKeys = redisKeys.map((key) => this.keyPrefix + key);
|
|
495
740
|
const options = getTimeoutRedisCommandOptions(this.timeoutMs);
|
|
496
741
|
const deleteKeysOperation = this.client.unlink(options, fullRedisKeys);
|
|
497
742
|
if (this.redisGetDeduplication && this.inMemoryCachingTime > 0) {
|
|
@@ -499,8 +744,12 @@ var RedisStringsHandler = class {
|
|
|
499
744
|
this.inMemoryDeduplicationCache.delete(key);
|
|
500
745
|
}
|
|
501
746
|
}
|
|
502
|
-
const deleteTagsOperation = this.sharedTagsMap.delete(
|
|
747
|
+
const deleteTagsOperation = this.sharedTagsMap.delete(redisKeys);
|
|
503
748
|
await Promise.all([deleteKeysOperation, deleteTagsOperation]);
|
|
749
|
+
debug(
|
|
750
|
+
"red",
|
|
751
|
+
"RedisStringsHandler.revalidateTag() finished delete operations"
|
|
752
|
+
);
|
|
504
753
|
}
|
|
505
754
|
};
|
|
506
755
|
|
|
@@ -514,12 +763,15 @@ var CachedHandler = class {
|
|
|
514
763
|
}
|
|
515
764
|
}
|
|
516
765
|
get(...args) {
|
|
766
|
+
debugVerbose("CachedHandler.get called with", args);
|
|
517
767
|
return cachedHandler.get(...args);
|
|
518
768
|
}
|
|
519
769
|
set(...args) {
|
|
770
|
+
debugVerbose("CachedHandler.set called with", args);
|
|
520
771
|
return cachedHandler.set(...args);
|
|
521
772
|
}
|
|
522
773
|
revalidateTag(...args) {
|
|
774
|
+
debugVerbose("CachedHandler.revalidateTag called with", args);
|
|
523
775
|
return cachedHandler.revalidateTag(...args);
|
|
524
776
|
}
|
|
525
777
|
resetRequestCache(...args) {
|
|
@@ -529,4 +781,8 @@ var CachedHandler = class {
|
|
|
529
781
|
|
|
530
782
|
// src/index.ts
|
|
531
783
|
var index_default = CachedHandler;
|
|
784
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
785
|
+
0 && (module.exports = {
|
|
786
|
+
RedisStringsHandler
|
|
787
|
+
});
|
|
532
788
|
//# sourceMappingURL=index.js.map
|