@trieb.work/nextjs-turbo-redis-cache 1.17.0 → 1.18.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/README.md +87 -24
- package/dist/index.d.mts +40 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +291 -116
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +288 -115
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -6
- package/.cursor/skills/testing/SKILL.md +0 -121
- package/.github/workflows/ci.yml +0 -309
- package/.github/workflows/pages.yml +0 -38
- package/.github/workflows/release.yml +0 -207
- package/.prettierrc.json +0 -6
- package/.vscode/settings.json +0 -10
- package/ARCHITECTURE.md +0 -431
- package/CHANGELOG.md +0 -411
- package/commitlint.config.cjs +0 -4
- package/docker/redis.conf +0 -1
- package/docs/index.html +0 -140
- package/docs/robots.txt +0 -4
- package/docs/sitemap.xml +0 -8
- package/docs/styles.css +0 -141
- package/eslint.config.mjs +0 -26
- package/hooks/commit-msg +0 -10
- package/hooks/pre-commit +0 -11
- package/playwright.config.ts +0 -18
- package/release.config.cjs +0 -31
- package/scripts/prepare.sh +0 -7
- package/scripts/setup-git.sh +0 -8
- package/scripts/setup-repo-name.sh +0 -17
- package/scripts/vitest-run-staged.cjs +0 -44
- package/src/CacheComponentsHandler.ts +0 -567
- package/src/CachedHandler.ts +0 -53
- package/src/DeduplicatedRequestHandler.ts +0 -110
- package/src/RedisStringsHandler.ts +0 -937
- package/src/SyncedMap.ts +0 -482
- package/src/ZodHandler.ts +0 -45
- package/src/index.ts +0 -16
- package/src/serializer.ts +0 -59
- package/src/utils/debug.ts +0 -30
- package/src/utils/json.ts +0 -49
- package/src/utils/prefix.ts +0 -44
- package/test/README.md +0 -194
- package/test/nextjs-test-projects/next-app-15-4-11/README.md +0 -36
- package/test/nextjs-test-projects/next-app-15-4-11/eslint.config.mjs +0 -16
- package/test/nextjs-test-projects/next-app-15-4-11/next.config.js +0 -9
- package/test/nextjs-test-projects/next-app-15-4-11/package-lock.json +0 -5969
- package/test/nextjs-test-projects/next-app-15-4-11/package.json +0 -29
- package/test/nextjs-test-projects/next-app-15-4-11/pnpm-lock.yaml +0 -3726
- package/test/nextjs-test-projects/next-app-15-4-11/postcss.config.mjs +0 -5
- package/test/nextjs-test-projects/next-app-15-4-11/public/file.svg +0 -1
- package/test/nextjs-test-projects/next-app-15-4-11/public/globe.svg +0 -1
- package/test/nextjs-test-projects/next-app-15-4-11/public/next.svg +0 -1
- package/test/nextjs-test-projects/next-app-15-4-11/public/vercel.svg +0 -1
- package/test/nextjs-test-projects/next-app-15-4-11/public/window.svg +0 -1
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/api/cached-static-fetch/route.ts +0 -18
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/api/revalidatePath/route.ts +0 -15
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/api/revalidateTag/route.ts +0 -15
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/api/revalidated-fetch/route.ts +0 -17
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/api/uncached-fetch/route.ts +0 -15
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/globals.css +0 -26
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/layout.tsx +0 -59
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/page.tsx +0 -755
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/no-fetch/default-page/page.tsx +0 -55
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-15-4-11/src/app/revalidation-interface.tsx +0 -267
- package/test/nextjs-test-projects/next-app-15-4-11/tsconfig.json +0 -27
- package/test/nextjs-test-projects/next-app-16-0-11/README.md +0 -36
- package/test/nextjs-test-projects/next-app-16-0-11/eslint.config.mjs +0 -18
- package/test/nextjs-test-projects/next-app-16-0-11/next.config.ts +0 -10
- package/test/nextjs-test-projects/next-app-16-0-11/package.json +0 -28
- package/test/nextjs-test-projects/next-app-16-0-11/pnpm-lock.yaml +0 -4163
- package/test/nextjs-test-projects/next-app-16-0-11/postcss.config.mjs +0 -7
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/api/cached-static-fetch/route.ts +0 -18
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/api/revalidatePath/route.ts +0 -15
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/api/revalidateTag/route.ts +0 -20
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/api/revalidated-fetch/route.ts +0 -17
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/api/uncached-fetch/route.ts +0 -15
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/favicon.ico +0 -0
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/globals.css +0 -26
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/layout.tsx +0 -59
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/page.tsx +0 -755
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/no-fetch/default-page/page.tsx +0 -55
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/revalidation-interface.tsx +0 -267
- package/test/nextjs-test-projects/next-app-16-0-11/src/app/update-tag-test/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-0-11/tsconfig.json +0 -34
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/README.md +0 -36
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/cache-handler.js +0 -3
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/eslint.config.mjs +0 -18
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/next.config.ts +0 -13
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/package.json +0 -28
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/pnpm-lock.yaml +0 -4206
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/postcss.config.mjs +0 -7
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/public/file.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/public/globe.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/public/next.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/public/vercel.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/public/window.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/cached-static-fetch/route.ts +0 -19
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/cached-with-cachelife/route.ts +0 -24
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/cached-with-tag/route.ts +0 -21
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/revalidate-tag/route.ts +0 -19
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/revalidated-fetch/route.ts +0 -19
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/cachelife-short/page.tsx +0 -110
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/page.tsx +0 -90
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/runtime-data-suspense/page.tsx +0 -127
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/stale-while-revalidate/page.tsx +0 -130
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/tag-invalidation/page.tsx +0 -127
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/use-cache-nondeterministic/page.tsx +0 -110
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/favicon.ico +0 -0
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/globals.css +0 -26
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/layout.tsx +0 -57
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/page.tsx +0 -755
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/revalidation-interface.tsx +0 -267
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/update-tag-test/page.tsx +0 -22
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/tsconfig.json +0 -34
- package/test/nextjs-test-projects/next-app-16-2-6/README.md +0 -36
- package/test/nextjs-test-projects/next-app-16-2-6/eslint.config.mjs +0 -18
- package/test/nextjs-test-projects/next-app-16-2-6/next.config.ts +0 -7
- package/test/nextjs-test-projects/next-app-16-2-6/package.json +0 -28
- package/test/nextjs-test-projects/next-app-16-2-6/pnpm-lock.yaml +0 -4165
- package/test/nextjs-test-projects/next-app-16-2-6/postcss.config.mjs +0 -7
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/cached-static-fetch/route.ts +0 -18
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/revalidatePath/route.ts +0 -15
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/revalidateTag/route.ts +0 -20
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/revalidated-fetch/route.ts +0 -17
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/uncached-fetch/route.ts +0 -15
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/favicon.ico +0 -0
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/globals.css +0 -26
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/layout.tsx +0 -59
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/page.tsx +0 -755
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/no-fetch/default-page/page.tsx +0 -55
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/revalidation-interface.tsx +0 -267
- package/test/nextjs-test-projects/next-app-16-2-6/src/app/update-tag-test/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-2-6/tsconfig.json +0 -34
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/README.md +0 -36
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/cache-handler.js +0 -3
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/eslint.config.mjs +0 -18
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/next.config.ts +0 -13
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/package.json +0 -28
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/pnpm-lock.yaml +0 -4165
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/postcss.config.mjs +0 -7
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/file.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/globe.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/next.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/vercel.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/window.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-static-fetch/route.ts +0 -19
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-with-cachelife/route.ts +0 -24
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-with-tag/route.ts +0 -21
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/revalidate-tag/route.ts +0 -19
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/revalidated-fetch/route.ts +0 -19
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/cachelife-short/page.tsx +0 -110
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/page.tsx +0 -90
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/runtime-data-suspense/page.tsx +0 -127
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/stale-while-revalidate/page.tsx +0 -130
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/tag-invalidation/page.tsx +0 -127
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/use-cache-nondeterministic/page.tsx +0 -110
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/favicon.ico +0 -0
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/globals.css +0 -26
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/layout.tsx +0 -57
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/page.tsx +0 -755
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/revalidation-interface.tsx +0 -267
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/update-tag-test/page.tsx +0 -22
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/tsconfig.json +0 -34
- package/test/nextjs-test-projects/next-app-16-3-0/README.md +0 -36
- package/test/nextjs-test-projects/next-app-16-3-0/eslint.config.mjs +0 -18
- package/test/nextjs-test-projects/next-app-16-3-0/next.config.ts +0 -7
- package/test/nextjs-test-projects/next-app-16-3-0/package.json +0 -28
- package/test/nextjs-test-projects/next-app-16-3-0/pnpm-lock.yaml +0 -4284
- package/test/nextjs-test-projects/next-app-16-3-0/postcss.config.mjs +0 -7
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/cached-static-fetch/route.ts +0 -18
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/revalidatePath/route.ts +0 -15
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/revalidateTag/route.ts +0 -20
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/revalidated-fetch/route.ts +0 -17
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/uncached-fetch/route.ts +0 -15
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/favicon.ico +0 -0
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/globals.css +0 -26
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/layout.tsx +0 -59
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/page.tsx +0 -755
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/no-fetch/default-page/page.tsx +0 -55
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/revalidation-interface.tsx +0 -267
- package/test/nextjs-test-projects/next-app-16-3-0/src/app/update-tag-test/page.tsx +0 -25
- package/test/nextjs-test-projects/next-app-16-3-0/tsconfig.json +0 -34
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/README.md +0 -36
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/cache-handler.js +0 -3
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/eslint.config.mjs +0 -18
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/next.config.ts +0 -13
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/package.json +0 -28
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/pnpm-lock.yaml +0 -4284
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/postcss.config.mjs +0 -7
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/file.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/globe.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/next.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/vercel.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/window.svg +0 -1
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/cached-static-fetch/route.ts +0 -19
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/cached-with-cachelife/route.ts +0 -24
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/cached-with-tag/route.ts +0 -21
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/revalidate-tag/route.ts +0 -19
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/revalidated-fetch/route.ts +0 -19
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/cachelife-short/page.tsx +0 -110
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/page.tsx +0 -90
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/runtime-data-suspense/page.tsx +0 -127
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/stale-while-revalidate/page.tsx +0 -130
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/tag-invalidation/page.tsx +0 -127
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/use-cache-nondeterministic/page.tsx +0 -110
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/favicon.ico +0 -0
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/globals.css +0 -26
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/layout.tsx +0 -57
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/page.tsx +0 -755
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/revalidation-interface.tsx +0 -267
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/update-tag-test/page.tsx +0 -22
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/tsconfig.json +0 -34
- package/test/nextjs-test-projects/next-app-customized/README.md +0 -36
- package/test/nextjs-test-projects/next-app-customized/customized-cache-handler.js +0 -34
- package/test/nextjs-test-projects/next-app-customized/eslint.config.mjs +0 -16
- package/test/nextjs-test-projects/next-app-customized/next.config.js +0 -6
- package/test/nextjs-test-projects/next-pages-16-2-6/README.md +0 -16
- package/test/nextjs-test-projects/next-pages-16-2-6/eslint.config.mjs +0 -18
- package/test/nextjs-test-projects/next-pages-16-2-6/next.config.ts +0 -7
- package/test/nextjs-test-projects/next-pages-16-2-6/package.json +0 -26
- package/test/nextjs-test-projects/next-pages-16-2-6/pnpm-lock.yaml +0 -3896
- package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/api/revalidate.ts +0 -24
- package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/index.tsx +0 -11
- package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/isr/[slug].tsx +0 -49
- package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/static-forever.tsx +0 -20
- package/test/nextjs-test-projects/next-pages-16-2-6/tsconfig.json +0 -34
- package/test/nextjs-test-projects/next-pages-16-3-0/README.md +0 -16
- package/test/nextjs-test-projects/next-pages-16-3-0/eslint.config.mjs +0 -18
- package/test/nextjs-test-projects/next-pages-16-3-0/next.config.ts +0 -7
- package/test/nextjs-test-projects/next-pages-16-3-0/package.json +0 -26
- package/test/nextjs-test-projects/next-pages-16-3-0/pnpm-lock.yaml +0 -3939
- package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/api/revalidate.ts +0 -24
- package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/index.tsx +0 -11
- package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/isr/[slug].tsx +0 -49
- package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/static-forever.tsx +0 -20
- package/test/nextjs-test-projects/next-pages-16-3-0/tsconfig.json +0 -34
- package/test/playwright/cache-lab.spec.ts +0 -157
- package/test/playwright/update-tag.spec.ts +0 -33
- package/test/vitest/integration/build-id-prefix.integration.test.ts +0 -102
- package/test/vitest/integration/cache-components/cache-components.integration.test.ts +0 -231
- package/test/vitest/integration/cache-components/redis-kill-reconnect.test.ts +0 -65
- package/test/vitest/integration/cache-components/redis-quit-vs-disconnect.test.ts +0 -59
- package/test/vitest/integration/cache-components/redis-subscriber-outage.test.ts +0 -100
- package/test/vitest/integration/cache-components/scripts/redis-kill-reconnect.ts +0 -243
- package/test/vitest/integration/cache-components/scripts/redis-quit-vs-disconnect.ts +0 -113
- package/test/vitest/integration/cache-components/scripts/redis-subscriber-outage.ts +0 -379
- package/test/vitest/integration/cache-components/scripts/redis-test-helpers.ts +0 -155
- package/test/vitest/integration/nextjs-cache-handler.integration.test.ts +0 -945
- package/test/vitest/integration/pages-router.integration.test.ts +0 -420
- package/test/vitest/unit/SyncedMap-reconnect-repro.test.ts +0 -154
- package/test/vitest/unit/index.test.ts +0 -173
- package/test/vitest/unit/pages-router-kinds.test.ts +0 -292
- package/test/vitest/unit/reconnect-socket-already-opened.test.ts +0 -117
- package/test/vitest/unit/serializer.test.ts +0 -182
- package/test/vitest/unit/utils/prefix.test.ts +0 -115
- package/tsconfig.json +0 -18
- package/tsup.config.ts +0 -14
- package/vite.config.ts +0 -34
- package/vitest.cache-components.config.ts +0 -16
package/src/SyncedMap.ts
DELETED
|
@@ -1,482 +0,0 @@
|
|
|
1
|
-
// SyncedMap.ts
|
|
2
|
-
import { Client, redisErrorHandler } from './RedisStringsHandler';
|
|
3
|
-
import { debugVerbose, debug } from './utils/debug';
|
|
4
|
-
|
|
5
|
-
type CustomizedSync = {
|
|
6
|
-
withoutRedisHashmap?: boolean;
|
|
7
|
-
withoutSetSync?: boolean;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
type SyncedMapOptions = {
|
|
11
|
-
client: Client;
|
|
12
|
-
keyPrefix: string;
|
|
13
|
-
redisKey: string; // Redis Hash key
|
|
14
|
-
database: number;
|
|
15
|
-
querySize: number;
|
|
16
|
-
filterKeys: (key: string) => boolean;
|
|
17
|
-
resyncIntervalMs?: number;
|
|
18
|
-
customizedSync?: CustomizedSync;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export type SyncMessage<V> = {
|
|
22
|
-
type: 'insert' | 'delete';
|
|
23
|
-
key?: string;
|
|
24
|
-
value?: V;
|
|
25
|
-
keys?: string[];
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const SYNC_CHANNEL_SUFFIX = ':sync-channel:';
|
|
29
|
-
export class SyncedMap<V> {
|
|
30
|
-
private client: Client;
|
|
31
|
-
private subscriberClient: Client;
|
|
32
|
-
private map: Map<string, V>;
|
|
33
|
-
private keyPrefix: string;
|
|
34
|
-
private syncChannel: string;
|
|
35
|
-
private redisKey: string;
|
|
36
|
-
private database: number;
|
|
37
|
-
private querySize: number;
|
|
38
|
-
private filterKeys: (key: string) => boolean;
|
|
39
|
-
private resyncIntervalMs?: number;
|
|
40
|
-
private customizedSync?: CustomizedSync;
|
|
41
|
-
|
|
42
|
-
private setupLock: Promise<void>;
|
|
43
|
-
private setupLockResolve!: () => void;
|
|
44
|
-
private isReconnecting = false;
|
|
45
|
-
|
|
46
|
-
constructor(options: SyncedMapOptions) {
|
|
47
|
-
this.client = options.client;
|
|
48
|
-
this.keyPrefix = options.keyPrefix;
|
|
49
|
-
this.redisKey = options.redisKey;
|
|
50
|
-
this.syncChannel = `${options.keyPrefix}${SYNC_CHANNEL_SUFFIX}${options.redisKey}`;
|
|
51
|
-
this.database = options.database;
|
|
52
|
-
this.querySize = options.querySize;
|
|
53
|
-
this.filterKeys = options.filterKeys;
|
|
54
|
-
this.resyncIntervalMs = options.resyncIntervalMs;
|
|
55
|
-
this.customizedSync = options.customizedSync;
|
|
56
|
-
|
|
57
|
-
this.map = new Map<string, V>();
|
|
58
|
-
this.subscriberClient = this.client.duplicate();
|
|
59
|
-
this.setupLock = new Promise<void>((resolve) => {
|
|
60
|
-
this.setupLockResolve = resolve;
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
this.setup().catch((error) => {
|
|
64
|
-
console.error('Failed to setup SyncedMap:', error);
|
|
65
|
-
throw error;
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
private async setup() {
|
|
70
|
-
let setupPromises: Promise<void>[] = [];
|
|
71
|
-
if (!this.customizedSync?.withoutRedisHashmap) {
|
|
72
|
-
setupPromises.push(this.initialSync());
|
|
73
|
-
this.setupPeriodicResync();
|
|
74
|
-
}
|
|
75
|
-
setupPromises.push(this.setupPubSub());
|
|
76
|
-
await Promise.all(setupPromises);
|
|
77
|
-
this.setupLockResolve();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
private async initialSync() {
|
|
81
|
-
let cursor = 0;
|
|
82
|
-
const hScanOptions = { COUNT: this.querySize };
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
do {
|
|
86
|
-
const remoteItems = await redisErrorHandler(
|
|
87
|
-
'SyncedMap.initialSync(), operation: hScan ' +
|
|
88
|
-
this.syncChannel +
|
|
89
|
-
' ' +
|
|
90
|
-
this.keyPrefix +
|
|
91
|
-
' ' +
|
|
92
|
-
this.redisKey +
|
|
93
|
-
' ' +
|
|
94
|
-
cursor +
|
|
95
|
-
' ' +
|
|
96
|
-
this.querySize,
|
|
97
|
-
this.client.hScan(
|
|
98
|
-
this.keyPrefix + this.redisKey,
|
|
99
|
-
cursor,
|
|
100
|
-
hScanOptions,
|
|
101
|
-
),
|
|
102
|
-
);
|
|
103
|
-
for (const { field, value } of remoteItems.tuples) {
|
|
104
|
-
if (this.filterKeys(field)) {
|
|
105
|
-
const parsedValue = JSON.parse(value);
|
|
106
|
-
this.map.set(field, parsedValue);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
cursor = remoteItems.cursor;
|
|
110
|
-
} while (cursor !== 0);
|
|
111
|
-
|
|
112
|
-
// Clean up keys not in Redis
|
|
113
|
-
await this.cleanupKeysNotInRedis();
|
|
114
|
-
} catch (error) {
|
|
115
|
-
console.error('Error during initial sync:', error);
|
|
116
|
-
throw error;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
private async cleanupKeysNotInRedis() {
|
|
121
|
-
let cursor = 0;
|
|
122
|
-
const scanOptions = { COUNT: this.querySize, MATCH: `${this.keyPrefix}*` };
|
|
123
|
-
let remoteKeys: string[] = [];
|
|
124
|
-
try {
|
|
125
|
-
do {
|
|
126
|
-
const remoteKeysPortion = await redisErrorHandler(
|
|
127
|
-
'SyncedMap.cleanupKeysNotInRedis(), operation: scan ' +
|
|
128
|
-
this.keyPrefix,
|
|
129
|
-
this.client.scan(cursor, scanOptions),
|
|
130
|
-
);
|
|
131
|
-
remoteKeys = remoteKeys.concat(remoteKeysPortion.keys);
|
|
132
|
-
cursor = remoteKeysPortion.cursor;
|
|
133
|
-
} while (cursor !== 0);
|
|
134
|
-
|
|
135
|
-
const remoteKeysSet = new Set(
|
|
136
|
-
remoteKeys.map((key) => key.substring(this.keyPrefix.length)),
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
const keysToDelete: string[] = [];
|
|
140
|
-
for (const key of this.map.keys()) {
|
|
141
|
-
const keyStr = key as unknown as string;
|
|
142
|
-
if (!remoteKeysSet.has(keyStr) && this.filterKeys(keyStr)) {
|
|
143
|
-
keysToDelete.push(keyStr);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (keysToDelete.length > 0) {
|
|
148
|
-
await this.delete(keysToDelete);
|
|
149
|
-
}
|
|
150
|
-
} catch (error) {
|
|
151
|
-
console.error('Error during cleanup of keys not in Redis:', error);
|
|
152
|
-
throw error;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
private setupPeriodicResync() {
|
|
157
|
-
if (this.resyncIntervalMs && this.resyncIntervalMs > 0) {
|
|
158
|
-
setInterval(() => {
|
|
159
|
-
this.initialSync().catch((error) => {
|
|
160
|
-
console.error('Error during periodic resync:', error);
|
|
161
|
-
});
|
|
162
|
-
}, this.resyncIntervalMs);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
private async setupPubSub() {
|
|
167
|
-
const syncHandler = async (message: string) => {
|
|
168
|
-
const syncMessage: SyncMessage<V> = JSON.parse(message);
|
|
169
|
-
if (syncMessage.type === 'insert') {
|
|
170
|
-
if (syncMessage.key !== undefined && syncMessage.value !== undefined) {
|
|
171
|
-
this.map.set(syncMessage.key, syncMessage.value);
|
|
172
|
-
}
|
|
173
|
-
} else if (syncMessage.type === 'delete') {
|
|
174
|
-
if (syncMessage.keys) {
|
|
175
|
-
for (const key of syncMessage.keys) {
|
|
176
|
-
this.map.delete(key);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
const keyEventHandler = async (key: string, message: string) => {
|
|
183
|
-
debug(
|
|
184
|
-
'yellow',
|
|
185
|
-
'SyncedMap.keyEventHandler() called with message',
|
|
186
|
-
this.redisKey,
|
|
187
|
-
message,
|
|
188
|
-
key,
|
|
189
|
-
);
|
|
190
|
-
// const key = message;
|
|
191
|
-
if (key.startsWith(this.keyPrefix)) {
|
|
192
|
-
const keyInMap = key.substring(this.keyPrefix.length);
|
|
193
|
-
if (this.filterKeys(keyInMap)) {
|
|
194
|
-
debugVerbose(
|
|
195
|
-
'SyncedMap.keyEventHandler() key matches filter and will be deleted',
|
|
196
|
-
this.redisKey,
|
|
197
|
-
message,
|
|
198
|
-
key,
|
|
199
|
-
);
|
|
200
|
-
await this.delete(keyInMap, true);
|
|
201
|
-
}
|
|
202
|
-
} else {
|
|
203
|
-
debugVerbose(
|
|
204
|
-
'SyncedMap.keyEventHandler() key does not have prefix',
|
|
205
|
-
this.redisKey,
|
|
206
|
-
message,
|
|
207
|
-
key,
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
try {
|
|
213
|
-
// Attach error handler BEFORE subscribing so that errors during
|
|
214
|
-
// subscribe or after connection loss are always caught and trigger
|
|
215
|
-
// a reconnection. Previously this was attached after the subscribe()
|
|
216
|
-
// calls, which meant if subscribe threw during an outage the handler
|
|
217
|
-
// was never attached and the subscriber was permanently dead.
|
|
218
|
-
this.subscriberClient.on('error', async (err) => {
|
|
219
|
-
console.error('Subscriber client error:', err);
|
|
220
|
-
await this.reconnectSubscriber();
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
const connectIfNeeded = async () => {
|
|
224
|
-
// Avoid overlapping connect() calls which can lead to
|
|
225
|
-
// "Socket already opened" errors when a connect is already in progress
|
|
226
|
-
// or the socket is still open.
|
|
227
|
-
if (!this.subscriberClient.isOpen && !this.subscriberClient.isReady) {
|
|
228
|
-
await this.subscriberClient.connect();
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
await connectIfNeeded().catch(async (error) => {
|
|
233
|
-
console.error('Failed to connect subscriber client. Retrying...');
|
|
234
|
-
try {
|
|
235
|
-
await connectIfNeeded();
|
|
236
|
-
} catch (err) {
|
|
237
|
-
console.error('Failed to connect subscriber client.', err);
|
|
238
|
-
throw err;
|
|
239
|
-
}
|
|
240
|
-
// keep original error in logs for context
|
|
241
|
-
void error;
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
// Check if keyspace event configuration is set correctly
|
|
245
|
-
if (
|
|
246
|
-
(process.env.SKIP_KEYSPACE_CONFIG_CHECK || '').toUpperCase() !== 'TRUE'
|
|
247
|
-
) {
|
|
248
|
-
const keyspaceEventConfig = (
|
|
249
|
-
await this.subscriberClient.configGet('notify-keyspace-events')
|
|
250
|
-
)?.['notify-keyspace-events'];
|
|
251
|
-
if (!keyspaceEventConfig.includes('E')) {
|
|
252
|
-
throw new Error(
|
|
253
|
-
'Keyspace event configuration is set to "' +
|
|
254
|
-
keyspaceEventConfig +
|
|
255
|
-
" but has to include 'E' for Keyevent events, " +
|
|
256
|
-
'published with __keyevent@<db>__ prefix. We recommend to set it to ' +
|
|
257
|
-
"'Exe' like so `redis-cli -h localhost config set notify-keyspace-events Exe`",
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
if (
|
|
261
|
-
!keyspaceEventConfig.includes('A') &&
|
|
262
|
-
!(
|
|
263
|
-
keyspaceEventConfig.includes('x') &&
|
|
264
|
-
keyspaceEventConfig.includes('e')
|
|
265
|
-
)
|
|
266
|
-
) {
|
|
267
|
-
throw new Error(
|
|
268
|
-
'Keyspace event configuration is set to "' +
|
|
269
|
-
keyspaceEventConfig +
|
|
270
|
-
" but has to include 'A' or 'x' and 'e' for expired and " +
|
|
271
|
-
'evicted events. We recommend to set it to ' +
|
|
272
|
-
"'Exe' like so `redis-cli -h localhost config set notify-keyspace-events Exe`",
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
await Promise.all([
|
|
278
|
-
// We use a custom channel for insert/delete For the following reason:
|
|
279
|
-
// With custom channel we can delete multiple entries in one message. If we would listen to unlink / del we
|
|
280
|
-
// could get thousands of messages for one revalidateTag (For example revalidateTag("algolia") would send an enormous amount of network packages)
|
|
281
|
-
// Also we can send the value in the message for insert
|
|
282
|
-
this.subscriberClient.subscribe(this.syncChannel, syncHandler),
|
|
283
|
-
// Subscribe to Redis keyevent notifications for evicted and expired keys
|
|
284
|
-
this.subscriberClient.subscribe(
|
|
285
|
-
`__keyevent@${this.database}__:evicted`,
|
|
286
|
-
keyEventHandler,
|
|
287
|
-
),
|
|
288
|
-
this.subscriberClient.subscribe(
|
|
289
|
-
`__keyevent@${this.database}__:expired`,
|
|
290
|
-
keyEventHandler,
|
|
291
|
-
),
|
|
292
|
-
]);
|
|
293
|
-
} catch (error) {
|
|
294
|
-
console.error('Error setting up pub/sub client:', error);
|
|
295
|
-
throw error;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Reconnect the subscriber client with exponential backoff.
|
|
301
|
-
* Guarded by `isReconnecting` to prevent concurrent reconnection
|
|
302
|
-
* attempts when multiple error events fire during an outage.
|
|
303
|
-
* Retries indefinitely (with capped delay) so the subscriber
|
|
304
|
-
* eventually recovers once Redis is available again.
|
|
305
|
-
*/
|
|
306
|
-
private async reconnectSubscriber(): Promise<void> {
|
|
307
|
-
if (this.isReconnecting) return;
|
|
308
|
-
this.isReconnecting = true;
|
|
309
|
-
|
|
310
|
-
try {
|
|
311
|
-
let attempt = 0;
|
|
312
|
-
while (true) {
|
|
313
|
-
try {
|
|
314
|
-
// Forcibly tear down the old subscriber so its internal reconnect
|
|
315
|
-
// loop cannot come back to life once Redis recovers. `quit()` waits
|
|
316
|
-
// for the server and can silently fail during an outage, leaving a
|
|
317
|
-
// zombie client that still fires our error handler. Remove that
|
|
318
|
-
// handler first so a delayed error from the old socket cannot tear
|
|
319
|
-
// down the replacement subscriber we are about to create.
|
|
320
|
-
try {
|
|
321
|
-
this.subscriberClient.removeAllListeners('error');
|
|
322
|
-
await this.subscriberClient.disconnect();
|
|
323
|
-
} catch {
|
|
324
|
-
// expected during outage
|
|
325
|
-
}
|
|
326
|
-
// Create a fresh subscriber client
|
|
327
|
-
this.subscriberClient = this.client.duplicate();
|
|
328
|
-
await this.setupPubSub();
|
|
329
|
-
return; // success
|
|
330
|
-
} catch (error) {
|
|
331
|
-
const delay = Math.min(
|
|
332
|
-
500 * Math.pow(2, Math.min(attempt, 5)),
|
|
333
|
-
10_000,
|
|
334
|
-
);
|
|
335
|
-
attempt++;
|
|
336
|
-
console.error(
|
|
337
|
-
`Subscriber reconnect attempt ${attempt} failed, retrying in ${delay}ms`,
|
|
338
|
-
error,
|
|
339
|
-
);
|
|
340
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
} finally {
|
|
344
|
-
this.isReconnecting = false;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
public async waitUntilReady() {
|
|
349
|
-
await this.setupLock;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
public get(key: string): V | undefined {
|
|
353
|
-
debugVerbose(
|
|
354
|
-
'SyncedMap.get() called with key',
|
|
355
|
-
key,
|
|
356
|
-
JSON.stringify(this.map.get(key))?.substring(0, 100),
|
|
357
|
-
);
|
|
358
|
-
return this.map.get(key);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
public async set(key: string, value: V): Promise<void> {
|
|
362
|
-
debugVerbose(
|
|
363
|
-
'SyncedMap.set() called with key',
|
|
364
|
-
key,
|
|
365
|
-
JSON.stringify(value)?.substring(0, 100),
|
|
366
|
-
);
|
|
367
|
-
this.map.set(key, value);
|
|
368
|
-
const operations = [];
|
|
369
|
-
|
|
370
|
-
// This is needed if we only want to sync delete commands. This is especially useful for non serializable data like a promise map
|
|
371
|
-
if (this.customizedSync?.withoutSetSync) {
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
if (!this.customizedSync?.withoutRedisHashmap) {
|
|
375
|
-
operations.push(
|
|
376
|
-
redisErrorHandler(
|
|
377
|
-
'SyncedMap.set(), operation: hSet ' +
|
|
378
|
-
this.syncChannel +
|
|
379
|
-
' ' +
|
|
380
|
-
this.keyPrefix +
|
|
381
|
-
' ' +
|
|
382
|
-
key,
|
|
383
|
-
this.client.hSet(
|
|
384
|
-
this.keyPrefix + this.redisKey,
|
|
385
|
-
key as unknown as string,
|
|
386
|
-
JSON.stringify(value),
|
|
387
|
-
),
|
|
388
|
-
),
|
|
389
|
-
);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const insertMessage: SyncMessage<V> = {
|
|
393
|
-
type: 'insert',
|
|
394
|
-
key: key as unknown as string,
|
|
395
|
-
value,
|
|
396
|
-
};
|
|
397
|
-
operations.push(
|
|
398
|
-
redisErrorHandler(
|
|
399
|
-
'SyncedMap.set(), operation: publish ' +
|
|
400
|
-
this.syncChannel +
|
|
401
|
-
' ' +
|
|
402
|
-
this.keyPrefix +
|
|
403
|
-
' ' +
|
|
404
|
-
key,
|
|
405
|
-
this.client.publish(this.syncChannel, JSON.stringify(insertMessage)),
|
|
406
|
-
),
|
|
407
|
-
);
|
|
408
|
-
await Promise.all(operations);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
public async delete(
|
|
412
|
-
keys: string[] | string,
|
|
413
|
-
withoutSyncMessage = false,
|
|
414
|
-
): Promise<void> {
|
|
415
|
-
debugVerbose(
|
|
416
|
-
'SyncedMap.delete() called with keys',
|
|
417
|
-
this.redisKey,
|
|
418
|
-
keys,
|
|
419
|
-
withoutSyncMessage,
|
|
420
|
-
);
|
|
421
|
-
|
|
422
|
-
const keysArray = Array.isArray(keys) ? keys : [keys];
|
|
423
|
-
const operations = [];
|
|
424
|
-
|
|
425
|
-
for (const key of keysArray) {
|
|
426
|
-
this.map.delete(key);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
if (!this.customizedSync?.withoutRedisHashmap) {
|
|
430
|
-
operations.push(
|
|
431
|
-
redisErrorHandler(
|
|
432
|
-
'SyncedMap.delete(), operation: hDel ' +
|
|
433
|
-
this.syncChannel +
|
|
434
|
-
' ' +
|
|
435
|
-
this.keyPrefix +
|
|
436
|
-
' ' +
|
|
437
|
-
this.redisKey +
|
|
438
|
-
' ' +
|
|
439
|
-
keysArray,
|
|
440
|
-
this.client.hDel(this.keyPrefix + this.redisKey, keysArray),
|
|
441
|
-
),
|
|
442
|
-
);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
if (!withoutSyncMessage) {
|
|
446
|
-
const deletionMessage: SyncMessage<V> = {
|
|
447
|
-
type: 'delete',
|
|
448
|
-
keys: keysArray,
|
|
449
|
-
};
|
|
450
|
-
operations.push(
|
|
451
|
-
redisErrorHandler(
|
|
452
|
-
'SyncedMap.delete(), operation: publish ' +
|
|
453
|
-
this.syncChannel +
|
|
454
|
-
' ' +
|
|
455
|
-
this.keyPrefix +
|
|
456
|
-
' ' +
|
|
457
|
-
keysArray,
|
|
458
|
-
this.client.publish(
|
|
459
|
-
this.syncChannel,
|
|
460
|
-
JSON.stringify(deletionMessage),
|
|
461
|
-
),
|
|
462
|
-
),
|
|
463
|
-
);
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
await Promise.all(operations);
|
|
467
|
-
debugVerbose(
|
|
468
|
-
'SyncedMap.delete() finished operations',
|
|
469
|
-
this.redisKey,
|
|
470
|
-
keys,
|
|
471
|
-
operations.length,
|
|
472
|
-
);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
public has(key: string): boolean {
|
|
476
|
-
return this.map.has(key);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
public entries(): IterableIterator<[string, V]> {
|
|
480
|
-
return this.map.entries();
|
|
481
|
-
}
|
|
482
|
-
}
|
package/src/ZodHandler.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This handler is used to validate the arguments of the get, set, revalidateTag methods against a zod schema.
|
|
3
|
-
* This is mainly used to test new nextjs versions and to ensure that the arguments are still valid. As we
|
|
4
|
-
* saw in the past, that arguments against the cache handler changed between different nextjs versions very often (even between patch versions).
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import RedisStringsHandler, {
|
|
8
|
-
CreateRedisStringsHandlerOptions,
|
|
9
|
-
} from './RedisStringsHandler';
|
|
10
|
-
import { debugVerbose } from './utils/debug';
|
|
11
|
-
|
|
12
|
-
let cachedHandler: RedisStringsHandler;
|
|
13
|
-
|
|
14
|
-
export default class CachedHandler {
|
|
15
|
-
constructor(options: CreateRedisStringsHandlerOptions) {
|
|
16
|
-
if (!cachedHandler) {
|
|
17
|
-
console.log('created development cached handler');
|
|
18
|
-
cachedHandler = new RedisStringsHandler(options);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
get(
|
|
22
|
-
...args: Parameters<RedisStringsHandler['get']>
|
|
23
|
-
): ReturnType<RedisStringsHandler['get']> {
|
|
24
|
-
debugVerbose('CachedHandler.get called with', args);
|
|
25
|
-
return cachedHandler.get(...args);
|
|
26
|
-
}
|
|
27
|
-
set(
|
|
28
|
-
...args: Parameters<RedisStringsHandler['set']>
|
|
29
|
-
): ReturnType<RedisStringsHandler['set']> {
|
|
30
|
-
debugVerbose('CachedHandler.set called with', args);
|
|
31
|
-
return cachedHandler.set(...args);
|
|
32
|
-
}
|
|
33
|
-
revalidateTag(
|
|
34
|
-
...args: Parameters<RedisStringsHandler['revalidateTag']>
|
|
35
|
-
): ReturnType<RedisStringsHandler['revalidateTag']> {
|
|
36
|
-
debugVerbose('CachedHandler.revalidateTag called with', args);
|
|
37
|
-
return cachedHandler.revalidateTag(...args);
|
|
38
|
-
}
|
|
39
|
-
resetRequestCache(
|
|
40
|
-
...args: Parameters<RedisStringsHandler['resetRequestCache']>
|
|
41
|
-
): ReturnType<RedisStringsHandler['resetRequestCache']> {
|
|
42
|
-
// debug("CachedHandler.resetRequestCache called with", args);
|
|
43
|
-
return cachedHandler.resetRequestCache(...args);
|
|
44
|
-
}
|
|
45
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import CachedHandler from './CachedHandler';
|
|
2
|
-
export default CachedHandler;
|
|
3
|
-
|
|
4
|
-
import RedisStringsHandler from './RedisStringsHandler';
|
|
5
|
-
export { RedisStringsHandler };
|
|
6
|
-
export type { CreateRedisStringsHandlerOptions } from './RedisStringsHandler';
|
|
7
|
-
|
|
8
|
-
export { jsonCacheValueSerializer } from './serializer';
|
|
9
|
-
export type { CacheValueSerializer } from './serializer';
|
|
10
|
-
export { bufferAndMapReplacer, bufferAndMapReviver } from './utils/json';
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
redisCacheHandler,
|
|
14
|
-
getRedisCacheComponentsHandler,
|
|
15
|
-
} from './CacheComponentsHandler';
|
|
16
|
-
export { redisCacheHandler, getRedisCacheComponentsHandler };
|
package/src/serializer.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pluggable wire-format codec for Redis string values used by `RedisStringsHandler`.
|
|
3
|
-
*
|
|
4
|
-
* The serializer is the single point at which an in-memory `CacheEntry` becomes the
|
|
5
|
-
* string written to Redis (and back). Plugging in a custom serializer lets you add
|
|
6
|
-
* compression (gzip/brotli), encryption, or any other custom encoding without forking
|
|
7
|
-
* this package or losing the existing dedup / batch / keyspace features.
|
|
8
|
-
*
|
|
9
|
-
* Both `serialize` and `deserialize` may return either a value directly or a `Promise`,
|
|
10
|
-
* which enables non-blocking async codecs such as stream-based compression
|
|
11
|
-
* (`zlib.brotliCompress`) or encryption (`crypto.subtle`). Synchronous implementations
|
|
12
|
-
* continue to work unchanged - awaiting a plain value is a no-op.
|
|
13
|
-
*
|
|
14
|
-
* The default export {@link jsonCacheValueSerializer} is `JSON.stringify` /
|
|
15
|
-
* `JSON.parse` paired with {@link bufferAndMapReplacer} / {@link bufferAndMapReviver},
|
|
16
|
-
* so native `Buffer` and `Map` values inside a `CacheEntry` round-trip transparently
|
|
17
|
-
* (this is required for Next.js RSC payloads).
|
|
18
|
-
*
|
|
19
|
-
* Operational note: changing the serializer (or any of its parameters such as a
|
|
20
|
-
* compression level or encryption key) makes existing Redis keys unreadable, because
|
|
21
|
-
* the deserializer will fail or return `null` for entries written by the previous
|
|
22
|
-
* format. Either flush the affected keys, bump `keyPrefix`, or migrate values
|
|
23
|
-
* out-of-band before deploying a new serializer.
|
|
24
|
-
*/
|
|
25
|
-
import type { CacheEntry } from './RedisStringsHandler';
|
|
26
|
-
import { bufferAndMapReplacer, bufferAndMapReviver } from './utils/json';
|
|
27
|
-
|
|
28
|
-
export 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
|
-
/**
|
|
44
|
-
* Default serializer used by `RedisStringsHandler` when no `valueSerializer` is
|
|
45
|
-
* configured. Wraps `JSON.stringify` / `JSON.parse` with this package's
|
|
46
|
-
* {@link bufferAndMapReplacer} / {@link bufferAndMapReviver} so native `Buffer`
|
|
47
|
-
* and `Map` values inside a `CacheEntry` survive the round-trip.
|
|
48
|
-
*
|
|
49
|
-
* Exported as a singleton so consumers can compare against the default by
|
|
50
|
-
* reference (e.g. to detect that no custom serializer was configured).
|
|
51
|
-
*/
|
|
52
|
-
export const jsonCacheValueSerializer: CacheValueSerializer = {
|
|
53
|
-
serialize(value) {
|
|
54
|
-
return JSON.stringify(value, bufferAndMapReplacer);
|
|
55
|
-
},
|
|
56
|
-
deserialize(stored) {
|
|
57
|
-
return JSON.parse(stored, bufferAndMapReviver) as CacheEntry | null;
|
|
58
|
-
},
|
|
59
|
-
};
|
package/src/utils/debug.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
export function debug(
|
|
2
|
-
color:
|
|
3
|
-
| 'red'
|
|
4
|
-
| 'blue'
|
|
5
|
-
| 'green'
|
|
6
|
-
| 'yellow'
|
|
7
|
-
| 'cyan'
|
|
8
|
-
| 'white'
|
|
9
|
-
| 'none' = 'none',
|
|
10
|
-
...args: unknown[]
|
|
11
|
-
): void {
|
|
12
|
-
const colorCode = {
|
|
13
|
-
red: '\x1b[31m',
|
|
14
|
-
blue: '\x1b[34m',
|
|
15
|
-
green: '\x1b[32m',
|
|
16
|
-
yellow: '\x1b[33m',
|
|
17
|
-
cyan: '\x1b[36m',
|
|
18
|
-
white: '\x1b[37m',
|
|
19
|
-
none: '',
|
|
20
|
-
};
|
|
21
|
-
if (process.env.DEBUG_CACHE_HANDLER) {
|
|
22
|
-
console.log(colorCode[color], 'DEBUG CACHE HANDLER: ', ...args);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function debugVerbose(color: string, ...args: unknown[]) {
|
|
27
|
-
if (process.env.DEBUG_CACHE_HANDLER_VERBOSE_VERBOSE) {
|
|
28
|
-
console.log('\x1b[35m', 'DEBUG SYNCED MAP: ', ...args);
|
|
29
|
-
}
|
|
30
|
-
}
|
package/src/utils/json.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2
|
-
export function bufferAndMapReviver(_: string, value: any): any {
|
|
3
|
-
if (value && typeof value === 'object' && typeof value.$binary === 'string') {
|
|
4
|
-
return Buffer.from(value.$binary, 'base64');
|
|
5
|
-
}
|
|
6
|
-
if (
|
|
7
|
-
value &&
|
|
8
|
-
typeof value === 'object' &&
|
|
9
|
-
typeof value.$map === 'object' &&
|
|
10
|
-
!!value.$map
|
|
11
|
-
) {
|
|
12
|
-
return new Map(
|
|
13
|
-
Object.entries(value.$map).map(([key, value]) => {
|
|
14
|
-
const revivedValue = bufferAndMapReviver('', value);
|
|
15
|
-
return [key, revivedValue];
|
|
16
|
-
}),
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
return value;
|
|
20
|
-
}
|
|
21
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
-
export function bufferAndMapReplacer(_: string, value: any): any {
|
|
23
|
-
if (Buffer.isBuffer(value)) {
|
|
24
|
-
return {
|
|
25
|
-
$binary: value.toString('base64'),
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
if (
|
|
29
|
-
value &&
|
|
30
|
-
typeof value === 'object' &&
|
|
31
|
-
value?.type === 'Buffer' &&
|
|
32
|
-
Array.isArray(value.data)
|
|
33
|
-
) {
|
|
34
|
-
return {
|
|
35
|
-
$binary: Buffer.from(value.data).toString('base64'),
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
if (value && typeof value === 'object' && value instanceof Map) {
|
|
39
|
-
return {
|
|
40
|
-
$map: Object.fromEntries(
|
|
41
|
-
Array.from(value.entries()).map(([key, value]) => {
|
|
42
|
-
const replacedValue = bufferAndMapReplacer('', value);
|
|
43
|
-
return [key, replacedValue];
|
|
44
|
-
}),
|
|
45
|
-
),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
return value;
|
|
49
|
-
}
|