@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.
Files changed (318) hide show
  1. package/dist/index.js +27 -20
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +27 -20
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +6 -1
  6. package/.cursor/skills/testing/SKILL.md +0 -121
  7. package/.github/workflows/ci.yml +0 -309
  8. package/.github/workflows/pages.yml +0 -38
  9. package/.github/workflows/release.yml +0 -58
  10. package/.prettierrc.json +0 -6
  11. package/.vscode/settings.json +0 -10
  12. package/ARCHITECTURE.md +0 -470
  13. package/commitlint.config.cjs +0 -4
  14. package/docker/redis.conf +0 -1
  15. package/docs/index.html +0 -140
  16. package/docs/robots.txt +0 -4
  17. package/docs/sitemap.xml +0 -8
  18. package/docs/styles.css +0 -141
  19. package/eslint.config.mjs +0 -26
  20. package/hooks/commit-msg +0 -10
  21. package/hooks/pre-commit +0 -11
  22. package/playwright.config.ts +0 -18
  23. package/release.config.cjs +0 -12
  24. package/scripts/prepare.sh +0 -7
  25. package/scripts/setup-git.sh +0 -8
  26. package/scripts/setup-repo-name.sh +0 -17
  27. package/scripts/vitest-run-staged.cjs +0 -44
  28. package/src/CacheComponentsHandler.ts +0 -597
  29. package/src/CachedHandler.ts +0 -53
  30. package/src/DeduplicatedRequestHandler.ts +0 -110
  31. package/src/RedisStringsHandler.ts +0 -949
  32. package/src/SyncedMap.ts +0 -496
  33. package/src/ZodHandler.ts +0 -45
  34. package/src/index.ts +0 -23
  35. package/src/serializer.ts +0 -59
  36. package/src/utils/cacheTtl.ts +0 -71
  37. package/src/utils/compareAndUnlink.ts +0 -29
  38. package/src/utils/debug.ts +0 -30
  39. package/src/utils/json.ts +0 -49
  40. package/src/utils/prefix.ts +0 -44
  41. package/src/utils/redisConnection.ts +0 -17
  42. package/src/utils/tagRevalidation.ts +0 -162
  43. package/test/README.md +0 -194
  44. package/test/nextjs-test-projects/next-app-15-4-11/README.md +0 -36
  45. package/test/nextjs-test-projects/next-app-15-4-11/eslint.config.mjs +0 -16
  46. package/test/nextjs-test-projects/next-app-15-4-11/next.config.js +0 -9
  47. package/test/nextjs-test-projects/next-app-15-4-11/package-lock.json +0 -5969
  48. package/test/nextjs-test-projects/next-app-15-4-11/package.json +0 -29
  49. package/test/nextjs-test-projects/next-app-15-4-11/pnpm-lock.yaml +0 -3726
  50. package/test/nextjs-test-projects/next-app-15-4-11/postcss.config.mjs +0 -5
  51. package/test/nextjs-test-projects/next-app-15-4-11/public/file.svg +0 -1
  52. package/test/nextjs-test-projects/next-app-15-4-11/public/globe.svg +0 -1
  53. package/test/nextjs-test-projects/next-app-15-4-11/public/next.svg +0 -1
  54. package/test/nextjs-test-projects/next-app-15-4-11/public/vercel.svg +0 -1
  55. package/test/nextjs-test-projects/next-app-15-4-11/public/window.svg +0 -1
  56. package/test/nextjs-test-projects/next-app-15-4-11/src/app/api/cached-static-fetch/route.ts +0 -18
  57. package/test/nextjs-test-projects/next-app-15-4-11/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
  58. package/test/nextjs-test-projects/next-app-15-4-11/src/app/api/revalidatePath/route.ts +0 -15
  59. package/test/nextjs-test-projects/next-app-15-4-11/src/app/api/revalidateTag/route.ts +0 -15
  60. package/test/nextjs-test-projects/next-app-15-4-11/src/app/api/revalidated-fetch/route.ts +0 -17
  61. package/test/nextjs-test-projects/next-app-15-4-11/src/app/api/uncached-fetch/route.ts +0 -15
  62. package/test/nextjs-test-projects/next-app-15-4-11/src/app/globals.css +0 -26
  63. package/test/nextjs-test-projects/next-app-15-4-11/src/app/layout.tsx +0 -59
  64. package/test/nextjs-test-projects/next-app-15-4-11/src/app/page.tsx +0 -755
  65. package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
  66. package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
  67. package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  68. package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/no-fetch/default-page/page.tsx +0 -55
  69. package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
  70. package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
  71. package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  72. package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
  73. package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
  74. package/test/nextjs-test-projects/next-app-15-4-11/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  75. package/test/nextjs-test-projects/next-app-15-4-11/src/app/revalidation-interface.tsx +0 -267
  76. package/test/nextjs-test-projects/next-app-15-4-11/tsconfig.json +0 -27
  77. package/test/nextjs-test-projects/next-app-16-0-11/README.md +0 -36
  78. package/test/nextjs-test-projects/next-app-16-0-11/eslint.config.mjs +0 -18
  79. package/test/nextjs-test-projects/next-app-16-0-11/next.config.ts +0 -10
  80. package/test/nextjs-test-projects/next-app-16-0-11/package.json +0 -28
  81. package/test/nextjs-test-projects/next-app-16-0-11/pnpm-lock.yaml +0 -4163
  82. package/test/nextjs-test-projects/next-app-16-0-11/postcss.config.mjs +0 -7
  83. package/test/nextjs-test-projects/next-app-16-0-11/src/app/api/cached-static-fetch/route.ts +0 -18
  84. package/test/nextjs-test-projects/next-app-16-0-11/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
  85. package/test/nextjs-test-projects/next-app-16-0-11/src/app/api/revalidatePath/route.ts +0 -15
  86. package/test/nextjs-test-projects/next-app-16-0-11/src/app/api/revalidateTag/route.ts +0 -20
  87. package/test/nextjs-test-projects/next-app-16-0-11/src/app/api/revalidated-fetch/route.ts +0 -17
  88. package/test/nextjs-test-projects/next-app-16-0-11/src/app/api/uncached-fetch/route.ts +0 -15
  89. package/test/nextjs-test-projects/next-app-16-0-11/src/app/favicon.ico +0 -0
  90. package/test/nextjs-test-projects/next-app-16-0-11/src/app/globals.css +0 -26
  91. package/test/nextjs-test-projects/next-app-16-0-11/src/app/layout.tsx +0 -59
  92. package/test/nextjs-test-projects/next-app-16-0-11/src/app/page.tsx +0 -755
  93. package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
  94. package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
  95. package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  96. package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/no-fetch/default-page/page.tsx +0 -55
  97. package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
  98. package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
  99. package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  100. package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
  101. package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
  102. package/test/nextjs-test-projects/next-app-16-0-11/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  103. package/test/nextjs-test-projects/next-app-16-0-11/src/app/revalidation-interface.tsx +0 -267
  104. package/test/nextjs-test-projects/next-app-16-0-11/src/app/update-tag-test/page.tsx +0 -25
  105. package/test/nextjs-test-projects/next-app-16-0-11/tsconfig.json +0 -34
  106. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/README.md +0 -36
  107. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/cache-handler.js +0 -3
  108. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/eslint.config.mjs +0 -18
  109. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/next.config.ts +0 -15
  110. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/package.json +0 -28
  111. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/pnpm-lock.yaml +0 -4206
  112. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/postcss.config.mjs +0 -7
  113. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/public/file.svg +0 -1
  114. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/public/globe.svg +0 -1
  115. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/public/next.svg +0 -1
  116. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/public/vercel.svg +0 -1
  117. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/public/window.svg +0 -1
  118. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/cached-static-fetch/route.ts +0 -19
  119. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/cached-with-cachelife/route.ts +0 -24
  120. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/cached-with-tag/route.ts +0 -21
  121. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/expire-matrix/revalidate/route.ts +0 -34
  122. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/expire-matrix/route.ts +0 -23
  123. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/revalidate-tag/route.ts +0 -19
  124. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/revalidated-fetch/route.ts +0 -19
  125. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/cachelife-short/page.tsx +0 -110
  126. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/page.tsx +0 -112
  127. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/revalidate-durations/page.tsx +0 -106
  128. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/runtime-data-suspense/page.tsx +0 -127
  129. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/stale-while-revalidate/page.tsx +0 -130
  130. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/tag-invalidation/page.tsx +0 -127
  131. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/use-cache-nondeterministic/page.tsx +0 -110
  132. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/use-cache-remote/page.tsx +0 -85
  133. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/favicon.ico +0 -0
  134. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/globals.css +0 -26
  135. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/layout.tsx +0 -57
  136. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/page.tsx +0 -755
  137. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/revalidation-interface.tsx +0 -267
  138. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/update-tag-test/page.tsx +0 -22
  139. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/tsconfig.json +0 -34
  140. package/test/nextjs-test-projects/next-app-16-2-6/README.md +0 -36
  141. package/test/nextjs-test-projects/next-app-16-2-6/eslint.config.mjs +0 -18
  142. package/test/nextjs-test-projects/next-app-16-2-6/next.config.ts +0 -7
  143. package/test/nextjs-test-projects/next-app-16-2-6/package.json +0 -28
  144. package/test/nextjs-test-projects/next-app-16-2-6/pnpm-lock.yaml +0 -4165
  145. package/test/nextjs-test-projects/next-app-16-2-6/postcss.config.mjs +0 -7
  146. package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/cached-static-fetch/route.ts +0 -18
  147. package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
  148. package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/revalidatePath/route.ts +0 -15
  149. package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/revalidateTag/route.ts +0 -20
  150. package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/revalidated-fetch/route.ts +0 -17
  151. package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/uncached-fetch/route.ts +0 -15
  152. package/test/nextjs-test-projects/next-app-16-2-6/src/app/favicon.ico +0 -0
  153. package/test/nextjs-test-projects/next-app-16-2-6/src/app/globals.css +0 -26
  154. package/test/nextjs-test-projects/next-app-16-2-6/src/app/layout.tsx +0 -59
  155. package/test/nextjs-test-projects/next-app-16-2-6/src/app/page.tsx +0 -755
  156. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
  157. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
  158. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  159. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/no-fetch/default-page/page.tsx +0 -55
  160. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
  161. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
  162. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  163. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
  164. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
  165. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  166. package/test/nextjs-test-projects/next-app-16-2-6/src/app/revalidation-interface.tsx +0 -267
  167. package/test/nextjs-test-projects/next-app-16-2-6/src/app/update-tag-test/page.tsx +0 -25
  168. package/test/nextjs-test-projects/next-app-16-2-6/tsconfig.json +0 -34
  169. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/README.md +0 -36
  170. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/cache-handler.js +0 -3
  171. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/eslint.config.mjs +0 -18
  172. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/next.config.ts +0 -15
  173. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/package.json +0 -28
  174. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/pnpm-lock.yaml +0 -4165
  175. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/postcss.config.mjs +0 -7
  176. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/file.svg +0 -1
  177. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/globe.svg +0 -1
  178. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/next.svg +0 -1
  179. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/vercel.svg +0 -1
  180. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/window.svg +0 -1
  181. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-static-fetch/route.ts +0 -19
  182. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-with-cachelife/route.ts +0 -24
  183. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-with-tag/route.ts +0 -21
  184. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/expire-matrix/revalidate/route.ts +0 -34
  185. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/expire-matrix/route.ts +0 -23
  186. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/revalidate-tag/route.ts +0 -19
  187. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/revalidated-fetch/route.ts +0 -19
  188. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/cachelife-short/page.tsx +0 -110
  189. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/page.tsx +0 -112
  190. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/revalidate-durations/page.tsx +0 -106
  191. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/runtime-data-suspense/page.tsx +0 -127
  192. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/stale-while-revalidate/page.tsx +0 -130
  193. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/tag-invalidation/page.tsx +0 -127
  194. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/use-cache-nondeterministic/page.tsx +0 -110
  195. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/use-cache-remote/page.tsx +0 -85
  196. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/favicon.ico +0 -0
  197. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/globals.css +0 -26
  198. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/layout.tsx +0 -57
  199. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/page.tsx +0 -755
  200. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/revalidation-interface.tsx +0 -267
  201. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/update-tag-test/page.tsx +0 -22
  202. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/tsconfig.json +0 -34
  203. package/test/nextjs-test-projects/next-app-16-3-0/README.md +0 -36
  204. package/test/nextjs-test-projects/next-app-16-3-0/eslint.config.mjs +0 -18
  205. package/test/nextjs-test-projects/next-app-16-3-0/next.config.ts +0 -7
  206. package/test/nextjs-test-projects/next-app-16-3-0/package.json +0 -28
  207. package/test/nextjs-test-projects/next-app-16-3-0/pnpm-lock.yaml +0 -4284
  208. package/test/nextjs-test-projects/next-app-16-3-0/postcss.config.mjs +0 -7
  209. package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/cached-static-fetch/route.ts +0 -18
  210. package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
  211. package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/revalidatePath/route.ts +0 -15
  212. package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/revalidateTag/route.ts +0 -20
  213. package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/revalidated-fetch/route.ts +0 -17
  214. package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/uncached-fetch/route.ts +0 -15
  215. package/test/nextjs-test-projects/next-app-16-3-0/src/app/favicon.ico +0 -0
  216. package/test/nextjs-test-projects/next-app-16-3-0/src/app/globals.css +0 -26
  217. package/test/nextjs-test-projects/next-app-16-3-0/src/app/layout.tsx +0 -59
  218. package/test/nextjs-test-projects/next-app-16-3-0/src/app/page.tsx +0 -755
  219. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
  220. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
  221. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  222. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/no-fetch/default-page/page.tsx +0 -55
  223. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
  224. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
  225. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  226. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
  227. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
  228. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  229. package/test/nextjs-test-projects/next-app-16-3-0/src/app/revalidation-interface.tsx +0 -267
  230. package/test/nextjs-test-projects/next-app-16-3-0/src/app/update-tag-test/page.tsx +0 -25
  231. package/test/nextjs-test-projects/next-app-16-3-0/tsconfig.json +0 -34
  232. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/README.md +0 -36
  233. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/cache-handler.js +0 -3
  234. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/eslint.config.mjs +0 -18
  235. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/next.config.ts +0 -15
  236. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/package.json +0 -28
  237. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/pnpm-lock.yaml +0 -4284
  238. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/postcss.config.mjs +0 -7
  239. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/file.svg +0 -1
  240. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/globe.svg +0 -1
  241. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/next.svg +0 -1
  242. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/vercel.svg +0 -1
  243. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/window.svg +0 -1
  244. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/cached-static-fetch/route.ts +0 -19
  245. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/cached-with-cachelife/route.ts +0 -24
  246. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/cached-with-tag/route.ts +0 -21
  247. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/expire-matrix/revalidate/route.ts +0 -34
  248. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/expire-matrix/route.ts +0 -23
  249. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/revalidate-tag/route.ts +0 -19
  250. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/revalidated-fetch/route.ts +0 -19
  251. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/cachelife-short/page.tsx +0 -110
  252. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/page.tsx +0 -112
  253. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/revalidate-durations/page.tsx +0 -106
  254. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/runtime-data-suspense/page.tsx +0 -127
  255. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/stale-while-revalidate/page.tsx +0 -130
  256. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/tag-invalidation/page.tsx +0 -127
  257. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/use-cache-nondeterministic/page.tsx +0 -110
  258. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/use-cache-remote/page.tsx +0 -85
  259. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/favicon.ico +0 -0
  260. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/globals.css +0 -26
  261. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/layout.tsx +0 -57
  262. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/page.tsx +0 -755
  263. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/revalidation-interface.tsx +0 -267
  264. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/update-tag-test/page.tsx +0 -22
  265. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/tsconfig.json +0 -34
  266. package/test/nextjs-test-projects/next-app-customized/README.md +0 -36
  267. package/test/nextjs-test-projects/next-app-customized/customized-cache-handler.js +0 -34
  268. package/test/nextjs-test-projects/next-app-customized/eslint.config.mjs +0 -16
  269. package/test/nextjs-test-projects/next-app-customized/next.config.js +0 -6
  270. package/test/nextjs-test-projects/next-pages-16-2-6/README.md +0 -16
  271. package/test/nextjs-test-projects/next-pages-16-2-6/eslint.config.mjs +0 -18
  272. package/test/nextjs-test-projects/next-pages-16-2-6/next.config.ts +0 -7
  273. package/test/nextjs-test-projects/next-pages-16-2-6/package.json +0 -26
  274. package/test/nextjs-test-projects/next-pages-16-2-6/pnpm-lock.yaml +0 -3896
  275. package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/api/revalidate.ts +0 -24
  276. package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/index.tsx +0 -11
  277. package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/isr/[slug].tsx +0 -49
  278. package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/static-forever.tsx +0 -20
  279. package/test/nextjs-test-projects/next-pages-16-2-6/tsconfig.json +0 -34
  280. package/test/nextjs-test-projects/next-pages-16-3-0/README.md +0 -16
  281. package/test/nextjs-test-projects/next-pages-16-3-0/eslint.config.mjs +0 -18
  282. package/test/nextjs-test-projects/next-pages-16-3-0/next.config.ts +0 -7
  283. package/test/nextjs-test-projects/next-pages-16-3-0/package.json +0 -26
  284. package/test/nextjs-test-projects/next-pages-16-3-0/pnpm-lock.yaml +0 -3939
  285. package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/api/revalidate.ts +0 -24
  286. package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/index.tsx +0 -11
  287. package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/isr/[slug].tsx +0 -49
  288. package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/static-forever.tsx +0 -20
  289. package/test/nextjs-test-projects/next-pages-16-3-0/tsconfig.json +0 -34
  290. package/test/playwright/cache-lab.spec.ts +0 -232
  291. package/test/playwright/update-tag.spec.ts +0 -33
  292. package/test/vitest/integration/build-id-prefix.integration.test.ts +0 -102
  293. package/test/vitest/integration/cache-components/cache-components.integration.test.ts +0 -389
  294. package/test/vitest/integration/cache-components/redis-kill-reconnect.test.ts +0 -65
  295. package/test/vitest/integration/cache-components/redis-quit-vs-disconnect.test.ts +0 -59
  296. package/test/vitest/integration/cache-components/redis-subscriber-outage.test.ts +0 -100
  297. package/test/vitest/integration/cache-components/scripts/redis-kill-reconnect.ts +0 -243
  298. package/test/vitest/integration/cache-components/scripts/redis-quit-vs-disconnect.ts +0 -113
  299. package/test/vitest/integration/cache-components/scripts/redis-subscriber-outage.ts +0 -379
  300. package/test/vitest/integration/cache-components/scripts/redis-test-helpers.ts +0 -155
  301. package/test/vitest/integration/nextjs-cache-handler.integration.test.ts +0 -959
  302. package/test/vitest/integration/pages-router.integration.test.ts +0 -430
  303. package/test/vitest/unit/SyncedMap-orphan-cleanup.test.ts +0 -111
  304. package/test/vitest/unit/SyncedMap-reconnect-repro.test.ts +0 -154
  305. package/test/vitest/unit/cache-components-update-tags.test.ts +0 -350
  306. package/test/vitest/unit/cache-ttl.test.ts +0 -67
  307. package/test/vitest/unit/compare-and-unlink.test.ts +0 -22
  308. package/test/vitest/unit/index.test.ts +0 -190
  309. package/test/vitest/unit/pages-router-kinds.test.ts +0 -303
  310. package/test/vitest/unit/reconnect-socket-already-opened.test.ts +0 -117
  311. package/test/vitest/unit/redis-connection.test.ts +0 -39
  312. package/test/vitest/unit/serializer.test.ts +0 -182
  313. package/test/vitest/unit/tag-revalidation.test.ts +0 -178
  314. package/test/vitest/unit/utils/prefix.test.ts +0 -115
  315. package/tsconfig.json +0 -18
  316. package/tsup.config.ts +0 -14
  317. package/vite.config.ts +0 -34
  318. package/vitest.cache-components.config.ts +0 -16
@@ -1,430 +0,0 @@
1
- import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
- import { ChildProcessWithoutNullStreams, spawn } from 'child_process';
3
- import fetch from 'node-fetch';
4
- import { createClient, RedisClientType } from 'redis';
5
- import { join } from 'path';
6
- import { readFileSync } from 'fs';
7
- import { CacheEntry } from '../../../src/RedisStringsHandler';
8
-
9
- // Select which Pages Router test app to use. Can be overridden via NEXT_PAGES_TEST_APP env var
10
- const NEXT_PAGES_TEST_APP =
11
- process.env.NEXT_PAGES_TEST_APP || 'next-pages-16-2-6';
12
- const NEXT_APP_DIR = join(
13
- __dirname,
14
- '..',
15
- '..',
16
- 'nextjs-test-projects',
17
- NEXT_PAGES_TEST_APP,
18
- );
19
- console.log('NEXT_APP_DIR', NEXT_APP_DIR);
20
-
21
- // Two instances of the same build sharing one Redis. This mirrors a
22
- // load-balanced multi-instance deployment and is used to prove that
23
- // on-demand revalidation (res.revalidate) on one instance is picked up
24
- // by the other instance.
25
- const INSTANCE_A_PORT = 3061;
26
- const INSTANCE_B_PORT = 3062;
27
- const INSTANCE_A_URL = `http://localhost:${INSTANCE_A_PORT}`;
28
- const INSTANCE_B_URL = `http://localhost:${INSTANCE_B_PORT}`;
29
-
30
- const REDIS_BACKGROUND_SYNC_DELAY = 250; //ms delay to prevent flaky tests in slow CI environments
31
-
32
- // Default inMemoryCachingTime of the handler is 10s. After a get(), an
33
- // instance may serve the entry from its in-memory deduplication cache for up
34
- // to this long, so cross-instance freshness tests wait it out first.
35
- const IN_MEMORY_CACHE_EXPIRY_DELAY = 11_000;
36
-
37
- // revalidate value of /isr/[slug] in the test app
38
- const ISR_REVALIDATE_SECONDS = 300;
39
- // default stale age of the cache handler (14 days)
40
- const DEFAULT_STALE_AGE = 60 * 60 * 24 * 14;
41
-
42
- let instanceA: ChildProcessWithoutNullStreams;
43
- let instanceB: ChildProcessWithoutNullStreams;
44
- let redisClient: RedisClientType;
45
- let buildId: string;
46
-
47
- async function delay(ms: number) {
48
- return new Promise((resolve) => setTimeout(resolve, ms));
49
- }
50
-
51
- function expectIsrRedisTtl(ttl: number) {
52
- // Next.js 16.3+ passes cacheControl.expire (~1 year) for ISR pages.
53
- // Redis TTL is keyed on expire, not 2 × revalidate.
54
- if (NEXT_PAGES_TEST_APP.includes('16-3')) {
55
- expect(ttl).toBeGreaterThan(2 * ISR_REVALIDATE_SECONDS);
56
- return;
57
- }
58
- // VERCEL_ENV=production -> expire age is 2 * revalidate
59
- expect(ttl).toBeGreaterThan(2 * ISR_REVALIDATE_SECONDS - 30);
60
- expect(ttl).toBeLessThanOrEqual(2 * ISR_REVALIDATE_SECONDS);
61
- }
62
-
63
- async function runCommand(cmd: string, args: string[], cwd: string) {
64
- return new Promise((resolve, reject) => {
65
- let stderr = '';
66
- let stdout = '';
67
- const proc = spawn(cmd, args, { cwd, stdio: 'pipe' });
68
-
69
- proc.stdout.on('data', (data) => {
70
- if (process.env.DEBUG_INTEGRATION) {
71
- console.log(data.toString());
72
- }
73
- stdout += data.toString();
74
- });
75
-
76
- proc.stderr.on('data', (data) => {
77
- if (process.env.DEBUG_INTEGRATION) {
78
- console.error(data.toString());
79
- }
80
- stderr += data.toString();
81
- });
82
-
83
- proc.on('exit', (code) => {
84
- if (code === 0) resolve(undefined);
85
- else {
86
- reject(
87
- new Error(
88
- `${cmd} ${args.join(' ')} failed with code ${code}\n` +
89
- `stdout: ${stdout}\n` +
90
- `stderr: ${stderr}`,
91
- ),
92
- );
93
- }
94
- });
95
- });
96
- }
97
-
98
- function startInstance(port: number): ChildProcessWithoutNullStreams {
99
- const proc = spawn('npx', ['next', 'start', '-p', String(port)], {
100
- cwd: NEXT_APP_DIR,
101
- env: {
102
- ...process.env,
103
- },
104
- stdio: 'pipe',
105
- });
106
- if (process.env.DEBUG_INTEGRATION) {
107
- proc.stdout.on('data', (data) => {
108
- console.log(`stdout(:${port}): ${data}`);
109
- });
110
- }
111
- proc.stderr.on('data', (data) => {
112
- console.error(`stderr(:${port}): ${data}`);
113
- });
114
- return proc;
115
- }
116
-
117
- async function waitForServer(url: string, timeout = 20000) {
118
- const start = Date.now();
119
- while (Date.now() - start < timeout) {
120
- try {
121
- const res = await fetch(url);
122
- if (res.ok) return;
123
- } catch {}
124
- await new Promise((r) => setTimeout(r, 300));
125
- }
126
- throw new Error(`Next.js server at ${url} did not start in time`);
127
- }
128
-
129
- function extractTimestamp(html: string): number {
130
- const match = html.match(/Timestamp: (?:<!-- -->)?(\d+)/)?.[1];
131
- expect(match).toBeDefined();
132
- return Number(match);
133
- }
134
-
135
- function extractCounter(html: string): number {
136
- const match = html.match(/Counter: (?:<!-- -->)?(\d+)/)?.[1];
137
- expect(match).toBeDefined();
138
- return Number(match);
139
- }
140
-
141
- describe('Pages Router Redis cache integration (two instances)', () => {
142
- beforeAll(async () => {
143
- // If old servers from a previous run are still around, kill them
144
- for (const port of [INSTANCE_A_PORT, INSTANCE_B_PORT]) {
145
- try {
146
- const res = await fetch(`http://localhost:${port}`);
147
- if (res.ok) {
148
- await runCommand('pkill', ['-f', `next start -p ${port}`], '.');
149
- }
150
- } catch {}
151
- }
152
-
153
- // Set up environment variables
154
- process.env.VERCEL_ENV = 'production';
155
- process.env.VERCEL_URL =
156
- 'pages-integration-test-' + Math.random().toString(36).substring(2, 15);
157
- console.log('redis key prefix is:', process.env.VERCEL_URL);
158
-
159
- // Only override redis env vars if not set. This can be set in the CI env.
160
- process.env.REDISHOST = process.env.REDISHOST || 'localhost';
161
- process.env.REDISPORT = process.env.REDISPORT || '6379';
162
-
163
- if (process.env.SKIP_BUILD === 'true') {
164
- console.log('skipping build');
165
- } else {
166
- await runCommand('pnpm', ['i'], NEXT_APP_DIR);
167
- console.log('pnpm i done');
168
- await runCommand('pnpm', ['build'], NEXT_APP_DIR);
169
- console.log('pnpm build done');
170
- }
171
-
172
- buildId = readFileSync(join(NEXT_APP_DIR, '.next', 'BUILD_ID'), 'utf-8')
173
- .toString()
174
- .trim();
175
-
176
- // Start two Next.js instances of the same build sharing one Redis
177
- instanceA = startInstance(INSTANCE_A_PORT);
178
- instanceB = startInstance(INSTANCE_B_PORT);
179
- await Promise.all([
180
- waitForServer(INSTANCE_A_URL),
181
- waitForServer(INSTANCE_B_URL),
182
- ]);
183
- console.log('both next instances started');
184
-
185
- // Connect to Redis
186
- redisClient = createClient({
187
- url: `redis://${process.env.REDISHOST}:${process.env.REDISPORT}`,
188
- });
189
- await redisClient.connect();
190
- }, 240_000);
191
-
192
- afterAll(async () => {
193
- if (process.env.KEEP_SERVER_RUNNING === 'true') {
194
- console.log('keeping servers running');
195
- } else {
196
- if (instanceA) instanceA.kill();
197
- if (instanceB) instanceB.kill();
198
- }
199
- if (redisClient) await redisClient.quit();
200
- });
201
-
202
- describe('PAGES entries (getStaticProps + revalidate)', () => {
203
- let firstTimestamp: number;
204
- let firstCounter: number;
205
-
206
- it('first request on instance A renders the page and stores a PAGES entry in Redis', async () => {
207
- const res = await fetch(INSTANCE_A_URL + '/isr/prebuilt');
208
- expect(res.status).toBe(200);
209
- const html = await res.text();
210
- firstTimestamp = extractTimestamp(html);
211
- firstCounter = extractCounter(html);
212
-
213
- await delay(REDIS_BACKGROUND_SYNC_DELAY);
214
-
215
- const value = (await redisClient.get(
216
- process.env.VERCEL_URL + '/isr/prebuilt',
217
- )) as string;
218
- expect(value).toBeDefined();
219
- const cacheEntry: CacheEntry = JSON.parse(value);
220
- expect(cacheEntry).toMatchObject({
221
- value: {
222
- kind: 'PAGES',
223
- html: expect.any(String),
224
- pageData: {
225
- pageProps: expect.objectContaining({
226
- slug: 'prebuilt',
227
- timestamp: firstTimestamp,
228
- }),
229
- },
230
- },
231
- lastModified: expect.any(Number),
232
- tags: ['_N_T_/isr/prebuilt'],
233
- });
234
-
235
- // The implicit path tag is registered in the shared tags hashmap so
236
- // that revalidatePath()/revalidateTag() can invalidate the page
237
- const hashmap = (await redisClient.hGet(
238
- process.env.VERCEL_URL + '__sharedTags__',
239
- '/isr/prebuilt',
240
- )) as string;
241
- expect(JSON.parse(hashmap)).toEqual(['_N_T_/isr/prebuilt']);
242
- });
243
-
244
- it('the TTL is derived from the getStaticProps revalidate value', async () => {
245
- const ttl = await redisClient.ttl(
246
- process.env.VERCEL_URL + '/isr/prebuilt',
247
- );
248
- expectIsrRedisTtl(ttl);
249
- });
250
-
251
- it('instance B serves the identical cached HTML from the shared Redis', async () => {
252
- const res = await fetch(INSTANCE_B_URL + '/isr/prebuilt');
253
- expect(res.status).toBe(200);
254
- const html = await res.text();
255
- expect(extractTimestamp(html)).toBe(firstTimestamp);
256
- expect(extractCounter(html)).toBe(firstCounter);
257
- });
258
-
259
- it('the pageData JSON (client navigation) is served from the same entry', async () => {
260
- const res = await fetch(
261
- `${INSTANCE_B_URL}/_next/data/${buildId}/isr/prebuilt.json`,
262
- );
263
- expect(res.status).toBe(200);
264
- const data: any = await res.json();
265
- expect(data.pageProps.slug).toBe('prebuilt');
266
- expect(data.pageProps.timestamp).toBe(firstTimestamp);
267
- });
268
-
269
- describe('two-instance on-demand revalidation (res.revalidate)', () => {
270
- it('res.revalidate on instance A updates Redis and instance B serves the fresh HTML and pageData', async () => {
271
- // Wait until instance B's in-memory deduplication cache entry for
272
- // the page has expired, so its next get() hits Redis again
273
- await delay(IN_MEMORY_CACHE_EXPIRY_DELAY);
274
-
275
- const revalidateRes = await fetch(
276
- INSTANCE_A_URL + '/api/revalidate?path=/isr/prebuilt',
277
- );
278
- const revalidateJson: any = await revalidateRes.json();
279
- expect(revalidateJson).toEqual({
280
- revalidated: true,
281
- path: '/isr/prebuilt',
282
- });
283
-
284
- await delay(REDIS_BACKGROUND_SYNC_DELAY);
285
-
286
- // Instance B must serve the fresh HTML rendered by instance A
287
- const resB = await fetch(INSTANCE_B_URL + '/isr/prebuilt');
288
- expect(resB.status).toBe(200);
289
- const htmlB = await resB.text();
290
- const timestampB = extractTimestamp(htmlB);
291
- const counterB = extractCounter(htmlB);
292
- expect(timestampB).toBeGreaterThan(firstTimestamp);
293
-
294
- // Instance A serves the same regenerated page - both instances
295
- // return the exact same render, proving it came through Redis and
296
- // not from an independent re-render per instance
297
- const resA = await fetch(INSTANCE_A_URL + '/isr/prebuilt');
298
- const htmlA = await resA.text();
299
- expect(extractTimestamp(htmlA)).toBe(timestampB);
300
- expect(extractCounter(htmlA)).toBe(counterB);
301
-
302
- // The updated pageData JSON for client-side navigation is also
303
- // served fresh on instance B
304
- const dataRes = await fetch(
305
- `${INSTANCE_B_URL}/_next/data/${buildId}/isr/prebuilt.json`,
306
- );
307
- expect(dataRes.status).toBe(200);
308
- const data: any = await dataRes.json();
309
- expect(data.pageProps.timestamp).toBe(timestampB);
310
- }, 30_000);
311
- });
312
- });
313
-
314
- describe('fallback: "blocking" (page not prerendered at build time)', () => {
315
- it('first hit renders the page (blocking) and stores it in Redis', async () => {
316
- const res = await fetch(INSTANCE_A_URL + '/isr/fallback-test');
317
- expect(res.status).toBe(200);
318
- const html = await res.text();
319
- const timestamp = extractTimestamp(html);
320
-
321
- await delay(REDIS_BACKGROUND_SYNC_DELAY);
322
-
323
- const value = (await redisClient.get(
324
- process.env.VERCEL_URL + '/isr/fallback-test',
325
- )) as string;
326
- expect(value).toBeDefined();
327
- const cacheEntry: CacheEntry = JSON.parse(value);
328
- expect((cacheEntry.value as { kind: string }).kind).toBe('PAGES');
329
-
330
- // The other instance serves the same render from Redis
331
- const resB = await fetch(INSTANCE_B_URL + '/isr/fallback-test');
332
- expect(resB.status).toBe(200);
333
- expect(extractTimestamp(await resB.text())).toBe(timestamp);
334
- });
335
- });
336
-
337
- describe('notFound: true (null cache entry)', () => {
338
- it('renders a 404 and stores a null-value entry in Redis', async () => {
339
- const res = await fetch(INSTANCE_A_URL + '/isr/not-found');
340
- expect(res.status).toBe(404);
341
-
342
- await delay(REDIS_BACKGROUND_SYNC_DELAY);
343
-
344
- const value = (await redisClient.get(
345
- process.env.VERCEL_URL + '/isr/not-found',
346
- )) as string;
347
- expect(value).toBeDefined();
348
- const cacheEntry: CacheEntry = JSON.parse(value);
349
- expect(cacheEntry).toEqual({
350
- value: null,
351
- lastModified: expect.any(Number),
352
- tags: ['_N_T_/isr/not-found'],
353
- });
354
-
355
- // TTL: Next.js 16.3+ keys on cacheControl.expire (SWR-safe); older
356
- // versions use estimateExpireAge(revalidate).
357
- const ttl = await redisClient.ttl(
358
- process.env.VERCEL_URL + '/isr/not-found',
359
- );
360
- expectIsrRedisTtl(ttl);
361
- });
362
-
363
- it('instance B serves the 404 from the shared cache', async () => {
364
- const res = await fetch(INSTANCE_B_URL + '/isr/not-found');
365
- expect(res.status).toBe(404);
366
- });
367
- });
368
-
369
- describe('redirect (getStaticProps redirect return)', () => {
370
- it('responds with a redirect and stores a REDIRECT entry in Redis', async () => {
371
- const res = await fetch(INSTANCE_A_URL + '/isr/redirect', {
372
- redirect: 'manual',
373
- });
374
- expect(res.status).toBe(307);
375
- expect(res.headers.get('location')).toContain('/static-forever');
376
-
377
- await delay(REDIS_BACKGROUND_SYNC_DELAY);
378
-
379
- const value = (await redisClient.get(
380
- process.env.VERCEL_URL + '/isr/redirect',
381
- )) as string;
382
- expect(value).toBeDefined();
383
- const cacheEntry: CacheEntry = JSON.parse(value);
384
- expect(cacheEntry).toMatchObject({
385
- value: {
386
- kind: 'REDIRECT',
387
- props: expect.objectContaining({
388
- pageProps: expect.objectContaining({
389
- __N_REDIRECT: '/static-forever',
390
- __N_REDIRECT_STATUS: 307,
391
- }),
392
- }),
393
- },
394
- lastModified: expect.any(Number),
395
- tags: ['_N_T_/isr/redirect'],
396
- });
397
- });
398
-
399
- it('instance B serves the redirect from the shared cache', async () => {
400
- const res = await fetch(INSTANCE_B_URL + '/isr/redirect', {
401
- redirect: 'manual',
402
- });
403
- expect(res.status).toBe(307);
404
- expect(res.headers.get('location')).toContain('/static-forever');
405
- });
406
- });
407
-
408
- describe('revalidate: false (fully static page)', () => {
409
- it('falls back to the defaultStaleAge based TTL', async () => {
410
- const res = await fetch(INSTANCE_A_URL + '/static-forever');
411
- expect(res.status).toBe(200);
412
-
413
- await delay(REDIS_BACKGROUND_SYNC_DELAY);
414
-
415
- const value = (await redisClient.get(
416
- process.env.VERCEL_URL + '/static-forever',
417
- )) as string;
418
- expect(value).toBeDefined();
419
- const cacheEntry: CacheEntry = JSON.parse(value);
420
- expect((cacheEntry.value as { kind: string }).kind).toBe('PAGES');
421
-
422
- const ttl = await redisClient.ttl(
423
- process.env.VERCEL_URL + '/static-forever',
424
- );
425
- // VERCEL_ENV=production -> expire age is 2 * defaultStaleAge (14 days)
426
- expect(ttl).toBeGreaterThan(2 * DEFAULT_STALE_AGE - 30);
427
- expect(ttl).toBeLessThanOrEqual(2 * DEFAULT_STALE_AGE);
428
- });
429
- });
430
- });
@@ -1,111 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { SyncedMap } from '../../../src/SyncedMap';
3
-
4
- class HashMockClient {
5
- hash = new Map<string, string>();
6
- stringKeys: string[] = [];
7
- hDelCalls: string[][] = [];
8
- isOpen = true;
9
- isReady = true;
10
-
11
- on() {
12
- return this;
13
- }
14
-
15
- async connect() {
16
- this.isOpen = true;
17
- this.isReady = true;
18
- }
19
-
20
- async configGet() {
21
- return { 'notify-keyspace-events': 'Exe' };
22
- }
23
-
24
- async subscribe() {}
25
-
26
- async quit() {}
27
-
28
- async disconnect() {
29
- this.isOpen = false;
30
- this.isReady = false;
31
- }
32
-
33
- duplicate() {
34
- return new HashMockClient();
35
- }
36
-
37
- async hScan() {
38
- return {
39
- cursor: 0,
40
- tuples: [...this.hash.entries()].map(([field, value]) => ({
41
- field,
42
- value,
43
- })),
44
- };
45
- }
46
-
47
- async scan() {
48
- return { cursor: 0, keys: this.stringKeys };
49
- }
50
-
51
- async hSet(_key: string, field: string, value: string) {
52
- this.hash.set(field, value);
53
- return 1;
54
- }
55
-
56
- async hDel(_key: string, fields: string | string[]) {
57
- const list = Array.isArray(fields) ? fields : [fields];
58
- this.hDelCalls.push(list);
59
- for (const field of list) {
60
- this.hash.delete(field);
61
- }
62
- return list.length;
63
- }
64
-
65
- async publish() {
66
- return 1;
67
- }
68
- }
69
-
70
- describe('SyncedMap orphan cleanup', () => {
71
- it('deletes hash fields whose names are not Redis string keys', async () => {
72
- const client = new HashMockClient();
73
- const map = new SyncedMap<number>({
74
- client: client as never,
75
- keyPrefix: 'app:',
76
- redisKey: '__revalidated_tags__',
77
- database: 0,
78
- querySize: 50,
79
- filterKeys: () => true,
80
- });
81
- await map.waitUntilReady();
82
- await map.set('posts', 1);
83
-
84
- client.stringKeys = ['app:some-cache-entry'];
85
- await (map as any).initialSync();
86
-
87
- expect(client.hDelCalls.flat()).toContain('posts');
88
- expect(map.get('posts')).toBeUndefined();
89
- });
90
-
91
- it('keeps tag-manifest fields when withoutOrphanCleanup is set', async () => {
92
- const client = new HashMockClient();
93
- const map = new SyncedMap<number>({
94
- client: client as never,
95
- keyPrefix: 'app:',
96
- redisKey: '__cacheComponents_revalidated_tags__',
97
- database: 0,
98
- querySize: 50,
99
- filterKeys: () => true,
100
- customizedSync: { withoutOrphanCleanup: true },
101
- });
102
- await map.waitUntilReady();
103
- await map.set('posts', 1);
104
-
105
- client.stringKeys = ['app:some-cache-entry'];
106
- await (map as any).initialSync();
107
-
108
- expect(client.hDelCalls).toEqual([]);
109
- expect(map.get('posts')).toBe(1);
110
- });
111
- });
@@ -1,154 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { SyncedMap } from '../../../src/SyncedMap';
3
-
4
- // Module-level knobs for the subscribe() mock so that behaviour persists
5
- // across the duplicate() clients created inside SyncedMap.
6
- let globalSubscribeCalls = 0;
7
- let globalSubscribeFailUntil = 0;
8
-
9
- class MockClient {
10
- static nextId = 1;
11
- id = MockClient.nextId++;
12
- private errorHandlers: Set<(err: Error) => void | Promise<void>> = new Set();
13
- quitShouldThrow = true;
14
-
15
- isOpen = true;
16
- isReady = true;
17
-
18
- on(event: string, handler: (err: Error) => void | Promise<void>) {
19
- if (event === 'error') this.errorHandlers.add(handler);
20
- return this as any;
21
- }
22
-
23
- emit(event: string, err: Error) {
24
- if (event === 'error') {
25
- for (const h of this.errorHandlers) {
26
- void h(err);
27
- }
28
- }
29
- return true;
30
- }
31
-
32
- removeAllListeners(event?: string) {
33
- if (!event || event === 'error') this.errorHandlers.clear();
34
- return this as any;
35
- }
36
-
37
- async connect() {
38
- this.isOpen = true;
39
- this.isReady = true;
40
- }
41
-
42
- async configGet() {
43
- return { 'notify-keyspace-events': 'Exe' };
44
- }
45
-
46
- async subscribe() {
47
- const attempt = globalSubscribeCalls++;
48
- if (attempt < globalSubscribeFailUntil) {
49
- throw new Error(`Mock subscribe failure ${attempt}`);
50
- }
51
- }
52
-
53
- async quit() {
54
- if (this.quitShouldThrow)
55
- throw new Error('Mock quit failure (disconnected)');
56
- }
57
-
58
- async disconnect() {
59
- this.isOpen = false;
60
- this.isReady = false;
61
- this.errorHandlers.clear();
62
- }
63
-
64
- duplicate() {
65
- return new MockClient();
66
- }
67
-
68
- hScan() {
69
- return { cursor: 0, tuples: [] };
70
- }
71
-
72
- scan() {
73
- return { cursor: 0, keys: [] };
74
- }
75
- }
76
-
77
- function createMap(rootClient = new MockClient()) {
78
- return new SyncedMap<string>({
79
- client: rootClient as any,
80
- keyPrefix: 'repro:',
81
- redisKey: '__sharedTags__',
82
- database: 0,
83
- querySize: 250,
84
- filterKeys: () => true,
85
- customizedSync: {
86
- withoutRedisHashmap: true,
87
- withoutSetSync: true,
88
- },
89
- });
90
- }
91
-
92
- describe('SyncedMap.reconnectSubscriber() repro tests', () => {
93
- let originalSetTimeout: typeof global.setTimeout;
94
-
95
- beforeEach(() => {
96
- globalSubscribeCalls = 0;
97
- globalSubscribeFailUntil = 0;
98
- originalSetTimeout = global.setTimeout;
99
- });
100
-
101
- afterEach(() => {
102
- global.setTimeout = originalSetTimeout;
103
- });
104
-
105
- it('Issue 2: first backoff delay should be 500ms, not 1000ms', async () => {
106
- const root = new MockClient();
107
- const map = createMap(root);
108
- // wait for initial setup to succeed (attempt 0)
109
- await (map as any).waitUntilReady().catch(() => undefined);
110
-
111
- const capturedDelays: number[] = [];
112
- global.setTimeout = ((callback: () => void, delay?: number) => {
113
- capturedDelays.push(Number(delay ?? 0));
114
- return originalSetTimeout(callback, 0) as any;
115
- }) as any;
116
-
117
- // initial subscribe consumed attempt 0; force the next 3 reconnection
118
- // attempts to fail, then succeed on the 4th
119
- globalSubscribeFailUntil = 4;
120
-
121
- await (map as any).reconnectSubscriber();
122
-
123
- expect(capturedDelays.length).toBeGreaterThanOrEqual(1);
124
- // Bug: delay is computed with attempt already incremented, so the first
125
- // retry waits 500 * 2^1 = 1000 ms. After the fix it should be 500 ms.
126
- expect(capturedDelays[0]).toBe(500);
127
- });
128
-
129
- it('Issue 1: stale error on old subscriber must not kill the healthy new subscriber', async () => {
130
- const root = new MockClient();
131
- const map = createMap(root);
132
- await (map as any).waitUntilReady().catch(() => undefined);
133
-
134
- const oldSubscriber = (map as any).subscriberClient as MockClient;
135
- expect(oldSubscriber).toBeDefined();
136
-
137
- await (map as any).reconnectSubscriber();
138
-
139
- const newSubscriber = (map as any).subscriberClient as MockClient;
140
- expect(newSubscriber.id).not.toBe(oldSubscriber.id);
141
-
142
- // Simulate a delayed error from the old (now replaced) subscriber.
143
- // With the current code the old 'error' listener is still attached,
144
- // so it triggers reconnectSubscriber() and tears down the healthy client.
145
- oldSubscriber.emit('error', new Error('delayed zombie error'));
146
-
147
- // Give the async error handler a chance to run.
148
- await new Promise((r) => originalSetTimeout(r, 50));
149
-
150
- const afterStaleError = (map as any).subscriberClient as MockClient;
151
-
152
- expect(afterStaleError.id).toBe(newSubscriber.id);
153
- });
154
- });