@trieb.work/nextjs-turbo-redis-cache 1.9.1 → 1.10.0-beta.14
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 +14 -2
- package/.github/workflows/release.yml +85 -7
- package/CHANGELOG.md +117 -0
- package/README.md +3 -1
- package/dist/index.js +13 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -11
- package/playwright.config.ts +9 -0
- package/release.config.cjs +3 -3
- package/src/RedisStringsHandler.ts +11 -11
- package/test/browser/update-tag.browser.test.ts +41 -0
- package/test/integration/next-app-16-0-3/README.md +36 -0
- package/test/integration/next-app-16-0-3/eslint.config.mjs +18 -0
- package/test/integration/next-app-16-0-3/next.config.ts +7 -0
- package/test/integration/next-app-16-0-3/package.json +28 -0
- package/test/integration/next-app-16-0-3/pnpm-lock.yaml +4127 -0
- package/test/integration/next-app-16-0-3/postcss.config.mjs +7 -0
- package/test/integration/next-app-16-0-3/src/app/api/cached-static-fetch/route.ts +18 -0
- package/test/integration/next-app-16-0-3/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +27 -0
- package/test/integration/next-app-16-0-3/src/app/api/revalidatePath/route.ts +15 -0
- package/test/integration/next-app-16-0-3/src/app/api/revalidateTag/route.ts +20 -0
- package/test/integration/next-app-16-0-3/src/app/api/revalidated-fetch/route.ts +17 -0
- package/test/integration/next-app-16-0-3/src/app/api/uncached-fetch/route.ts +15 -0
- package/test/integration/next-app-16-0-3/src/app/favicon.ico +0 -0
- package/test/integration/next-app-16-0-3/src/app/globals.css +26 -0
- package/test/integration/next-app-16-0-3/src/app/layout.tsx +59 -0
- package/test/integration/next-app-16-0-3/src/app/page.tsx +755 -0
- package/test/integration/next-app-16-0-3/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +19 -0
- package/test/integration/next-app-16-0-3/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +34 -0
- package/test/integration/next-app-16-0-3/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +25 -0
- package/test/integration/next-app-16-0-3/src/app/pages/no-fetch/default-page/page.tsx +55 -0
- package/test/integration/next-app-16-0-3/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +19 -0
- package/test/integration/next-app-16-0-3/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +35 -0
- package/test/integration/next-app-16-0-3/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +25 -0
- package/test/integration/next-app-16-0-3/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +19 -0
- package/test/integration/next-app-16-0-3/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +32 -0
- package/test/integration/next-app-16-0-3/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +25 -0
- package/test/integration/next-app-16-0-3/src/app/revalidation-interface.tsx +267 -0
- package/test/integration/next-app-16-0-3/src/app/update-tag-test/page.tsx +25 -0
- package/test/integration/next-app-16-0-3/tsconfig.json +34 -0
- package/test/integration/nextjs-cache-handler.integration.test.ts +81 -24
- package/tests/update-tag.spec.ts +33 -0
- package/vitest.browser.config.ts +10 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
-
import { spawn } from 'child_process';
|
|
2
|
+
import { ChildProcessWithoutNullStreams, spawn } from 'child_process';
|
|
3
3
|
import fetch from 'node-fetch';
|
|
4
|
-
import { createClient } from 'redis';
|
|
4
|
+
import { createClient, RedisClientType } from 'redis';
|
|
5
5
|
import { join } from 'path';
|
|
6
6
|
import { CacheEntry } from '../../src/RedisStringsHandler';
|
|
7
7
|
import { revalidate as next1503_revalidatedFetch_route } from './next-app-15-0-3/src/app/api/revalidated-fetch/route';
|
|
@@ -16,8 +16,8 @@ const NEXT_START_URL = `http://localhost:${NEXT_START_PORT}`;
|
|
|
16
16
|
|
|
17
17
|
const REDIS_BACKGROUND_SYNC_DELAY = 250; //ms delay to prevent flaky tests in slow CI environments
|
|
18
18
|
|
|
19
|
-
let nextProcess;
|
|
20
|
-
let redisClient;
|
|
19
|
+
let nextProcess: ChildProcessWithoutNullStreams;
|
|
20
|
+
let redisClient: RedisClientType;
|
|
21
21
|
|
|
22
22
|
async function delay(ms: number) {
|
|
23
23
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -167,9 +167,9 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
167
167
|
expect(keys.length).toBeGreaterThan(0);
|
|
168
168
|
|
|
169
169
|
// check the content of redis key
|
|
170
|
-
const value = await redisClient.get(
|
|
170
|
+
const value = (await redisClient.get(
|
|
171
171
|
process.env.VERCEL_URL + '/api/cached-static-fetch',
|
|
172
|
-
);
|
|
172
|
+
)) as string;
|
|
173
173
|
expect(value).toBeDefined();
|
|
174
174
|
const cacheEntry: CacheEntry = JSON.parse(value);
|
|
175
175
|
|
|
@@ -240,10 +240,10 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
240
240
|
);
|
|
241
241
|
expect(keys.length).toBe(1);
|
|
242
242
|
|
|
243
|
-
const hashmap = await redisClient.hGet(
|
|
243
|
+
const hashmap = (await redisClient.hGet(
|
|
244
244
|
process.env.VERCEL_URL + '__sharedTags__',
|
|
245
245
|
'/api/cached-static-fetch',
|
|
246
|
-
);
|
|
246
|
+
)) as string;
|
|
247
247
|
expect(JSON.parse(hashmap)).toEqual([
|
|
248
248
|
'_N_T_/layout',
|
|
249
249
|
'_N_T_/api/layout',
|
|
@@ -319,6 +319,63 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
319
319
|
}
|
|
320
320
|
});
|
|
321
321
|
|
|
322
|
+
// Next 16-only caching API tests. These routes exist only in the Next 16 test app
|
|
323
|
+
// and exercise the new revalidateTag profiles and updateTag semantics.
|
|
324
|
+
if (NEXT_TEST_APP.includes('16.')) {
|
|
325
|
+
describe('Next 16 caching APIs', () => {
|
|
326
|
+
const cachedStaticPath = '/api/cached-static-fetch';
|
|
327
|
+
|
|
328
|
+
async function assertCachedStaticFetchCleared() {
|
|
329
|
+
await delay(REDIS_BACKGROUND_SYNC_DELAY);
|
|
330
|
+
const keys = await redisClient.keys(
|
|
331
|
+
process.env.VERCEL_URL + cachedStaticPath,
|
|
332
|
+
);
|
|
333
|
+
expect(keys.length).toBe(0);
|
|
334
|
+
|
|
335
|
+
const hashmap = await redisClient.hGet(
|
|
336
|
+
process.env.VERCEL_URL + '__sharedTags__',
|
|
337
|
+
cachedStaticPath,
|
|
338
|
+
);
|
|
339
|
+
expect(hashmap).toBeNull();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
it('revalidateTag(tag, "max") should invalidate cached-static-fetch by tag', async () => {
|
|
343
|
+
// Warm up cache and sharedTagsMap for cached-static-fetch
|
|
344
|
+
await fetch(NEXT_START_URL + cachedStaticPath);
|
|
345
|
+
await delay(REDIS_BACKGROUND_SYNC_DELAY);
|
|
346
|
+
|
|
347
|
+
// Use explicit tag for this route as set by Next.js
|
|
348
|
+
const tag = '_N_T_/api/cached-static-fetch';
|
|
349
|
+
const res = await fetch(
|
|
350
|
+
`${NEXT_START_URL}/api/revalidateTag?tag=${encodeURIComponent(
|
|
351
|
+
tag,
|
|
352
|
+
)}&profile=max`,
|
|
353
|
+
);
|
|
354
|
+
const json: any = await res.json();
|
|
355
|
+
expect(json.success).toBe(true);
|
|
356
|
+
|
|
357
|
+
await assertCachedStaticFetchCleared();
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('revalidateTag(tag, { expire: 60 }) should also invalidate cached-static-fetch', async () => {
|
|
361
|
+
// Warm up cache again
|
|
362
|
+
await fetch(NEXT_START_URL + cachedStaticPath);
|
|
363
|
+
await delay(REDIS_BACKGROUND_SYNC_DELAY);
|
|
364
|
+
|
|
365
|
+
const tag = '_N_T_/api/cached-static-fetch';
|
|
366
|
+
const res = await fetch(
|
|
367
|
+
`${NEXT_START_URL}/api/revalidateTag?tag=${encodeURIComponent(
|
|
368
|
+
tag,
|
|
369
|
+
)}&profile=expire`,
|
|
370
|
+
);
|
|
371
|
+
const json: any = await res.json();
|
|
372
|
+
expect(json.success).toBe(true);
|
|
373
|
+
|
|
374
|
+
await assertCachedStaticFetchCleared();
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
322
379
|
describe('should not cache uncached API routes in Redis', () => {
|
|
323
380
|
let counter1: number;
|
|
324
381
|
|
|
@@ -462,10 +519,10 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
462
519
|
);
|
|
463
520
|
expect(keys.length).toBe(1);
|
|
464
521
|
|
|
465
|
-
const hashmap = await redisClient.hGet(
|
|
522
|
+
const hashmap = (await redisClient.hGet(
|
|
466
523
|
process.env.VERCEL_URL + '__sharedTags__',
|
|
467
524
|
cacheEntryKey,
|
|
468
|
-
);
|
|
525
|
+
)) as string;
|
|
469
526
|
expect(JSON.parse(hashmap)).toEqual([
|
|
470
527
|
'revalidated-fetch-revalidate3-nested-fetch-in-api-route',
|
|
471
528
|
]);
|
|
@@ -517,10 +574,10 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
517
574
|
);
|
|
518
575
|
expect(keys.length).toBe(1);
|
|
519
576
|
|
|
520
|
-
const hashmap = await redisClient.hGet(
|
|
577
|
+
const hashmap = (await redisClient.hGet(
|
|
521
578
|
process.env.VERCEL_URL + '__sharedTags__',
|
|
522
579
|
cacheEntryKey,
|
|
523
|
-
);
|
|
580
|
+
)) as string;
|
|
524
581
|
expect(JSON.parse(hashmap)).toEqual([
|
|
525
582
|
'revalidated-fetch-revalidate3-nested-fetch-in-api-route',
|
|
526
583
|
]);
|
|
@@ -570,9 +627,9 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
570
627
|
});
|
|
571
628
|
|
|
572
629
|
it('The data in the redis key should match the expected format', async () => {
|
|
573
|
-
const data = await redisClient.get(
|
|
630
|
+
const data = (await redisClient.get(
|
|
574
631
|
process.env.VERCEL_URL + '/pages/no-fetch/default-page',
|
|
575
|
-
);
|
|
632
|
+
)) as string;
|
|
576
633
|
expect(data).toBeDefined();
|
|
577
634
|
const cacheEntry: CacheEntry = JSON.parse(data);
|
|
578
635
|
|
|
@@ -667,10 +724,10 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
667
724
|
);
|
|
668
725
|
expect(keys.length).toBe(1);
|
|
669
726
|
|
|
670
|
-
const hashmap = await redisClient.hGet(
|
|
727
|
+
const hashmap = (await redisClient.hGet(
|
|
671
728
|
process.env.VERCEL_URL + '__sharedTags__',
|
|
672
729
|
'/pages/no-fetch/default-page',
|
|
673
|
-
);
|
|
730
|
+
)) as string;
|
|
674
731
|
expect(JSON.parse(hashmap)).toEqual([
|
|
675
732
|
'_N_T_/layout',
|
|
676
733
|
'_N_T_/pages/layout',
|
|
@@ -723,10 +780,10 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
723
780
|
expect(keys3.length).toBe(1);
|
|
724
781
|
|
|
725
782
|
// test shared tag hashmap to be set for all keys
|
|
726
|
-
const hashmap1 = await redisClient.hGet(
|
|
783
|
+
const hashmap1 = (await redisClient.hGet(
|
|
727
784
|
process.env.VERCEL_URL + '__sharedTags__',
|
|
728
785
|
'/pages/revalidated-fetch/revalidate15--default-page',
|
|
729
|
-
);
|
|
786
|
+
)) as string;
|
|
730
787
|
expect(JSON.parse(hashmap1)).toEqual([
|
|
731
788
|
'_N_T_/layout',
|
|
732
789
|
'_N_T_/pages/layout',
|
|
@@ -736,17 +793,17 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
736
793
|
'_N_T_/pages/revalidated-fetch/revalidate15--default-page',
|
|
737
794
|
'revalidated-fetch-revalidate15-default-page',
|
|
738
795
|
]);
|
|
739
|
-
const hashmap2 = await redisClient.hGet(
|
|
796
|
+
const hashmap2 = (await redisClient.hGet(
|
|
740
797
|
process.env.VERCEL_URL + '__sharedTags__',
|
|
741
798
|
'e978cf5ddb8bf799209e828635cfe9ae6862f6735cea97f01ab752ff6fa489b4',
|
|
742
|
-
);
|
|
799
|
+
)) as string;
|
|
743
800
|
expect(JSON.parse(hashmap2)).toEqual([
|
|
744
801
|
'revalidated-fetch-revalidate15-default-page',
|
|
745
802
|
]);
|
|
746
|
-
const hashmap3 = await redisClient.hGet(
|
|
803
|
+
const hashmap3 = (await redisClient.hGet(
|
|
747
804
|
process.env.VERCEL_URL + '__sharedTags__',
|
|
748
805
|
'/api/revalidated-fetch',
|
|
749
|
-
);
|
|
806
|
+
)) as string;
|
|
750
807
|
expect(JSON.parse(hashmap3)).toEqual([
|
|
751
808
|
'_N_T_/layout',
|
|
752
809
|
'_N_T_/api/layout',
|
|
@@ -815,10 +872,10 @@ describe('Next.js Turbo Redis Cache Integration', () => {
|
|
|
815
872
|
process.env.VERCEL_URL + '/api/revalidated-fetch',
|
|
816
873
|
);
|
|
817
874
|
expect(keys3.length).toBe(1);
|
|
818
|
-
const hashmap3 = await redisClient.hGet(
|
|
875
|
+
const hashmap3 = (await redisClient.hGet(
|
|
819
876
|
process.env.VERCEL_URL + '__sharedTags__',
|
|
820
877
|
'/api/revalidated-fetch',
|
|
821
|
-
);
|
|
878
|
+
)) as string;
|
|
822
879
|
expect(JSON.parse(hashmap3)).toEqual([
|
|
823
880
|
'_N_T_/layout',
|
|
824
881
|
'_N_T_/api/layout',
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
// This test assumes the Next 16 app is already running, e.g.:
|
|
4
|
+
// cd test/integration/next-app-16-0-3 && pnpm dev
|
|
5
|
+
// on the same origin as baseURL.
|
|
6
|
+
|
|
7
|
+
test('button click triggers Server Action using updateTag', async ({
|
|
8
|
+
page,
|
|
9
|
+
}) => {
|
|
10
|
+
await page.goto('/update-tag-test');
|
|
11
|
+
|
|
12
|
+
await expect(page.getByText('UpdateTag Test')).toBeVisible();
|
|
13
|
+
|
|
14
|
+
const clicks = page.getByTestId('clicks');
|
|
15
|
+
const before = await clicks.textContent();
|
|
16
|
+
|
|
17
|
+
await page.getByRole('button', { name: 'Increment' }).click();
|
|
18
|
+
|
|
19
|
+
// Wait for network and any navigation caused by the Server Action
|
|
20
|
+
await page.waitForLoadState('networkidle');
|
|
21
|
+
|
|
22
|
+
const after = await clicks.textContent();
|
|
23
|
+
|
|
24
|
+
// Server Action should have run and updated the UI
|
|
25
|
+
expect(after).not.toBe(before);
|
|
26
|
+
|
|
27
|
+
// Ensure there is no error message about updateTag usage
|
|
28
|
+
const errorLocator = page.getByText(
|
|
29
|
+
'updateTag can only be called from within a Server Action',
|
|
30
|
+
{ exact: false },
|
|
31
|
+
);
|
|
32
|
+
await expect(errorLocator).toHaveCount(0);
|
|
33
|
+
});
|