@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.
Files changed (45) hide show
  1. package/.github/workflows/ci.yml +14 -2
  2. package/.github/workflows/release.yml +85 -7
  3. package/CHANGELOG.md +117 -0
  4. package/README.md +3 -1
  5. package/dist/index.js +13 -15
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +13 -15
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +15 -11
  10. package/playwright.config.ts +9 -0
  11. package/release.config.cjs +3 -3
  12. package/src/RedisStringsHandler.ts +11 -11
  13. package/test/browser/update-tag.browser.test.ts +41 -0
  14. package/test/integration/next-app-16-0-3/README.md +36 -0
  15. package/test/integration/next-app-16-0-3/eslint.config.mjs +18 -0
  16. package/test/integration/next-app-16-0-3/next.config.ts +7 -0
  17. package/test/integration/next-app-16-0-3/package.json +28 -0
  18. package/test/integration/next-app-16-0-3/pnpm-lock.yaml +4127 -0
  19. package/test/integration/next-app-16-0-3/postcss.config.mjs +7 -0
  20. package/test/integration/next-app-16-0-3/src/app/api/cached-static-fetch/route.ts +18 -0
  21. package/test/integration/next-app-16-0-3/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +27 -0
  22. package/test/integration/next-app-16-0-3/src/app/api/revalidatePath/route.ts +15 -0
  23. package/test/integration/next-app-16-0-3/src/app/api/revalidateTag/route.ts +20 -0
  24. package/test/integration/next-app-16-0-3/src/app/api/revalidated-fetch/route.ts +17 -0
  25. package/test/integration/next-app-16-0-3/src/app/api/uncached-fetch/route.ts +15 -0
  26. package/test/integration/next-app-16-0-3/src/app/favicon.ico +0 -0
  27. package/test/integration/next-app-16-0-3/src/app/globals.css +26 -0
  28. package/test/integration/next-app-16-0-3/src/app/layout.tsx +59 -0
  29. package/test/integration/next-app-16-0-3/src/app/page.tsx +755 -0
  30. package/test/integration/next-app-16-0-3/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +19 -0
  31. package/test/integration/next-app-16-0-3/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +34 -0
  32. package/test/integration/next-app-16-0-3/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +25 -0
  33. package/test/integration/next-app-16-0-3/src/app/pages/no-fetch/default-page/page.tsx +55 -0
  34. package/test/integration/next-app-16-0-3/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +19 -0
  35. package/test/integration/next-app-16-0-3/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +35 -0
  36. package/test/integration/next-app-16-0-3/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +25 -0
  37. package/test/integration/next-app-16-0-3/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +19 -0
  38. package/test/integration/next-app-16-0-3/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +32 -0
  39. package/test/integration/next-app-16-0-3/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +25 -0
  40. package/test/integration/next-app-16-0-3/src/app/revalidation-interface.tsx +267 -0
  41. package/test/integration/next-app-16-0-3/src/app/update-tag-test/page.tsx +25 -0
  42. package/test/integration/next-app-16-0-3/tsconfig.json +34 -0
  43. package/test/integration/nextjs-cache-handler.integration.test.ts +81 -24
  44. package/tests/update-tag.spec.ts +33 -0
  45. 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
+ });
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ // Browser mode disabled; Playwright is used via its own runner instead.
4
+ export default defineConfig({
5
+ test: {
6
+ browser: {
7
+ enabled: false,
8
+ },
9
+ },
10
+ });