@trieb.work/nextjs-turbo-redis-cache 1.14.1 → 1.15.1
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/.cursor/skills/testing/SKILL.md +121 -0
- package/.github/workflows/ci.yml +155 -70
- package/.github/workflows/pages.yml +38 -0
- package/ARCHITECTURE.md +431 -0
- package/CHANGELOG.md +19 -0
- package/README.md +220 -48
- package/dist/index.d.mts +77 -2
- package/dist/index.d.ts +77 -2
- package/dist/index.js +109 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +106 -23
- package/dist/index.mjs.map +1 -1
- package/docs/index.html +139 -0
- package/docs/robots.txt +4 -0
- package/docs/sitemap.xml +8 -0
- package/docs/styles.css +141 -0
- package/eslint.config.mjs +1 -1
- package/package.json +16 -18
- package/playwright.config.ts +5 -3
- package/src/CacheComponentsHandler.ts +57 -2
- package/src/RedisStringsHandler.ts +48 -11
- package/src/SyncedMap.ts +62 -13
- package/src/index.ts +4 -0
- package/src/serializer.ts +59 -0
- package/test/README.md +179 -0
- package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/package.json +6 -6
- package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-15-4-11}/pnpm-lock.yaml +89 -105
- package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11}/package.json +3 -3
- package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11}/pnpm-lock.yaml +53 -53
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/package.json +28 -0
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/pnpm-lock.yaml +4206 -0
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/cached-with-cachelife/route.ts +24 -0
- package/test/nextjs-test-projects/next-app-16-2-6/eslint.config.mjs +18 -0
- package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6}/package.json +3 -3
- package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6}/pnpm-lock.yaml +54 -60
- package/test/nextjs-test-projects/next-app-16-2-6/postcss.config.mjs +7 -0
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/favicon.ico +0 -0
- package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-2-6}/tsconfig.json +9 -2
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/cache-handler.js +3 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/next.config.ts +13 -0
- package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6-cache-components}/package.json +3 -3
- package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6-cache-components}/pnpm-lock.yaml +54 -60
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-static-fetch/route.ts +19 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-with-cachelife/route.ts +24 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-with-tag/route.ts +21 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/revalidate-tag/route.ts +19 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/revalidated-fetch/route.ts +19 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/cachelife-short/page.tsx +110 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/page.tsx +90 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/runtime-data-suspense/page.tsx +127 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/stale-while-revalidate/page.tsx +130 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/tag-invalidation/page.tsx +127 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/use-cache-nondeterministic/page.tsx +110 -0
- package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/src/app/layout.tsx +0 -2
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/update-tag-test/page.tsx +22 -0
- package/{tests → test/playwright}/update-tag.spec.ts +1 -1
- package/test/{integration → vitest/integration}/build-id-prefix.integration.test.ts +2 -2
- package/test/{cache-components/cache-components.integration.spec.ts → vitest/integration/cache-components/cache-components.integration.test.ts} +46 -3
- package/test/{cache-components/redis-kill-reconnect.spec.ts → vitest/integration/cache-components/redis-kill-reconnect.test.ts} +28 -7
- package/test/vitest/integration/cache-components/redis-quit-vs-disconnect.test.ts +59 -0
- package/test/vitest/integration/cache-components/redis-subscriber-outage.test.ts +100 -0
- package/test/{cache-components → vitest/integration/cache-components}/scripts/redis-kill-reconnect.ts +31 -8
- package/test/vitest/integration/cache-components/scripts/redis-quit-vs-disconnect.ts +113 -0
- package/test/vitest/integration/cache-components/scripts/redis-subscriber-outage.ts +379 -0
- package/test/vitest/integration/cache-components/scripts/redis-test-helpers.ts +155 -0
- package/test/{integration → vitest/integration}/nextjs-cache-handler.integration.test.ts +17 -10
- package/test/vitest/unit/SyncedMap-reconnect-repro.test.ts +154 -0
- package/{src → test/vitest/unit}/index.test.ts +3 -3
- package/test/{cache-components/reconnect-socket-already-opened.spec.ts → vitest/unit/reconnect-socket-already-opened.test.ts} +1 -1
- package/test/vitest/unit/serializer.test.ts +182 -0
- package/{src → test/vitest/unit}/utils/prefix.test.ts +1 -1
- package/vite.config.ts +4 -3
- package/vitest.cache-components.config.ts +1 -1
- package/test/browser/update-tag.browser.test.ts +0 -41
- package/test/integration/next-app-15-0-3/next.config.js +0 -6
- package/test/integration/next-app-15-0-3/package-lock.json +0 -5833
- package/test/integration/next-app-15-0-3/pnpm-lock.yaml +0 -3710
- package/test/integration/next-app-15-3-2/next.config.js +0 -6
- package/test/integration/next-app-15-3-2/package.json +0 -33
- package/test/integration/next-app-15-3-2/pnpm-lock.yaml +0 -3720
- package/test/integration/next-app-15-3-2/postcss.config.mjs +0 -5
- package/test/integration/next-app-15-3-2/src/app/api/revalidateTag/route.ts +0 -15
- package/test/integration/next-app-15-4-7/eslint.config.mjs +0 -16
- package/test/integration/next-app-15-4-7/package-lock.json +0 -5969
- package/test/integration/next-app-15-4-7/package.json +0 -33
- package/test/integration/next-app-15-4-7/postcss.config.mjs +0 -5
- package/test/integration/next-app-15-4-7/src/app/api/revalidateTag/route.ts +0 -15
- package/test/integration/next-app-15-4-7/tsconfig.json +0 -27
- package/test/integration/next-app-16-0-3/src/app/api/cached-static-fetch/route.ts +0 -18
- package/test/integration/next-app-16-0-3/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
- package/test/integration/next-app-16-0-3/src/app/api/revalidatePath/route.ts +0 -15
- package/test/integration/next-app-16-0-3/src/app/api/revalidated-fetch/route.ts +0 -17
- package/test/integration/next-app-16-0-3/src/app/api/uncached-fetch/route.ts +0 -15
- package/test/integration/next-app-16-0-3/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/integration/next-app-16-0-3/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
- package/test/integration/next-app-16-0-3/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/integration/next-app-16-0-3/src/app/pages/no-fetch/default-page/page.tsx +0 -55
- package/test/integration/next-app-16-0-3/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/integration/next-app-16-0-3/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
- package/test/integration/next-app-16-0-3/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/integration/next-app-16-0-3/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/integration/next-app-16-0-3/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
- package/test/integration/next-app-16-0-3/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/integration/next-app-16-2-3/src/app/api/cached-static-fetch/route.ts +0 -18
- package/test/integration/next-app-16-2-3/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
- package/test/integration/next-app-16-2-3/src/app/api/revalidatePath/route.ts +0 -15
- package/test/integration/next-app-16-2-3/src/app/api/revalidated-fetch/route.ts +0 -17
- package/test/integration/next-app-16-2-3/src/app/api/uncached-fetch/route.ts +0 -15
- package/test/integration/next-app-16-2-3/src/app/globals.css +0 -26
- package/test/integration/next-app-16-2-3/src/app/layout.tsx +0 -59
- package/test/integration/next-app-16-2-3/src/app/page.tsx +0 -755
- package/test/integration/next-app-16-2-3/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/integration/next-app-16-2-3/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
- package/test/integration/next-app-16-2-3/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/integration/next-app-16-2-3/src/app/pages/no-fetch/default-page/page.tsx +0 -55
- package/test/integration/next-app-16-2-3/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/integration/next-app-16-2-3/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
- package/test/integration/next-app-16-2-3/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/integration/next-app-16-2-3/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/integration/next-app-16-2-3/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
- package/test/integration/next-app-16-2-3/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/integration/next-app-16-2-3/src/app/revalidation-interface.tsx +0 -267
- package/test/integration/next-app-16-2-3-cache-components/public/file.svg +0 -1
- package/test/integration/next-app-16-2-3-cache-components/public/globe.svg +0 -1
- package/test/integration/next-app-16-2-3-cache-components/public/next.svg +0 -1
- package/test/integration/next-app-16-2-3-cache-components/public/vercel.svg +0 -1
- package/test/integration/next-app-16-2-3-cache-components/public/window.svg +0 -1
- package/test/integration/next-app-customized/README.md +0 -36
- package/test/integration/next-app-customized/eslint.config.mjs +0 -16
- package/vitest.browser.config.ts +0 -10
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/README.md +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/eslint.config.mjs +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-15-4-11}/next.config.js +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-15-4-11}/package-lock.json +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/postcss.config.mjs +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/file.svg +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/globe.svg +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/next.svg +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/vercel.svg +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/window.svg +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/cached-static-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/revalidatePath/route.ts +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/revalidateTag/route.ts +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/revalidated-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/uncached-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/globals.css +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/layout.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/no-fetch/default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/revalidation-interface.tsx +0 -0
- /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/tsconfig.json +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-0-11}/README.md +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11}/eslint.config.mjs +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11}/next.config.ts +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11}/postcss.config.mjs +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/api/cached-static-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/api/revalidatePath/route.ts +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11}/src/app/api/revalidateTag/route.ts +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/api/revalidated-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/api/uncached-fetch/route.ts +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11}/src/app/favicon.ico +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-0-11}/src/app/globals.css +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/layout.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-0-11}/src/app/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/no-fetch/default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-0-11}/src/app/revalidation-interface.tsx +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11}/src/app/update-tag-test/page.tsx +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11}/tsconfig.json +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/README.md +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/cache-handler.js +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11-cache-components}/eslint.config.mjs +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/next.config.ts +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11-cache-components}/postcss.config.mjs +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/file.svg +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/globe.svg +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/next.svg +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/vercel.svg +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/window.svg +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/api/cached-static-fetch/route.ts +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/api/cached-with-tag/route.ts +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/api/revalidate-tag/route.ts +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/api/revalidated-fetch/route.ts +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/cache-lab/cachelife-short/page.tsx +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/cache-lab/page.tsx +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/cache-lab/runtime-data-suspense/page.tsx +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/cache-lab/stale-while-revalidate/page.tsx +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/cache-lab/tag-invalidation/page.tsx +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/cache-lab/use-cache-nondeterministic/page.tsx +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/favicon.ico +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/globals.css +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/layout.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/page.tsx +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/revalidation-interface.tsx +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/update-tag-test/page.tsx +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11-cache-components}/tsconfig.json +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6}/README.md +0 -0
- /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6}/next.config.ts +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/api/cached-static-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/api/revalidatePath/route.ts +0 -0
- /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6}/src/app/api/revalidateTag/route.ts +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/api/revalidated-fetch/route.ts +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/api/uncached-fetch/route.ts +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6}/src/app/globals.css +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/layout.tsx +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6}/src/app/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/no-fetch/default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
- /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6}/src/app/revalidation-interface.tsx +0 -0
- /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6}/src/app/update-tag-test/page.tsx +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/README.md +0 -0
- /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/eslint.config.mjs +0 -0
- /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/postcss.config.mjs +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/file.svg +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/globe.svg +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/next.svg +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/vercel.svg +0 -0
- /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/window.svg +0 -0
- /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/src/app/favicon.ico +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/src/app/globals.css +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/src/app/page.tsx +0 -0
- /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/src/app/revalidation-interface.tsx +0 -0
- /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/tsconfig.json +0 -0
- /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-customized}/README.md +0 -0
- /package/test/{integration → nextjs-test-projects}/next-app-customized/customized-cache-handler.js +0 -0
- /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-customized}/eslint.config.mjs +0 -0
- /package/test/{integration → nextjs-test-projects}/next-app-customized/next.config.js +0 -0
- /package/{tests → test/playwright}/cache-lab.spec.ts +0 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
import { Suspense } from 'react';
|
|
3
|
+
import { cookies } from 'next/headers';
|
|
4
|
+
|
|
5
|
+
export default function RuntimeDataSuspensePage() {
|
|
6
|
+
return (
|
|
7
|
+
<main className="mx-auto max-w-3xl p-10">
|
|
8
|
+
<div className="mb-6">
|
|
9
|
+
<Link
|
|
10
|
+
className="text-sm text-blue-600 hover:underline"
|
|
11
|
+
href="/cache-lab"
|
|
12
|
+
>
|
|
13
|
+
← Back to Cache Lab
|
|
14
|
+
</Link>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<header className="mb-6">
|
|
18
|
+
<h1 className="text-2xl font-semibold">Runtime data + Suspense</h1>
|
|
19
|
+
<p className="mt-2 text-sm text-slate-600">
|
|
20
|
+
This page reads <code>cookies()</code> at request time (runtime data),
|
|
21
|
+
then passes a value into a cached function. The runtime read is
|
|
22
|
+
wrapped in <code>{'<Suspense>'}</code> so it is an explicit dynamic
|
|
23
|
+
boundary.
|
|
24
|
+
</p>
|
|
25
|
+
</header>
|
|
26
|
+
|
|
27
|
+
<Suspense
|
|
28
|
+
fallback={
|
|
29
|
+
<div className="rounded-lg border bg-slate-50 p-5 text-sm text-slate-700">
|
|
30
|
+
Loading runtime cookie…
|
|
31
|
+
</div>
|
|
32
|
+
}
|
|
33
|
+
>
|
|
34
|
+
<RuntimeCookieBlock />
|
|
35
|
+
</Suspense>
|
|
36
|
+
|
|
37
|
+
<div className="mt-6 rounded-lg border bg-slate-50 p-5 text-sm text-slate-700">
|
|
38
|
+
<div className="space-y-2">
|
|
39
|
+
<p>
|
|
40
|
+
Expected: changing the cookie changes the <em>input</em> to the
|
|
41
|
+
cached function, producing a different cache entry.
|
|
42
|
+
</p>
|
|
43
|
+
<p>
|
|
44
|
+
The cached function itself uses <code>use cache</code>, so for the
|
|
45
|
+
same cookie value, it should return a stable payload across reloads.
|
|
46
|
+
</p>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</main>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function setCookie(value: string) {
|
|
54
|
+
'use server';
|
|
55
|
+
const store = await cookies();
|
|
56
|
+
store.set('cache_lab_session', value, { path: '/' });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function setCookieUserA() {
|
|
60
|
+
'use server';
|
|
61
|
+
await setCookie('user-a');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function setCookieUserB() {
|
|
65
|
+
'use server';
|
|
66
|
+
await setCookie('user-b');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function RuntimeCookieBlock() {
|
|
70
|
+
const store = await cookies();
|
|
71
|
+
const sessionId = store.get('cache_lab_session')?.value || 'anonymous';
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div className="rounded-lg border p-5">
|
|
75
|
+
<div className="mb-4 text-sm">
|
|
76
|
+
<div>
|
|
77
|
+
<span className="font-medium">cookie cache_lab_session:</span>{' '}
|
|
78
|
+
<span data-testid="cookie" className="font-mono text-slate-700">
|
|
79
|
+
{sessionId}
|
|
80
|
+
</span>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<div className="flex flex-col gap-3 sm:flex-row">
|
|
85
|
+
<form action={setCookieUserA}>
|
|
86
|
+
<button
|
|
87
|
+
className="w-full rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700"
|
|
88
|
+
type="submit"
|
|
89
|
+
>
|
|
90
|
+
Set cookie: user-a
|
|
91
|
+
</button>
|
|
92
|
+
</form>
|
|
93
|
+
<form action={setCookieUserB}>
|
|
94
|
+
<button
|
|
95
|
+
className="w-full rounded-md border border-blue-600 bg-white px-4 py-2 text-sm font-medium text-blue-700 hover:bg-blue-50"
|
|
96
|
+
type="submit"
|
|
97
|
+
>
|
|
98
|
+
Set cookie: user-b
|
|
99
|
+
</button>
|
|
100
|
+
</form>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div className="mt-5">
|
|
104
|
+
<CachedPerSession sessionId={sessionId} />
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function CachedPerSession({ sessionId }: { sessionId: string }) {
|
|
111
|
+
'use cache';
|
|
112
|
+
|
|
113
|
+
const payload = {
|
|
114
|
+
sessionId,
|
|
115
|
+
createdAt: Date.now(),
|
|
116
|
+
random: Math.random(),
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<div className="rounded-md bg-slate-50 p-4 text-sm">
|
|
121
|
+
<div className="font-medium">Cached payload (keyed by sessionId)</div>
|
|
122
|
+
<pre data-testid="payload" className="mt-2 overflow-x-auto text-xs">
|
|
123
|
+
{JSON.stringify(payload, null, 2)}
|
|
124
|
+
</pre>
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
import { Suspense } from 'react';
|
|
3
|
+
import { cacheLife, cacheTag, revalidateTag } from 'next/cache';
|
|
4
|
+
|
|
5
|
+
function sleep(ms: number): Promise<void> {
|
|
6
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function getSlowTaggedValue() {
|
|
10
|
+
'use cache';
|
|
11
|
+
|
|
12
|
+
// Keep this short so you can observe SWR behavior quickly.
|
|
13
|
+
// stale: after 2s the entry is considered stale
|
|
14
|
+
// revalidate: after 8s Next may attempt to refresh
|
|
15
|
+
// expire: after 60s it must be recomputed
|
|
16
|
+
cacheLife({ stale: 2, revalidate: 8, expire: 60 });
|
|
17
|
+
cacheTag('cache-lab:swr');
|
|
18
|
+
|
|
19
|
+
// Make regeneration visibly expensive so you can tell whether the request was
|
|
20
|
+
// served from stale cache (fast) or blocked on recomputation (slow).
|
|
21
|
+
await sleep(3000);
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
computedAt: Date.now(),
|
|
25
|
+
value: Math.random(),
|
|
26
|
+
note: 'This function always sleeps ~3s when it actually executes.',
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function triggerRevalidateTag() {
|
|
31
|
+
'use server';
|
|
32
|
+
revalidateTag('cache-lab:swr', { expire: 1 });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function CachedPanel() {
|
|
36
|
+
const data = await getSlowTaggedValue();
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="rounded-lg border p-5">
|
|
40
|
+
<div className="text-sm text-slate-700">
|
|
41
|
+
<div>
|
|
42
|
+
<span className="font-medium">computedAt:</span>{' '}
|
|
43
|
+
<span data-testid="computedAt" className="font-mono">
|
|
44
|
+
{data.computedAt}
|
|
45
|
+
</span>
|
|
46
|
+
</div>
|
|
47
|
+
<div className="mt-1">
|
|
48
|
+
<span className="font-medium">value:</span>{' '}
|
|
49
|
+
<span data-testid="value" className="font-mono">
|
|
50
|
+
{data.value}
|
|
51
|
+
</span>
|
|
52
|
+
</div>
|
|
53
|
+
<div className="mt-1 text-xs text-slate-500">{data.note}</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default function StaleWhileRevalidatePage() {
|
|
60
|
+
return (
|
|
61
|
+
<main className="mx-auto max-w-3xl p-10">
|
|
62
|
+
<div className="mb-6">
|
|
63
|
+
<Link
|
|
64
|
+
className="text-sm text-blue-600 hover:underline"
|
|
65
|
+
href="/cache-lab"
|
|
66
|
+
>
|
|
67
|
+
← Back to Cache Lab
|
|
68
|
+
</Link>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<header className="mb-6">
|
|
72
|
+
<h1 className="text-2xl font-semibold">
|
|
73
|
+
SWR: stale-while-revalidate (slow)
|
|
74
|
+
</h1>
|
|
75
|
+
<p className="mt-2 text-sm text-slate-600">
|
|
76
|
+
This page is designed to make stale-while-revalidate behavior visible.
|
|
77
|
+
The cached function sleeps ~3 seconds whenever it really runs.
|
|
78
|
+
</p>
|
|
79
|
+
</header>
|
|
80
|
+
|
|
81
|
+
<Suspense
|
|
82
|
+
fallback={
|
|
83
|
+
<div className="rounded-lg border bg-slate-50 p-5 text-sm text-slate-700">
|
|
84
|
+
Loading cached content… (first time can take ~3s)
|
|
85
|
+
</div>
|
|
86
|
+
}
|
|
87
|
+
>
|
|
88
|
+
<CachedPanel />
|
|
89
|
+
</Suspense>
|
|
90
|
+
|
|
91
|
+
<div className="mt-6 flex flex-col gap-3 sm:flex-row">
|
|
92
|
+
<form action={triggerRevalidateTag}>
|
|
93
|
+
<button
|
|
94
|
+
className="w-full rounded-md border border-blue-600 bg-white px-4 py-2 text-sm font-medium text-blue-700 hover:bg-blue-50"
|
|
95
|
+
type="submit"
|
|
96
|
+
>
|
|
97
|
+
revalidateTag('cache-lab:swr')
|
|
98
|
+
</button>
|
|
99
|
+
</form>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div className="mt-6 rounded-lg border bg-slate-50 p-5 text-sm text-slate-700">
|
|
103
|
+
<div className="space-y-2">
|
|
104
|
+
<p className="font-medium">How to observe SWR (non-blocking)</p>
|
|
105
|
+
<p>1) Load this page once (expect ~3s on the very first load).</p>
|
|
106
|
+
<p>
|
|
107
|
+
2) Wait ~3 seconds so the entry becomes <code>stale</code>{' '}
|
|
108
|
+
(stale=2s).
|
|
109
|
+
</p>
|
|
110
|
+
<p>
|
|
111
|
+
3) Click <code>revalidateTag</code>.
|
|
112
|
+
</p>
|
|
113
|
+
<p>4) Immediately reload the page:</p>
|
|
114
|
+
<div className="rounded bg-white p-3 text-xs">
|
|
115
|
+
If SWR is working, the reload should be fast and you may still see
|
|
116
|
+
the old computedAt/value (stale served).
|
|
117
|
+
<br />
|
|
118
|
+
Reload again after ~3-4 seconds and you should see a new
|
|
119
|
+
computedAt/value.
|
|
120
|
+
<br />
|
|
121
|
+
If SWR is not working, the reload will block for ~3 seconds.
|
|
122
|
+
</div>
|
|
123
|
+
<div className="text-xs text-slate-500">
|
|
124
|
+
cacheLife: {'{ stale: 2, revalidate: 8, expire: 60 }'}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</main>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
import { cacheTag, updateTag, revalidateTag, cacheLife } from 'next/cache';
|
|
3
|
+
import { Suspense } from 'react';
|
|
4
|
+
|
|
5
|
+
async function getTaggedData() {
|
|
6
|
+
'use cache';
|
|
7
|
+
|
|
8
|
+
cacheLife({ stale: 5, revalidate: 10, expire: 60 });
|
|
9
|
+
cacheTag('cache-lab:tag');
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
value: Math.random(),
|
|
13
|
+
createdAt: Date.now(),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function doUpdateTag() {
|
|
18
|
+
'use server';
|
|
19
|
+
updateTag('cache-lab:tag');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function doRevalidateTag() {
|
|
23
|
+
'use server';
|
|
24
|
+
revalidateTag('cache-lab:tag', { expire: 1 });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function CachedTaggedPanel() {
|
|
28
|
+
const data = await getTaggedData();
|
|
29
|
+
return (
|
|
30
|
+
<div className="rounded-lg border p-5">
|
|
31
|
+
<dl className="grid grid-cols-1 gap-3 text-sm">
|
|
32
|
+
<div>
|
|
33
|
+
<dt className="font-medium">createdAt</dt>
|
|
34
|
+
<dd data-testid="createdAt" className="font-mono text-slate-700">
|
|
35
|
+
{data.createdAt}
|
|
36
|
+
</dd>
|
|
37
|
+
</div>
|
|
38
|
+
<div>
|
|
39
|
+
<dt className="font-medium">value</dt>
|
|
40
|
+
<dd data-testid="value" className="font-mono text-slate-700">
|
|
41
|
+
{data.value}
|
|
42
|
+
</dd>
|
|
43
|
+
</div>
|
|
44
|
+
<div>
|
|
45
|
+
<dt className="font-medium">cacheLife</dt>
|
|
46
|
+
<dd className="font-mono text-slate-700">
|
|
47
|
+
{'{ stale: 5, revalidate: 10, expire: 60 }'}
|
|
48
|
+
</dd>
|
|
49
|
+
</div>
|
|
50
|
+
</dl>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default function TagInvalidationPage() {
|
|
56
|
+
return (
|
|
57
|
+
<main className="mx-auto max-w-3xl p-10">
|
|
58
|
+
<div className="mb-6">
|
|
59
|
+
<Link
|
|
60
|
+
className="text-sm text-blue-600 hover:underline"
|
|
61
|
+
href="/cache-lab"
|
|
62
|
+
>
|
|
63
|
+
← Back to Cache Lab
|
|
64
|
+
</Link>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<header className="mb-6">
|
|
68
|
+
<h1 className="text-2xl font-semibold">
|
|
69
|
+
Tags: updateTag vs revalidateTag
|
|
70
|
+
</h1>
|
|
71
|
+
<p className="mt-2 text-sm text-slate-600">
|
|
72
|
+
This page caches a function tagged with{' '}
|
|
73
|
+
<code>cacheTag('cache-lab:tag')</code>. Use the buttons to invalidate.
|
|
74
|
+
</p>
|
|
75
|
+
</header>
|
|
76
|
+
|
|
77
|
+
<Suspense
|
|
78
|
+
fallback={
|
|
79
|
+
<div className="rounded-lg border bg-slate-50 p-5 text-sm text-slate-700">
|
|
80
|
+
Loading cached tagged content…
|
|
81
|
+
</div>
|
|
82
|
+
}
|
|
83
|
+
>
|
|
84
|
+
<CachedTaggedPanel />
|
|
85
|
+
</Suspense>
|
|
86
|
+
|
|
87
|
+
<div className="mt-6 flex flex-col gap-3 sm:flex-row">
|
|
88
|
+
<form action={doUpdateTag}>
|
|
89
|
+
<button
|
|
90
|
+
className="w-full rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700"
|
|
91
|
+
type="submit"
|
|
92
|
+
>
|
|
93
|
+
updateTag('cache-lab:tag')
|
|
94
|
+
</button>
|
|
95
|
+
</form>
|
|
96
|
+
|
|
97
|
+
<form action={doRevalidateTag}>
|
|
98
|
+
<button
|
|
99
|
+
className="w-full rounded-md border border-blue-600 bg-white px-4 py-2 text-sm font-medium text-blue-700 hover:bg-blue-50"
|
|
100
|
+
type="submit"
|
|
101
|
+
>
|
|
102
|
+
revalidateTag('cache-lab:tag')
|
|
103
|
+
</button>
|
|
104
|
+
</form>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div className="mt-6 rounded-lg border bg-slate-50 p-5 text-sm text-slate-700">
|
|
108
|
+
<div className="space-y-2">
|
|
109
|
+
<p>Expected: values should stay stable on reloads.</p>
|
|
110
|
+
<p>
|
|
111
|
+
<strong>updateTag</strong> is intended for “expire + immediately
|
|
112
|
+
refresh within the same request” (used after mutations in Server
|
|
113
|
+
Actions).
|
|
114
|
+
</p>
|
|
115
|
+
<p>
|
|
116
|
+
<strong>revalidateTag</strong> invalidates tagged entries with
|
|
117
|
+
stale-while-revalidate behavior.
|
|
118
|
+
</p>
|
|
119
|
+
<p>
|
|
120
|
+
This page deliberately puts the async work behind{' '}
|
|
121
|
+
<code>{'<Suspense>'}</code> to avoid the "Blocking Route" warning.
|
|
122
|
+
</p>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
</main>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
import { cacheTag, updateTag } from 'next/cache';
|
|
3
|
+
|
|
4
|
+
async function getCachedNonDeterministicValues() {
|
|
5
|
+
'use cache';
|
|
6
|
+
|
|
7
|
+
cacheTag('cache-lab:nondet');
|
|
8
|
+
|
|
9
|
+
const random1 = Math.random();
|
|
10
|
+
const random2 = Math.random();
|
|
11
|
+
const now = Date.now();
|
|
12
|
+
const uuid = crypto.randomUUID();
|
|
13
|
+
const bytes = crypto.getRandomValues(new Uint8Array(8));
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
random1,
|
|
17
|
+
random2,
|
|
18
|
+
now,
|
|
19
|
+
uuid,
|
|
20
|
+
bytes: Array.from(bytes),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function refresh() {
|
|
25
|
+
'use server';
|
|
26
|
+
updateTag('cache-lab:nondet');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default async function UseCacheNonDeterministicPage() {
|
|
30
|
+
const data = await getCachedNonDeterministicValues();
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<main className="mx-auto max-w-3xl p-10">
|
|
34
|
+
<div className="mb-6">
|
|
35
|
+
<Link
|
|
36
|
+
className="text-sm text-blue-600 hover:underline"
|
|
37
|
+
href="/cache-lab"
|
|
38
|
+
>
|
|
39
|
+
← Back to Cache Lab
|
|
40
|
+
</Link>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<header className="mb-6">
|
|
44
|
+
<h1 className="text-2xl font-semibold">use cache: non-deterministic</h1>
|
|
45
|
+
<p className="mt-2 text-sm text-slate-600">
|
|
46
|
+
Per docs, non-deterministic operations inside a <code>use cache</code>{' '}
|
|
47
|
+
scope run once and then stay stable for all requests until you
|
|
48
|
+
invalidate.
|
|
49
|
+
</p>
|
|
50
|
+
</header>
|
|
51
|
+
|
|
52
|
+
<form action={refresh} className="mb-6">
|
|
53
|
+
<button
|
|
54
|
+
className="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700"
|
|
55
|
+
type="submit"
|
|
56
|
+
>
|
|
57
|
+
updateTag('cache-lab:nondet')
|
|
58
|
+
</button>
|
|
59
|
+
</form>
|
|
60
|
+
|
|
61
|
+
<div className="rounded-lg border p-5">
|
|
62
|
+
<dl className="grid grid-cols-1 gap-3 text-sm">
|
|
63
|
+
<div>
|
|
64
|
+
<dt className="font-medium">random1</dt>
|
|
65
|
+
<dd data-testid="random1" className="font-mono text-slate-700">
|
|
66
|
+
{data.random1}
|
|
67
|
+
</dd>
|
|
68
|
+
</div>
|
|
69
|
+
<div>
|
|
70
|
+
<dt className="font-medium">random2</dt>
|
|
71
|
+
<dd data-testid="random2" className="font-mono text-slate-700">
|
|
72
|
+
{data.random2}
|
|
73
|
+
</dd>
|
|
74
|
+
</div>
|
|
75
|
+
<div>
|
|
76
|
+
<dt className="font-medium">now (Date.now)</dt>
|
|
77
|
+
<dd data-testid="now" className="font-mono text-slate-700">
|
|
78
|
+
{data.now}
|
|
79
|
+
</dd>
|
|
80
|
+
</div>
|
|
81
|
+
<div>
|
|
82
|
+
<dt className="font-medium">uuid</dt>
|
|
83
|
+
<dd data-testid="uuid" className="font-mono text-slate-700">
|
|
84
|
+
{data.uuid}
|
|
85
|
+
</dd>
|
|
86
|
+
</div>
|
|
87
|
+
<div>
|
|
88
|
+
<dt className="font-medium">bytes</dt>
|
|
89
|
+
<dd data-testid="bytes" className="font-mono text-slate-700">
|
|
90
|
+
{JSON.stringify(data.bytes)}
|
|
91
|
+
</dd>
|
|
92
|
+
</div>
|
|
93
|
+
</dl>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div className="mt-6 rounded-lg border bg-slate-50 p-5 text-sm text-slate-700">
|
|
97
|
+
<div className="space-y-2">
|
|
98
|
+
<p>
|
|
99
|
+
Expected: Reloading the page should keep values identical across
|
|
100
|
+
requests.
|
|
101
|
+
</p>
|
|
102
|
+
<p>
|
|
103
|
+
Click the button (Server Action) to run <code>updateTag</code> and
|
|
104
|
+
refresh the cached entry.
|
|
105
|
+
</p>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</main>
|
|
109
|
+
);
|
|
110
|
+
}
|
package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/update-tag-test/page.tsx
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { updateTag } from 'next/cache';
|
|
2
|
+
|
|
3
|
+
let clicks = 0;
|
|
4
|
+
|
|
5
|
+
async function increment() {
|
|
6
|
+
'use server';
|
|
7
|
+
|
|
8
|
+
clicks++;
|
|
9
|
+
updateTag('update-tag-test');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function UpdateTagTestPage() {
|
|
13
|
+
return (
|
|
14
|
+
<main style={{ padding: 40, fontFamily: 'system-ui, sans-serif' }}>
|
|
15
|
+
<h1>UpdateTag Test</h1>
|
|
16
|
+
<form action={increment}>
|
|
17
|
+
<p data-testid="clicks">Clicks: {clicks}</p>
|
|
18
|
+
<button type="submit">Increment</button>
|
|
19
|
+
</form>
|
|
20
|
+
</main>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { test, expect } from '@playwright/test';
|
|
2
2
|
|
|
3
3
|
// This test assumes the Next 16 app is already running, e.g.:
|
|
4
|
-
// cd test/
|
|
4
|
+
// cd test/nextjs-test-projects/next-app-16-0-11 && pnpm dev
|
|
5
5
|
// on the same origin as baseURL.
|
|
6
6
|
|
|
7
7
|
test('button click triggers Server Action using updateTag', async ({
|
|
@@ -5,7 +5,7 @@ import path from 'path';
|
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import fetch from 'node-fetch';
|
|
7
7
|
|
|
8
|
-
const NEXT_APP = 'next-app-15-4-
|
|
8
|
+
const NEXT_APP = 'next-app-15-4-11';
|
|
9
9
|
const PORT = 3075;
|
|
10
10
|
const BASE_URL = `http://localhost:${PORT}`;
|
|
11
11
|
|
|
@@ -28,7 +28,7 @@ describe('BUILD_ID-based key prefix (Next handlers)', () => {
|
|
|
28
28
|
let buildId: string;
|
|
29
29
|
|
|
30
30
|
beforeAll(async () => {
|
|
31
|
-
appDir = path.join(__dirname, NEXT_APP);
|
|
31
|
+
appDir = path.join(__dirname, '..', '..', 'nextjs-test-projects', NEXT_APP);
|
|
32
32
|
|
|
33
33
|
// Ensure clean env so BUILD_ID precedence kicks in
|
|
34
34
|
delete (process.env as Record<string, string | undefined>).KEY_PREFIX;
|
|
@@ -11,6 +11,10 @@ describe('Next.js 16 Cache Components Integration', () => {
|
|
|
11
11
|
let redisClient: RedisClientType;
|
|
12
12
|
let keyPrefix: string;
|
|
13
13
|
|
|
14
|
+
async function delay(ms: number) {
|
|
15
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
16
|
+
}
|
|
17
|
+
|
|
14
18
|
beforeAll(async () => {
|
|
15
19
|
// Connect to Redis
|
|
16
20
|
redisClient = createClient({
|
|
@@ -23,12 +27,16 @@ describe('Next.js 16 Cache Components Integration', () => {
|
|
|
23
27
|
keyPrefix = `cache-components-test-${Math.random().toString(36).substring(7)}`;
|
|
24
28
|
process.env.VERCEL_URL = keyPrefix;
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
const cacheComponentsApp =
|
|
31
|
+
process.env.CACHE_COMPONENTS_APP || 'next-app-16-2-6-cache-components';
|
|
32
|
+
|
|
27
33
|
const appDir = path.join(
|
|
28
34
|
__dirname,
|
|
29
35
|
'..',
|
|
30
|
-
'
|
|
31
|
-
'
|
|
36
|
+
'..',
|
|
37
|
+
'..',
|
|
38
|
+
'nextjs-test-projects',
|
|
39
|
+
cacheComponentsApp,
|
|
32
40
|
);
|
|
33
41
|
|
|
34
42
|
console.log('Installing Next.js app dependencies...');
|
|
@@ -155,6 +163,41 @@ describe('Next.js 16 Cache Components Integration', () => {
|
|
|
155
163
|
}, 20_000);
|
|
156
164
|
});
|
|
157
165
|
|
|
166
|
+
describe('cacheLife functionality', () => {
|
|
167
|
+
it('should respect expire window and eventually return refreshed data', async () => {
|
|
168
|
+
const res1 = await fetch(`${BASE_URL}/api/cached-with-cachelife`);
|
|
169
|
+
const data1 = await res1.json();
|
|
170
|
+
expect(data1.counter).toBe(1);
|
|
171
|
+
|
|
172
|
+
const res2 = await fetch(`${BASE_URL}/api/cached-with-cachelife`);
|
|
173
|
+
const data2 = await res2.json();
|
|
174
|
+
expect(data2.counter).toBe(data1.counter);
|
|
175
|
+
expect(data2.timestamp).toBe(data1.timestamp);
|
|
176
|
+
|
|
177
|
+
await delay(6500);
|
|
178
|
+
|
|
179
|
+
let refreshedData: any;
|
|
180
|
+
for (let i = 0; i < 10; i++) {
|
|
181
|
+
const res = await fetch(`${BASE_URL}/api/cached-with-cachelife`);
|
|
182
|
+
const data = await res.json();
|
|
183
|
+
|
|
184
|
+
if (
|
|
185
|
+
data.counter !== data1.counter ||
|
|
186
|
+
data.timestamp !== data1.timestamp
|
|
187
|
+
) {
|
|
188
|
+
refreshedData = data;
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
await delay(500);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
expect(refreshedData).toBeDefined();
|
|
196
|
+
expect(refreshedData.counter).toBeGreaterThan(data1.counter);
|
|
197
|
+
expect(refreshedData.timestamp).not.toBe(data1.timestamp);
|
|
198
|
+
}, 20_000);
|
|
199
|
+
});
|
|
200
|
+
|
|
158
201
|
describe('Redis cache handler integration', () => {
|
|
159
202
|
it('should call cache handler get and set methods', async () => {
|
|
160
203
|
// Make request to trigger cache (don't clear first)
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import { spawn } from 'child_process';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import { spawnSync } from 'child_process';
|
|
5
|
+
|
|
6
|
+
function hasContainerRuntime() {
|
|
7
|
+
const podman = spawnSync('podman', ['--version'], {
|
|
8
|
+
encoding: 'utf8',
|
|
9
|
+
timeout: 3_000,
|
|
10
|
+
});
|
|
11
|
+
if (!podman.error && (podman.status ?? 1) === 0) return true;
|
|
12
|
+
|
|
13
|
+
const docker = spawnSync('docker', ['--version'], {
|
|
14
|
+
encoding: 'utf8',
|
|
15
|
+
timeout: 3_000,
|
|
16
|
+
});
|
|
17
|
+
return !docker.error && (docker.status ?? 1) === 0;
|
|
18
|
+
}
|
|
4
19
|
|
|
5
20
|
function runNode(script: string, timeoutMs = 120_000) {
|
|
6
21
|
return new Promise<{ code: number; stdout: string; stderr: string }>(
|
|
@@ -26,13 +41,19 @@ function runNode(script: string, timeoutMs = 120_000) {
|
|
|
26
41
|
}
|
|
27
42
|
|
|
28
43
|
describe('redis kill/reconnect end-to-end (both handlers)', () => {
|
|
29
|
-
|
|
30
|
-
const script = path.join(__dirname, 'scripts', 'redis-kill-reconnect.ts');
|
|
44
|
+
const runOrSkip = hasContainerRuntime() ? it : it.skip;
|
|
31
45
|
|
|
32
|
-
|
|
46
|
+
runOrSkip(
|
|
47
|
+
'survives redis restart without Socket already opened',
|
|
48
|
+
async () => {
|
|
49
|
+
const script = path.join(__dirname, 'scripts', 'redis-kill-reconnect.ts');
|
|
33
50
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
51
|
+
const res = await runNode(script, 180_000);
|
|
52
|
+
|
|
53
|
+
expect(res.code).toBe(0);
|
|
54
|
+
expect(res.stdout).toContain('OK');
|
|
55
|
+
expect(res.stderr).not.toContain('Socket already opened');
|
|
56
|
+
},
|
|
57
|
+
180_000,
|
|
58
|
+
);
|
|
38
59
|
});
|