@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
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# nextjs-turbo-redis-cache
|
|
1
|
+
# nextjs-turbo-redis-cache - Next.js Cache Handler
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@trieb.work/nextjs-turbo-redis-cache)
|
|
4
4
|

|
|
5
5
|
|
|
6
|
-
The ultimate Redis
|
|
6
|
+
The ultimate Redis Cache Handler for Next.js 15 / 16 and the app router. Built for production-ready, large-scale projects, it delivers unparalleled performance and efficiency with features tailored for high-traffic applications. This package has been created after extensibly testing the @neshca package and finding several major issues with it.
|
|
7
7
|
|
|
8
8
|
Key Features:
|
|
9
9
|
|
|
@@ -15,6 +15,8 @@ Key Features:
|
|
|
15
15
|
|
|
16
16
|
This library offers you an easy and high performant caching solution for docker, Kubernetes or Google Cloud Run deployments of Next.js. Read more on how it originated at [TRWK> Case Study](https://trwk.de/case-studies/nextjs-turbo-redis-cache).
|
|
17
17
|
|
|
18
|
+
For a deep dive into the internal architecture (shared hash maps, SyncedMap, request deduplication, and how `get`/`set`/`revalidateTag` work under the hood), see [ARCHITECTURE.md](./ARCHITECTURE.md).
|
|
19
|
+
|
|
18
20
|
## Compatibility
|
|
19
21
|
|
|
20
22
|
This package is compatible with Next.js 15.0.3 and above while using App Router. It is not compatible with Next.js 14.x. or 15-canary or if you are using Pages Router.
|
|
@@ -25,12 +27,12 @@ Tested versions are:
|
|
|
25
27
|
- Nextjs 15.0.3 + redis client 4.7.0
|
|
26
28
|
- Nextjs 15.2.4 + redis client 4.7.0
|
|
27
29
|
- Nextjs 15.3.2 + redis client 4.7.0
|
|
28
|
-
- Nextjs 15.4.
|
|
29
|
-
- Nextjs 16.0.
|
|
30
|
-
- Nextjs 16.2.
|
|
31
|
-
- Nextjs 16.2.
|
|
30
|
+
- Nextjs 15.4.11 + redis client 4.7.0
|
|
31
|
+
- Nextjs 16.0.11 + redis client 4.7.0 (cacheComponents: false)
|
|
32
|
+
- Nextjs 16.2.6 + redis client 4.7.0 (cacheComponents: false)
|
|
33
|
+
- Nextjs 16.2.6 + redis client 4.7.0 (cacheComponents: true)
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
_Cache Components_ (Next.js 16+) are fully supported. Automated test coverage includes `'use cache'`, `cacheTag`, and `cacheLife` flows in the Cache Components integration suite.
|
|
34
36
|
|
|
35
37
|
For Cache Components, see the "Cache Components handler (Next.js 16+)" section below.
|
|
36
38
|
|
|
@@ -133,26 +135,172 @@ const nextConfig = {
|
|
|
133
135
|
}
|
|
134
136
|
```
|
|
135
137
|
|
|
136
|
-
A working example of above can be found in the `test/
|
|
138
|
+
A working example of above can be found in the `test/nextjs-test-projects/next-app-customized` folder.
|
|
137
139
|
|
|
138
140
|
## Available Options
|
|
139
141
|
|
|
140
|
-
| Option | Description
|
|
141
|
-
| ----------------------------- |
|
|
142
|
-
| redisUrl | Redis connection url
|
|
143
|
-
| database | Redis database number to use. Uses DB 0 for production, DB 1 otherwise
|
|
144
|
-
| keyPrefix | Prefix added to all Redis keys
|
|
145
|
-
| sharedTagsKey | Key used to store shared tags hash map in Redis
|
|
146
|
-
| getTimeoutMs | Timeout in milliseconds for time critical Redis operations. If Redis get is not fulfilled within this time, returns null to avoid blocking site rendering.
|
|
147
|
-
| revalidateTagQuerySize | Number of entries to query in one batch during full sync of shared tags hash map
|
|
148
|
-
| avgResyncIntervalMs | Average interval in milliseconds between tag map full re-syncs
|
|
149
|
-
| redisGetDeduplication | Enable deduplication of Redis get requests via internal in-memory cache.
|
|
150
|
-
| inMemoryCachingTime | Time in milliseconds to cache Redis get results in memory. Set this to 0 to disable in-memory caching completely.
|
|
151
|
-
| defaultStaleAge | Default stale age in seconds for cached items
|
|
152
|
-
| estimateExpireAge | Function to calculate expire age (redis TTL value) from stale age
|
|
153
|
-
| socketOptions | Redis client socket options for TLS/SSL configuration (e.g., `{ tls: true, rejectUnauthorized: false }`)
|
|
154
|
-
| clientOptions | Additional Redis client options (e.g., username, password)
|
|
155
|
-
| killContainerOnErrorThreshold | Number of consecutive errors before the container is killed. Set to 0 to disable.
|
|
142
|
+
| Option | Description | Default Value |
|
|
143
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
144
|
+
| redisUrl | Redis connection url | `process.env.REDIS_URL? process.env.REDIS_URL : process.env.REDISHOST ? redis://${process.env.REDISHOST}:${process.env.REDISPORT} : 'redis://localhost:6379'` |
|
|
145
|
+
| database | Redis database number to use. Uses DB 0 for production, DB 1 otherwise | `process.env.VERCEL_ENV === 'production' ? 0 : 1` |
|
|
146
|
+
| keyPrefix | Prefix added to all Redis keys | `RedisStringsHandler` default: `process.env.KEY_PREFIX \|\| process.env.VERCEL_URL \|\| 'UNDEFINED_URL_'`<br> Next handlers resolve: `options.keyPrefix \|\| KEY_PREFIX \|\| VERCEL_URL \|\| BUILD_ID \|\| 'UNDEFINED_URL_'` |
|
|
147
|
+
| sharedTagsKey | Key used to store shared tags hash map in Redis | `'__sharedTags__'` |
|
|
148
|
+
| getTimeoutMs | Timeout in milliseconds for time critical Redis operations. If Redis get is not fulfilled within this time, returns null to avoid blocking site rendering. | `process.env.REDIS_COMMAND_TIMEOUT_MS ? (Number.parseInt(process.env.REDIS_COMMAND_TIMEOUT_MS) ?? 500) : 500` |
|
|
149
|
+
| revalidateTagQuerySize | Number of entries to query in one batch during full sync of shared tags hash map | `250` |
|
|
150
|
+
| avgResyncIntervalMs | Average interval in milliseconds between tag map full re-syncs | `3600000` (1 hour) |
|
|
151
|
+
| redisGetDeduplication | Enable deduplication of Redis get requests via internal in-memory cache. | `true` |
|
|
152
|
+
| inMemoryCachingTime | Time in milliseconds to cache Redis get results in memory. Set this to 0 to disable in-memory caching completely. | `10000` |
|
|
153
|
+
| defaultStaleAge | Default stale age in seconds for cached items | `1209600` (14 days) |
|
|
154
|
+
| estimateExpireAge | Function to calculate expire age (redis TTL value) from stale age | Production: `staleAge * 2`<br> Other: `staleAge * 1.2` |
|
|
155
|
+
| socketOptions | Redis client socket options for TLS/SSL configuration (e.g., `{ tls: true, rejectUnauthorized: false }`) | `{ connectTimeout: timeoutMs }` |
|
|
156
|
+
| clientOptions | Additional Redis client options (e.g., username, password) | `undefined` |
|
|
157
|
+
| killContainerOnErrorThreshold | Number of consecutive errors before the container is killed. Set to 0 to disable. | `Number.parseInt(process.env.KILL_CONTAINER_ON_ERROR_THRESHOLD) ?? 0 : 0` |
|
|
158
|
+
| valueSerializer | Pluggable wire-format codec for Redis string values (compression, encryption, custom encoding). See [Custom value serializer](#custom-value-serializer-compression-encryption). | `jsonCacheValueSerializer` (`JSON.stringify` with built-in `Buffer` and `Map` encoding) |
|
|
159
|
+
|
|
160
|
+
## Custom value serializer (compression, encryption)
|
|
161
|
+
|
|
162
|
+
By default cache entries are stored as `JSON.stringify(...)` with built-in
|
|
163
|
+
`Buffer` and `Map` encoding. For workloads where the encoded payload is large
|
|
164
|
+
(big RSC trees, large fetch responses) or sensitive (PII), you can plug in a
|
|
165
|
+
custom codec - gzip, brotli, AES, anything - via the `valueSerializer` option,
|
|
166
|
+
without forking this package or losing the existing dedup / batch / keyspace
|
|
167
|
+
features.
|
|
168
|
+
|
|
169
|
+
### Contract
|
|
170
|
+
|
|
171
|
+
- `serialize(entry)` is called on every `set()` with the in-memory `CacheEntry`. It must return the string written to Redis, or a `Promise<string>` for async codecs.
|
|
172
|
+
- `deserialize(stored)` is called on every cache hit with the exact string read from Redis. It must return a `CacheEntry`, `null`, or a `Promise` of either. Returning `null` is treated as a cache miss - the handler returns `null` from `get()` without surfacing an error.
|
|
173
|
+
- Both methods may be async, enabling non-blocking codecs such as `zlib.brotliCompress` or `crypto.subtle` that don't block the Node.js event loop. Synchronous implementations continue to work unchanged.
|
|
174
|
+
- Only the main cache-entry storage path is routed through the serializer. Internal structures (`__sharedTags__`, `__revalidated_tags__`, `inMemoryDeduplicationCache`) are not affected.
|
|
175
|
+
|
|
176
|
+
### Default export for reuse
|
|
177
|
+
|
|
178
|
+
The default serializer is exported so you can wrap it (e.g. compress + JSON
|
|
179
|
+
fallback) or compare against it by reference to detect that no custom
|
|
180
|
+
serializer was configured. The underlying `Buffer` / `Map` JSON helpers used by
|
|
181
|
+
the default are also exported for use inside custom codecs:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
import {
|
|
185
|
+
jsonCacheValueSerializer,
|
|
186
|
+
bufferAndMapReplacer,
|
|
187
|
+
bufferAndMapReviver,
|
|
188
|
+
} from '@trieb.work/nextjs-turbo-redis-cache';
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
> **Important:** a plain `JSON.stringify(value)` does not preserve native
|
|
192
|
+
> `Buffer` or `Map` values inside a cache entry. RSC payloads contain
|
|
193
|
+
> `Buffer`s. If you write a custom codec that doesn't use the exported
|
|
194
|
+
> `bufferAndMapReplacer` / `bufferAndMapReviver` (or doesn't wrap
|
|
195
|
+
> `jsonCacheValueSerializer`), expect those to come back as plain objects.
|
|
196
|
+
|
|
197
|
+
### Example: gzip (sync)
|
|
198
|
+
|
|
199
|
+
Wraps `bufferAndMapReplacer` / `bufferAndMapReviver` so native `Buffer` and
|
|
200
|
+
`Map` values inside the cache entry round-trip unchanged. `gzipSync` /
|
|
201
|
+
`gunzipSync` block the event loop - prefer the async brotli example below for
|
|
202
|
+
hot workloads.
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
import { gzipSync, gunzipSync } from 'node:zlib';
|
|
206
|
+
import {
|
|
207
|
+
RedisStringsHandler,
|
|
208
|
+
bufferAndMapReplacer,
|
|
209
|
+
bufferAndMapReviver,
|
|
210
|
+
} from '@trieb.work/nextjs-turbo-redis-cache';
|
|
211
|
+
|
|
212
|
+
const gzipSerializer = {
|
|
213
|
+
serialize(value) {
|
|
214
|
+
const json = JSON.stringify(value, bufferAndMapReplacer);
|
|
215
|
+
return gzipSync(json).toString('base64');
|
|
216
|
+
},
|
|
217
|
+
deserialize(stored) {
|
|
218
|
+
const buf = Buffer.from(stored, 'base64');
|
|
219
|
+
return JSON.parse(gunzipSync(buf).toString('utf8'), bufferAndMapReviver);
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export default class CustomizedCacheHandler {
|
|
224
|
+
constructor() {
|
|
225
|
+
this.handler = new RedisStringsHandler({
|
|
226
|
+
valueSerializer: gzipSerializer,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
// ... delegate get/set/revalidateTag/resetRequestCache to this.handler
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Example: brotli (async, non-blocking)
|
|
234
|
+
|
|
235
|
+
Uses `promisify(brotliCompress)` and `promisify(brotliDecompress)` so
|
|
236
|
+
compression runs on a worker thread and doesn't block the event loop.
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
import { promisify } from 'node:util';
|
|
240
|
+
import { brotliCompress, brotliDecompress } from 'node:zlib';
|
|
241
|
+
import {
|
|
242
|
+
RedisStringsHandler,
|
|
243
|
+
bufferAndMapReplacer,
|
|
244
|
+
bufferAndMapReviver,
|
|
245
|
+
} from '@trieb.work/nextjs-turbo-redis-cache';
|
|
246
|
+
|
|
247
|
+
const brotliCompressAsync = promisify(brotliCompress);
|
|
248
|
+
const brotliDecompressAsync = promisify(brotliDecompress);
|
|
249
|
+
|
|
250
|
+
const brotliSerializer = {
|
|
251
|
+
async serialize(value) {
|
|
252
|
+
const json = JSON.stringify(value, bufferAndMapReplacer);
|
|
253
|
+
const compressed = await brotliCompressAsync(Buffer.from(json, 'utf8'));
|
|
254
|
+
return compressed.toString('base64');
|
|
255
|
+
},
|
|
256
|
+
async deserialize(stored) {
|
|
257
|
+
const buf = Buffer.from(stored, 'base64');
|
|
258
|
+
const decompressed = await brotliDecompressAsync(buf);
|
|
259
|
+
return JSON.parse(decompressed.toString('utf8'), bufferAndMapReviver);
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
export default class CustomizedCacheHandler {
|
|
264
|
+
constructor() {
|
|
265
|
+
this.handler = new RedisStringsHandler({
|
|
266
|
+
valueSerializer: brotliSerializer,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
// ... delegate get/set/revalidateTag/resetRequestCache to this.handler
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Operational notes
|
|
274
|
+
|
|
275
|
+
- **Changing the serializer makes existing Redis keys unreadable.** Any change
|
|
276
|
+
to the codec - swapping JSON for gzip, bumping a compression level, rotating
|
|
277
|
+
an encryption key - means previously written entries can no longer be
|
|
278
|
+
decoded. Either flush the affected keys (`FLUSHDB`, or scoped `UNLINK` of
|
|
279
|
+
`keyPrefix*`) or bump `keyPrefix` before deploying so old and new entries
|
|
280
|
+
live in disjoint keyspaces.
|
|
281
|
+
- **`Buffer` and `Map` encoding is built into the default.** The default
|
|
282
|
+
`jsonCacheValueSerializer` uses this package's `bufferAndMapReplacer` /
|
|
283
|
+
`bufferAndMapReviver` so native `Buffer` and `Map` values inside a
|
|
284
|
+
`CacheEntry` round-trip transparently. If you write a custom serializer that
|
|
285
|
+
doesn't reuse those (e.g. plain `JSON.stringify` over a binary payload),
|
|
286
|
+
expect RSC payload `Buffer`s to come back as plain `{ type: 'Buffer', data:
|
|
287
|
+
[...] }` objects. Reuse the exported default inside your codec, or use the
|
|
288
|
+
exported `bufferAndMapReplacer` / `bufferAndMapReviver`, to keep that
|
|
289
|
+
behavior.
|
|
290
|
+
- **The in-memory deduplication cache stores the wire-format string verbatim.**
|
|
291
|
+
When `redisGetDeduplication` is enabled (default), the value seeded after
|
|
292
|
+
`set()` and returned to subsequent `get()` calls is the exact string
|
|
293
|
+
produced by `serialize()`. With a compressing or encrypting codec that
|
|
294
|
+
means every dedup hit re-runs `deserialize()` (i.e. re-decompresses or
|
|
295
|
+
re-decrypts). For very hot keys, evaluate whether the per-hit codec cost
|
|
296
|
+
outweighs the Redis round-trip the dedup is saving.
|
|
297
|
+
- **Other internal trieb structures are not affected by `valueSerializer`.**
|
|
298
|
+
Only the main cache entries written by `set()` and read by `get()` go
|
|
299
|
+
through the codec. The shared-tags map and the revalidated-tags map are
|
|
300
|
+
untouched.
|
|
301
|
+
- **Cache Components handler is out of scope for now.** This option only
|
|
302
|
+
affects `RedisStringsHandler`. The Next.js 16+ `CacheComponentsHandler` does
|
|
303
|
+
not currently route through `valueSerializer`; that's a candidate follow-up.
|
|
156
304
|
|
|
157
305
|
## TLS Configuration
|
|
158
306
|
|
|
@@ -181,6 +329,8 @@ module.exports = class CustomizedCacheHandler {
|
|
|
181
329
|
|
|
182
330
|
## Consistency of Redis and this caching implementation
|
|
183
331
|
|
|
332
|
+
> For a detailed description of the shared hash maps (`sharedTagsMap`, `revalidatedTagsMap`), the `SyncedMap` Pub/Sub mechanism, and the `DeduplicatedRequestHandler`, see [ARCHITECTURE.md](./ARCHITECTURE.md).
|
|
333
|
+
|
|
184
334
|
To understand consistency levels of this caching implementation we first have to understand the consistency of redis itself:
|
|
185
335
|
Redis executes commands in a single-threaded manner. This ensures that all operations are processed sequentially, so clients always see a consistent view of the data.
|
|
186
336
|
But depending on the setup of redis this can change:
|
|
@@ -189,23 +339,43 @@ But depending on the setup of redis this can change:
|
|
|
189
339
|
- Eventual consistency: In a master-replica setup (strong consistency only while there is no failover)
|
|
190
340
|
- Eventual consistency: In Redis Cluster mode
|
|
191
341
|
|
|
192
|
-
Consistency levels of Caching Handler
|
|
193
|
-
|
|
342
|
+
### Consistency levels of the Caching Handler
|
|
343
|
+
|
|
344
|
+
Strong consistency is only achievable when **all** of the following conditions are met:
|
|
345
|
+
|
|
346
|
+
1. Redis is used in a single node setup
|
|
347
|
+
2. Only a **single application instance** is running (no cross-instance Pub/Sub propagation delay)
|
|
348
|
+
3. Request Deduplication is turned off (`redisGetDeduplication: false`)
|
|
349
|
+
|
|
350
|
+
In practice, most deployments run multiple application instances, which introduces eventual consistency through two mechanisms:
|
|
351
|
+
|
|
352
|
+
#### Source 1: SyncedMap Pub/Sub propagation (both handlers)
|
|
353
|
+
|
|
354
|
+
Both `RedisStringsHandler` and `CacheComponentsHandler` maintain in-memory maps (`sharedTagsMap`, `revalidatedTagsMap`) that are synchronized across instances via Redis Pub/Sub (see [SyncedMap in ARCHITECTURE.md](./ARCHITECTURE.md#syncedmap--the-synchronization-primitive)). When a tag is revalidated on instance 1, the Pub/Sub message must propagate to instance 2 before its local maps reflect the change. Until the message arrives, `get()` on instance 2 may still consider a cache entry valid.
|
|
355
|
+
|
|
356
|
+
This is especially relevant for **implicit tags** (`_N_T_` prefix): when `revalidatePath("/products")` is called, the handler records a timestamp in `revalidatedTagsMap` and lazily invalidates nested fetch entries on the next `get()`. If another instance hasn't received the Pub/Sub update yet, it can serve a stale fetch result.
|
|
194
357
|
|
|
195
|
-
|
|
358
|
+
The propagation delay is typically in the order of a few milliseconds (Redis Pub/Sub round-trip).
|
|
359
|
+
|
|
360
|
+
#### Source 2: Request Deduplication (both handlers)
|
|
361
|
+
|
|
362
|
+
Both `RedisStringsHandler` and `CacheComponentsHandler` use `DeduplicatedRequestHandler` (enabled by default via `redisGetDeduplication: true`). It caches Redis GET results in memory for `inMemoryCachingTime` (default 10s). The following sequence can occur across instances:
|
|
363
|
+
|
|
364
|
+
```
|
|
196
365
|
Instance 1: call set A 1
|
|
197
366
|
Instance 1: served set A 1
|
|
198
|
-
Instance 2: call get A
|
|
199
|
-
Instance 1: call delete A
|
|
200
|
-
Instance 2: call get A
|
|
201
|
-
Instance 2: served get A
|
|
202
|
-
Instance 2: served get A -> 1 (served 1 but should already be deleted)
|
|
367
|
+
Instance 2: call get A → result cached in dedup cache
|
|
368
|
+
Instance 1: call delete A → revalidateTag deletes from Redis + publishes Pub/Sub delete
|
|
369
|
+
Instance 2: call get A → served from dedup cache (stale!) if Pub/Sub delete hasn't arrived
|
|
370
|
+
Instance 2: served get A → 1 (should already be deleted)
|
|
203
371
|
Instance 1: served delete A
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
When `revalidateTag` / `updateTags` runs, it also deletes the affected keys from the `inMemoryDeduplicationCache` and broadcasts this deletion via Pub/Sub. So the consistency window is bounded by the Pub/Sub propagation time (typically 5–100ms), not the full `inMemoryCachingTime`. Only if the Pub/Sub message is lost would the stale entry persist for the full caching duration.
|
|
204
375
|
|
|
205
|
-
|
|
206
|
-
If using local in-memory caching (Enabled by RedisStringsHandler option inMemoryCachingTime), the window for this eventual consistency to occur can be even higher because additionally also synchronization messages have to get delivered. Depending on your load typically ranging around 50ms to max of 120ms.
|
|
376
|
+
#### Practical impact
|
|
207
377
|
|
|
208
|
-
Since all caching calls
|
|
378
|
+
Since all caching calls within one API/page/server action request are always served by the same instance, this problem will not occur inside a single request but rather in a combination of multiple parallel requests across instances. The probability that this will affect a single user during a request sequence is very low, since typically a single user will not make a follow-up request during this small time window of typically <100ms. To further mitigate the problem and increase performance (increase local in-memory cache hit ratio) make sure that your load balancer will always serve one user to the same instance (sticky sessions).
|
|
209
379
|
|
|
210
380
|
By accepting and tolerating this eventual consistency, the performance of the caching handler is significantly increased.
|
|
211
381
|
|
|
@@ -235,9 +405,9 @@ pnpm test:integration:build-id-prefix
|
|
|
235
405
|
|
|
236
406
|
Folder layout / runners:
|
|
237
407
|
|
|
238
|
-
- **Vitest**
|
|
239
|
-
- **Playwright** (E2E) lives in `
|
|
240
|
-
-
|
|
408
|
+
- **Vitest** unit tests live in `test/vitest/unit/**`; integration tests in `test/vitest/integration/**`.
|
|
409
|
+
- **Playwright** (E2E) lives in `test/playwright/**` (see `playwright.config.ts`).
|
|
410
|
+
- **Test fixtures** (Next.js apps) live in `test/nextjs-test-projects/`.
|
|
241
411
|
|
|
242
412
|
### Unit tests
|
|
243
413
|
|
|
@@ -261,14 +431,6 @@ To run the BUILD_ID integration test independently:
|
|
|
261
431
|
pnpm build && pnpm test:integration:build-id-prefix
|
|
262
432
|
```
|
|
263
433
|
|
|
264
|
-
### E2E tests (Playwright)
|
|
265
|
-
|
|
266
|
-
To run Playwright tests (`tests/**`) you can use:
|
|
267
|
-
|
|
268
|
-
```bash
|
|
269
|
-
pnpm test:e2e
|
|
270
|
-
```
|
|
271
|
-
|
|
272
434
|
The integration tests will start a Next.js server and test the caching handler. You can modify testing behavior by setting the following environment variables:
|
|
273
435
|
|
|
274
436
|
- SKIP_BUILD: If set to true, the integration tests will not build the Next.js app. Therefore the nextjs app needs to be built before running the tests. Or you execute the test once without skip build and the re-execute `pnpm test:integration` with skip build set to true.
|
|
@@ -277,6 +439,14 @@ The integration tests will start a Next.js server and test the caching handler.
|
|
|
277
439
|
|
|
278
440
|
Integration tests may have dependencies between test cases, so individual test failures should be evaluated in the context of the full test suite rather than in isolation.
|
|
279
441
|
|
|
442
|
+
### E2E tests (Playwright)
|
|
443
|
+
|
|
444
|
+
To run Playwright tests (`test/playwright/**`) you can use:
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
pnpm test:e2e
|
|
448
|
+
```
|
|
449
|
+
|
|
280
450
|
## Cache Components handler (Next.js 16+)
|
|
281
451
|
|
|
282
452
|
This package can be used as a Cache Components handler (Node.js runtime) for Next.js apps that enable Cache Components.
|
|
@@ -340,8 +510,8 @@ This repo includes a dedicated Next.js Cache Components integration app with rea
|
|
|
340
510
|
1. Install + start the Cache Components test app:
|
|
341
511
|
|
|
342
512
|
```bash
|
|
343
|
-
pnpm -C test/
|
|
344
|
-
pnpm -C test/
|
|
513
|
+
pnpm -C test/nextjs-test-projects/next-app-16-2-6-cache-components install
|
|
514
|
+
pnpm -C test/nextjs-test-projects/next-app-16-2-6-cache-components dev
|
|
345
515
|
```
|
|
346
516
|
|
|
347
517
|
Then open the Cache Lab pages:
|
|
@@ -369,6 +539,8 @@ app/<segment>/page.tsx files will request using the APP_PAGE kind.
|
|
|
369
539
|
|
|
370
540
|
Fetch requests (inside app route or page) will request using the FETCH kind.
|
|
371
541
|
|
|
542
|
+
For details on how these kinds are handled internally (tag maps, deduplication, value transformation), see [ARCHITECTURE.md](./ARCHITECTURE.md).
|
|
543
|
+
|
|
372
544
|
## License
|
|
373
545
|
|
|
374
546
|
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
import { RedisClientOptions } from 'redis';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Pluggable wire-format codec for Redis string values used by `RedisStringsHandler`.
|
|
5
|
+
*
|
|
6
|
+
* The serializer is the single point at which an in-memory `CacheEntry` becomes the
|
|
7
|
+
* string written to Redis (and back). Plugging in a custom serializer lets you add
|
|
8
|
+
* compression (gzip/brotli), encryption, or any other custom encoding without forking
|
|
9
|
+
* this package or losing the existing dedup / batch / keyspace features.
|
|
10
|
+
*
|
|
11
|
+
* Both `serialize` and `deserialize` may return either a value directly or a `Promise`,
|
|
12
|
+
* which enables non-blocking async codecs such as stream-based compression
|
|
13
|
+
* (`zlib.brotliCompress`) or encryption (`crypto.subtle`). Synchronous implementations
|
|
14
|
+
* continue to work unchanged - awaiting a plain value is a no-op.
|
|
15
|
+
*
|
|
16
|
+
* The default export {@link jsonCacheValueSerializer} is `JSON.stringify` /
|
|
17
|
+
* `JSON.parse` paired with {@link bufferAndMapReplacer} / {@link bufferAndMapReviver},
|
|
18
|
+
* so native `Buffer` and `Map` values inside a `CacheEntry` round-trip transparently
|
|
19
|
+
* (this is required for Next.js RSC payloads).
|
|
20
|
+
*
|
|
21
|
+
* Operational note: changing the serializer (or any of its parameters such as a
|
|
22
|
+
* compression level or encryption key) makes existing Redis keys unreadable, because
|
|
23
|
+
* the deserializer will fail or return `null` for entries written by the previous
|
|
24
|
+
* format. Either flush the affected keys, bump `keyPrefix`, or migrate values
|
|
25
|
+
* out-of-band before deploying a new serializer.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
type CacheValueSerializer = {
|
|
29
|
+
/**
|
|
30
|
+
* Encode an in-memory `CacheEntry` into the string written to Redis.
|
|
31
|
+
* May return a `Promise` for async codecs (e.g. compression, encryption).
|
|
32
|
+
*/
|
|
33
|
+
serialize(value: CacheEntry): string | Promise<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Decode a string read from Redis back into a `CacheEntry`.
|
|
36
|
+
* Returning `null` (or a `Promise<null>`) signals "treat as cache miss" -
|
|
37
|
+
* the handler will return `null` from `get()` without surfacing an error.
|
|
38
|
+
* May return a `Promise` for async codecs.
|
|
39
|
+
*/
|
|
40
|
+
deserialize(stored: string): CacheEntry | null | Promise<CacheEntry | null>;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Default serializer used by `RedisStringsHandler` when no `valueSerializer` is
|
|
44
|
+
* configured. Wraps `JSON.stringify` / `JSON.parse` with this package's
|
|
45
|
+
* {@link bufferAndMapReplacer} / {@link bufferAndMapReviver} so native `Buffer`
|
|
46
|
+
* and `Map` values inside a `CacheEntry` survive the round-trip.
|
|
47
|
+
*
|
|
48
|
+
* Exported as a singleton so consumers can compare against the default by
|
|
49
|
+
* reference (e.g. to detect that no custom serializer was configured).
|
|
50
|
+
*/
|
|
51
|
+
declare const jsonCacheValueSerializer: CacheValueSerializer;
|
|
52
|
+
|
|
3
53
|
type CacheEntry = {
|
|
4
54
|
value: unknown;
|
|
5
55
|
lastModified: number;
|
|
@@ -66,6 +116,27 @@ type CreateRedisStringsHandlerOptions = {
|
|
|
66
116
|
* @example { username: 'user', password: 'pass' }
|
|
67
117
|
*/
|
|
68
118
|
clientOptions?: Omit<RedisClientOptions, 'url' | 'database' | 'socket'>;
|
|
119
|
+
/** Pluggable wire-format codec for Redis string values. Lets you plug in
|
|
120
|
+
* compression (gzip/brotli), encryption, or any other custom encoding without
|
|
121
|
+
* forking this package or losing the existing dedup / batch / keyspace features.
|
|
122
|
+
*
|
|
123
|
+
* Both `serialize` and `deserialize` may return a `Promise`, enabling
|
|
124
|
+
* non-blocking async codecs (e.g. `zlib.brotliCompress`) that don't block the
|
|
125
|
+
* Node.js event loop. Synchronous implementations continue to work unchanged.
|
|
126
|
+
*
|
|
127
|
+
* Only the main cache-entry storage path is routed through the serializer.
|
|
128
|
+
* The shared-tags map and the revalidated-tags map are not. The in-memory
|
|
129
|
+
* deduplication cache stores the wire-format string verbatim - its contents
|
|
130
|
+
* change with the serializer, but the cache itself is not re-encoded.
|
|
131
|
+
*
|
|
132
|
+
* Operational note: changing the serializer (or any of its parameters such as
|
|
133
|
+
* a compression level or encryption key) makes existing Redis keys unreadable.
|
|
134
|
+
* Either flush the affected keys or bump `keyPrefix` before deploying.
|
|
135
|
+
*
|
|
136
|
+
* @default jsonCacheValueSerializer (JSON.stringify with bufferAndMapReplacer
|
|
137
|
+
* so native Buffer and Map values inside a CacheEntry round-trip transparently)
|
|
138
|
+
*/
|
|
139
|
+
valueSerializer?: CacheValueSerializer;
|
|
69
140
|
};
|
|
70
141
|
declare class RedisStringsHandler {
|
|
71
142
|
private client;
|
|
@@ -82,7 +153,8 @@ declare class RedisStringsHandler {
|
|
|
82
153
|
private defaultStaleAge;
|
|
83
154
|
private estimateExpireAge;
|
|
84
155
|
private killContainerOnErrorThreshold;
|
|
85
|
-
|
|
156
|
+
private valueSerializer;
|
|
157
|
+
constructor({ redisUrl, database, keyPrefix, sharedTagsKey, getTimeoutMs, revalidateTagQuerySize, avgResyncIntervalMs, redisGetDeduplication, inMemoryCachingTime, defaultStaleAge, estimateExpireAge, killContainerOnErrorThreshold, socketOptions, clientOptions, valueSerializer, }: CreateRedisStringsHandlerOptions);
|
|
86
158
|
resetRequestCache(): void;
|
|
87
159
|
private clientReadyCalls;
|
|
88
160
|
private assertClientIsReady;
|
|
@@ -152,6 +224,9 @@ declare class CachedHandler {
|
|
|
152
224
|
resetRequestCache(...args: Parameters<RedisStringsHandler['resetRequestCache']>): ReturnType<RedisStringsHandler['resetRequestCache']>;
|
|
153
225
|
}
|
|
154
226
|
|
|
227
|
+
declare function bufferAndMapReviver(_: string, value: any): any;
|
|
228
|
+
declare function bufferAndMapReplacer(_: string, value: any): any;
|
|
229
|
+
|
|
155
230
|
interface CacheComponentsEntry {
|
|
156
231
|
value: ReadableStream<Uint8Array>;
|
|
157
232
|
tags: string[];
|
|
@@ -175,4 +250,4 @@ type CreateCacheComponentsHandlerOptions = CreateRedisStringsHandlerOptions & {
|
|
|
175
250
|
declare function getRedisCacheComponentsHandler(options?: CreateCacheComponentsHandlerOptions): CacheComponentsHandler;
|
|
176
251
|
declare const redisCacheHandler: CacheComponentsHandler;
|
|
177
252
|
|
|
178
|
-
export { type CreateRedisStringsHandlerOptions, RedisStringsHandler, CachedHandler as default, getRedisCacheComponentsHandler, redisCacheHandler };
|
|
253
|
+
export { type CacheValueSerializer, type CreateRedisStringsHandlerOptions, RedisStringsHandler, bufferAndMapReplacer, bufferAndMapReviver, CachedHandler as default, getRedisCacheComponentsHandler, jsonCacheValueSerializer, redisCacheHandler };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
import { RedisClientOptions } from 'redis';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Pluggable wire-format codec for Redis string values used by `RedisStringsHandler`.
|
|
5
|
+
*
|
|
6
|
+
* The serializer is the single point at which an in-memory `CacheEntry` becomes the
|
|
7
|
+
* string written to Redis (and back). Plugging in a custom serializer lets you add
|
|
8
|
+
* compression (gzip/brotli), encryption, or any other custom encoding without forking
|
|
9
|
+
* this package or losing the existing dedup / batch / keyspace features.
|
|
10
|
+
*
|
|
11
|
+
* Both `serialize` and `deserialize` may return either a value directly or a `Promise`,
|
|
12
|
+
* which enables non-blocking async codecs such as stream-based compression
|
|
13
|
+
* (`zlib.brotliCompress`) or encryption (`crypto.subtle`). Synchronous implementations
|
|
14
|
+
* continue to work unchanged - awaiting a plain value is a no-op.
|
|
15
|
+
*
|
|
16
|
+
* The default export {@link jsonCacheValueSerializer} is `JSON.stringify` /
|
|
17
|
+
* `JSON.parse` paired with {@link bufferAndMapReplacer} / {@link bufferAndMapReviver},
|
|
18
|
+
* so native `Buffer` and `Map` values inside a `CacheEntry` round-trip transparently
|
|
19
|
+
* (this is required for Next.js RSC payloads).
|
|
20
|
+
*
|
|
21
|
+
* Operational note: changing the serializer (or any of its parameters such as a
|
|
22
|
+
* compression level or encryption key) makes existing Redis keys unreadable, because
|
|
23
|
+
* the deserializer will fail or return `null` for entries written by the previous
|
|
24
|
+
* format. Either flush the affected keys, bump `keyPrefix`, or migrate values
|
|
25
|
+
* out-of-band before deploying a new serializer.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
type CacheValueSerializer = {
|
|
29
|
+
/**
|
|
30
|
+
* Encode an in-memory `CacheEntry` into the string written to Redis.
|
|
31
|
+
* May return a `Promise` for async codecs (e.g. compression, encryption).
|
|
32
|
+
*/
|
|
33
|
+
serialize(value: CacheEntry): string | Promise<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Decode a string read from Redis back into a `CacheEntry`.
|
|
36
|
+
* Returning `null` (or a `Promise<null>`) signals "treat as cache miss" -
|
|
37
|
+
* the handler will return `null` from `get()` without surfacing an error.
|
|
38
|
+
* May return a `Promise` for async codecs.
|
|
39
|
+
*/
|
|
40
|
+
deserialize(stored: string): CacheEntry | null | Promise<CacheEntry | null>;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Default serializer used by `RedisStringsHandler` when no `valueSerializer` is
|
|
44
|
+
* configured. Wraps `JSON.stringify` / `JSON.parse` with this package's
|
|
45
|
+
* {@link bufferAndMapReplacer} / {@link bufferAndMapReviver} so native `Buffer`
|
|
46
|
+
* and `Map` values inside a `CacheEntry` survive the round-trip.
|
|
47
|
+
*
|
|
48
|
+
* Exported as a singleton so consumers can compare against the default by
|
|
49
|
+
* reference (e.g. to detect that no custom serializer was configured).
|
|
50
|
+
*/
|
|
51
|
+
declare const jsonCacheValueSerializer: CacheValueSerializer;
|
|
52
|
+
|
|
3
53
|
type CacheEntry = {
|
|
4
54
|
value: unknown;
|
|
5
55
|
lastModified: number;
|
|
@@ -66,6 +116,27 @@ type CreateRedisStringsHandlerOptions = {
|
|
|
66
116
|
* @example { username: 'user', password: 'pass' }
|
|
67
117
|
*/
|
|
68
118
|
clientOptions?: Omit<RedisClientOptions, 'url' | 'database' | 'socket'>;
|
|
119
|
+
/** Pluggable wire-format codec for Redis string values. Lets you plug in
|
|
120
|
+
* compression (gzip/brotli), encryption, or any other custom encoding without
|
|
121
|
+
* forking this package or losing the existing dedup / batch / keyspace features.
|
|
122
|
+
*
|
|
123
|
+
* Both `serialize` and `deserialize` may return a `Promise`, enabling
|
|
124
|
+
* non-blocking async codecs (e.g. `zlib.brotliCompress`) that don't block the
|
|
125
|
+
* Node.js event loop. Synchronous implementations continue to work unchanged.
|
|
126
|
+
*
|
|
127
|
+
* Only the main cache-entry storage path is routed through the serializer.
|
|
128
|
+
* The shared-tags map and the revalidated-tags map are not. The in-memory
|
|
129
|
+
* deduplication cache stores the wire-format string verbatim - its contents
|
|
130
|
+
* change with the serializer, but the cache itself is not re-encoded.
|
|
131
|
+
*
|
|
132
|
+
* Operational note: changing the serializer (or any of its parameters such as
|
|
133
|
+
* a compression level or encryption key) makes existing Redis keys unreadable.
|
|
134
|
+
* Either flush the affected keys or bump `keyPrefix` before deploying.
|
|
135
|
+
*
|
|
136
|
+
* @default jsonCacheValueSerializer (JSON.stringify with bufferAndMapReplacer
|
|
137
|
+
* so native Buffer and Map values inside a CacheEntry round-trip transparently)
|
|
138
|
+
*/
|
|
139
|
+
valueSerializer?: CacheValueSerializer;
|
|
69
140
|
};
|
|
70
141
|
declare class RedisStringsHandler {
|
|
71
142
|
private client;
|
|
@@ -82,7 +153,8 @@ declare class RedisStringsHandler {
|
|
|
82
153
|
private defaultStaleAge;
|
|
83
154
|
private estimateExpireAge;
|
|
84
155
|
private killContainerOnErrorThreshold;
|
|
85
|
-
|
|
156
|
+
private valueSerializer;
|
|
157
|
+
constructor({ redisUrl, database, keyPrefix, sharedTagsKey, getTimeoutMs, revalidateTagQuerySize, avgResyncIntervalMs, redisGetDeduplication, inMemoryCachingTime, defaultStaleAge, estimateExpireAge, killContainerOnErrorThreshold, socketOptions, clientOptions, valueSerializer, }: CreateRedisStringsHandlerOptions);
|
|
86
158
|
resetRequestCache(): void;
|
|
87
159
|
private clientReadyCalls;
|
|
88
160
|
private assertClientIsReady;
|
|
@@ -152,6 +224,9 @@ declare class CachedHandler {
|
|
|
152
224
|
resetRequestCache(...args: Parameters<RedisStringsHandler['resetRequestCache']>): ReturnType<RedisStringsHandler['resetRequestCache']>;
|
|
153
225
|
}
|
|
154
226
|
|
|
227
|
+
declare function bufferAndMapReviver(_: string, value: any): any;
|
|
228
|
+
declare function bufferAndMapReplacer(_: string, value: any): any;
|
|
229
|
+
|
|
155
230
|
interface CacheComponentsEntry {
|
|
156
231
|
value: ReadableStream<Uint8Array>;
|
|
157
232
|
tags: string[];
|
|
@@ -175,4 +250,4 @@ type CreateCacheComponentsHandlerOptions = CreateRedisStringsHandlerOptions & {
|
|
|
175
250
|
declare function getRedisCacheComponentsHandler(options?: CreateCacheComponentsHandlerOptions): CacheComponentsHandler;
|
|
176
251
|
declare const redisCacheHandler: CacheComponentsHandler;
|
|
177
252
|
|
|
178
|
-
export { type CreateRedisStringsHandlerOptions, RedisStringsHandler, CachedHandler as default, getRedisCacheComponentsHandler, redisCacheHandler };
|
|
253
|
+
export { type CacheValueSerializer, type CreateRedisStringsHandlerOptions, RedisStringsHandler, bufferAndMapReplacer, bufferAndMapReviver, CachedHandler as default, getRedisCacheComponentsHandler, jsonCacheValueSerializer, redisCacheHandler };
|