@trieb.work/nextjs-turbo-redis-cache 1.18.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/dist/index.js +27 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +27 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -1
- 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 -58
- package/.prettierrc.json +0 -6
- package/.vscode/settings.json +0 -10
- package/ARCHITECTURE.md +0 -470
- 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 -12
- 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 -597
- package/src/CachedHandler.ts +0 -53
- package/src/DeduplicatedRequestHandler.ts +0 -110
- package/src/RedisStringsHandler.ts +0 -949
- package/src/SyncedMap.ts +0 -496
- package/src/ZodHandler.ts +0 -45
- package/src/index.ts +0 -23
- package/src/serializer.ts +0 -59
- package/src/utils/cacheTtl.ts +0 -71
- package/src/utils/compareAndUnlink.ts +0 -29
- package/src/utils/debug.ts +0 -30
- package/src/utils/json.ts +0 -49
- package/src/utils/prefix.ts +0 -44
- package/src/utils/redisConnection.ts +0 -17
- package/src/utils/tagRevalidation.ts +0 -162
- 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 -15
- 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/expire-matrix/revalidate/route.ts +0 -34
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/expire-matrix/route.ts +0 -23
- 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 -112
- package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/revalidate-durations/page.tsx +0 -106
- 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/cache-lab/use-cache-remote/page.tsx +0 -85
- 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 -15
- 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/expire-matrix/revalidate/route.ts +0 -34
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/expire-matrix/route.ts +0 -23
- 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 -112
- package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/revalidate-durations/page.tsx +0 -106
- 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/cache-lab/use-cache-remote/page.tsx +0 -85
- 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 -15
- 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/expire-matrix/revalidate/route.ts +0 -34
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/expire-matrix/route.ts +0 -23
- 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 -112
- package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/revalidate-durations/page.tsx +0 -106
- 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/cache-lab/use-cache-remote/page.tsx +0 -85
- 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 -232
- 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 -389
- 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 -959
- package/test/vitest/integration/pages-router.integration.test.ts +0 -430
- package/test/vitest/unit/SyncedMap-orphan-cleanup.test.ts +0 -111
- package/test/vitest/unit/SyncedMap-reconnect-repro.test.ts +0 -154
- package/test/vitest/unit/cache-components-update-tags.test.ts +0 -350
- package/test/vitest/unit/cache-ttl.test.ts +0 -67
- package/test/vitest/unit/compare-and-unlink.test.ts +0 -22
- package/test/vitest/unit/index.test.ts +0 -190
- package/test/vitest/unit/pages-router-kinds.test.ts +0 -303
- package/test/vitest/unit/reconnect-socket-already-opened.test.ts +0 -117
- package/test/vitest/unit/redis-connection.test.ts +0 -39
- package/test/vitest/unit/serializer.test.ts +0 -182
- package/test/vitest/unit/tag-revalidation.test.ts +0 -178
- 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
|
@@ -1,389 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
-
import { spawn, ChildProcess } from 'child_process';
|
|
3
|
-
import { createClient, RedisClientType } from 'redis';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
const PORT = Number(process.env.CACHE_COMPONENTS_PORT || '3065');
|
|
7
|
-
const BASE_URL = `http://localhost:${PORT}`;
|
|
8
|
-
|
|
9
|
-
describe('Next.js 16 Cache Components Integration', () => {
|
|
10
|
-
let nextProcess: ChildProcess;
|
|
11
|
-
let redisClient: RedisClientType;
|
|
12
|
-
let keyPrefix: string;
|
|
13
|
-
|
|
14
|
-
async function delay(ms: number) {
|
|
15
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async function waitForRedisKeys(
|
|
19
|
-
pattern: string,
|
|
20
|
-
minCount = 1,
|
|
21
|
-
timeoutMs = 10_000,
|
|
22
|
-
) {
|
|
23
|
-
for (let elapsed = 0; elapsed < timeoutMs; elapsed += 100) {
|
|
24
|
-
const keys = await redisClient.keys(pattern);
|
|
25
|
-
if (keys.length >= minCount) {
|
|
26
|
-
return keys;
|
|
27
|
-
}
|
|
28
|
-
await delay(100);
|
|
29
|
-
}
|
|
30
|
-
return redisClient.keys(pattern);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
beforeAll(async () => {
|
|
34
|
-
// Connect to Redis
|
|
35
|
-
redisClient = createClient({
|
|
36
|
-
url: process.env.REDIS_URL || 'redis://localhost:6379',
|
|
37
|
-
database: 1,
|
|
38
|
-
});
|
|
39
|
-
await redisClient.connect();
|
|
40
|
-
|
|
41
|
-
// Generate unique key prefix for this test run
|
|
42
|
-
keyPrefix = `cache-components-test-${Math.random().toString(36).substring(7)}`;
|
|
43
|
-
process.env.VERCEL_URL = keyPrefix;
|
|
44
|
-
|
|
45
|
-
const cacheComponentsApp =
|
|
46
|
-
process.env.CACHE_COMPONENTS_APP || 'next-app-16-2-6-cache-components';
|
|
47
|
-
|
|
48
|
-
const appDir = path.join(
|
|
49
|
-
__dirname,
|
|
50
|
-
'..',
|
|
51
|
-
'..',
|
|
52
|
-
'..',
|
|
53
|
-
'nextjs-test-projects',
|
|
54
|
-
cacheComponentsApp,
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
console.log('Installing Next.js app dependencies...');
|
|
58
|
-
await new Promise<void>((resolve, reject) => {
|
|
59
|
-
const installProcess = spawn('pnpm', ['install'], {
|
|
60
|
-
cwd: appDir,
|
|
61
|
-
stdio: 'inherit',
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
installProcess.on('close', (code) => {
|
|
65
|
-
if (code === 0) resolve();
|
|
66
|
-
else reject(new Error(`Install failed with code ${code}`));
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
console.log('Building Next.js app...');
|
|
71
|
-
await new Promise<void>((resolve, reject) => {
|
|
72
|
-
const buildProcess = spawn('pnpm', ['build'], {
|
|
73
|
-
cwd: appDir,
|
|
74
|
-
stdio: 'inherit',
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
buildProcess.on('close', (code) => {
|
|
78
|
-
if (code === 0) resolve();
|
|
79
|
-
else reject(new Error(`Build failed with code ${code}`));
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
console.log('Starting Next.js app...');
|
|
84
|
-
nextProcess = spawn('pnpm', ['start', '-p', PORT.toString()], {
|
|
85
|
-
cwd: appDir,
|
|
86
|
-
env: { ...process.env, VERCEL_URL: keyPrefix },
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// Wait for server to be ready
|
|
90
|
-
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
91
|
-
}, 120000);
|
|
92
|
-
|
|
93
|
-
afterAll(async () => {
|
|
94
|
-
// Clean up Redis keys
|
|
95
|
-
const keys = await redisClient.keys(`${keyPrefix}*`);
|
|
96
|
-
if (keys.length > 0) {
|
|
97
|
-
await redisClient.del(keys);
|
|
98
|
-
}
|
|
99
|
-
await redisClient.quit();
|
|
100
|
-
|
|
101
|
-
// Kill Next.js process
|
|
102
|
-
if (nextProcess) {
|
|
103
|
-
nextProcess.kill();
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe('Basic use cache functionality', () => {
|
|
108
|
-
it('should cache data and return same counter value on subsequent requests', async () => {
|
|
109
|
-
// First request
|
|
110
|
-
const res1 = await fetch(`${BASE_URL}/api/cached-static-fetch`);
|
|
111
|
-
const data1 = await res1.json();
|
|
112
|
-
|
|
113
|
-
expect(data1.counter).toBe(1);
|
|
114
|
-
|
|
115
|
-
// Second request should return cached data
|
|
116
|
-
const res2 = await fetch(`${BASE_URL}/api/cached-static-fetch`);
|
|
117
|
-
const data2 = await res2.json();
|
|
118
|
-
|
|
119
|
-
expect(data2.counter).toBe(1); // Same counter value
|
|
120
|
-
expect(data2.timestamp).toBe(data1.timestamp); // Same timestamp
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('should store cache entry in Redis', async () => {
|
|
124
|
-
// expire-matrix is dynamic (`ƒ`); static prerendered `use cache` routes may
|
|
125
|
-
// not write through to Redis at runtime on some Next.js versions.
|
|
126
|
-
await fetch(`${BASE_URL}/api/expire-matrix?id=redis-smoke`);
|
|
127
|
-
|
|
128
|
-
const keys = await waitForRedisKeys(`${keyPrefix}*`);
|
|
129
|
-
expect(keys.length).toBeGreaterThan(0);
|
|
130
|
-
}, 15_000);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
describe('cacheTag functionality', () => {
|
|
134
|
-
it('should cache data with tags', async () => {
|
|
135
|
-
const res1 = await fetch(`${BASE_URL}/api/cached-with-tag`);
|
|
136
|
-
const data1 = await res1.json();
|
|
137
|
-
|
|
138
|
-
expect(data1.counter).toBeDefined();
|
|
139
|
-
|
|
140
|
-
// Second request should return cached data
|
|
141
|
-
const res2 = await fetch(`${BASE_URL}/api/cached-with-tag`);
|
|
142
|
-
const data2 = await res2.json();
|
|
143
|
-
|
|
144
|
-
expect(data2.counter).toBe(data1.counter);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('should invalidate cache when tag is revalidated', async () => {
|
|
148
|
-
const id = `tag-invalidation-${Date.now()}`;
|
|
149
|
-
const matrixUrl = `${BASE_URL}/api/expire-matrix?id=${encodeURIComponent(id)}`;
|
|
150
|
-
|
|
151
|
-
const data1 = await (await fetch(matrixUrl)).json();
|
|
152
|
-
const data2 = await (await fetch(matrixUrl)).json();
|
|
153
|
-
expect(data2.counter).toBe(data1.counter);
|
|
154
|
-
|
|
155
|
-
const revalidateRes = await fetch(
|
|
156
|
-
`${BASE_URL}/api/expire-matrix/revalidate`,
|
|
157
|
-
{
|
|
158
|
-
method: 'POST',
|
|
159
|
-
headers: { 'Content-Type': 'application/json' },
|
|
160
|
-
body: JSON.stringify({
|
|
161
|
-
tag: `expire-matrix-${id}`,
|
|
162
|
-
profile: { expire: 0 },
|
|
163
|
-
}),
|
|
164
|
-
},
|
|
165
|
-
);
|
|
166
|
-
expect(revalidateRes.status).toBe(200);
|
|
167
|
-
|
|
168
|
-
const after = await (await fetch(matrixUrl)).json();
|
|
169
|
-
expect(after.counter).toBeGreaterThan(data1.counter);
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
describe('cacheLife functionality', () => {
|
|
174
|
-
it('should respect expire window and eventually return refreshed data', async () => {
|
|
175
|
-
const res1 = await fetch(`${BASE_URL}/api/cached-with-cachelife`);
|
|
176
|
-
const data1 = await res1.json();
|
|
177
|
-
|
|
178
|
-
const res2 = await fetch(`${BASE_URL}/api/cached-with-cachelife`);
|
|
179
|
-
const data2 = await res2.json();
|
|
180
|
-
expect(data2.counter).toBe(data1.counter);
|
|
181
|
-
expect(data2.timestamp).toBe(data1.timestamp);
|
|
182
|
-
|
|
183
|
-
await delay(6500);
|
|
184
|
-
|
|
185
|
-
let refreshedData: any;
|
|
186
|
-
for (let i = 0; i < 10; i++) {
|
|
187
|
-
const res = await fetch(`${BASE_URL}/api/cached-with-cachelife`);
|
|
188
|
-
const data = await res.json();
|
|
189
|
-
|
|
190
|
-
if (
|
|
191
|
-
data.counter !== data1.counter ||
|
|
192
|
-
data.timestamp !== data1.timestamp
|
|
193
|
-
) {
|
|
194
|
-
refreshedData = data;
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
await delay(500);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
expect(refreshedData).toBeDefined();
|
|
202
|
-
expect(refreshedData.counter).toBeGreaterThan(data1.counter);
|
|
203
|
-
expect(refreshedData.timestamp).not.toBe(data1.timestamp);
|
|
204
|
-
}, 20_000);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
describe('revalidateTag expire settings (next start)', () => {
|
|
208
|
-
// Expire seconds from next/dist/server/config-shared.js cacheLife presets
|
|
209
|
-
// (identical in Next.js 16.0.11, 16.2.6, 16.3.0). `default.expire` is
|
|
210
|
-
// INFINITE_CACHE = 0xfffffffe.
|
|
211
|
-
const CACHE_LIFE_EXPIRE_SECONDS = {
|
|
212
|
-
default: 0xfffffffe,
|
|
213
|
-
seconds: 60,
|
|
214
|
-
minutes: 60 * 60,
|
|
215
|
-
hours: 60 * 60 * 24,
|
|
216
|
-
days: 60 * 60 * 24 * 7,
|
|
217
|
-
weeks: 60 * 60 * 24 * 30,
|
|
218
|
-
max: 60 * 60 * 24 * 365,
|
|
219
|
-
} as const;
|
|
220
|
-
|
|
221
|
-
async function getMatrix(id: string) {
|
|
222
|
-
const res = await fetch(
|
|
223
|
-
`${BASE_URL}/api/expire-matrix?id=${encodeURIComponent(id)}`,
|
|
224
|
-
);
|
|
225
|
-
expect(res.status).toBe(200);
|
|
226
|
-
return res.json() as Promise<{
|
|
227
|
-
counter: number;
|
|
228
|
-
timestamp: number;
|
|
229
|
-
id: string;
|
|
230
|
-
}>;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async function revalidateMatrix(
|
|
234
|
-
id: string,
|
|
235
|
-
profile: string | { expire: number } | null,
|
|
236
|
-
) {
|
|
237
|
-
const tag = `expire-matrix-${id}`;
|
|
238
|
-
const body = profile === null ? { tag, profile: null } : { tag, profile };
|
|
239
|
-
const res = await fetch(`${BASE_URL}/api/expire-matrix/revalidate`, {
|
|
240
|
-
method: 'POST',
|
|
241
|
-
headers: { 'Content-Type': 'application/json' },
|
|
242
|
-
body: JSON.stringify(body),
|
|
243
|
-
});
|
|
244
|
-
expect(res.status).toBe(200);
|
|
245
|
-
return res.json() as Promise<{ timestamp: number }>;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
async function readTagManifest(tag: string) {
|
|
249
|
-
const hashKey = `${keyPrefix}__cacheComponents_revalidated_tags__`;
|
|
250
|
-
for (let elapsed = 0; elapsed < 5_000; elapsed += 50) {
|
|
251
|
-
const raw = await redisClient.hGet(hashKey, tag);
|
|
252
|
-
if (raw) {
|
|
253
|
-
const parsed: unknown = JSON.parse(raw);
|
|
254
|
-
if (typeof parsed === 'number') {
|
|
255
|
-
return { expired: parsed } as { stale?: number; expired?: number };
|
|
256
|
-
}
|
|
257
|
-
return parsed as { stale?: number; expired?: number };
|
|
258
|
-
}
|
|
259
|
-
await delay(50);
|
|
260
|
-
}
|
|
261
|
-
throw new Error(`tag manifest missing for ${tag} in ${hashKey}`);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function expectApproxMs(actual: number | undefined, expected: number) {
|
|
265
|
-
expect(actual, `expected ~${expected}, got ${actual}`).toBeDefined();
|
|
266
|
-
expect(Math.abs(actual! - expected)).toBeLessThan(5_000);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
it.each(Object.entries(CACHE_LIFE_EXPIRE_SECONDS) as [string, number][])(
|
|
270
|
-
'revalidateTag(tag, %s) marks stale now and expired=now+expire*1000, then SWR',
|
|
271
|
-
async (profile, expireSeconds) => {
|
|
272
|
-
const id = `profile-${profile}`;
|
|
273
|
-
await getMatrix(id);
|
|
274
|
-
const before = await getMatrix(id);
|
|
275
|
-
|
|
276
|
-
const startedAt = Date.now();
|
|
277
|
-
await revalidateMatrix(id, profile);
|
|
278
|
-
const manifest = await readTagManifest(`expire-matrix-${id}`);
|
|
279
|
-
|
|
280
|
-
expectApproxMs(manifest.stale, startedAt);
|
|
281
|
-
expectApproxMs(manifest.expired, startedAt + expireSeconds * 1000);
|
|
282
|
-
|
|
283
|
-
const after = await getMatrix(id);
|
|
284
|
-
expect(after.counter).toBe(before.counter);
|
|
285
|
-
expect(after.timestamp).toBe(before.timestamp);
|
|
286
|
-
},
|
|
287
|
-
);
|
|
288
|
-
|
|
289
|
-
it('{ expire: 5 } uses the object expire, not a named profile', async () => {
|
|
290
|
-
const id = 'object-expire-5';
|
|
291
|
-
await getMatrix(id);
|
|
292
|
-
const before = await getMatrix(id);
|
|
293
|
-
|
|
294
|
-
const startedAt = Date.now();
|
|
295
|
-
await revalidateMatrix(id, { expire: 5 });
|
|
296
|
-
const manifest = await readTagManifest(`expire-matrix-${id}`);
|
|
297
|
-
|
|
298
|
-
expectApproxMs(manifest.stale, startedAt);
|
|
299
|
-
expectApproxMs(manifest.expired, startedAt + 5_000);
|
|
300
|
-
|
|
301
|
-
const after = await getMatrix(id);
|
|
302
|
-
expect(after.counter).toBe(before.counter);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
it('{ expire: 0 } writes expired=now and the next GET is a blocking miss', async () => {
|
|
306
|
-
const id = 'expire-zero';
|
|
307
|
-
await getMatrix(id);
|
|
308
|
-
const before = await getMatrix(id);
|
|
309
|
-
|
|
310
|
-
const startedAt = Date.now();
|
|
311
|
-
await revalidateMatrix(id, { expire: 0 });
|
|
312
|
-
const manifest = await readTagManifest(`expire-matrix-${id}`);
|
|
313
|
-
|
|
314
|
-
expectApproxMs(manifest.expired, startedAt);
|
|
315
|
-
expect(manifest.stale).toBeUndefined();
|
|
316
|
-
|
|
317
|
-
const after = await getMatrix(id);
|
|
318
|
-
expect(after.counter).toBeGreaterThan(before.counter);
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
it('omitted profile writes expired=now without stale and the next GET misses', async () => {
|
|
322
|
-
const id = 'omit-profile';
|
|
323
|
-
await getMatrix(id);
|
|
324
|
-
const before = await getMatrix(id);
|
|
325
|
-
|
|
326
|
-
const startedAt = Date.now();
|
|
327
|
-
await revalidateMatrix(id, null);
|
|
328
|
-
const manifest = await readTagManifest(`expire-matrix-${id}`);
|
|
329
|
-
|
|
330
|
-
expect(manifest.stale).toBeUndefined();
|
|
331
|
-
expectApproxMs(manifest.expired, startedAt);
|
|
332
|
-
|
|
333
|
-
const after = await getMatrix(id);
|
|
334
|
-
expect(after.counter).toBeGreaterThan(before.counter);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
it('{ expire: 2 } serves stale, then hard-expires after the window', async () => {
|
|
338
|
-
const id = 'expire-two';
|
|
339
|
-
await getMatrix(id);
|
|
340
|
-
const before = await getMatrix(id);
|
|
341
|
-
|
|
342
|
-
const startedAt = Date.now();
|
|
343
|
-
await revalidateMatrix(id, { expire: 2 });
|
|
344
|
-
const manifest = await readTagManifest(`expire-matrix-${id}`);
|
|
345
|
-
|
|
346
|
-
expectApproxMs(manifest.stale, startedAt);
|
|
347
|
-
expectApproxMs(manifest.expired, startedAt + 2_000);
|
|
348
|
-
|
|
349
|
-
const stale = await getMatrix(id);
|
|
350
|
-
expect(stale.counter).toBe(before.counter);
|
|
351
|
-
|
|
352
|
-
await delay(3_000);
|
|
353
|
-
|
|
354
|
-
const afterWindow = await getMatrix(id);
|
|
355
|
-
expect(afterWindow.counter).toBeGreaterThan(before.counter);
|
|
356
|
-
}, 15_000);
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
describe('Redis cache handler integration', () => {
|
|
360
|
-
it('should call cache handler get and set methods', async () => {
|
|
361
|
-
// Make request to trigger cache (don't clear first)
|
|
362
|
-
await fetch(`${BASE_URL}/api/cached-static-fetch`);
|
|
363
|
-
|
|
364
|
-
// Verify Redis has the cached data
|
|
365
|
-
const redisKeys = await redisClient.keys(`${keyPrefix}*`);
|
|
366
|
-
expect(redisKeys.length).toBeGreaterThan(0);
|
|
367
|
-
|
|
368
|
-
// Filter out hash keys (sharedTagsMap) and only check string keys (cache entries)
|
|
369
|
-
// Try to get each key and verify at least one is a string value
|
|
370
|
-
let foundStringKey = false;
|
|
371
|
-
for (const key of redisKeys) {
|
|
372
|
-
try {
|
|
373
|
-
const type = await redisClient.type(key);
|
|
374
|
-
if (type === 'string') {
|
|
375
|
-
const cachedValue = await redisClient.get(key);
|
|
376
|
-
if (cachedValue) {
|
|
377
|
-
foundStringKey = true;
|
|
378
|
-
expect(cachedValue).toBeTruthy();
|
|
379
|
-
break;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
} catch (e) {
|
|
383
|
-
// Skip non-string keys
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
expect(foundStringKey).toBe(true);
|
|
387
|
-
});
|
|
388
|
-
});
|
|
389
|
-
});
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { spawn } from 'child_process';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { spawnSync } from 'child_process';
|
|
5
|
-
|
|
6
|
-
function hasContainerRuntime() {
|
|
7
|
-
const podman = spawnSync('podman', ['--version'], {
|
|
8
|
-
encoding: 'utf8',
|
|
9
|
-
timeout: 3_000,
|
|
10
|
-
});
|
|
11
|
-
if (!podman.error && (podman.status ?? 1) === 0) return true;
|
|
12
|
-
|
|
13
|
-
const docker = spawnSync('docker', ['--version'], {
|
|
14
|
-
encoding: 'utf8',
|
|
15
|
-
timeout: 3_000,
|
|
16
|
-
});
|
|
17
|
-
return !docker.error && (docker.status ?? 1) === 0;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function runNode(script: string, timeoutMs = 120_000) {
|
|
21
|
-
return new Promise<{ code: number; stdout: string; stderr: string }>(
|
|
22
|
-
(resolve, reject) => {
|
|
23
|
-
const p = spawn('pnpm', ['-s', 'tsx', script], {
|
|
24
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
25
|
-
env: { ...process.env },
|
|
26
|
-
});
|
|
27
|
-
let stdout = '';
|
|
28
|
-
let stderr = '';
|
|
29
|
-
p.stdout.on('data', (d) => (stdout += d.toString()));
|
|
30
|
-
p.stderr.on('data', (d) => (stderr += d.toString()));
|
|
31
|
-
const t = setTimeout(() => {
|
|
32
|
-
p.kill('SIGKILL');
|
|
33
|
-
reject(new Error('timeout'));
|
|
34
|
-
}, timeoutMs);
|
|
35
|
-
p.on('close', (code) => {
|
|
36
|
-
clearTimeout(t);
|
|
37
|
-
resolve({ code: code ?? -1, stdout, stderr });
|
|
38
|
-
});
|
|
39
|
-
},
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
describe('redis kill/reconnect end-to-end (both handlers)', () => {
|
|
44
|
-
const runOrSkip = hasContainerRuntime() ? it : it.skip;
|
|
45
|
-
|
|
46
|
-
runOrSkip(
|
|
47
|
-
'survives redis restart without Socket already opened',
|
|
48
|
-
async () => {
|
|
49
|
-
const script = path.join(__dirname, 'scripts', 'redis-kill-reconnect.ts');
|
|
50
|
-
|
|
51
|
-
const res = await runNode(script, 180_000);
|
|
52
|
-
|
|
53
|
-
if (res.code !== 0) {
|
|
54
|
-
console.error('Script exited with code', res.code);
|
|
55
|
-
console.error('stdout:', res.stdout);
|
|
56
|
-
console.error('stderr:', res.stderr);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
expect(res.code).toBe(0);
|
|
60
|
-
expect(res.stdout).toContain('OK');
|
|
61
|
-
expect(res.stderr).not.toContain('Socket already opened');
|
|
62
|
-
},
|
|
63
|
-
180_000,
|
|
64
|
-
);
|
|
65
|
-
});
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll } from 'vitest';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import {
|
|
4
|
-
hasContainerRuntime,
|
|
5
|
-
runNode,
|
|
6
|
-
parseResults,
|
|
7
|
-
type TestResult,
|
|
8
|
-
} from './scripts/redis-test-helpers';
|
|
9
|
-
|
|
10
|
-
const describeOrSkip = hasContainerRuntime() ? describe : describe.skip;
|
|
11
|
-
|
|
12
|
-
describeOrSkip('subscriber teardown: quit() vs disconnect()', () => {
|
|
13
|
-
let results: Map<string, TestResult>;
|
|
14
|
-
let exitCode: number;
|
|
15
|
-
let stderr: string;
|
|
16
|
-
|
|
17
|
-
beforeAll(async () => {
|
|
18
|
-
const script = path.join(
|
|
19
|
-
__dirname,
|
|
20
|
-
'scripts',
|
|
21
|
-
'redis-quit-vs-disconnect.ts',
|
|
22
|
-
);
|
|
23
|
-
const res = await runNode(script, 60_000);
|
|
24
|
-
exitCode = res.code;
|
|
25
|
-
stderr = res.stderr;
|
|
26
|
-
results = parseResults(res.stdout);
|
|
27
|
-
}, 60_000);
|
|
28
|
-
|
|
29
|
-
it('script exits successfully', () => {
|
|
30
|
-
if (exitCode !== 0) {
|
|
31
|
-
process.stderr.write(stderr);
|
|
32
|
-
}
|
|
33
|
-
expect(exitCode).toBe(0);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('subscriber clients are ready before outage', () => {
|
|
37
|
-
const r = results.get('subscribers-ready');
|
|
38
|
-
expect(r).toBeDefined();
|
|
39
|
-
expect(r!.pass, r!.detail).toBe(true);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('quit() does not immediately close a subscriber during outage', () => {
|
|
43
|
-
const r = results.get('quit-does-not-immediately-close');
|
|
44
|
-
expect(r).toBeDefined();
|
|
45
|
-
expect(r!.pass, r!.detail).toBe(true);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('disconnect() closes the subscriber immediately during outage', () => {
|
|
49
|
-
const r = results.get('disconnect-closes-immediately');
|
|
50
|
-
expect(r).toBeDefined();
|
|
51
|
-
expect(r!.pass, r!.detail).toBe(true);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('disconnected subscriber stays dead after Redis restarts', () => {
|
|
55
|
-
const r = results.get('disconnected-client-stays-dead');
|
|
56
|
-
expect(r).toBeDefined();
|
|
57
|
-
expect(r!.pass, r!.detail).toBe(true);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll } from 'vitest';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import {
|
|
4
|
-
hasContainerRuntime,
|
|
5
|
-
runNode,
|
|
6
|
-
parseResults,
|
|
7
|
-
type TestResult,
|
|
8
|
-
} from './scripts/redis-test-helpers';
|
|
9
|
-
|
|
10
|
-
const describeOrSkip = hasContainerRuntime() ? describe : describe.skip;
|
|
11
|
-
|
|
12
|
-
describeOrSkip('issue #86: subscriber outage recovery', () => {
|
|
13
|
-
let results: Map<string, TestResult>;
|
|
14
|
-
|
|
15
|
-
beforeAll(async () => {
|
|
16
|
-
const script = path.join(
|
|
17
|
-
__dirname,
|
|
18
|
-
'scripts',
|
|
19
|
-
'redis-subscriber-outage.ts',
|
|
20
|
-
);
|
|
21
|
-
const res = await runNode(script, 300_000);
|
|
22
|
-
// Script always exits 0 (it records pass/fail per sub-test)
|
|
23
|
-
if (res.code !== 0) {
|
|
24
|
-
throw new Error(
|
|
25
|
-
`Script exited with code ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`,
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
results = parseResults(res.stdout);
|
|
29
|
-
}, 300_000);
|
|
30
|
-
|
|
31
|
-
// --- Tests that should PASS (verify correct behavior before outage) ---
|
|
32
|
-
|
|
33
|
-
it('PubSub sync works before outage', () => {
|
|
34
|
-
const r = results.get('pubsub-initial-strings');
|
|
35
|
-
expect(r).toBeDefined();
|
|
36
|
-
expect(r!.pass, r!.detail).toBe(true);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('subscriber clients have error listeners before outage', () => {
|
|
40
|
-
const r = results.get('error-listener-initial');
|
|
41
|
-
expect(r).toBeDefined();
|
|
42
|
-
expect(r!.pass, r!.detail).toBe(true);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// --- Tests that verify symptoms during outage ---
|
|
46
|
-
|
|
47
|
-
it('get() returns null during outage', () => {
|
|
48
|
-
const r = results.get('get-during-outage');
|
|
49
|
-
expect(r).toBeDefined();
|
|
50
|
-
expect(r!.pass, r!.detail).toBe(true);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('heap does not grow excessively during 50 get() calls in outage', () => {
|
|
54
|
-
const r = results.get('heap-growth-during-outage');
|
|
55
|
-
expect(r).toBeDefined();
|
|
56
|
-
expect(r!.pass, r!.detail).toBe(true);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('main client reconnects after Redis restart', () => {
|
|
60
|
-
const r = results.get('main-client-reconnects');
|
|
61
|
-
expect(r).toBeDefined();
|
|
62
|
-
expect(r!.pass, r!.detail).toBe(true);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('get() works after Redis restart (main client path)', () => {
|
|
66
|
-
const r = results.get('get-after-outage');
|
|
67
|
-
expect(r).toBeDefined();
|
|
68
|
-
expect(r!.pass, r!.detail).toBe(true);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
// --- Tests that reproduce the bug (EXPECTED TO FAIL with current code) ---
|
|
72
|
-
|
|
73
|
-
it('PubSub sync works after outage (issue #86 — subscriber never recovers)', () => {
|
|
74
|
-
const r = results.get('pubsub-after-outage-strings');
|
|
75
|
-
expect(r).toBeDefined();
|
|
76
|
-
expect(r!.pass, r!.detail).toBe(true);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('subscriber error listener state after outage (informational)', () => {
|
|
80
|
-
const r = results.get('error-listener-after-outage');
|
|
81
|
-
expect(r).toBeDefined();
|
|
82
|
-
// Informational — always passes. Detail shows whether the listener
|
|
83
|
-
// survived and whether the subscriber client object was replaced.
|
|
84
|
-
expect(r!.pass, r!.detail).toBe(true);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('duplicate() call count during outage (informational)', () => {
|
|
88
|
-
const r = results.get('duplicate-count-during-outage');
|
|
89
|
-
expect(r).toBeDefined();
|
|
90
|
-
// Informational — always passes. Detail has the actual count.
|
|
91
|
-
expect(r!.pass, r!.detail).toBe(true);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('total duplicate() call count (informational)', () => {
|
|
95
|
-
const r = results.get('duplicate-count-total');
|
|
96
|
-
expect(r).toBeDefined();
|
|
97
|
-
// Informational — always passes. Detail has the actual count.
|
|
98
|
-
expect(r!.pass, r!.detail).toBe(true);
|
|
99
|
-
});
|
|
100
|
-
});
|