@trieb.work/nextjs-turbo-redis-cache 1.15.0 → 1.16.0

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 (263) hide show
  1. package/.cursor/skills/testing/SKILL.md +121 -0
  2. package/.github/workflows/ci.yml +199 -69
  3. package/.github/workflows/release.yml +9 -1
  4. package/ARCHITECTURE.md +431 -0
  5. package/CHANGELOG.md +28 -0
  6. package/README.md +77 -49
  7. package/dist/index.d.mts +71 -45
  8. package/dist/index.d.ts +71 -45
  9. package/dist/index.js +107 -21
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +107 -21
  12. package/dist/index.mjs.map +1 -1
  13. package/docs/index.html +7 -6
  14. package/eslint.config.mjs +1 -1
  15. package/package.json +15 -16
  16. package/playwright.config.ts +5 -3
  17. package/src/CacheComponentsHandler.ts +57 -2
  18. package/src/RedisStringsHandler.ts +125 -66
  19. package/src/SyncedMap.ts +62 -13
  20. package/test/README.md +194 -0
  21. package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/package.json +6 -6
  22. package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-15-4-11}/pnpm-lock.yaml +89 -105
  23. package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11}/package.json +3 -3
  24. package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11}/pnpm-lock.yaml +53 -53
  25. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/package.json +28 -0
  26. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/pnpm-lock.yaml +4206 -0
  27. package/test/nextjs-test-projects/next-app-16-2-6/eslint.config.mjs +18 -0
  28. package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6}/package.json +3 -3
  29. package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6}/pnpm-lock.yaml +54 -60
  30. package/test/nextjs-test-projects/next-app-16-2-6/postcss.config.mjs +7 -0
  31. package/test/nextjs-test-projects/next-app-16-2-6/src/app/favicon.ico +0 -0
  32. package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-2-6}/tsconfig.json +9 -2
  33. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/cache-handler.js +3 -0
  34. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/next.config.ts +13 -0
  35. package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6-cache-components}/package.json +3 -3
  36. package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6-cache-components}/pnpm-lock.yaml +54 -60
  37. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-static-fetch/route.ts +19 -0
  38. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-with-cachelife/route.ts +24 -0
  39. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-with-tag/route.ts +21 -0
  40. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/revalidate-tag/route.ts +19 -0
  41. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/revalidated-fetch/route.ts +19 -0
  42. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/cachelife-short/page.tsx +110 -0
  43. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/page.tsx +90 -0
  44. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/runtime-data-suspense/page.tsx +127 -0
  45. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/stale-while-revalidate/page.tsx +130 -0
  46. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/tag-invalidation/page.tsx +127 -0
  47. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/use-cache-nondeterministic/page.tsx +110 -0
  48. package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/src/app/layout.tsx +0 -2
  49. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/update-tag-test/page.tsx +22 -0
  50. package/test/nextjs-test-projects/next-pages-16-2-6/README.md +16 -0
  51. package/test/nextjs-test-projects/next-pages-16-2-6/eslint.config.mjs +18 -0
  52. package/test/nextjs-test-projects/next-pages-16-2-6/next.config.ts +7 -0
  53. package/test/nextjs-test-projects/next-pages-16-2-6/package.json +26 -0
  54. package/test/nextjs-test-projects/next-pages-16-2-6/pnpm-lock.yaml +3896 -0
  55. package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/api/revalidate.ts +24 -0
  56. package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/index.tsx +11 -0
  57. package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/isr/[slug].tsx +49 -0
  58. package/test/nextjs-test-projects/next-pages-16-2-6/src/pages/static-forever.tsx +20 -0
  59. package/test/nextjs-test-projects/next-pages-16-2-6/tsconfig.json +34 -0
  60. package/{tests → test/playwright}/update-tag.spec.ts +1 -1
  61. package/test/{integration → vitest/integration}/build-id-prefix.integration.test.ts +2 -2
  62. package/test/{cache-components/cache-components.integration.spec.ts → vitest/integration/cache-components/cache-components.integration.test.ts} +7 -3
  63. package/test/{cache-components/redis-kill-reconnect.spec.ts → vitest/integration/cache-components/redis-kill-reconnect.test.ts} +28 -7
  64. package/test/vitest/integration/cache-components/redis-quit-vs-disconnect.test.ts +59 -0
  65. package/test/vitest/integration/cache-components/redis-subscriber-outage.test.ts +100 -0
  66. package/test/{cache-components → vitest/integration/cache-components}/scripts/redis-kill-reconnect.ts +31 -8
  67. package/test/vitest/integration/cache-components/scripts/redis-quit-vs-disconnect.ts +113 -0
  68. package/test/vitest/integration/cache-components/scripts/redis-subscriber-outage.ts +379 -0
  69. package/test/vitest/integration/cache-components/scripts/redis-test-helpers.ts +155 -0
  70. package/test/{integration → vitest/integration}/nextjs-cache-handler.integration.test.ts +15 -9
  71. package/test/vitest/integration/pages-router.integration.test.ts +420 -0
  72. package/test/vitest/unit/SyncedMap-reconnect-repro.test.ts +154 -0
  73. package/{src → test/vitest/unit}/index.test.ts +3 -3
  74. package/test/vitest/unit/pages-router-kinds.test.ts +292 -0
  75. package/test/{cache-components/reconnect-socket-already-opened.spec.ts → vitest/unit/reconnect-socket-already-opened.test.ts} +1 -1
  76. package/{src → test/vitest/unit}/serializer.test.ts +7 -3
  77. package/{src → test/vitest/unit}/utils/prefix.test.ts +1 -1
  78. package/vite.config.ts +4 -3
  79. package/vitest.cache-components.config.ts +1 -1
  80. package/test/browser/update-tag.browser.test.ts +0 -41
  81. package/test/integration/next-app-15-0-3/next.config.js +0 -6
  82. package/test/integration/next-app-15-0-3/package-lock.json +0 -5833
  83. package/test/integration/next-app-15-0-3/pnpm-lock.yaml +0 -3710
  84. package/test/integration/next-app-15-3-2/next.config.js +0 -6
  85. package/test/integration/next-app-15-3-2/package.json +0 -33
  86. package/test/integration/next-app-15-3-2/pnpm-lock.yaml +0 -3720
  87. package/test/integration/next-app-15-3-2/postcss.config.mjs +0 -5
  88. package/test/integration/next-app-15-3-2/src/app/api/revalidateTag/route.ts +0 -15
  89. package/test/integration/next-app-15-4-7/eslint.config.mjs +0 -16
  90. package/test/integration/next-app-15-4-7/package-lock.json +0 -5969
  91. package/test/integration/next-app-15-4-7/package.json +0 -33
  92. package/test/integration/next-app-15-4-7/postcss.config.mjs +0 -5
  93. package/test/integration/next-app-15-4-7/src/app/api/revalidateTag/route.ts +0 -15
  94. package/test/integration/next-app-15-4-7/tsconfig.json +0 -27
  95. package/test/integration/next-app-16-0-3/src/app/api/cached-static-fetch/route.ts +0 -18
  96. package/test/integration/next-app-16-0-3/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
  97. package/test/integration/next-app-16-0-3/src/app/api/revalidatePath/route.ts +0 -15
  98. package/test/integration/next-app-16-0-3/src/app/api/revalidated-fetch/route.ts +0 -17
  99. package/test/integration/next-app-16-0-3/src/app/api/uncached-fetch/route.ts +0 -15
  100. package/test/integration/next-app-16-0-3/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
  101. package/test/integration/next-app-16-0-3/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
  102. package/test/integration/next-app-16-0-3/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  103. package/test/integration/next-app-16-0-3/src/app/pages/no-fetch/default-page/page.tsx +0 -55
  104. package/test/integration/next-app-16-0-3/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
  105. package/test/integration/next-app-16-0-3/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
  106. package/test/integration/next-app-16-0-3/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  107. package/test/integration/next-app-16-0-3/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
  108. package/test/integration/next-app-16-0-3/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
  109. package/test/integration/next-app-16-0-3/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  110. package/test/integration/next-app-16-2-3/src/app/api/cached-static-fetch/route.ts +0 -18
  111. package/test/integration/next-app-16-2-3/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
  112. package/test/integration/next-app-16-2-3/src/app/api/revalidatePath/route.ts +0 -15
  113. package/test/integration/next-app-16-2-3/src/app/api/revalidated-fetch/route.ts +0 -17
  114. package/test/integration/next-app-16-2-3/src/app/api/uncached-fetch/route.ts +0 -15
  115. package/test/integration/next-app-16-2-3/src/app/globals.css +0 -26
  116. package/test/integration/next-app-16-2-3/src/app/layout.tsx +0 -59
  117. package/test/integration/next-app-16-2-3/src/app/page.tsx +0 -755
  118. package/test/integration/next-app-16-2-3/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
  119. package/test/integration/next-app-16-2-3/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
  120. package/test/integration/next-app-16-2-3/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  121. package/test/integration/next-app-16-2-3/src/app/pages/no-fetch/default-page/page.tsx +0 -55
  122. package/test/integration/next-app-16-2-3/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
  123. package/test/integration/next-app-16-2-3/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
  124. package/test/integration/next-app-16-2-3/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  125. package/test/integration/next-app-16-2-3/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
  126. package/test/integration/next-app-16-2-3/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
  127. package/test/integration/next-app-16-2-3/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  128. package/test/integration/next-app-16-2-3/src/app/revalidation-interface.tsx +0 -267
  129. package/test/integration/next-app-16-2-3-cache-components/public/file.svg +0 -1
  130. package/test/integration/next-app-16-2-3-cache-components/public/globe.svg +0 -1
  131. package/test/integration/next-app-16-2-3-cache-components/public/next.svg +0 -1
  132. package/test/integration/next-app-16-2-3-cache-components/public/vercel.svg +0 -1
  133. package/test/integration/next-app-16-2-3-cache-components/public/window.svg +0 -1
  134. package/test/integration/next-app-customized/README.md +0 -36
  135. package/test/integration/next-app-customized/eslint.config.mjs +0 -16
  136. package/vitest.browser.config.ts +0 -10
  137. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/README.md +0 -0
  138. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/eslint.config.mjs +0 -0
  139. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-15-4-11}/next.config.js +0 -0
  140. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-15-4-11}/package-lock.json +0 -0
  141. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/postcss.config.mjs +0 -0
  142. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/file.svg +0 -0
  143. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/globe.svg +0 -0
  144. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/next.svg +0 -0
  145. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/vercel.svg +0 -0
  146. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/window.svg +0 -0
  147. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/cached-static-fetch/route.ts +0 -0
  148. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -0
  149. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/revalidatePath/route.ts +0 -0
  150. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/revalidateTag/route.ts +0 -0
  151. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/revalidated-fetch/route.ts +0 -0
  152. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/uncached-fetch/route.ts +0 -0
  153. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/globals.css +0 -0
  154. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/layout.tsx +0 -0
  155. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/page.tsx +0 -0
  156. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -0
  157. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -0
  158. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
  159. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/no-fetch/default-page/page.tsx +0 -0
  160. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -0
  161. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -0
  162. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
  163. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -0
  164. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -0
  165. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
  166. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/revalidation-interface.tsx +0 -0
  167. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/tsconfig.json +0 -0
  168. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-0-11}/README.md +0 -0
  169. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11}/eslint.config.mjs +0 -0
  170. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11}/next.config.ts +0 -0
  171. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11}/postcss.config.mjs +0 -0
  172. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/api/cached-static-fetch/route.ts +0 -0
  173. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -0
  174. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/api/revalidatePath/route.ts +0 -0
  175. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11}/src/app/api/revalidateTag/route.ts +0 -0
  176. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/api/revalidated-fetch/route.ts +0 -0
  177. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/api/uncached-fetch/route.ts +0 -0
  178. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11}/src/app/favicon.ico +0 -0
  179. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-0-11}/src/app/globals.css +0 -0
  180. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/layout.tsx +0 -0
  181. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-0-11}/src/app/page.tsx +0 -0
  182. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -0
  183. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -0
  184. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
  185. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/no-fetch/default-page/page.tsx +0 -0
  186. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -0
  187. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -0
  188. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
  189. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -0
  190. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -0
  191. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
  192. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-0-11}/src/app/revalidation-interface.tsx +0 -0
  193. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11}/src/app/update-tag-test/page.tsx +0 -0
  194. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11}/tsconfig.json +0 -0
  195. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/README.md +0 -0
  196. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/cache-handler.js +0 -0
  197. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11-cache-components}/eslint.config.mjs +0 -0
  198. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/next.config.ts +0 -0
  199. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11-cache-components}/postcss.config.mjs +0 -0
  200. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/file.svg +0 -0
  201. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/globe.svg +0 -0
  202. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/next.svg +0 -0
  203. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/vercel.svg +0 -0
  204. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/window.svg +0 -0
  205. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/api/cached-static-fetch/route.ts +0 -0
  206. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/api/cached-with-cachelife/route.ts +0 -0
  207. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/api/cached-with-tag/route.ts +0 -0
  208. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/api/revalidate-tag/route.ts +0 -0
  209. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/api/revalidated-fetch/route.ts +0 -0
  210. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/cache-lab/cachelife-short/page.tsx +0 -0
  211. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/cache-lab/page.tsx +0 -0
  212. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/cache-lab/runtime-data-suspense/page.tsx +0 -0
  213. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/cache-lab/stale-while-revalidate/page.tsx +0 -0
  214. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/cache-lab/tag-invalidation/page.tsx +0 -0
  215. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/cache-lab/use-cache-nondeterministic/page.tsx +0 -0
  216. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/favicon.ico +0 -0
  217. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/globals.css +0 -0
  218. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/layout.tsx +0 -0
  219. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/page.tsx +0 -0
  220. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/revalidation-interface.tsx +0 -0
  221. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/update-tag-test/page.tsx +0 -0
  222. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11-cache-components}/tsconfig.json +0 -0
  223. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6}/README.md +0 -0
  224. /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6}/next.config.ts +0 -0
  225. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/api/cached-static-fetch/route.ts +0 -0
  226. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -0
  227. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/api/revalidatePath/route.ts +0 -0
  228. /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6}/src/app/api/revalidateTag/route.ts +0 -0
  229. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/api/revalidated-fetch/route.ts +0 -0
  230. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/api/uncached-fetch/route.ts +0 -0
  231. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6}/src/app/globals.css +0 -0
  232. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/layout.tsx +0 -0
  233. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6}/src/app/page.tsx +0 -0
  234. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -0
  235. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -0
  236. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
  237. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/no-fetch/default-page/page.tsx +0 -0
  238. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -0
  239. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -0
  240. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
  241. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -0
  242. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -0
  243. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -0
  244. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6}/src/app/revalidation-interface.tsx +0 -0
  245. /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6}/src/app/update-tag-test/page.tsx +0 -0
  246. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/README.md +0 -0
  247. /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/eslint.config.mjs +0 -0
  248. /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/postcss.config.mjs +0 -0
  249. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/file.svg +0 -0
  250. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/globe.svg +0 -0
  251. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/next.svg +0 -0
  252. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/vercel.svg +0 -0
  253. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/window.svg +0 -0
  254. /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/src/app/favicon.ico +0 -0
  255. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/src/app/globals.css +0 -0
  256. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/src/app/page.tsx +0 -0
  257. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/src/app/revalidation-interface.tsx +0 -0
  258. /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/tsconfig.json +0 -0
  259. /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-customized}/README.md +0 -0
  260. /package/test/{integration → nextjs-test-projects}/next-app-customized/customized-cache-handler.js +0 -0
  261. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-customized}/eslint.config.mjs +0 -0
  262. /package/test/{integration → nextjs-test-projects}/next-app-customized/next.config.js +0 -0
  263. /package/{tests → test/playwright}/cache-lab.spec.ts +0 -0
package/dist/index.mjs CHANGED
@@ -26,6 +26,7 @@ function debugVerbose(color, ...args) {
26
26
  var SYNC_CHANNEL_SUFFIX = ":sync-channel:";
27
27
  var SyncedMap = class {
28
28
  constructor(options) {
29
+ this.isReconnecting = false;
29
30
  this.client = options.client;
30
31
  this.keyPrefix = options.keyPrefix;
31
32
  this.redisKey = options.redisKey;
@@ -166,6 +167,10 @@ var SyncedMap = class {
166
167
  }
167
168
  };
168
169
  try {
170
+ this.subscriberClient.on("error", async (err) => {
171
+ console.error("Subscriber client error:", err);
172
+ await this.reconnectSubscriber();
173
+ });
169
174
  const connectIfNeeded = async () => {
170
175
  if (!this.subscriberClient.isOpen && !this.subscriberClient.isReady) {
171
176
  await this.subscriberClient.connect();
@@ -185,12 +190,12 @@ var SyncedMap = class {
185
190
  const keyspaceEventConfig = (await this.subscriberClient.configGet("notify-keyspace-events"))?.["notify-keyspace-events"];
186
191
  if (!keyspaceEventConfig.includes("E")) {
187
192
  throw new Error(
188
- 'Keyspace event configuration is set to "' + keyspaceEventConfig + "\" but has to include 'E' for Keyevent events, published with __keyevent@<db>__ prefix. We recommend to set it to 'Exe' like so `redis-cli -h localhost config set notify-keyspace-events Exe`"
193
+ 'Keyspace event configuration is set to "' + keyspaceEventConfig + " but has to include 'E' for Keyevent events, published with __keyevent@<db>__ prefix. We recommend to set it to 'Exe' like so `redis-cli -h localhost config set notify-keyspace-events Exe`"
189
194
  );
190
195
  }
191
196
  if (!keyspaceEventConfig.includes("A") && !(keyspaceEventConfig.includes("x") && keyspaceEventConfig.includes("e"))) {
192
197
  throw new Error(
193
- 'Keyspace event configuration is set to "' + keyspaceEventConfig + "\" but has to include 'A' or 'x' and 'e' for expired and evicted events. We recommend to set it to 'Exe' like so `redis-cli -h localhost config set notify-keyspace-events Exe`"
198
+ 'Keyspace event configuration is set to "' + keyspaceEventConfig + " but has to include 'A' or 'x' and 'e' for expired and evicted events. We recommend to set it to 'Exe' like so `redis-cli -h localhost config set notify-keyspace-events Exe`"
194
199
  );
195
200
  }
196
201
  }
@@ -210,22 +215,48 @@ var SyncedMap = class {
210
215
  keyEventHandler
211
216
  )
212
217
  ]);
213
- this.subscriberClient.on("error", async (err) => {
214
- console.error("Subscriber client error:", err);
218
+ } catch (error) {
219
+ console.error("Error setting up pub/sub client:", error);
220
+ throw error;
221
+ }
222
+ }
223
+ /**
224
+ * Reconnect the subscriber client with exponential backoff.
225
+ * Guarded by `isReconnecting` to prevent concurrent reconnection
226
+ * attempts when multiple error events fire during an outage.
227
+ * Retries indefinitely (with capped delay) so the subscriber
228
+ * eventually recovers once Redis is available again.
229
+ */
230
+ async reconnectSubscriber() {
231
+ if (this.isReconnecting) return;
232
+ this.isReconnecting = true;
233
+ try {
234
+ let attempt = 0;
235
+ while (true) {
215
236
  try {
216
- await this.subscriberClient.quit();
237
+ try {
238
+ this.subscriberClient.removeAllListeners("error");
239
+ await this.subscriberClient.disconnect();
240
+ } catch {
241
+ }
217
242
  this.subscriberClient = this.client.duplicate();
218
243
  await this.setupPubSub();
219
- } catch (reconnectError) {
244
+ return;
245
+ } catch (error) {
246
+ const delay = Math.min(
247
+ 500 * Math.pow(2, Math.min(attempt, 5)),
248
+ 1e4
249
+ );
250
+ attempt++;
220
251
  console.error(
221
- "Failed to reconnect subscriber client:",
222
- reconnectError
252
+ `Subscriber reconnect attempt ${attempt} failed, retrying in ${delay}ms`,
253
+ error
223
254
  );
255
+ await new Promise((r) => setTimeout(r, delay));
224
256
  }
225
- });
226
- } catch (error) {
227
- console.error("Error setting up pub/sub client:", error);
228
- throw error;
257
+ }
258
+ } finally {
259
+ this.isReconnecting = false;
229
260
  }
230
261
  }
231
262
  async waitUntilReady() {
@@ -493,6 +524,19 @@ if (process.env.DEBUG_CACHE_HANDLER) {
493
524
  }
494
525
  var NEXT_CACHE_IMPLICIT_TAG_ID = "_N_T_";
495
526
  var REVALIDATED_TAGS_KEY = "__revalidated_tags__";
527
+ var SUPPORTED_GET_KINDS = [
528
+ "APP_ROUTE",
529
+ "APP_PAGE",
530
+ "PAGES",
531
+ "FETCH"
532
+ ];
533
+ var SUPPORTED_SET_KINDS = [
534
+ "APP_ROUTE",
535
+ "APP_PAGE",
536
+ "PAGES",
537
+ "REDIRECT",
538
+ "FETCH"
539
+ ];
496
540
  var killContainerOnErrorCount = 0;
497
541
  var RedisStringsHandler = class {
498
542
  constructor({
@@ -658,12 +702,12 @@ var RedisStringsHandler = class {
658
702
  }
659
703
  async get(key, ctx) {
660
704
  try {
661
- if (ctx.kind !== "APP_ROUTE" && ctx.kind !== "APP_PAGE" && ctx.kind !== "FETCH") {
705
+ if (!SUPPORTED_GET_KINDS.includes(ctx.kind)) {
662
706
  console.warn(
663
707
  "RedisStringsHandler.get() called with",
664
708
  key,
665
709
  ctx,
666
- " this cache handler is only designed and tested for kind APP_ROUTE and APP_PAGE and not for kind ",
710
+ `this cache handler is only designed and tested for kinds ${SUPPORTED_GET_KINDS.join(", ")} and not for kind`,
667
711
  ctx?.kind
668
712
  );
669
713
  }
@@ -718,7 +762,7 @@ var RedisStringsHandler = class {
718
762
  "cacheEntry is mall formed (missing tags)"
719
763
  );
720
764
  }
721
- if (!cacheEntry?.value) {
765
+ if (cacheEntry?.value === void 0) {
722
766
  console.warn(
723
767
  "RedisStringsHandler.get() called with",
724
768
  key,
@@ -791,21 +835,27 @@ var RedisStringsHandler = class {
791
835
  }
792
836
  async set(key, data, ctx) {
793
837
  try {
794
- if (data.kind !== "APP_ROUTE" && data.kind !== "APP_PAGE" && data.kind !== "FETCH") {
838
+ if (data !== null && !SUPPORTED_SET_KINDS.includes(data.kind)) {
795
839
  console.warn(
796
840
  "RedisStringsHandler.set() called with",
797
841
  key,
798
842
  ctx,
799
843
  data,
800
- " this cache handler is only designed and tested for kind APP_ROUTE and APP_PAGE and not for kind ",
844
+ `this cache handler is only designed and tested for kinds ${SUPPORTED_SET_KINDS.join(", ")} and not for kind`,
801
845
  data?.kind
802
846
  );
803
847
  }
804
848
  await this.assertClientIsReady();
805
- if (data.kind === "APP_PAGE" || data.kind === "APP_ROUTE") {
849
+ if (data?.kind === "APP_PAGE" || data?.kind === "APP_ROUTE") {
806
850
  const tags = data.headers["x-next-cache-tags"]?.split(",");
807
851
  ctx.tags = [...ctx.tags || [], ...tags || []];
808
852
  }
853
+ if (data === null || data.kind === "PAGES" || data.kind === "REDIRECT") {
854
+ const implicitTag = NEXT_CACHE_IMPLICIT_TAG_ID + key;
855
+ if (!ctx.tags?.includes(implicitTag)) {
856
+ ctx.tags = [...ctx.tags || [], implicitTag];
857
+ }
858
+ }
809
859
  const cacheEntry = {
810
860
  lastModified: Date.now(),
811
861
  tags: ctx?.tags || [],
@@ -820,7 +870,10 @@ var RedisStringsHandler = class {
820
870
  }
821
871
  const revalidate = (
822
872
  // For fetch requests in newest versions, the revalidate context property is never used, and instead the revalidate property of the passed-in data is used
823
- data.kind === "FETCH" && data.revalidate || ctx.revalidate || ctx.cacheControl?.revalidate || data?.revalidate
873
+ data?.kind === "FETCH" && data.revalidate || ctx.revalidate || ctx.cacheControl?.revalidate || // Legacy fallback: older Next.js versions attached `revalidate` directly
874
+ // to the data payload. FETCH is already handled above, so this only
875
+ // matters for those older, non-discriminated shapes.
876
+ data?.revalidate
824
877
  );
825
878
  const expireAt = revalidate && Number.isSafeInteger(revalidate) && revalidate > 0 ? this.estimateExpireAge(revalidate) : this.estimateExpireAge(this.defaultStaleAge);
826
879
  const setOperation = redisErrorHandler(
@@ -1068,6 +1121,8 @@ var RedisCacheComponentsHandler = class {
1068
1121
  getTimeoutMs = process.env.REDIS_COMMAND_TIMEOUT_MS ? Number.parseInt(process.env.REDIS_COMMAND_TIMEOUT_MS) ?? 500 : 500,
1069
1122
  revalidateTagQuerySize = 250,
1070
1123
  avgResyncIntervalMs = 60 * 60 * 1e3,
1124
+ redisGetDeduplication = true,
1125
+ inMemoryCachingTime = 1e4,
1071
1126
  socketOptions,
1072
1127
  clientOptions,
1073
1128
  killContainerOnErrorThreshold = process.env.KILL_CONTAINER_ON_ERROR_THRESHOLD ? Number.parseInt(process.env.KILL_CONTAINER_ON_ERROR_THRESHOLD) ?? 0 : 0,
@@ -1080,6 +1135,8 @@ var RedisCacheComponentsHandler = class {
1080
1135
  env: process.env
1081
1136
  });
1082
1137
  this.getTimeoutMs = getTimeoutMs;
1138
+ this.redisGetDeduplication = redisGetDeduplication;
1139
+ this.inMemoryCachingTime = inMemoryCachingTime;
1083
1140
  this.client = createClient2({
1084
1141
  url: redisUrl,
1085
1142
  pingInterval: 1e4,
@@ -1146,6 +1203,26 @@ var RedisCacheComponentsHandler = class {
1146
1203
  filterKeys,
1147
1204
  resyncIntervalMs: avgResyncIntervalMs - avgResyncIntervalMs / 10 + Math.random() * (avgResyncIntervalMs / 10)
1148
1205
  });
1206
+ this.inMemoryDeduplicationCache = new SyncedMap({
1207
+ client: this.client,
1208
+ keyPrefix: this.keyPrefix,
1209
+ redisKey: "__cacheComponents_inMemoryDeduplicationCache__",
1210
+ database,
1211
+ querySize: revalidateTagQuerySize,
1212
+ filterKeys,
1213
+ customizedSync: {
1214
+ withoutRedisHashmap: true,
1215
+ withoutSetSync: true
1216
+ }
1217
+ });
1218
+ const redisGet = this.client.get.bind(this.client);
1219
+ this.redisDeduplicationHandler = new DeduplicatedRequestHandler(
1220
+ redisGet,
1221
+ inMemoryCachingTime,
1222
+ this.inMemoryDeduplicationCache
1223
+ );
1224
+ this.redisGet = redisGet;
1225
+ this.deduplicatedRedisGet = this.redisDeduplicationHandler.deduplicatedFunction;
1149
1226
  } catch (error) {
1150
1227
  console.error("RedisCacheComponentsHandler constructor error", error);
1151
1228
  throw error;
@@ -1180,9 +1257,10 @@ var RedisCacheComponentsHandler = class {
1180
1257
  const redisKey = `${this.keyPrefix}${cacheKey}`;
1181
1258
  try {
1182
1259
  await this.assertClientIsReady();
1260
+ const clientGet = this.redisGetDeduplication ? this.deduplicatedRedisGet(cacheKey) : this.redisGet;
1183
1261
  const serialized = await redisErrorHandler(
1184
- "RedisCacheComponentsHandler.get(), operation: get " + this.getTimeoutMs + "ms " + redisKey,
1185
- this.client.get(
1262
+ "RedisCacheComponentsHandler.get(), operation: get" + (this.redisGetDeduplication ? " deduplicated" : "") + " " + this.getTimeoutMs + "ms " + redisKey,
1263
+ clientGet(
1186
1264
  commandOptions2({ signal: AbortSignal.timeout(this.getTimeoutMs) }),
1187
1265
  redisKey
1188
1266
  )
@@ -1255,6 +1333,9 @@ var RedisCacheComponentsHandler = class {
1255
1333
  console.error("Stored object:", stored);
1256
1334
  throw jsonError;
1257
1335
  }
1336
+ if (this.redisGetDeduplication) {
1337
+ this.redisDeduplicationHandler.seedRequestReturn(cacheKey, serialized);
1338
+ }
1258
1339
  const ttlSeconds = Number.isFinite(stored.expire) && stored.expire > 0 ? Math.floor(stored.expire) : void 0;
1259
1340
  const redisKey = `${this.keyPrefix}${cacheKey}`;
1260
1341
  const setOperation = redisErrorHandler(
@@ -1321,6 +1402,11 @@ var RedisCacheComponentsHandler = class {
1321
1402
  "RedisCacheComponentsHandler.updateTags(), operation: unlink",
1322
1403
  this.client.unlink(fullRedisKeys)
1323
1404
  );
1405
+ if (this.redisGetDeduplication && this.inMemoryCachingTime > 0) {
1406
+ for (const key of keysToDelete) {
1407
+ this.inMemoryDeduplicationCache.delete(key);
1408
+ }
1409
+ }
1324
1410
  const deleteTagsOperation = this.sharedTagsMap.delete(cacheKeys);
1325
1411
  await deleteTagsOperation;
1326
1412
  } catch (error) {