@trieb.work/nextjs-turbo-redis-cache 1.17.0 → 1.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/README.md +87 -24
  2. package/dist/index.d.mts +40 -1
  3. package/dist/index.d.ts +40 -1
  4. package/dist/index.js +291 -116
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +288 -115
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +9 -6
  9. package/.cursor/skills/testing/SKILL.md +0 -121
  10. package/.github/workflows/ci.yml +0 -309
  11. package/.github/workflows/pages.yml +0 -38
  12. package/.github/workflows/release.yml +0 -207
  13. package/.prettierrc.json +0 -6
  14. package/.vscode/settings.json +0 -10
  15. package/ARCHITECTURE.md +0 -431
  16. package/CHANGELOG.md +0 -411
  17. package/commitlint.config.cjs +0 -4
  18. package/docker/redis.conf +0 -1
  19. package/docs/index.html +0 -140
  20. package/docs/robots.txt +0 -4
  21. package/docs/sitemap.xml +0 -8
  22. package/docs/styles.css +0 -141
  23. package/eslint.config.mjs +0 -26
  24. package/hooks/commit-msg +0 -10
  25. package/hooks/pre-commit +0 -11
  26. package/playwright.config.ts +0 -18
  27. package/release.config.cjs +0 -31
  28. package/scripts/prepare.sh +0 -7
  29. package/scripts/setup-git.sh +0 -8
  30. package/scripts/setup-repo-name.sh +0 -17
  31. package/scripts/vitest-run-staged.cjs +0 -44
  32. package/src/CacheComponentsHandler.ts +0 -567
  33. package/src/CachedHandler.ts +0 -53
  34. package/src/DeduplicatedRequestHandler.ts +0 -110
  35. package/src/RedisStringsHandler.ts +0 -937
  36. package/src/SyncedMap.ts +0 -482
  37. package/src/ZodHandler.ts +0 -45
  38. package/src/index.ts +0 -16
  39. package/src/serializer.ts +0 -59
  40. package/src/utils/debug.ts +0 -30
  41. package/src/utils/json.ts +0 -49
  42. package/src/utils/prefix.ts +0 -44
  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 -13
  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/revalidate-tag/route.ts +0 -19
  122. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/revalidated-fetch/route.ts +0 -19
  123. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/cachelife-short/page.tsx +0 -110
  124. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/page.tsx +0 -90
  125. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/runtime-data-suspense/page.tsx +0 -127
  126. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/stale-while-revalidate/page.tsx +0 -130
  127. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/tag-invalidation/page.tsx +0 -127
  128. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/cache-lab/use-cache-nondeterministic/page.tsx +0 -110
  129. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/favicon.ico +0 -0
  130. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/globals.css +0 -26
  131. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/layout.tsx +0 -57
  132. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/page.tsx +0 -755
  133. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/revalidation-interface.tsx +0 -267
  134. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/update-tag-test/page.tsx +0 -22
  135. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/tsconfig.json +0 -34
  136. package/test/nextjs-test-projects/next-app-16-2-6/README.md +0 -36
  137. package/test/nextjs-test-projects/next-app-16-2-6/eslint.config.mjs +0 -18
  138. package/test/nextjs-test-projects/next-app-16-2-6/next.config.ts +0 -7
  139. package/test/nextjs-test-projects/next-app-16-2-6/package.json +0 -28
  140. package/test/nextjs-test-projects/next-app-16-2-6/pnpm-lock.yaml +0 -4165
  141. package/test/nextjs-test-projects/next-app-16-2-6/postcss.config.mjs +0 -7
  142. package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/cached-static-fetch/route.ts +0 -18
  143. package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
  144. package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/revalidatePath/route.ts +0 -15
  145. package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/revalidateTag/route.ts +0 -20
  146. package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/revalidated-fetch/route.ts +0 -17
  147. package/test/nextjs-test-projects/next-app-16-2-6/src/app/api/uncached-fetch/route.ts +0 -15
  148. package/test/nextjs-test-projects/next-app-16-2-6/src/app/favicon.ico +0 -0
  149. package/test/nextjs-test-projects/next-app-16-2-6/src/app/globals.css +0 -26
  150. package/test/nextjs-test-projects/next-app-16-2-6/src/app/layout.tsx +0 -59
  151. package/test/nextjs-test-projects/next-app-16-2-6/src/app/page.tsx +0 -755
  152. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
  153. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
  154. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  155. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/no-fetch/default-page/page.tsx +0 -55
  156. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
  157. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
  158. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  159. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
  160. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
  161. package/test/nextjs-test-projects/next-app-16-2-6/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  162. package/test/nextjs-test-projects/next-app-16-2-6/src/app/revalidation-interface.tsx +0 -267
  163. package/test/nextjs-test-projects/next-app-16-2-6/src/app/update-tag-test/page.tsx +0 -25
  164. package/test/nextjs-test-projects/next-app-16-2-6/tsconfig.json +0 -34
  165. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/README.md +0 -36
  166. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/cache-handler.js +0 -3
  167. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/eslint.config.mjs +0 -18
  168. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/next.config.ts +0 -13
  169. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/package.json +0 -28
  170. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/pnpm-lock.yaml +0 -4165
  171. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/postcss.config.mjs +0 -7
  172. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/file.svg +0 -1
  173. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/globe.svg +0 -1
  174. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/next.svg +0 -1
  175. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/vercel.svg +0 -1
  176. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/public/window.svg +0 -1
  177. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-static-fetch/route.ts +0 -19
  178. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-with-cachelife/route.ts +0 -24
  179. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-with-tag/route.ts +0 -21
  180. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/revalidate-tag/route.ts +0 -19
  181. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/revalidated-fetch/route.ts +0 -19
  182. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/cachelife-short/page.tsx +0 -110
  183. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/page.tsx +0 -90
  184. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/runtime-data-suspense/page.tsx +0 -127
  185. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/stale-while-revalidate/page.tsx +0 -130
  186. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/tag-invalidation/page.tsx +0 -127
  187. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/use-cache-nondeterministic/page.tsx +0 -110
  188. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/favicon.ico +0 -0
  189. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/globals.css +0 -26
  190. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/layout.tsx +0 -57
  191. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/page.tsx +0 -755
  192. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/revalidation-interface.tsx +0 -267
  193. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/update-tag-test/page.tsx +0 -22
  194. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/tsconfig.json +0 -34
  195. package/test/nextjs-test-projects/next-app-16-3-0/README.md +0 -36
  196. package/test/nextjs-test-projects/next-app-16-3-0/eslint.config.mjs +0 -18
  197. package/test/nextjs-test-projects/next-app-16-3-0/next.config.ts +0 -7
  198. package/test/nextjs-test-projects/next-app-16-3-0/package.json +0 -28
  199. package/test/nextjs-test-projects/next-app-16-3-0/pnpm-lock.yaml +0 -4284
  200. package/test/nextjs-test-projects/next-app-16-3-0/postcss.config.mjs +0 -7
  201. package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/cached-static-fetch/route.ts +0 -18
  202. package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
  203. package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/revalidatePath/route.ts +0 -15
  204. package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/revalidateTag/route.ts +0 -20
  205. package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/revalidated-fetch/route.ts +0 -17
  206. package/test/nextjs-test-projects/next-app-16-3-0/src/app/api/uncached-fetch/route.ts +0 -15
  207. package/test/nextjs-test-projects/next-app-16-3-0/src/app/favicon.ico +0 -0
  208. package/test/nextjs-test-projects/next-app-16-3-0/src/app/globals.css +0 -26
  209. package/test/nextjs-test-projects/next-app-16-3-0/src/app/layout.tsx +0 -59
  210. package/test/nextjs-test-projects/next-app-16-3-0/src/app/page.tsx +0 -755
  211. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
  212. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
  213. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  214. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/no-fetch/default-page/page.tsx +0 -55
  215. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
  216. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
  217. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  218. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
  219. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
  220. package/test/nextjs-test-projects/next-app-16-3-0/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  221. package/test/nextjs-test-projects/next-app-16-3-0/src/app/revalidation-interface.tsx +0 -267
  222. package/test/nextjs-test-projects/next-app-16-3-0/src/app/update-tag-test/page.tsx +0 -25
  223. package/test/nextjs-test-projects/next-app-16-3-0/tsconfig.json +0 -34
  224. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/README.md +0 -36
  225. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/cache-handler.js +0 -3
  226. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/eslint.config.mjs +0 -18
  227. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/next.config.ts +0 -13
  228. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/package.json +0 -28
  229. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/pnpm-lock.yaml +0 -4284
  230. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/postcss.config.mjs +0 -7
  231. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/file.svg +0 -1
  232. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/globe.svg +0 -1
  233. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/next.svg +0 -1
  234. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/vercel.svg +0 -1
  235. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/public/window.svg +0 -1
  236. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/cached-static-fetch/route.ts +0 -19
  237. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/cached-with-cachelife/route.ts +0 -24
  238. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/cached-with-tag/route.ts +0 -21
  239. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/revalidate-tag/route.ts +0 -19
  240. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/api/revalidated-fetch/route.ts +0 -19
  241. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/cachelife-short/page.tsx +0 -110
  242. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/page.tsx +0 -90
  243. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/runtime-data-suspense/page.tsx +0 -127
  244. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/stale-while-revalidate/page.tsx +0 -130
  245. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/tag-invalidation/page.tsx +0 -127
  246. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/cache-lab/use-cache-nondeterministic/page.tsx +0 -110
  247. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/favicon.ico +0 -0
  248. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/globals.css +0 -26
  249. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/layout.tsx +0 -57
  250. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/page.tsx +0 -755
  251. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/revalidation-interface.tsx +0 -267
  252. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/src/app/update-tag-test/page.tsx +0 -22
  253. package/test/nextjs-test-projects/next-app-16-3-0-cache-components/tsconfig.json +0 -34
  254. package/test/nextjs-test-projects/next-app-customized/README.md +0 -36
  255. package/test/nextjs-test-projects/next-app-customized/customized-cache-handler.js +0 -34
  256. package/test/nextjs-test-projects/next-app-customized/eslint.config.mjs +0 -16
  257. package/test/nextjs-test-projects/next-app-customized/next.config.js +0 -6
  258. package/test/nextjs-test-projects/next-pages-16-2-6/README.md +0 -16
  259. package/test/nextjs-test-projects/next-pages-16-2-6/eslint.config.mjs +0 -18
  260. package/test/nextjs-test-projects/next-pages-16-2-6/next.config.ts +0 -7
  261. package/test/nextjs-test-projects/next-pages-16-2-6/package.json +0 -26
  262. package/test/nextjs-test-projects/next-pages-16-2-6/pnpm-lock.yaml +0 -3896
  263. package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/api/revalidate.ts +0 -24
  264. package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/index.tsx +0 -11
  265. package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/isr/[slug].tsx +0 -49
  266. package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/static-forever.tsx +0 -20
  267. package/test/nextjs-test-projects/next-pages-16-2-6/tsconfig.json +0 -34
  268. package/test/nextjs-test-projects/next-pages-16-3-0/README.md +0 -16
  269. package/test/nextjs-test-projects/next-pages-16-3-0/eslint.config.mjs +0 -18
  270. package/test/nextjs-test-projects/next-pages-16-3-0/next.config.ts +0 -7
  271. package/test/nextjs-test-projects/next-pages-16-3-0/package.json +0 -26
  272. package/test/nextjs-test-projects/next-pages-16-3-0/pnpm-lock.yaml +0 -3939
  273. package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/api/revalidate.ts +0 -24
  274. package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/index.tsx +0 -11
  275. package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/isr/[slug].tsx +0 -49
  276. package/test/nextjs-test-projects/next-pages-16-3-0/src/pages/static-forever.tsx +0 -20
  277. package/test/nextjs-test-projects/next-pages-16-3-0/tsconfig.json +0 -34
  278. package/test/playwright/cache-lab.spec.ts +0 -157
  279. package/test/playwright/update-tag.spec.ts +0 -33
  280. package/test/vitest/integration/build-id-prefix.integration.test.ts +0 -102
  281. package/test/vitest/integration/cache-components/cache-components.integration.test.ts +0 -231
  282. package/test/vitest/integration/cache-components/redis-kill-reconnect.test.ts +0 -65
  283. package/test/vitest/integration/cache-components/redis-quit-vs-disconnect.test.ts +0 -59
  284. package/test/vitest/integration/cache-components/redis-subscriber-outage.test.ts +0 -100
  285. package/test/vitest/integration/cache-components/scripts/redis-kill-reconnect.ts +0 -243
  286. package/test/vitest/integration/cache-components/scripts/redis-quit-vs-disconnect.ts +0 -113
  287. package/test/vitest/integration/cache-components/scripts/redis-subscriber-outage.ts +0 -379
  288. package/test/vitest/integration/cache-components/scripts/redis-test-helpers.ts +0 -155
  289. package/test/vitest/integration/nextjs-cache-handler.integration.test.ts +0 -945
  290. package/test/vitest/integration/pages-router.integration.test.ts +0 -420
  291. package/test/vitest/unit/SyncedMap-reconnect-repro.test.ts +0 -154
  292. package/test/vitest/unit/index.test.ts +0 -173
  293. package/test/vitest/unit/pages-router-kinds.test.ts +0 -292
  294. package/test/vitest/unit/reconnect-socket-already-opened.test.ts +0 -117
  295. package/test/vitest/unit/serializer.test.ts +0 -182
  296. package/test/vitest/unit/utils/prefix.test.ts +0 -115
  297. package/tsconfig.json +0 -18
  298. package/tsup.config.ts +0 -14
  299. package/vite.config.ts +0 -34
  300. package/vitest.cache-components.config.ts +0 -16
@@ -1,24 +0,0 @@
1
- import type { NextApiRequest, NextApiResponse } from 'next';
2
-
3
- // On-demand revalidation endpoint: res.revalidate(path) re-renders the page
4
- // and writes the fresh entry through the cache handler, so all instances
5
- // sharing the same Redis pick it up.
6
- export default async function handler(
7
- req: NextApiRequest,
8
- res: NextApiResponse,
9
- ) {
10
- const path = req.query.path;
11
- if (typeof path !== 'string') {
12
- return res
13
- .status(400)
14
- .json({ revalidated: false, error: 'Missing ?path= query parameter' });
15
- }
16
- try {
17
- await res.revalidate(path);
18
- return res.json({ revalidated: true, path });
19
- } catch (err) {
20
- return res
21
- .status(500)
22
- .json({ revalidated: false, path, error: String(err) });
23
- }
24
- }
@@ -1,11 +0,0 @@
1
- export default function Home() {
2
- return (
3
- <main>
4
- <h1>Pages Router test app</h1>
5
- <p>
6
- Fixture for testing the Redis cache handler with the Next.js Pages
7
- Router (PAGES, REDIRECT and notFound cache entries).
8
- </p>
9
- </main>
10
- );
11
- }
@@ -1,49 +0,0 @@
1
- import type { GetStaticPaths, GetStaticProps } from 'next';
2
-
3
- type Props = { slug: string; timestamp: number; counter: number };
4
-
5
- // Per-process regeneration counter. Two server instances of the same build
6
- // have independent counters, while the timestamp proves cross-instance
7
- // freshness after on-demand revalidation.
8
- let regenerationCounter = 0;
9
-
10
- export default function IsrPage({ slug, timestamp, counter }: Props) {
11
- return (
12
- <main>
13
- <h1>ISR page</h1>
14
- <p>Slug: {slug}</p>
15
- <p>Timestamp: {timestamp}</p>
16
- <p>Counter: {counter}</p>
17
- </main>
18
- );
19
- }
20
-
21
- export const getStaticPaths: GetStaticPaths = async () => {
22
- return {
23
- paths: [{ params: { slug: 'prebuilt' } }],
24
- fallback: 'blocking',
25
- };
26
- };
27
-
28
- // revalidate is set high (300s) so that natural ISR regeneration does not
29
- // interfere with the on-demand revalidation (res.revalidate) tests.
30
- export const getStaticProps: GetStaticProps<Props> = async (context) => {
31
- const slug = context.params?.slug as string;
32
-
33
- if (slug === 'not-found') {
34
- return { notFound: true, revalidate: 300 };
35
- }
36
-
37
- if (slug === 'redirect') {
38
- return {
39
- redirect: { destination: '/static-forever', permanent: false },
40
- revalidate: 300,
41
- };
42
- }
43
-
44
- regenerationCounter += 1;
45
- return {
46
- props: { slug, timestamp: Date.now(), counter: regenerationCounter },
47
- revalidate: 300,
48
- };
49
- };
@@ -1,20 +0,0 @@
1
- import type { GetStaticProps } from 'next';
2
-
3
- type Props = { timestamp: number };
4
-
5
- export default function StaticForever({ timestamp }: Props) {
6
- return (
7
- <main>
8
- <h1>Static forever</h1>
9
- <p>Timestamp: {timestamp}</p>
10
- </main>
11
- );
12
- }
13
-
14
- // revalidate is intentionally omitted (revalidate: false): the cache entry
15
- // must fall through to the defaultStaleAge based TTL in the cache handler.
16
- export const getStaticProps: GetStaticProps<Props> = async () => {
17
- return {
18
- props: { timestamp: Date.now() },
19
- };
20
- };
@@ -1,34 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2017",
4
- "lib": ["dom", "dom.iterable", "esnext"],
5
- "allowJs": true,
6
- "skipLibCheck": true,
7
- "strict": true,
8
- "noEmit": true,
9
- "esModuleInterop": true,
10
- "module": "esnext",
11
- "moduleResolution": "bundler",
12
- "resolveJsonModule": true,
13
- "isolatedModules": true,
14
- "jsx": "react-jsx",
15
- "incremental": true,
16
- "plugins": [
17
- {
18
- "name": "next"
19
- }
20
- ],
21
- "paths": {
22
- "@/*": ["./src/*"]
23
- }
24
- },
25
- "include": [
26
- "next-env.d.ts",
27
- "**/*.ts",
28
- "**/*.tsx",
29
- ".next/types/**/*.ts",
30
- ".next/dev/types/**/*.ts",
31
- "**/*.mts"
32
- ],
33
- "exclude": ["node_modules"]
34
- }
@@ -1,157 +0,0 @@
1
- import { test, expect } from '@playwright/test';
2
-
3
- // These tests assume the Cache Components Next.js app is already running on the same origin as baseURL.
4
- // Recommended:
5
- // PLAYWRIGHT_BASE_URL=http://localhost:3001 pnpm test:e2e
6
-
7
- test.describe('Cache Lab (Cache Components)', () => {
8
- test('use cache non-deterministic values are stable until updateTag', async ({
9
- page,
10
- }) => {
11
- await page.goto('/cache-lab/use-cache-nondeterministic');
12
-
13
- const random1 = page.getByTestId('random1');
14
- const now = page.getByTestId('now');
15
-
16
- await expect(random1).toBeVisible();
17
-
18
- const beforeRandom = await random1.textContent();
19
- const beforeNow = await now.textContent();
20
-
21
- await page.reload();
22
- await expect(random1).toBeVisible();
23
-
24
- const afterReloadRandom = await random1.textContent();
25
- const afterReloadNow = await now.textContent();
26
-
27
- expect(afterReloadRandom).toBe(beforeRandom);
28
- expect(afterReloadNow).toBe(beforeNow);
29
-
30
- await page
31
- .getByRole('button', { name: "updateTag('cache-lab:nondet')" })
32
- .click();
33
- await page.waitForLoadState('networkidle');
34
-
35
- await expect(random1).toBeVisible();
36
-
37
- const afterInvalidateRandom = await random1.textContent();
38
- const afterInvalidateNow = await now.textContent();
39
-
40
- expect(afterInvalidateRandom).not.toBe(beforeRandom);
41
- expect(afterInvalidateNow).not.toBe(beforeNow);
42
- });
43
-
44
- test('tag invalidation changes cached values (updateTag + revalidateTag)', async ({
45
- page,
46
- }) => {
47
- await page.goto('/cache-lab/tag-invalidation');
48
-
49
- const createdAt = page.getByTestId('createdAt');
50
- const value = page.getByTestId('value');
51
-
52
- await expect(createdAt).toBeVisible();
53
-
54
- const beforeCreatedAt = await createdAt.textContent();
55
- const beforeValue = await value.textContent();
56
-
57
- await page.reload();
58
- await expect(createdAt).toBeVisible();
59
- expect(await createdAt.textContent()).toBe(beforeCreatedAt);
60
- expect(await value.textContent()).toBe(beforeValue);
61
-
62
- await page
63
- .getByRole('button', { name: "updateTag('cache-lab:tag')" })
64
- .click();
65
- await page.waitForLoadState('networkidle');
66
-
67
- await expect(createdAt).toBeVisible();
68
- expect(await createdAt.textContent()).not.toBe(beforeCreatedAt);
69
- expect(await value.textContent()).not.toBe(beforeValue);
70
-
71
- const afterUpdateCreatedAt = await createdAt.textContent();
72
- const afterUpdateValue = await value.textContent();
73
-
74
- await page
75
- .getByRole('button', { name: "revalidateTag('cache-lab:tag')" })
76
- .click();
77
- await page.waitForLoadState('networkidle');
78
-
79
- await expect(createdAt).toBeVisible();
80
-
81
- const maybeStaleCreatedAt = await createdAt.textContent();
82
- const maybeStaleValue = await value.textContent();
83
-
84
- // Revalidate can be SWR; allow either immediate refresh or stale served,
85
- // but the value should eventually change on subsequent reloads.
86
- if (maybeStaleCreatedAt === afterUpdateCreatedAt) {
87
- // give background refresh some time
88
- await page.waitForTimeout(4000);
89
- await page.reload();
90
- await expect(createdAt).toBeVisible();
91
- }
92
-
93
- expect(await createdAt.textContent()).not.toBe(afterUpdateCreatedAt);
94
- expect(await value.textContent()).not.toBe(afterUpdateValue);
95
- });
96
-
97
- test('runtime cookie changes cache key (cached payload differs per cookie)', async ({
98
- page,
99
- }) => {
100
- await page.goto('/cache-lab/runtime-data-suspense');
101
-
102
- const cookieValue = page.getByTestId('cookie');
103
- const payload = page.getByTestId('payload');
104
-
105
- await expect(cookieValue).toBeVisible();
106
-
107
- await page.getByRole('button', { name: 'Set cookie: user-a' }).click();
108
- await page.waitForLoadState('networkidle');
109
-
110
- await expect(cookieValue).toHaveText('user-a');
111
- const payloadA1 = await payload.textContent();
112
-
113
- await page.reload();
114
- await expect(cookieValue).toHaveText('user-a');
115
- const payloadA2 = await payload.textContent();
116
- expect(payloadA2).toBe(payloadA1);
117
-
118
- await page.getByRole('button', { name: 'Set cookie: user-b' }).click();
119
- await page.waitForLoadState('networkidle');
120
-
121
- await expect(cookieValue).toHaveText('user-b');
122
- const payloadB = await payload.textContent();
123
-
124
- expect(payloadB).not.toBe(payloadA1);
125
- });
126
-
127
- test('stale-while-revalidate demo eventually produces a new value after revalidateTag', async ({
128
- page,
129
- }) => {
130
- await page.goto('/cache-lab/stale-while-revalidate');
131
-
132
- const computedAt = page.getByTestId('computedAt');
133
- const value = page.getByTestId('value');
134
-
135
- await expect(computedAt).toBeVisible();
136
-
137
- const beforeComputedAt = await computedAt.textContent();
138
- const beforeValue = await value.textContent();
139
-
140
- // Ensure it becomes stale.
141
- await page.waitForTimeout(3000);
142
-
143
- await page
144
- .getByRole('button', { name: "revalidateTag('cache-lab:swr')" })
145
- .click();
146
- await page.waitForLoadState('networkidle');
147
-
148
- // Allow some time for background refresh to potentially complete.
149
- await page.waitForTimeout(4500);
150
-
151
- await page.reload();
152
- await expect(computedAt).toBeVisible();
153
-
154
- expect(await computedAt.textContent()).not.toBe(beforeComputedAt);
155
- expect(await value.textContent()).not.toBe(beforeValue);
156
- });
157
- });
@@ -1,33 +0,0 @@
1
- import { test, expect } from '@playwright/test';
2
-
3
- // This test assumes the Next 16 app is already running, e.g.:
4
- // cd test/nextjs-test-projects/next-app-16-0-11 && pnpm dev
5
- // on the same origin as baseURL.
6
-
7
- test('button click triggers Server Action using updateTag', async ({
8
- page,
9
- }) => {
10
- await page.goto('/update-tag-test');
11
-
12
- await expect(page.getByText('UpdateTag Test')).toBeVisible();
13
-
14
- const clicks = page.getByTestId('clicks');
15
- const before = await clicks.textContent();
16
-
17
- await page.getByRole('button', { name: 'Increment' }).click();
18
-
19
- // Wait for network and any navigation caused by the Server Action
20
- await page.waitForLoadState('networkidle');
21
-
22
- const after = await clicks.textContent();
23
-
24
- // Server Action should have run and updated the UI
25
- expect(after).not.toBe(before);
26
-
27
- // Ensure there is no error message about updateTag usage
28
- const errorLocator = page.getByText(
29
- 'updateTag can only be called from within a Server Action',
30
- { exact: false },
31
- );
32
- await expect(errorLocator).toHaveCount(0);
33
- });
@@ -1,102 +0,0 @@
1
- import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
- import { spawn, ChildProcessWithoutNullStreams } from 'child_process';
3
- import { createClient, RedisClientType } from 'redis';
4
- import path from 'path';
5
- import fs from 'fs';
6
- import fetch from 'node-fetch';
7
-
8
- const NEXT_APP = 'next-app-15-4-11';
9
- const PORT = 3075;
10
- const BASE_URL = `http://localhost:${PORT}`;
11
-
12
- async function waitForServer(url: string, timeout = 20000) {
13
- const start = Date.now();
14
- while (Date.now() - start < timeout) {
15
- try {
16
- const res = await fetch(url + '/api/cached-static-fetch');
17
- if (res.ok) return;
18
- } catch {}
19
- await new Promise((r) => setTimeout(r, 300));
20
- }
21
- throw new Error('Next.js server did not start in time');
22
- }
23
-
24
- describe('BUILD_ID-based key prefix (Next handlers)', () => {
25
- let nextProcess: ChildProcessWithoutNullStreams | undefined;
26
- let redis: RedisClientType;
27
- let appDir: string;
28
- let buildId: string;
29
-
30
- beforeAll(async () => {
31
- appDir = path.join(__dirname, '..', '..', 'nextjs-test-projects', NEXT_APP);
32
-
33
- // Ensure clean env so BUILD_ID precedence kicks in
34
- delete (process.env as Record<string, string | undefined>).KEY_PREFIX;
35
- delete (process.env as Record<string, string | undefined>).VERCEL_URL;
36
- process.env.VERCEL_ENV = 'production';
37
- process.env.REDISHOST = process.env.REDISHOST || 'localhost';
38
- process.env.REDISPORT = process.env.REDISPORT || '6379';
39
-
40
- // Install + build
41
- await new Promise<void>((resolve, reject) => {
42
- const p = spawn('pnpm', ['install'], { cwd: appDir, stdio: 'inherit' });
43
- p.on('close', (code) =>
44
- code === 0 ? resolve() : reject(new Error('pnpm i failed')),
45
- );
46
- });
47
- await new Promise<void>((resolve, reject) => {
48
- const p = spawn('pnpm', ['build'], { cwd: appDir, stdio: 'inherit' });
49
- p.on('close', (code) =>
50
- code === 0 ? resolve() : reject(new Error('pnpm build failed')),
51
- );
52
- });
53
-
54
- // Read BUILD_ID from the built app
55
- buildId = fs
56
- .readFileSync(path.join(appDir, '.next', 'BUILD_ID'), 'utf8')
57
- .trim();
58
-
59
- // Start server without KEY_PREFIX/VERCEL_URL
60
- nextProcess = spawn('npx', ['next', 'start', '-p', String(PORT)], {
61
- cwd: appDir,
62
- env: { ...process.env, SKIP_KEYSPACE_CONFIG_CHECK: 'true' },
63
- stdio: 'pipe',
64
- });
65
-
66
- nextProcess.stderr?.on(
67
- 'data',
68
- (d) => process.env.DEBUG_INTEGRATION && console.error(String(d)),
69
- );
70
-
71
- await waitForServer(BASE_URL);
72
-
73
- // Connect Redis
74
- redis = createClient({
75
- url: `redis://${process.env.REDISHOST}:${process.env.REDISPORT}`,
76
- });
77
- await redis.connect();
78
- }, 180000);
79
-
80
- afterAll(async () => {
81
- try {
82
- if (redis) await redis.quit();
83
- } finally {
84
- if (nextProcess) nextProcess.kill();
85
- }
86
- });
87
-
88
- it('uses BUILD_ID as the Redis key prefix when no KEY_PREFIX/VERCEL_URL set', async () => {
89
- // Warm cache twice to ensure a set occurs even if first was during startup
90
- const res1 = await fetch(BASE_URL + '/api/cached-static-fetch');
91
- expect(res1.ok).toBe(true);
92
- await new Promise((r) => setTimeout(r, 500));
93
- const res2 = await fetch(BASE_URL + '/api/cached-static-fetch');
94
- expect(res2.ok).toBe(true);
95
-
96
- // Allow background syncs
97
- await new Promise((r) => setTimeout(r, 1500));
98
-
99
- const keys = await redis.keys(`${buildId}*`);
100
- expect(keys.length).toBeGreaterThan(0);
101
- });
102
- });
@@ -1,231 +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
- beforeAll(async () => {
19
- // Connect to Redis
20
- redisClient = createClient({
21
- url: process.env.REDIS_URL || 'redis://localhost:6379',
22
- database: 1,
23
- });
24
- await redisClient.connect();
25
-
26
- // Generate unique key prefix for this test run
27
- keyPrefix = `cache-components-test-${Math.random().toString(36).substring(7)}`;
28
- process.env.VERCEL_URL = keyPrefix;
29
-
30
- const cacheComponentsApp =
31
- process.env.CACHE_COMPONENTS_APP || 'next-app-16-2-6-cache-components';
32
-
33
- const appDir = path.join(
34
- __dirname,
35
- '..',
36
- '..',
37
- '..',
38
- 'nextjs-test-projects',
39
- cacheComponentsApp,
40
- );
41
-
42
- console.log('Installing Next.js app dependencies...');
43
- await new Promise<void>((resolve, reject) => {
44
- const installProcess = spawn('pnpm', ['install'], {
45
- cwd: appDir,
46
- stdio: 'inherit',
47
- });
48
-
49
- installProcess.on('close', (code) => {
50
- if (code === 0) resolve();
51
- else reject(new Error(`Install failed with code ${code}`));
52
- });
53
- });
54
-
55
- console.log('Building Next.js app...');
56
- await new Promise<void>((resolve, reject) => {
57
- const buildProcess = spawn('pnpm', ['build'], {
58
- cwd: appDir,
59
- stdio: 'inherit',
60
- });
61
-
62
- buildProcess.on('close', (code) => {
63
- if (code === 0) resolve();
64
- else reject(new Error(`Build failed with code ${code}`));
65
- });
66
- });
67
-
68
- console.log('Starting Next.js app...');
69
- nextProcess = spawn('pnpm', ['start', '-p', PORT.toString()], {
70
- cwd: appDir,
71
- env: { ...process.env, VERCEL_URL: keyPrefix },
72
- });
73
-
74
- // Wait for server to be ready
75
- await new Promise((resolve) => setTimeout(resolve, 3000));
76
- }, 120000);
77
-
78
- afterAll(async () => {
79
- // Clean up Redis keys
80
- const keys = await redisClient.keys(`${keyPrefix}*`);
81
- if (keys.length > 0) {
82
- await redisClient.del(keys);
83
- }
84
- await redisClient.quit();
85
-
86
- // Kill Next.js process
87
- if (nextProcess) {
88
- nextProcess.kill();
89
- }
90
- });
91
-
92
- describe('Basic use cache functionality', () => {
93
- it('should cache data and return same counter value on subsequent requests', async () => {
94
- // First request
95
- const res1 = await fetch(`${BASE_URL}/api/cached-static-fetch`);
96
- const data1 = await res1.json();
97
-
98
- expect(data1.counter).toBe(1);
99
-
100
- // Second request should return cached data
101
- const res2 = await fetch(`${BASE_URL}/api/cached-static-fetch`);
102
- const data2 = await res2.json();
103
-
104
- expect(data2.counter).toBe(1); // Same counter value
105
- expect(data2.timestamp).toBe(data1.timestamp); // Same timestamp
106
- });
107
-
108
- it('should store cache entry in Redis', async () => {
109
- await fetch(`${BASE_URL}/api/cached-static-fetch`);
110
-
111
- // Check Redis for cache keys
112
- const keys = await redisClient.keys(`${keyPrefix}*`);
113
- expect(keys.length).toBeGreaterThan(0);
114
- });
115
- });
116
-
117
- describe('cacheTag functionality', () => {
118
- it('should cache data with tags', async () => {
119
- const res1 = await fetch(`${BASE_URL}/api/cached-with-tag`);
120
- const data1 = await res1.json();
121
-
122
- expect(data1.counter).toBeDefined();
123
-
124
- // Second request should return cached data
125
- const res2 = await fetch(`${BASE_URL}/api/cached-with-tag`);
126
- const data2 = await res2.json();
127
-
128
- expect(data2.counter).toBe(data1.counter);
129
- });
130
-
131
- it('should invalidate cache when tag is revalidated (Stale while revalidate)', async () => {
132
- // Get initial cached data
133
- const res1 = await fetch(`${BASE_URL}/api/cached-with-tag`);
134
- const data1 = await res1.json();
135
-
136
- // Revalidate the tag
137
- await fetch(`${BASE_URL}/api/revalidate-tag`, {
138
- method: 'POST',
139
- headers: { 'Content-Type': 'application/json' },
140
- body: JSON.stringify({ tag: 'test-tag' }),
141
- });
142
-
143
- // The cache should be invalidated - verify by making multiple requests
144
- // until we get fresh data (with retries for async revalidation)
145
- let freshDataReceived = false;
146
- // Next.js tag revalidation can be async and may take longer under some runtimes.
147
- // Use a more tolerant window to avoid flaky failures.
148
- for (let i = 0; i < 60; i++) {
149
- await new Promise((resolve) => setTimeout(resolve, 500));
150
- const res = await fetch(`${BASE_URL}/api/cached-with-tag`);
151
- const data = await res.json();
152
-
153
- if (
154
- data.counter !== data1.counter ||
155
- data.timestamp !== data1.timestamp
156
- ) {
157
- freshDataReceived = true;
158
- break;
159
- }
160
- }
161
-
162
- expect(freshDataReceived).toBe(true);
163
- }, 20_000);
164
- });
165
-
166
- describe('cacheLife functionality', () => {
167
- it('should respect expire window and eventually return refreshed data', async () => {
168
- const res1 = await fetch(`${BASE_URL}/api/cached-with-cachelife`);
169
- const data1 = await res1.json();
170
- expect(data1.counter).toBe(1);
171
-
172
- const res2 = await fetch(`${BASE_URL}/api/cached-with-cachelife`);
173
- const data2 = await res2.json();
174
- expect(data2.counter).toBe(data1.counter);
175
- expect(data2.timestamp).toBe(data1.timestamp);
176
-
177
- await delay(6500);
178
-
179
- let refreshedData: any;
180
- for (let i = 0; i < 10; i++) {
181
- const res = await fetch(`${BASE_URL}/api/cached-with-cachelife`);
182
- const data = await res.json();
183
-
184
- if (
185
- data.counter !== data1.counter ||
186
- data.timestamp !== data1.timestamp
187
- ) {
188
- refreshedData = data;
189
- break;
190
- }
191
-
192
- await delay(500);
193
- }
194
-
195
- expect(refreshedData).toBeDefined();
196
- expect(refreshedData.counter).toBeGreaterThan(data1.counter);
197
- expect(refreshedData.timestamp).not.toBe(data1.timestamp);
198
- }, 20_000);
199
- });
200
-
201
- describe('Redis cache handler integration', () => {
202
- it('should call cache handler get and set methods', async () => {
203
- // Make request to trigger cache (don't clear first)
204
- await fetch(`${BASE_URL}/api/cached-static-fetch`);
205
-
206
- // Verify Redis has the cached data
207
- const redisKeys = await redisClient.keys(`${keyPrefix}*`);
208
- expect(redisKeys.length).toBeGreaterThan(0);
209
-
210
- // Filter out hash keys (sharedTagsMap) and only check string keys (cache entries)
211
- // Try to get each key and verify at least one is a string value
212
- let foundStringKey = false;
213
- for (const key of redisKeys) {
214
- try {
215
- const type = await redisClient.type(key);
216
- if (type === 'string') {
217
- const cachedValue = await redisClient.get(key);
218
- if (cachedValue) {
219
- foundStringKey = true;
220
- expect(cachedValue).toBeTruthy();
221
- break;
222
- }
223
- }
224
- } catch (e) {
225
- // Skip non-string keys
226
- }
227
- }
228
- expect(foundStringKey).toBe(true);
229
- });
230
- });
231
- });