@trieb.work/nextjs-turbo-redis-cache 1.14.1 → 1.15.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 (256) hide show
  1. package/.cursor/skills/testing/SKILL.md +121 -0
  2. package/.github/workflows/ci.yml +155 -70
  3. package/.github/workflows/pages.yml +38 -0
  4. package/ARCHITECTURE.md +431 -0
  5. package/CHANGELOG.md +19 -0
  6. package/README.md +220 -48
  7. package/dist/index.d.mts +77 -2
  8. package/dist/index.d.ts +77 -2
  9. package/dist/index.js +109 -23
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +106 -23
  12. package/dist/index.mjs.map +1 -1
  13. package/docs/index.html +139 -0
  14. package/docs/robots.txt +4 -0
  15. package/docs/sitemap.xml +8 -0
  16. package/docs/styles.css +141 -0
  17. package/eslint.config.mjs +1 -1
  18. package/package.json +16 -18
  19. package/playwright.config.ts +5 -3
  20. package/src/CacheComponentsHandler.ts +57 -2
  21. package/src/RedisStringsHandler.ts +48 -11
  22. package/src/SyncedMap.ts +62 -13
  23. package/src/index.ts +4 -0
  24. package/src/serializer.ts +59 -0
  25. package/test/README.md +179 -0
  26. package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/package.json +6 -6
  27. package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-15-4-11}/pnpm-lock.yaml +89 -105
  28. package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11}/package.json +3 -3
  29. package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11}/pnpm-lock.yaml +53 -53
  30. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/package.json +28 -0
  31. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/pnpm-lock.yaml +4206 -0
  32. package/test/nextjs-test-projects/next-app-16-0-11-cache-components/src/app/api/cached-with-cachelife/route.ts +24 -0
  33. package/test/nextjs-test-projects/next-app-16-2-6/eslint.config.mjs +18 -0
  34. package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6}/package.json +3 -3
  35. package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6}/pnpm-lock.yaml +54 -60
  36. package/test/nextjs-test-projects/next-app-16-2-6/postcss.config.mjs +7 -0
  37. package/test/nextjs-test-projects/next-app-16-2-6/src/app/favicon.ico +0 -0
  38. package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-2-6}/tsconfig.json +9 -2
  39. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/cache-handler.js +3 -0
  40. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/next.config.ts +13 -0
  41. package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6-cache-components}/package.json +3 -3
  42. 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
  43. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-static-fetch/route.ts +19 -0
  44. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-with-cachelife/route.ts +24 -0
  45. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/cached-with-tag/route.ts +21 -0
  46. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/revalidate-tag/route.ts +19 -0
  47. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/api/revalidated-fetch/route.ts +19 -0
  48. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/cachelife-short/page.tsx +110 -0
  49. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/page.tsx +90 -0
  50. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/runtime-data-suspense/page.tsx +127 -0
  51. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/stale-while-revalidate/page.tsx +130 -0
  52. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/tag-invalidation/page.tsx +127 -0
  53. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/cache-lab/use-cache-nondeterministic/page.tsx +110 -0
  54. package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/src/app/layout.tsx +0 -2
  55. package/test/nextjs-test-projects/next-app-16-2-6-cache-components/src/app/update-tag-test/page.tsx +22 -0
  56. package/{tests → test/playwright}/update-tag.spec.ts +1 -1
  57. package/test/{integration → vitest/integration}/build-id-prefix.integration.test.ts +2 -2
  58. package/test/{cache-components/cache-components.integration.spec.ts → vitest/integration/cache-components/cache-components.integration.test.ts} +46 -3
  59. package/test/{cache-components/redis-kill-reconnect.spec.ts → vitest/integration/cache-components/redis-kill-reconnect.test.ts} +28 -7
  60. package/test/vitest/integration/cache-components/redis-quit-vs-disconnect.test.ts +59 -0
  61. package/test/vitest/integration/cache-components/redis-subscriber-outage.test.ts +100 -0
  62. package/test/{cache-components → vitest/integration/cache-components}/scripts/redis-kill-reconnect.ts +31 -8
  63. package/test/vitest/integration/cache-components/scripts/redis-quit-vs-disconnect.ts +113 -0
  64. package/test/vitest/integration/cache-components/scripts/redis-subscriber-outage.ts +379 -0
  65. package/test/vitest/integration/cache-components/scripts/redis-test-helpers.ts +155 -0
  66. package/test/{integration → vitest/integration}/nextjs-cache-handler.integration.test.ts +17 -10
  67. package/test/vitest/unit/SyncedMap-reconnect-repro.test.ts +154 -0
  68. package/{src → test/vitest/unit}/index.test.ts +3 -3
  69. package/test/{cache-components/reconnect-socket-already-opened.spec.ts → vitest/unit/reconnect-socket-already-opened.test.ts} +1 -1
  70. package/test/vitest/unit/serializer.test.ts +182 -0
  71. package/{src → test/vitest/unit}/utils/prefix.test.ts +1 -1
  72. package/vite.config.ts +4 -3
  73. package/vitest.cache-components.config.ts +1 -1
  74. package/test/browser/update-tag.browser.test.ts +0 -41
  75. package/test/integration/next-app-15-0-3/next.config.js +0 -6
  76. package/test/integration/next-app-15-0-3/package-lock.json +0 -5833
  77. package/test/integration/next-app-15-0-3/pnpm-lock.yaml +0 -3710
  78. package/test/integration/next-app-15-3-2/next.config.js +0 -6
  79. package/test/integration/next-app-15-3-2/package.json +0 -33
  80. package/test/integration/next-app-15-3-2/pnpm-lock.yaml +0 -3720
  81. package/test/integration/next-app-15-3-2/postcss.config.mjs +0 -5
  82. package/test/integration/next-app-15-3-2/src/app/api/revalidateTag/route.ts +0 -15
  83. package/test/integration/next-app-15-4-7/eslint.config.mjs +0 -16
  84. package/test/integration/next-app-15-4-7/package-lock.json +0 -5969
  85. package/test/integration/next-app-15-4-7/package.json +0 -33
  86. package/test/integration/next-app-15-4-7/postcss.config.mjs +0 -5
  87. package/test/integration/next-app-15-4-7/src/app/api/revalidateTag/route.ts +0 -15
  88. package/test/integration/next-app-15-4-7/tsconfig.json +0 -27
  89. package/test/integration/next-app-16-0-3/src/app/api/cached-static-fetch/route.ts +0 -18
  90. package/test/integration/next-app-16-0-3/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
  91. package/test/integration/next-app-16-0-3/src/app/api/revalidatePath/route.ts +0 -15
  92. package/test/integration/next-app-16-0-3/src/app/api/revalidated-fetch/route.ts +0 -17
  93. package/test/integration/next-app-16-0-3/src/app/api/uncached-fetch/route.ts +0 -15
  94. package/test/integration/next-app-16-0-3/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
  95. package/test/integration/next-app-16-0-3/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
  96. package/test/integration/next-app-16-0-3/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  97. package/test/integration/next-app-16-0-3/src/app/pages/no-fetch/default-page/page.tsx +0 -55
  98. package/test/integration/next-app-16-0-3/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
  99. package/test/integration/next-app-16-0-3/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
  100. package/test/integration/next-app-16-0-3/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  101. package/test/integration/next-app-16-0-3/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
  102. package/test/integration/next-app-16-0-3/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
  103. package/test/integration/next-app-16-0-3/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  104. package/test/integration/next-app-16-2-3/src/app/api/cached-static-fetch/route.ts +0 -18
  105. package/test/integration/next-app-16-2-3/src/app/api/nested-fetch-in-api-route/revalidated-fetch/route.ts +0 -27
  106. package/test/integration/next-app-16-2-3/src/app/api/revalidatePath/route.ts +0 -15
  107. package/test/integration/next-app-16-2-3/src/app/api/revalidated-fetch/route.ts +0 -17
  108. package/test/integration/next-app-16-2-3/src/app/api/uncached-fetch/route.ts +0 -15
  109. package/test/integration/next-app-16-2-3/src/app/globals.css +0 -26
  110. package/test/integration/next-app-16-2-3/src/app/layout.tsx +0 -59
  111. package/test/integration/next-app-16-2-3/src/app/page.tsx +0 -755
  112. package/test/integration/next-app-16-2-3/src/app/pages/cached-static-fetch/default--force-dynamic-page/page.tsx +0 -19
  113. package/test/integration/next-app-16-2-3/src/app/pages/cached-static-fetch/revalidate15--default-page/page.tsx +0 -34
  114. package/test/integration/next-app-16-2-3/src/app/pages/cached-static-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  115. package/test/integration/next-app-16-2-3/src/app/pages/no-fetch/default-page/page.tsx +0 -55
  116. package/test/integration/next-app-16-2-3/src/app/pages/revalidated-fetch/default--force-dynamic-page/page.tsx +0 -19
  117. package/test/integration/next-app-16-2-3/src/app/pages/revalidated-fetch/revalidate15--default-page/page.tsx +0 -35
  118. package/test/integration/next-app-16-2-3/src/app/pages/revalidated-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  119. package/test/integration/next-app-16-2-3/src/app/pages/uncached-fetch/default--force-dynamic-page/page.tsx +0 -19
  120. package/test/integration/next-app-16-2-3/src/app/pages/uncached-fetch/revalidate15--default-page/page.tsx +0 -32
  121. package/test/integration/next-app-16-2-3/src/app/pages/uncached-fetch/revalidate15--force-dynamic-page/page.tsx +0 -25
  122. package/test/integration/next-app-16-2-3/src/app/revalidation-interface.tsx +0 -267
  123. package/test/integration/next-app-16-2-3-cache-components/public/file.svg +0 -1
  124. package/test/integration/next-app-16-2-3-cache-components/public/globe.svg +0 -1
  125. package/test/integration/next-app-16-2-3-cache-components/public/next.svg +0 -1
  126. package/test/integration/next-app-16-2-3-cache-components/public/vercel.svg +0 -1
  127. package/test/integration/next-app-16-2-3-cache-components/public/window.svg +0 -1
  128. package/test/integration/next-app-customized/README.md +0 -36
  129. package/test/integration/next-app-customized/eslint.config.mjs +0 -16
  130. package/vitest.browser.config.ts +0 -10
  131. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/README.md +0 -0
  132. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/eslint.config.mjs +0 -0
  133. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-15-4-11}/next.config.js +0 -0
  134. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-15-4-11}/package-lock.json +0 -0
  135. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/postcss.config.mjs +0 -0
  136. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/file.svg +0 -0
  137. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/globe.svg +0 -0
  138. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/next.svg +0 -0
  139. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/vercel.svg +0 -0
  140. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/public/window.svg +0 -0
  141. /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
  142. /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
  143. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/revalidatePath/route.ts +0 -0
  144. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/api/revalidateTag/route.ts +0 -0
  145. /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
  146. /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
  147. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/globals.css +0 -0
  148. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/layout.tsx +0 -0
  149. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/page.tsx +0 -0
  150. /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
  151. /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
  152. /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
  153. /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
  154. /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
  155. /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
  156. /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
  157. /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
  158. /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
  159. /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
  160. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/src/app/revalidation-interface.tsx +0 -0
  161. /package/test/{integration/next-app-15-0-3 → nextjs-test-projects/next-app-15-4-11}/tsconfig.json +0 -0
  162. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-0-11}/README.md +0 -0
  163. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11}/eslint.config.mjs +0 -0
  164. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11}/next.config.ts +0 -0
  165. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11}/postcss.config.mjs +0 -0
  166. /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
  167. /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
  168. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/api/revalidatePath/route.ts +0 -0
  169. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11}/src/app/api/revalidateTag/route.ts +0 -0
  170. /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
  171. /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
  172. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11}/src/app/favicon.ico +0 -0
  173. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-0-11}/src/app/globals.css +0 -0
  174. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11}/src/app/layout.tsx +0 -0
  175. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-0-11}/src/app/page.tsx +0 -0
  176. /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
  177. /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
  178. /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
  179. /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
  180. /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
  181. /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
  182. /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
  183. /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
  184. /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
  185. /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
  186. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-0-11}/src/app/revalidation-interface.tsx +0 -0
  187. /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
  188. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-0-11}/tsconfig.json +0 -0
  189. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/README.md +0 -0
  190. /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
  191. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11-cache-components}/eslint.config.mjs +0 -0
  192. /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
  193. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11-cache-components}/postcss.config.mjs +0 -0
  194. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/file.svg +0 -0
  195. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/globe.svg +0 -0
  196. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/next.svg +0 -0
  197. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/vercel.svg +0 -0
  198. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/public/window.svg +0 -0
  199. /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
  200. /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
  201. /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
  202. /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
  203. /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
  204. /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
  205. /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
  206. /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
  207. /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
  208. /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
  209. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/favicon.ico +0 -0
  210. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/globals.css +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/layout.tsx +0 -0
  212. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-16-0-11-cache-components}/src/app/page.tsx +0 -0
  213. /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
  214. /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
  215. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-0-11-cache-components}/tsconfig.json +0 -0
  216. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6}/README.md +0 -0
  217. /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6}/next.config.ts +0 -0
  218. /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
  219. /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
  220. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/api/revalidatePath/route.ts +0 -0
  221. /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6}/src/app/api/revalidateTag/route.ts +0 -0
  222. /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
  223. /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
  224. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6}/src/app/globals.css +0 -0
  225. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6}/src/app/layout.tsx +0 -0
  226. /package/test/{integration/next-app-16-2-3-cache-components → nextjs-test-projects/next-app-16-2-6}/src/app/page.tsx +0 -0
  227. /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
  228. /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
  229. /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
  230. /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
  231. /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
  232. /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
  233. /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
  234. /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
  235. /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
  236. /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
  237. /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
  238. /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
  239. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/README.md +0 -0
  240. /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/eslint.config.mjs +0 -0
  241. /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/postcss.config.mjs +0 -0
  242. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/file.svg +0 -0
  243. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/globe.svg +0 -0
  244. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/next.svg +0 -0
  245. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/vercel.svg +0 -0
  246. /package/test/{integration/next-app-15-4-7 → nextjs-test-projects/next-app-16-2-6-cache-components}/public/window.svg +0 -0
  247. /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/src/app/favicon.ico +0 -0
  248. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/src/app/globals.css +0 -0
  249. /package/test/{integration/next-app-16-0-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/src/app/page.tsx +0 -0
  250. /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
  251. /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-16-2-6-cache-components}/tsconfig.json +0 -0
  252. /package/test/{integration/next-app-16-2-3 → nextjs-test-projects/next-app-customized}/README.md +0 -0
  253. /package/test/{integration → nextjs-test-projects}/next-app-customized/customized-cache-handler.js +0 -0
  254. /package/test/{integration/next-app-15-3-2 → nextjs-test-projects/next-app-customized}/eslint.config.mjs +0 -0
  255. /package/test/{integration → nextjs-test-projects}/next-app-customized/next.config.js +0 -0
  256. /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() {
@@ -448,6 +479,16 @@ function bufferAndMapReplacer(_, value) {
448
479
  return value;
449
480
  }
450
481
 
482
+ // src/serializer.ts
483
+ var jsonCacheValueSerializer = {
484
+ serialize(value) {
485
+ return JSON.stringify(value, bufferAndMapReplacer);
486
+ },
487
+ deserialize(stored) {
488
+ return JSON.parse(stored, bufferAndMapReviver);
489
+ }
490
+ };
491
+
451
492
  // src/RedisStringsHandler.ts
452
493
  function redisErrorHandler(debugInfo, redisCommandResult) {
453
494
  const beforeTimestamp = performance.now();
@@ -499,7 +540,8 @@ var RedisStringsHandler = class {
499
540
  estimateExpireAge = (staleAge) => process.env.VERCEL_ENV === "production" ? staleAge * 2 : staleAge * 1.2,
500
541
  killContainerOnErrorThreshold = process.env.KILL_CONTAINER_ON_ERROR_THRESHOLD ? Number.parseInt(process.env.KILL_CONTAINER_ON_ERROR_THRESHOLD) ?? 0 : 0,
501
542
  socketOptions,
502
- clientOptions
543
+ clientOptions,
544
+ valueSerializer = jsonCacheValueSerializer
503
545
  }) {
504
546
  this.clientReadyCalls = 0;
505
547
  try {
@@ -510,6 +552,7 @@ var RedisStringsHandler = class {
510
552
  this.estimateExpireAge = estimateExpireAge;
511
553
  this.killContainerOnErrorThreshold = killContainerOnErrorThreshold;
512
554
  this.getTimeoutMs = getTimeoutMs;
555
+ this.valueSerializer = valueSerializer;
513
556
  try {
514
557
  this.client = createClient({
515
558
  url: redisUrl,
@@ -679,10 +722,17 @@ var RedisStringsHandler = class {
679
722
  if (!serializedCacheEntry) {
680
723
  return null;
681
724
  }
682
- const cacheEntry = JSON.parse(
683
- serializedCacheEntry,
684
- bufferAndMapReviver
685
- );
725
+ let cacheEntry;
726
+ try {
727
+ cacheEntry = await this.valueSerializer.deserialize(serializedCacheEntry);
728
+ } catch (err) {
729
+ console.warn(
730
+ "RedisStringsHandler.get() valueSerializer.deserialize failed, treating as cache miss",
731
+ this.keyPrefix + key,
732
+ err
733
+ );
734
+ return null;
735
+ }
686
736
  debug(
687
737
  "green",
688
738
  "RedisStringsHandler.get() finished with result (cacheEntry)",
@@ -792,10 +842,7 @@ var RedisStringsHandler = class {
792
842
  tags: ctx?.tags || [],
793
843
  value: data
794
844
  };
795
- const serializedCacheEntry = JSON.stringify(
796
- cacheEntry,
797
- bufferAndMapReplacer
798
- );
845
+ const serializedCacheEntry = await this.valueSerializer.serialize(cacheEntry);
799
846
  if (this.redisGetDeduplication) {
800
847
  this.redisDeduplicationHandler.seedRequestReturn(
801
848
  key,
@@ -1052,6 +1099,8 @@ var RedisCacheComponentsHandler = class {
1052
1099
  getTimeoutMs = process.env.REDIS_COMMAND_TIMEOUT_MS ? Number.parseInt(process.env.REDIS_COMMAND_TIMEOUT_MS) ?? 500 : 500,
1053
1100
  revalidateTagQuerySize = 250,
1054
1101
  avgResyncIntervalMs = 60 * 60 * 1e3,
1102
+ redisGetDeduplication = true,
1103
+ inMemoryCachingTime = 1e4,
1055
1104
  socketOptions,
1056
1105
  clientOptions,
1057
1106
  killContainerOnErrorThreshold = process.env.KILL_CONTAINER_ON_ERROR_THRESHOLD ? Number.parseInt(process.env.KILL_CONTAINER_ON_ERROR_THRESHOLD) ?? 0 : 0,
@@ -1064,6 +1113,8 @@ var RedisCacheComponentsHandler = class {
1064
1113
  env: process.env
1065
1114
  });
1066
1115
  this.getTimeoutMs = getTimeoutMs;
1116
+ this.redisGetDeduplication = redisGetDeduplication;
1117
+ this.inMemoryCachingTime = inMemoryCachingTime;
1067
1118
  this.client = createClient2({
1068
1119
  url: redisUrl,
1069
1120
  pingInterval: 1e4,
@@ -1130,6 +1181,26 @@ var RedisCacheComponentsHandler = class {
1130
1181
  filterKeys,
1131
1182
  resyncIntervalMs: avgResyncIntervalMs - avgResyncIntervalMs / 10 + Math.random() * (avgResyncIntervalMs / 10)
1132
1183
  });
1184
+ this.inMemoryDeduplicationCache = new SyncedMap({
1185
+ client: this.client,
1186
+ keyPrefix: this.keyPrefix,
1187
+ redisKey: "__cacheComponents_inMemoryDeduplicationCache__",
1188
+ database,
1189
+ querySize: revalidateTagQuerySize,
1190
+ filterKeys,
1191
+ customizedSync: {
1192
+ withoutRedisHashmap: true,
1193
+ withoutSetSync: true
1194
+ }
1195
+ });
1196
+ const redisGet = this.client.get.bind(this.client);
1197
+ this.redisDeduplicationHandler = new DeduplicatedRequestHandler(
1198
+ redisGet,
1199
+ inMemoryCachingTime,
1200
+ this.inMemoryDeduplicationCache
1201
+ );
1202
+ this.redisGet = redisGet;
1203
+ this.deduplicatedRedisGet = this.redisDeduplicationHandler.deduplicatedFunction;
1133
1204
  } catch (error) {
1134
1205
  console.error("RedisCacheComponentsHandler constructor error", error);
1135
1206
  throw error;
@@ -1164,9 +1235,10 @@ var RedisCacheComponentsHandler = class {
1164
1235
  const redisKey = `${this.keyPrefix}${cacheKey}`;
1165
1236
  try {
1166
1237
  await this.assertClientIsReady();
1238
+ const clientGet = this.redisGetDeduplication ? this.deduplicatedRedisGet(cacheKey) : this.redisGet;
1167
1239
  const serialized = await redisErrorHandler(
1168
- "RedisCacheComponentsHandler.get(), operation: get " + this.getTimeoutMs + "ms " + redisKey,
1169
- this.client.get(
1240
+ "RedisCacheComponentsHandler.get(), operation: get" + (this.redisGetDeduplication ? " deduplicated" : "") + " " + this.getTimeoutMs + "ms " + redisKey,
1241
+ clientGet(
1170
1242
  commandOptions2({ signal: AbortSignal.timeout(this.getTimeoutMs) }),
1171
1243
  redisKey
1172
1244
  )
@@ -1239,6 +1311,9 @@ var RedisCacheComponentsHandler = class {
1239
1311
  console.error("Stored object:", stored);
1240
1312
  throw jsonError;
1241
1313
  }
1314
+ if (this.redisGetDeduplication) {
1315
+ this.redisDeduplicationHandler.seedRequestReturn(cacheKey, serialized);
1316
+ }
1242
1317
  const ttlSeconds = Number.isFinite(stored.expire) && stored.expire > 0 ? Math.floor(stored.expire) : void 0;
1243
1318
  const redisKey = `${this.keyPrefix}${cacheKey}`;
1244
1319
  const setOperation = redisErrorHandler(
@@ -1305,6 +1380,11 @@ var RedisCacheComponentsHandler = class {
1305
1380
  "RedisCacheComponentsHandler.updateTags(), operation: unlink",
1306
1381
  this.client.unlink(fullRedisKeys)
1307
1382
  );
1383
+ if (this.redisGetDeduplication && this.inMemoryCachingTime > 0) {
1384
+ for (const key of keysToDelete) {
1385
+ this.inMemoryDeduplicationCache.delete(key);
1386
+ }
1387
+ }
1308
1388
  const deleteTagsOperation = this.sharedTagsMap.delete(cacheKeys);
1309
1389
  await deleteTagsOperation;
1310
1390
  } catch (error) {
@@ -1330,8 +1410,11 @@ var redisCacheHandler = getRedisCacheComponentsHandler();
1330
1410
  var index_default = CachedHandler;
1331
1411
  export {
1332
1412
  RedisStringsHandler,
1413
+ bufferAndMapReplacer,
1414
+ bufferAndMapReviver,
1333
1415
  index_default as default,
1334
1416
  getRedisCacheComponentsHandler,
1417
+ jsonCacheValueSerializer,
1335
1418
  redisCacheHandler
1336
1419
  };
1337
1420
  //# sourceMappingURL=index.mjs.map