@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,154 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { SyncedMap } from '../../../src/SyncedMap';
|
|
3
|
+
|
|
4
|
+
// Module-level knobs for the subscribe() mock so that behaviour persists
|
|
5
|
+
// across the duplicate() clients created inside SyncedMap.
|
|
6
|
+
let globalSubscribeCalls = 0;
|
|
7
|
+
let globalSubscribeFailUntil = 0;
|
|
8
|
+
|
|
9
|
+
class MockClient {
|
|
10
|
+
static nextId = 1;
|
|
11
|
+
id = MockClient.nextId++;
|
|
12
|
+
private errorHandlers: Set<(err: Error) => void | Promise<void>> = new Set();
|
|
13
|
+
quitShouldThrow = true;
|
|
14
|
+
|
|
15
|
+
isOpen = true;
|
|
16
|
+
isReady = true;
|
|
17
|
+
|
|
18
|
+
on(event: string, handler: (err: Error) => void | Promise<void>) {
|
|
19
|
+
if (event === 'error') this.errorHandlers.add(handler);
|
|
20
|
+
return this as any;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
emit(event: string, err: Error) {
|
|
24
|
+
if (event === 'error') {
|
|
25
|
+
for (const h of this.errorHandlers) {
|
|
26
|
+
void h(err);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
removeAllListeners(event?: string) {
|
|
33
|
+
if (!event || event === 'error') this.errorHandlers.clear();
|
|
34
|
+
return this as any;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async connect() {
|
|
38
|
+
this.isOpen = true;
|
|
39
|
+
this.isReady = true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async configGet() {
|
|
43
|
+
return { 'notify-keyspace-events': 'Exe' };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async subscribe() {
|
|
47
|
+
const attempt = globalSubscribeCalls++;
|
|
48
|
+
if (attempt < globalSubscribeFailUntil) {
|
|
49
|
+
throw new Error(`Mock subscribe failure ${attempt}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async quit() {
|
|
54
|
+
if (this.quitShouldThrow)
|
|
55
|
+
throw new Error('Mock quit failure (disconnected)');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async disconnect() {
|
|
59
|
+
this.isOpen = false;
|
|
60
|
+
this.isReady = false;
|
|
61
|
+
this.errorHandlers.clear();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
duplicate() {
|
|
65
|
+
return new MockClient();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
hScan() {
|
|
69
|
+
return { cursor: 0, tuples: [] };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
scan() {
|
|
73
|
+
return { cursor: 0, keys: [] };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function createMap(rootClient = new MockClient()) {
|
|
78
|
+
return new SyncedMap<string>({
|
|
79
|
+
client: rootClient as any,
|
|
80
|
+
keyPrefix: 'repro:',
|
|
81
|
+
redisKey: '__sharedTags__',
|
|
82
|
+
database: 0,
|
|
83
|
+
querySize: 250,
|
|
84
|
+
filterKeys: () => true,
|
|
85
|
+
customizedSync: {
|
|
86
|
+
withoutRedisHashmap: true,
|
|
87
|
+
withoutSetSync: true,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
describe('SyncedMap.reconnectSubscriber() repro tests', () => {
|
|
93
|
+
let originalSetTimeout: typeof global.setTimeout;
|
|
94
|
+
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
globalSubscribeCalls = 0;
|
|
97
|
+
globalSubscribeFailUntil = 0;
|
|
98
|
+
originalSetTimeout = global.setTimeout;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
afterEach(() => {
|
|
102
|
+
global.setTimeout = originalSetTimeout;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('Issue 2: first backoff delay should be 500ms, not 1000ms', async () => {
|
|
106
|
+
const root = new MockClient();
|
|
107
|
+
const map = createMap(root);
|
|
108
|
+
// wait for initial setup to succeed (attempt 0)
|
|
109
|
+
await (map as any).waitUntilReady().catch(() => undefined);
|
|
110
|
+
|
|
111
|
+
const capturedDelays: number[] = [];
|
|
112
|
+
global.setTimeout = ((callback: () => void, delay?: number) => {
|
|
113
|
+
capturedDelays.push(Number(delay ?? 0));
|
|
114
|
+
return originalSetTimeout(callback, 0) as any;
|
|
115
|
+
}) as any;
|
|
116
|
+
|
|
117
|
+
// initial subscribe consumed attempt 0; force the next 3 reconnection
|
|
118
|
+
// attempts to fail, then succeed on the 4th
|
|
119
|
+
globalSubscribeFailUntil = 4;
|
|
120
|
+
|
|
121
|
+
await (map as any).reconnectSubscriber();
|
|
122
|
+
|
|
123
|
+
expect(capturedDelays.length).toBeGreaterThanOrEqual(1);
|
|
124
|
+
// Bug: delay is computed with attempt already incremented, so the first
|
|
125
|
+
// retry waits 500 * 2^1 = 1000 ms. After the fix it should be 500 ms.
|
|
126
|
+
expect(capturedDelays[0]).toBe(500);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('Issue 1: stale error on old subscriber must not kill the healthy new subscriber', async () => {
|
|
130
|
+
const root = new MockClient();
|
|
131
|
+
const map = createMap(root);
|
|
132
|
+
await (map as any).waitUntilReady().catch(() => undefined);
|
|
133
|
+
|
|
134
|
+
const oldSubscriber = (map as any).subscriberClient as MockClient;
|
|
135
|
+
expect(oldSubscriber).toBeDefined();
|
|
136
|
+
|
|
137
|
+
await (map as any).reconnectSubscriber();
|
|
138
|
+
|
|
139
|
+
const newSubscriber = (map as any).subscriberClient as MockClient;
|
|
140
|
+
expect(newSubscriber.id).not.toBe(oldSubscriber.id);
|
|
141
|
+
|
|
142
|
+
// Simulate a delayed error from the old (now replaced) subscriber.
|
|
143
|
+
// With the current code the old 'error' listener is still attached,
|
|
144
|
+
// so it triggers reconnectSubscriber() and tears down the healthy client.
|
|
145
|
+
oldSubscriber.emit('error', new Error('delayed zombie error'));
|
|
146
|
+
|
|
147
|
+
// Give the async error handler a chance to run.
|
|
148
|
+
await new Promise((r) => originalSetTimeout(r, 50));
|
|
149
|
+
|
|
150
|
+
const afterStaleError = (map as any).subscriberClient as MockClient;
|
|
151
|
+
|
|
152
|
+
expect(afterStaleError.id).toBe(newSubscriber.id);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
|
|
3
|
-
import type { CreateRedisStringsHandlerOptions } from '
|
|
4
|
-
import RedisStringsHandler from '
|
|
3
|
+
import type { CreateRedisStringsHandlerOptions } from '../../../src/index';
|
|
4
|
+
import RedisStringsHandler from '../../../src/RedisStringsHandler';
|
|
5
5
|
|
|
6
6
|
vi.mock('redis', () => {
|
|
7
7
|
const createClient = () => {
|
|
@@ -38,7 +38,7 @@ vi.mock('redis', () => {
|
|
|
38
38
|
};
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
vi.mock('
|
|
41
|
+
vi.mock('../../src/SyncedMap', () => {
|
|
42
42
|
class SyncedMap {
|
|
43
43
|
waitUntilReady = vi.fn(async () => undefined);
|
|
44
44
|
get = vi.fn(() => undefined);
|
|
@@ -22,7 +22,7 @@ describe('RedisCacheComponentsHandler reconnect logic', () => {
|
|
|
22
22
|
.spyOn(console, 'error')
|
|
23
23
|
.mockImplementation(() => {});
|
|
24
24
|
|
|
25
|
-
const { getRedisCacheComponentsHandler } = await import('
|
|
25
|
+
const { getRedisCacheComponentsHandler } = await import('../../../src');
|
|
26
26
|
|
|
27
27
|
const handler = getRedisCacheComponentsHandler({
|
|
28
28
|
redisUrl: 'redis://127.0.0.1:6399',
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
CacheValueSerializer,
|
|
5
|
+
jsonCacheValueSerializer,
|
|
6
|
+
} from '../../../src/serializer';
|
|
7
|
+
import type { CacheEntry } from '../../../src/RedisStringsHandler';
|
|
8
|
+
|
|
9
|
+
function makeEntry(value: unknown): CacheEntry {
|
|
10
|
+
return {
|
|
11
|
+
value,
|
|
12
|
+
lastModified: 1700000000000,
|
|
13
|
+
tags: ['tag-a', 'tag-b'],
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('jsonCacheValueSerializer (default)', () => {
|
|
18
|
+
it('round-trips a CacheEntry with a top-level Buffer value (Buffer + Map encoding preserved)', async () => {
|
|
19
|
+
const original = makeEntry(Buffer.from('hello world', 'utf8'));
|
|
20
|
+
|
|
21
|
+
const serialized = await jsonCacheValueSerializer.serialize(original);
|
|
22
|
+
expect(typeof serialized).toBe('string');
|
|
23
|
+
|
|
24
|
+
const restored = await jsonCacheValueSerializer.deserialize(serialized);
|
|
25
|
+
expect(restored).not.toBeNull();
|
|
26
|
+
expect(Buffer.isBuffer(restored!.value)).toBe(true);
|
|
27
|
+
expect((restored!.value as Buffer).toString('utf8')).toBe('hello world');
|
|
28
|
+
expect(restored!.lastModified).toBe(original.lastModified);
|
|
29
|
+
expect(restored!.tags).toEqual(original.tags);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('round-trips a Buffer nested inside a CacheEntry value object', async () => {
|
|
33
|
+
const original = makeEntry({
|
|
34
|
+
kind: 'APP_ROUTE',
|
|
35
|
+
body: Buffer.from([0x01, 0x02, 0x03, 0x04]),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const serialized = await jsonCacheValueSerializer.serialize(original);
|
|
39
|
+
const restored = await jsonCacheValueSerializer.deserialize(serialized);
|
|
40
|
+
|
|
41
|
+
const nested = (restored!.value as { body: Buffer }).body;
|
|
42
|
+
expect(Buffer.isBuffer(nested)).toBe(true);
|
|
43
|
+
expect(Array.from(nested)).toEqual([0x01, 0x02, 0x03, 0x04]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('round-trips a Map value back to a native Map instance', async () => {
|
|
47
|
+
const map = new Map<string, string>([
|
|
48
|
+
['k1', 'v1'],
|
|
49
|
+
['k2', 'v2'],
|
|
50
|
+
]);
|
|
51
|
+
const original = makeEntry(map);
|
|
52
|
+
|
|
53
|
+
const serialized = await jsonCacheValueSerializer.serialize(original);
|
|
54
|
+
const restored = await jsonCacheValueSerializer.deserialize(serialized);
|
|
55
|
+
|
|
56
|
+
expect(restored!.value).toBeInstanceOf(Map);
|
|
57
|
+
const restoredMap = restored!.value as Map<string, string>;
|
|
58
|
+
expect(restoredMap.size).toBe(2);
|
|
59
|
+
expect(restoredMap.get('k1')).toBe('v1');
|
|
60
|
+
expect(restoredMap.get('k2')).toBe('v2');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('is referentially stable so consumers can detect default usage', async () => {
|
|
64
|
+
// Importing twice should yield the same singleton; this guards against
|
|
65
|
+
// accidental "factory" refactors that would break reference equality.
|
|
66
|
+
const reimport = (await import('../../../src/serializer'))
|
|
67
|
+
.jsonCacheValueSerializer;
|
|
68
|
+
expect(reimport).toBe(jsonCacheValueSerializer);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('CacheValueSerializer custom implementations', () => {
|
|
73
|
+
it('invokes a synchronous custom serializer exactly once per call', async () => {
|
|
74
|
+
const serialize = vi.fn(
|
|
75
|
+
(value: CacheEntry) => `sync:${JSON.stringify(value)}`,
|
|
76
|
+
);
|
|
77
|
+
const deserialize = vi.fn(
|
|
78
|
+
(stored: string) =>
|
|
79
|
+
JSON.parse(stored.replace(/^sync:/, '')) as CacheEntry,
|
|
80
|
+
);
|
|
81
|
+
const custom: CacheValueSerializer = { serialize, deserialize };
|
|
82
|
+
|
|
83
|
+
const entry = makeEntry({ kind: 'FETCH', payload: 42 });
|
|
84
|
+
|
|
85
|
+
const out = await custom.serialize(entry);
|
|
86
|
+
expect(serialize).toHaveBeenCalledTimes(1);
|
|
87
|
+
expect(out.startsWith('sync:')).toBe(true);
|
|
88
|
+
|
|
89
|
+
const restored = await custom.deserialize(out);
|
|
90
|
+
expect(deserialize).toHaveBeenCalledTimes(1);
|
|
91
|
+
expect(restored).toEqual(entry);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('awaits an asynchronous custom serializer (Promise-returning serialize/deserialize)', async () => {
|
|
95
|
+
const custom: CacheValueSerializer = {
|
|
96
|
+
async serialize(value) {
|
|
97
|
+
// Simulate async work (e.g. zlib.brotliCompress promisified).
|
|
98
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
99
|
+
return `async:${JSON.stringify(value)}`;
|
|
100
|
+
},
|
|
101
|
+
async deserialize(stored) {
|
|
102
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
103
|
+
return JSON.parse(stored.replace(/^async:/, '')) as CacheEntry;
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const entry = makeEntry({ kind: 'FETCH', payload: 'async-value' });
|
|
108
|
+
|
|
109
|
+
const out = await custom.serialize(entry);
|
|
110
|
+
expect(typeof out).toBe('string');
|
|
111
|
+
expect(out.startsWith('async:')).toBe(true);
|
|
112
|
+
|
|
113
|
+
const restored = await custom.deserialize(out);
|
|
114
|
+
expect(restored).toEqual(entry);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('treats a deserializer returning null as a cache miss (sync and async)', async () => {
|
|
118
|
+
const syncMiss: CacheValueSerializer = {
|
|
119
|
+
serialize: () => 'ignored',
|
|
120
|
+
deserialize: () => null,
|
|
121
|
+
};
|
|
122
|
+
const asyncMiss: CacheValueSerializer = {
|
|
123
|
+
serialize: async () => 'ignored',
|
|
124
|
+
deserialize: async () => null,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
expect(await syncMiss.deserialize('whatever')).toBeNull();
|
|
128
|
+
expect(await asyncMiss.deserialize('whatever')).toBeNull();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('propagates synchronous serializer throws and asynchronous rejections to the caller', async () => {
|
|
132
|
+
const syncThrowing: CacheValueSerializer = {
|
|
133
|
+
serialize() {
|
|
134
|
+
throw new Error('sync serialize boom');
|
|
135
|
+
},
|
|
136
|
+
deserialize() {
|
|
137
|
+
throw new Error('sync deserialize boom');
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
const asyncRejecting: CacheValueSerializer = {
|
|
141
|
+
async serialize() {
|
|
142
|
+
throw new Error('async serialize boom');
|
|
143
|
+
},
|
|
144
|
+
async deserialize() {
|
|
145
|
+
throw new Error('async deserialize boom');
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const entry = makeEntry({ kind: 'FETCH', payload: 'x' });
|
|
150
|
+
|
|
151
|
+
expect(() => syncThrowing.serialize(entry)).toThrow('sync serialize boom');
|
|
152
|
+
expect(() => syncThrowing.deserialize('x')).toThrow(
|
|
153
|
+
'sync deserialize boom',
|
|
154
|
+
);
|
|
155
|
+
await expect(asyncRejecting.serialize(entry)).rejects.toThrow(
|
|
156
|
+
'async serialize boom',
|
|
157
|
+
);
|
|
158
|
+
await expect(asyncRejecting.deserialize('x')).rejects.toThrow(
|
|
159
|
+
'async deserialize boom',
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('passes the exact stored string to deserialize (no pre-parsing by the caller)', async () => {
|
|
164
|
+
const serialize = (value: CacheEntry) =>
|
|
165
|
+
`wrapped(${JSON.stringify(value)})`;
|
|
166
|
+
const deserialize = vi.fn((stored: string) => {
|
|
167
|
+
// If the caller mutated the wire format, this would explode.
|
|
168
|
+
const m = /^wrapped\((.+)\)$/.exec(stored);
|
|
169
|
+
if (!m) return null;
|
|
170
|
+
return JSON.parse(m[1]) as CacheEntry;
|
|
171
|
+
});
|
|
172
|
+
const custom: CacheValueSerializer = { serialize, deserialize };
|
|
173
|
+
|
|
174
|
+
const entry = makeEntry({ kind: 'APP_PAGE', html: '<p>hi</p>' });
|
|
175
|
+
|
|
176
|
+
const wire = await custom.serialize(entry);
|
|
177
|
+
const restored = await custom.deserialize(wire);
|
|
178
|
+
|
|
179
|
+
expect(deserialize).toHaveBeenCalledWith(wire);
|
|
180
|
+
expect(restored).toEqual(entry);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach } from 'vitest';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import { resolveKeyPrefix } from '
|
|
5
|
+
import { resolveKeyPrefix } from '../../../../src/utils/prefix';
|
|
6
6
|
|
|
7
7
|
function withEnv<T>(env: Partial<NodeJS.ProcessEnv>, fn: () => T): T {
|
|
8
8
|
const original: NodeJS.ProcessEnv = { ...process.env };
|
package/vite.config.ts
CHANGED
|
@@ -5,9 +5,9 @@ export default defineConfig({
|
|
|
5
5
|
globals: true,
|
|
6
6
|
environment: 'node',
|
|
7
7
|
include: [
|
|
8
|
-
'
|
|
9
|
-
'
|
|
10
|
-
'test/integration/**/*.test.ts',
|
|
8
|
+
'test/vitest/unit/**/*.test.ts',
|
|
9
|
+
'test/vitest/unit/**/*.test.tsx',
|
|
10
|
+
'test/vitest/integration/**/*.test.ts',
|
|
11
11
|
],
|
|
12
12
|
exclude: [
|
|
13
13
|
'node_modules',
|
|
@@ -15,6 +15,7 @@ export default defineConfig({
|
|
|
15
15
|
'.git',
|
|
16
16
|
'**/node_modules/**',
|
|
17
17
|
'**/node_modules',
|
|
18
|
+
'test/vitest/integration/cache-components/**',
|
|
18
19
|
],
|
|
19
20
|
coverage: {
|
|
20
21
|
provider: 'v8',
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { test, expect } from 'vitest';
|
|
2
|
-
|
|
3
|
-
// Base URL of the running Next 16 app.
|
|
4
|
-
// Start it separately, e.g.:
|
|
5
|
-
// cd test/integration/next-app-16-0-3 && pnpm dev
|
|
6
|
-
// and ensure it listens on the same origin as below.
|
|
7
|
-
const BASE_URL =
|
|
8
|
-
(globalThis as any).NEXT_BROWSER_BASE_URL || 'http://localhost:3000';
|
|
9
|
-
|
|
10
|
-
// This test exercises a real Server Action that calls updateTag.
|
|
11
|
-
// It does not assert Redis state, but it verifies that calling the
|
|
12
|
-
// server action endpoint does not trigger the "Server Action only" error.
|
|
13
|
-
|
|
14
|
-
test('Server Action calling updateTag responds without server-action-only error', async () => {
|
|
15
|
-
// 1) Fetch the SSR HTML for the page containing the Server Action form
|
|
16
|
-
const res = await fetch(`${BASE_URL}/update-tag-test`);
|
|
17
|
-
expect(res.status).toBeLessThan(500);
|
|
18
|
-
const html = await res.text();
|
|
19
|
-
|
|
20
|
-
// 2) Parse the HTML to find the form action URL that Next generated
|
|
21
|
-
const parser = new DOMParser();
|
|
22
|
-
const doc = parser.parseFromString(html, 'text/html');
|
|
23
|
-
const form = doc.querySelector('form');
|
|
24
|
-
expect(form).not.toBeNull();
|
|
25
|
-
|
|
26
|
-
const actionAttr = form!.getAttribute('action');
|
|
27
|
-
expect(actionAttr).toBeTruthy();
|
|
28
|
-
|
|
29
|
-
const actionUrl = new URL(actionAttr!, BASE_URL).toString();
|
|
30
|
-
|
|
31
|
-
// 3) Call the Server Action endpoint directly.
|
|
32
|
-
// If updateTag were not allowed here, Next.js would respond with
|
|
33
|
-
// an error page that contains the specific error message.
|
|
34
|
-
const actionRes = await fetch(actionUrl, { method: 'POST' });
|
|
35
|
-
const actionText = await actionRes.text();
|
|
36
|
-
|
|
37
|
-
expect(actionRes.status).toBeLessThan(500);
|
|
38
|
-
expect(actionText).not.toContain(
|
|
39
|
-
'updateTag can only be called from within a Server Action',
|
|
40
|
-
);
|
|
41
|
-
});
|