@trieb.work/nextjs-turbo-redis-cache 1.16.1 → 1.17.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 +11 -2
- package/CHANGELOG.md +14 -0
- package/README.md +4 -1
- package/dist/index.js +20 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +20 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/RedisStringsHandler.ts +26 -15
- package/test/README.md +3 -3
- package/test/nextjs-test-projects/next-app-15-4-11/pnpm-lock.yaml +1 -1
- package/test/nextjs-test-projects/next-app-16-0-11/pnpm-lock.yaml +1 -1
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/pnpm-lock.yaml +1 -1
- package/test/nextjs-test-projects/next-app-16-2-6/pnpm-lock.yaml +1 -1
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/pnpm-lock.yaml +1 -1
- package/test/nextjs-test-projects/next-app-16-3-0/README.md +36 -0
- package/test/nextjs-test-projects/next-app-16-3-0/eslint.config.mjs +18 -0
- package/test/nextjs-test-projects/next-app-16-3-0/next.config.ts +7 -0
- package/test/nextjs-test-projects/next-app-16-3-0/package.json +28 -0
- package/test/nextjs-test-projects/next-app-16-3-0/pnpm-lock.yaml +4284 -0
- package/test/nextjs-test-projects/next-app-16-3-0/postcss.config.mjs +7 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/cached-static-fetch/route.ts +18 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +27 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/revalidatePath/route.ts +15 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/revalidateTag/route.ts +20 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/revalidated-fetch/route.ts +17 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/uncached-fetch/route.ts +15 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/favicon.ico +0 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/globals.css +26 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/layout.tsx +59 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/page.tsx +755 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +19 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +34 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +25 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/no-fetch/default-page/page.tsx +55 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +19 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +35 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +25 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +19 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +32 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +25 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/revalidation-interface.tsx +267 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/update-tag-test/page.tsx +25 -0
- package/test/nextjs-test-projects/next-app-16-3-0/tsconfig.json +34 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/README.md +36 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/cache-handler.js +3 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/eslint.config.mjs +18 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/next.config.ts +13 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/package.json +28 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/pnpm-lock.yaml +4284 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/postcss.config.mjs +7 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/file.svg +1 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/globe.svg +1 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/next.svg +1 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/vercel.svg +1 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/window.svg +1 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/cached-static-fetch/route.ts +19 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/cached-with-cachelife/route.ts +24 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/cached-with-tag/route.ts +21 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/revalidate-tag/route.ts +19 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/revalidated-fetch/route.ts +19 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/cachelife-short/page.tsx +110 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/page.tsx +90 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/runtime-data-suspense/page.tsx +127 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/stale-while-revalidate/page.tsx +130 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/tag-invalidation/page.tsx +127 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/use-cache-nondeterministic/page.tsx +110 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/favicon.ico +0 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/globals.css +26 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/layout.tsx +57 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/page.tsx +755 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/revalidation-interface.tsx +267 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/update-tag-test/page.tsx +22 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/tsconfig.json +34 -0
- package/test/nextjs-test-projects/next-pages-16-2-6/pnpm-lock.yaml +4 -4
- package/test/nextjs-test-projects/next-pages-16-3-0/README.md +16 -0
- package/test/nextjs-test-projects/next-pages-16-3-0/eslint.config.mjs +18 -0
- package/test/nextjs-test-projects/next-pages-16-3-0/next.config.ts +7 -0
- package/test/nextjs-test-projects/next-pages-16-3-0/package.json +26 -0
- package/test/nextjs-test-projects/next-pages-16-3-0/pnpm-lock.yaml +3939 -0
- package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/api/revalidate.ts +24 -0
- package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/index.tsx +11 -0
- package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/isr/[slug].tsx +49 -0
- package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/static-forever.tsx +20 -0
- package/test/nextjs-test-projects/next-pages-16-3-0/tsconfig.json +34 -0
- package/test/vitest/integration/nextjs-cache-handler.integration.test.ts +51 -32
- package/test/vitest/unit/index.test.ts +28 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
|
|
3
|
+
// On-demand revalidation endpoint: res.revalidate(path) re-renders the page
|
|
4
|
+
// and writes the fresh entry through the cache handler, so all instances
|
|
5
|
+
// sharing the same Redis pick it up.
|
|
6
|
+
export default async function handler(
|
|
7
|
+
req: NextApiRequest,
|
|
8
|
+
res: NextApiResponse,
|
|
9
|
+
) {
|
|
10
|
+
const path = req.query.path;
|
|
11
|
+
if (typeof path !== 'string') {
|
|
12
|
+
return res
|
|
13
|
+
.status(400)
|
|
14
|
+
.json({ revalidated: false, error: 'Missing ?path= query parameter' });
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
await res.revalidate(path);
|
|
18
|
+
return res.json({ revalidated: true, path });
|
|
19
|
+
} catch (err) {
|
|
20
|
+
return res
|
|
21
|
+
.status(500)
|
|
22
|
+
.json({ revalidated: false, path, error: String(err) });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { GetStaticPaths, GetStaticProps } from 'next';
|
|
2
|
+
|
|
3
|
+
type Props = { slug: string; timestamp: number; counter: number };
|
|
4
|
+
|
|
5
|
+
// Per-process regeneration counter. Two server instances of the same build
|
|
6
|
+
// have independent counters, while the timestamp proves cross-instance
|
|
7
|
+
// freshness after on-demand revalidation.
|
|
8
|
+
let regenerationCounter = 0;
|
|
9
|
+
|
|
10
|
+
export default function IsrPage({ slug, timestamp, counter }: Props) {
|
|
11
|
+
return (
|
|
12
|
+
<main>
|
|
13
|
+
<h1>ISR page</h1>
|
|
14
|
+
<p>Slug: {slug}</p>
|
|
15
|
+
<p>Timestamp: {timestamp}</p>
|
|
16
|
+
<p>Counter: {counter}</p>
|
|
17
|
+
</main>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const getStaticPaths: GetStaticPaths = async () => {
|
|
22
|
+
return {
|
|
23
|
+
paths: [{ params: { slug: 'prebuilt' } }],
|
|
24
|
+
fallback: 'blocking',
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// revalidate is set high (300s) so that natural ISR regeneration does not
|
|
29
|
+
// interfere with the on-demand revalidation (res.revalidate) tests.
|
|
30
|
+
export const getStaticProps: GetStaticProps<Props> = async (context) => {
|
|
31
|
+
const slug = context.params?.slug as string;
|
|
32
|
+
|
|
33
|
+
if (slug === 'not-found') {
|
|
34
|
+
return { notFound: true, revalidate: 300 };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (slug === 'redirect') {
|
|
38
|
+
return {
|
|
39
|
+
redirect: { destination: '/static-forever', permanent: false },
|
|
40
|
+
revalidate: 300,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
regenerationCounter += 1;
|
|
45
|
+
return {
|
|
46
|
+
props: { slug, timestamp: Date.now(), counter: regenerationCounter },
|
|
47
|
+
revalidate: 300,
|
|
48
|
+
};
|
|
49
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { GetStaticProps } from 'next';
|
|
2
|
+
|
|
3
|
+
type Props = { timestamp: number };
|
|
4
|
+
|
|
5
|
+
export default function StaticForever({ timestamp }: Props) {
|
|
6
|
+
return (
|
|
7
|
+
<main>
|
|
8
|
+
<h1>Static forever</h1>
|
|
9
|
+
<p>Timestamp: {timestamp}</p>
|
|
10
|
+
</main>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// revalidate is intentionally omitted (revalidate: false): the cache entry
|
|
15
|
+
// must fall through to the defaultStaleAge based TTL in the cache handler.
|
|
16
|
+
export const getStaticProps: GetStaticProps<Props> = async () => {
|
|
17
|
+
return {
|
|
18
|
+
props: { timestamp: Date.now() },
|
|
19
|
+
};
|
|
20
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [
|
|
17
|
+
{
|
|
18
|
+
"name": "next"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["./src/*"]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"include": [
|
|
26
|
+
"next-env.d.ts",
|
|
27
|
+
"**/*.ts",
|
|
28
|
+
"**/*.tsx",
|
|
29
|
+
".next/types/**/*.ts",
|
|
30
|
+
".next/dev/types/**/*.ts",
|
|
31
|
+
"**/*.mts"
|
|
32
|
+
],
|
|
33
|
+
"exclude": ["node_modules"]
|
|
34
|
+
}
|
|
@@ -7,7 +7,7 @@ import { CacheEntry } from '../../../src/RedisStringsHandler';
|
|
|
7
7
|
import { revalidate as revalidatedFetchRouteRevalidate } from '../../nextjs-test-projects/next-app-15-4-11/src/app/api/revalidated-fetch/route';
|
|
8
8
|
|
|
9
9
|
// Select which Next.js test app to use. Can be overridden via NEXT_TEST_APP env var
|
|
10
|
-
// Examples: next-app-15-4-11, next-app-16-0-11, next-app-16-2-6
|
|
10
|
+
// Examples: next-app-15-4-11, next-app-16-0-11, next-app-16-2-6, next-app-16-3-0
|
|
11
11
|
const NEXT_TEST_APP = process.env.NEXT_TEST_APP || 'next-app-15-4-11';
|
|
12
12
|
const NEXT_APP_DIR = join(
|
|
13
13
|
__dirname,
|
|
@@ -17,6 +17,8 @@ const NEXT_APP_DIR = join(
|
|
|
17
17
|
NEXT_TEST_APP,
|
|
18
18
|
);
|
|
19
19
|
console.log('NEXT_APP_DIR', NEXT_APP_DIR);
|
|
20
|
+
|
|
21
|
+
const SHARED_TAGS_KEY = '__sharedTags__';
|
|
20
22
|
const NEXT_START_PORT = 3055;
|
|
21
23
|
const NEXT_START_URL = `http://localhost:${NEXT_START_PORT}`;
|
|
22
24
|
|
|
@@ -29,6 +31,20 @@ async function delay(ms: number) {
|
|
|
29
31
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
async function findFetchCacheEntryKey(tag: string): Promise<string> {
|
|
35
|
+
const sharedTags = await redisClient.hGetAll(
|
|
36
|
+
process.env.VERCEL_URL + SHARED_TAGS_KEY,
|
|
37
|
+
);
|
|
38
|
+
const matches = Object.entries(sharedTags).filter(
|
|
39
|
+
([key, serializedTags]) =>
|
|
40
|
+
!key.startsWith('/') &&
|
|
41
|
+
(JSON.parse(serializedTags) as string[]).includes(tag),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
expect(matches).toHaveLength(1);
|
|
45
|
+
return matches[0]![0];
|
|
46
|
+
}
|
|
47
|
+
|
|
32
48
|
async function runCommand(cmd: string, args: string[], cwd: string) {
|
|
33
49
|
return new Promise((resolve, reject) => {
|
|
34
50
|
let stderr = '';
|
|
@@ -64,7 +80,7 @@ async function runCommand(cmd: string, args: string[], cwd: string) {
|
|
|
64
80
|
});
|
|
65
81
|
}
|
|
66
82
|
|
|
67
|
-
async function waitForServer(url, timeout = 20000) {
|
|
83
|
+
async function waitForServer(url: string, timeout = 20000) {
|
|
68
84
|
const start = Date.now();
|
|
69
85
|
while (Date.now() - start < timeout) {
|
|
70
86
|
try {
|
|
@@ -226,7 +242,7 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
226
242
|
expect(keys.length).toBe(0);
|
|
227
243
|
|
|
228
244
|
const hashmap = await redisClient.hGet(
|
|
229
|
-
process.env.VERCEL_URL +
|
|
245
|
+
process.env.VERCEL_URL + SHARED_TAGS_KEY,
|
|
230
246
|
'/api/cached-static-fetch',
|
|
231
247
|
);
|
|
232
248
|
expect(hashmap).toBeNull();
|
|
@@ -247,7 +263,7 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
247
263
|
expect(keys.length).toBe(1);
|
|
248
264
|
|
|
249
265
|
const hashmap = (await redisClient.hGet(
|
|
250
|
-
process.env.VERCEL_URL +
|
|
266
|
+
process.env.VERCEL_URL + SHARED_TAGS_KEY,
|
|
251
267
|
'/api/cached-static-fetch',
|
|
252
268
|
)) as string;
|
|
253
269
|
expect(JSON.parse(hashmap)).toEqual([
|
|
@@ -317,7 +333,7 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
317
333
|
|
|
318
334
|
// The key should also be removed from the hashmap
|
|
319
335
|
const hashmap = await redisClient.hGet(
|
|
320
|
-
process.env.VERCEL_URL +
|
|
336
|
+
process.env.VERCEL_URL + SHARED_TAGS_KEY,
|
|
321
337
|
'/api/revalidated-fetch',
|
|
322
338
|
);
|
|
323
339
|
expect(hashmap).toBeNull();
|
|
@@ -339,7 +355,7 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
339
355
|
expect(keys.length).toBe(0);
|
|
340
356
|
|
|
341
357
|
const hashmap = await redisClient.hGet(
|
|
342
|
-
process.env.VERCEL_URL +
|
|
358
|
+
process.env.VERCEL_URL + SHARED_TAGS_KEY,
|
|
343
359
|
cachedStaticPath,
|
|
344
360
|
);
|
|
345
361
|
expect(hashmap).toBeNull();
|
|
@@ -495,7 +511,7 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
495
511
|
expect(keys.length).toBe(0);
|
|
496
512
|
|
|
497
513
|
const hashmap = await redisClient.hGet(
|
|
498
|
-
process.env.VERCEL_URL +
|
|
514
|
+
process.env.VERCEL_URL + SHARED_TAGS_KEY,
|
|
499
515
|
'/api/nested-fetch-in-api-route/revalidated-fetch',
|
|
500
516
|
);
|
|
501
517
|
expect(hashmap).toBeNull();
|
|
@@ -514,10 +530,9 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
514
530
|
it('After the new request was made the redis key and hashmap should be set again', async () => {
|
|
515
531
|
await delay(REDIS_BACKGROUND_SYNC_DELAY);
|
|
516
532
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
'094a786b7ad391852168d3a7bcf75736777697d24a856a0089837f4b7de921df';
|
|
533
|
+
const cacheEntryKey = await findFetchCacheEntryKey(
|
|
534
|
+
'revalidated-fetch-revalidate3-nested-fetch-in-api-route',
|
|
535
|
+
);
|
|
521
536
|
|
|
522
537
|
// check Redis keys
|
|
523
538
|
const keys = await redisClient.keys(
|
|
@@ -526,7 +541,7 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
526
541
|
expect(keys.length).toBe(1);
|
|
527
542
|
|
|
528
543
|
const hashmap = (await redisClient.hGet(
|
|
529
|
-
process.env.VERCEL_URL +
|
|
544
|
+
process.env.VERCEL_URL + SHARED_TAGS_KEY,
|
|
530
545
|
cacheEntryKey,
|
|
531
546
|
)) as string;
|
|
532
547
|
expect(JSON.parse(hashmap)).toEqual([
|
|
@@ -551,7 +566,7 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
551
566
|
expect(keys.length).toBe(0);
|
|
552
567
|
|
|
553
568
|
const hashmap = await redisClient.hGet(
|
|
554
|
-
process.env.VERCEL_URL +
|
|
569
|
+
process.env.VERCEL_URL + SHARED_TAGS_KEY,
|
|
555
570
|
'/api/nested-fetch-in-api-route/revalidated-fetch',
|
|
556
571
|
);
|
|
557
572
|
expect(hashmap).toBeNull();
|
|
@@ -569,10 +584,9 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
569
584
|
|
|
570
585
|
it('After the new request was made the redis key and hashmap should be set again', async () => {
|
|
571
586
|
await delay(REDIS_BACKGROUND_SYNC_DELAY);
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
'094a786b7ad391852168d3a7bcf75736777697d24a856a0089837f4b7de921df';
|
|
587
|
+
const cacheEntryKey = await findFetchCacheEntryKey(
|
|
588
|
+
'revalidated-fetch-revalidate3-nested-fetch-in-api-route',
|
|
589
|
+
);
|
|
576
590
|
|
|
577
591
|
// check Redis keys
|
|
578
592
|
const keys = await redisClient.keys(
|
|
@@ -581,7 +595,7 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
581
595
|
expect(keys.length).toBe(1);
|
|
582
596
|
|
|
583
597
|
const hashmap = (await redisClient.hGet(
|
|
584
|
-
process.env.VERCEL_URL +
|
|
598
|
+
process.env.VERCEL_URL + SHARED_TAGS_KEY,
|
|
585
599
|
cacheEntryKey,
|
|
586
600
|
)) as string;
|
|
587
601
|
expect(JSON.parse(hashmap)).toEqual([
|
|
@@ -708,7 +722,7 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
708
722
|
expect(keys.length).toBe(0);
|
|
709
723
|
|
|
710
724
|
const hashmap = await redisClient.hGet(
|
|
711
|
-
process.env.VERCEL_URL +
|
|
725
|
+
process.env.VERCEL_URL + SHARED_TAGS_KEY,
|
|
712
726
|
'/pages/no-fetch/default-page',
|
|
713
727
|
);
|
|
714
728
|
expect(hashmap).toBeNull();
|
|
@@ -733,7 +747,7 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
733
747
|
expect(keys.length).toBe(1);
|
|
734
748
|
|
|
735
749
|
const hashmap = (await redisClient.hGet(
|
|
736
|
-
process.env.VERCEL_URL +
|
|
750
|
+
process.env.VERCEL_URL + SHARED_TAGS_KEY,
|
|
737
751
|
'/pages/no-fetch/default-page',
|
|
738
752
|
)) as string;
|
|
739
753
|
expect(JSON.parse(hashmap)).toEqual([
|
|
@@ -771,6 +785,10 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
771
785
|
|
|
772
786
|
await delay(REDIS_BACKGROUND_SYNC_DELAY);
|
|
773
787
|
|
|
788
|
+
const nestedFetchCacheEntryKey = await findFetchCacheEntryKey(
|
|
789
|
+
'revalidated-fetch-revalidate15-default-page',
|
|
790
|
+
);
|
|
791
|
+
|
|
774
792
|
// test cache entry for 3 keys are set
|
|
775
793
|
const keys1 = await redisClient.keys(
|
|
776
794
|
process.env.VERCEL_URL +
|
|
@@ -778,8 +796,7 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
778
796
|
);
|
|
779
797
|
expect(keys1.length).toBe(1);
|
|
780
798
|
const keys2 = await redisClient.keys(
|
|
781
|
-
process.env.VERCEL_URL +
|
|
782
|
-
'e978cf5ddb8bf799209e828635cfe9ae6862f6735cea97f01ab752ff6fa489b4',
|
|
799
|
+
process.env.VERCEL_URL + nestedFetchCacheEntryKey,
|
|
783
800
|
);
|
|
784
801
|
expect(keys2.length).toBe(1);
|
|
785
802
|
const keys3 = await redisClient.keys(
|
|
@@ -789,7 +806,7 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
789
806
|
|
|
790
807
|
// test shared tag hashmap to be set for all keys
|
|
791
808
|
const hashmap1 = (await redisClient.hGet(
|
|
792
|
-
process.env.VERCEL_URL +
|
|
809
|
+
process.env.VERCEL_URL + SHARED_TAGS_KEY,
|
|
793
810
|
'/pages/revalidated-fetch/revalidate15--default-page',
|
|
794
811
|
)) as string;
|
|
795
812
|
expect(JSON.parse(hashmap1)).toEqual([
|
|
@@ -802,14 +819,14 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
802
819
|
'revalidated-fetch-revalidate15-default-page',
|
|
803
820
|
]);
|
|
804
821
|
const hashmap2 = (await redisClient.hGet(
|
|
805
|
-
process.env.VERCEL_URL +
|
|
806
|
-
|
|
822
|
+
process.env.VERCEL_URL + SHARED_TAGS_KEY,
|
|
823
|
+
nestedFetchCacheEntryKey,
|
|
807
824
|
)) as string;
|
|
808
825
|
expect(JSON.parse(hashmap2)).toEqual([
|
|
809
826
|
'revalidated-fetch-revalidate15-default-page',
|
|
810
827
|
]);
|
|
811
828
|
const hashmap3 = (await redisClient.hGet(
|
|
812
|
-
process.env.VERCEL_URL +
|
|
829
|
+
process.env.VERCEL_URL + SHARED_TAGS_KEY,
|
|
813
830
|
'/api/revalidated-fetch',
|
|
814
831
|
)) as string;
|
|
815
832
|
expect(JSON.parse(hashmap3)).toEqual([
|
|
@@ -849,20 +866,22 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
849
866
|
expect(keys1.length).toBe(0);
|
|
850
867
|
|
|
851
868
|
const hashmap1 = await redisClient.hGet(
|
|
852
|
-
process.env.VERCEL_URL +
|
|
869
|
+
process.env.VERCEL_URL + SHARED_TAGS_KEY,
|
|
853
870
|
'/pages/revalidated-fetch/revalidate15--default-page',
|
|
854
871
|
);
|
|
855
872
|
expect(hashmap1).toBeNull();
|
|
856
873
|
|
|
857
874
|
// sub-fetch-request is not removed directly but will be removed on next get request
|
|
875
|
+
const nestedFetchCacheEntryKey = await findFetchCacheEntryKey(
|
|
876
|
+
'revalidated-fetch-revalidate15-default-page',
|
|
877
|
+
);
|
|
858
878
|
const keys2 = await redisClient.keys(
|
|
859
|
-
process.env.VERCEL_URL +
|
|
860
|
-
'e978cf5ddb8bf799209e828635cfe9ae6862f6735cea97f01ab752ff6fa489b4',
|
|
879
|
+
process.env.VERCEL_URL + nestedFetchCacheEntryKey,
|
|
861
880
|
);
|
|
862
881
|
expect(keys2.length).toBe(1);
|
|
863
882
|
const hashmap2 = await redisClient.hGet(
|
|
864
|
-
process.env.VERCEL_URL +
|
|
865
|
-
|
|
883
|
+
process.env.VERCEL_URL + SHARED_TAGS_KEY,
|
|
884
|
+
nestedFetchCacheEntryKey,
|
|
866
885
|
);
|
|
867
886
|
expect(hashmap2).toBeDefined();
|
|
868
887
|
|
|
@@ -881,7 +900,7 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
881
900
|
);
|
|
882
901
|
expect(keys3.length).toBe(1);
|
|
883
902
|
const hashmap3 = (await redisClient.hGet(
|
|
884
|
-
process.env.VERCEL_URL +
|
|
903
|
+
process.env.VERCEL_URL + SHARED_TAGS_KEY,
|
|
885
904
|
'/api/revalidated-fetch',
|
|
886
905
|
)) as string;
|
|
887
906
|
expect(JSON.parse(hashmap3)).toEqual([
|
|
@@ -85,6 +85,34 @@ describe('RedisStringsHandler', () => {
|
|
|
85
85
|
expect(res).toBeNull();
|
|
86
86
|
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
|
87
87
|
});
|
|
88
|
+
|
|
89
|
+
it('does not leave the readiness timeout timer pending after an operation', async () => {
|
|
90
|
+
vi.useFakeTimers();
|
|
91
|
+
try {
|
|
92
|
+
const handler = new RedisStringsHandler({
|
|
93
|
+
redisUrl: 'redis://localhost:6379',
|
|
94
|
+
keyPrefix: 'test:',
|
|
95
|
+
database: 0,
|
|
96
|
+
getTimeoutMs: 1,
|
|
97
|
+
redisGetDeduplication: false,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const timersBefore = vi.getTimerCount();
|
|
101
|
+
await handler.get('missing-key', {
|
|
102
|
+
kind: 'APP_PAGE',
|
|
103
|
+
isRoutePPREnabled: false,
|
|
104
|
+
isFallback: false,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// assertClientIsReady() races waitUntilReady() against a 30s timeout;
|
|
108
|
+
// the timer must not stay pending once the race is settled, because
|
|
109
|
+
// pending timers retain the ambient async context (and with it, in a
|
|
110
|
+
// Next.js server, the whole per-request object graph).
|
|
111
|
+
expect(vi.getTimerCount()).toBe(timersBefore);
|
|
112
|
+
} finally {
|
|
113
|
+
vi.useRealTimers();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
88
116
|
});
|
|
89
117
|
|
|
90
118
|
describe('Public exports', () => {
|