@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,65 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { spawn } from 'child_process';
3
- import path from 'path';
4
- import { spawnSync } from 'child_process';
5
-
6
- function hasContainerRuntime() {
7
- const podman = spawnSync('podman', ['--version'], {
8
- encoding: 'utf8',
9
- timeout: 3_000,
10
- });
11
- if (!podman.error && (podman.status ?? 1) === 0) return true;
12
-
13
- const docker = spawnSync('docker', ['--version'], {
14
- encoding: 'utf8',
15
- timeout: 3_000,
16
- });
17
- return !docker.error && (docker.status ?? 1) === 0;
18
- }
19
-
20
- function runNode(script: string, timeoutMs = 120_000) {
21
- return new Promise<{ code: number; stdout: string; stderr: string }>(
22
- (resolve, reject) => {
23
- const p = spawn('pnpm', ['-s', 'tsx', script], {
24
- stdio: ['ignore', 'pipe', 'pipe'],
25
- env: { ...process.env },
26
- });
27
- let stdout = '';
28
- let stderr = '';
29
- p.stdout.on('data', (d) => (stdout += d.toString()));
30
- p.stderr.on('data', (d) => (stderr += d.toString()));
31
- const t = setTimeout(() => {
32
- p.kill('SIGKILL');
33
- reject(new Error('timeout'));
34
- }, timeoutMs);
35
- p.on('close', (code) => {
36
- clearTimeout(t);
37
- resolve({ code: code ?? -1, stdout, stderr });
38
- });
39
- },
40
- );
41
- }
42
-
43
- describe('redis kill/reconnect end-to-end (both handlers)', () => {
44
- const runOrSkip = hasContainerRuntime() ? it : it.skip;
45
-
46
- runOrSkip(
47
- 'survives redis restart without Socket already opened',
48
- async () => {
49
- const script = path.join(__dirname, 'scripts', 'redis-kill-reconnect.ts');
50
-
51
- const res = await runNode(script, 180_000);
52
-
53
- if (res.code !== 0) {
54
- console.error('Script exited with code', res.code);
55
- console.error('stdout:', res.stdout);
56
- console.error('stderr:', res.stderr);
57
- }
58
-
59
- expect(res.code).toBe(0);
60
- expect(res.stdout).toContain('OK');
61
- expect(res.stderr).not.toContain('Socket already opened');
62
- },
63
- 180_000,
64
- );
65
- });
@@ -1,59 +0,0 @@
1
- import { describe, it, expect, beforeAll } from 'vitest';
2
- import path from 'path';
3
- import {
4
- hasContainerRuntime,
5
- runNode,
6
- parseResults,
7
- type TestResult,
8
- } from './scripts/redis-test-helpers';
9
-
10
- const describeOrSkip = hasContainerRuntime() ? describe : describe.skip;
11
-
12
- describeOrSkip('subscriber teardown: quit() vs disconnect()', () => {
13
- let results: Map<string, TestResult>;
14
- let exitCode: number;
15
- let stderr: string;
16
-
17
- beforeAll(async () => {
18
- const script = path.join(
19
- __dirname,
20
- 'scripts',
21
- 'redis-quit-vs-disconnect.ts',
22
- );
23
- const res = await runNode(script, 60_000);
24
- exitCode = res.code;
25
- stderr = res.stderr;
26
- results = parseResults(res.stdout);
27
- }, 60_000);
28
-
29
- it('script exits successfully', () => {
30
- if (exitCode !== 0) {
31
- process.stderr.write(stderr);
32
- }
33
- expect(exitCode).toBe(0);
34
- });
35
-
36
- it('subscriber clients are ready before outage', () => {
37
- const r = results.get('subscribers-ready');
38
- expect(r).toBeDefined();
39
- expect(r!.pass, r!.detail).toBe(true);
40
- });
41
-
42
- it('quit() does not immediately close a subscriber during outage', () => {
43
- const r = results.get('quit-does-not-immediately-close');
44
- expect(r).toBeDefined();
45
- expect(r!.pass, r!.detail).toBe(true);
46
- });
47
-
48
- it('disconnect() closes the subscriber immediately during outage', () => {
49
- const r = results.get('disconnect-closes-immediately');
50
- expect(r).toBeDefined();
51
- expect(r!.pass, r!.detail).toBe(true);
52
- });
53
-
54
- it('disconnected subscriber stays dead after Redis restarts', () => {
55
- const r = results.get('disconnected-client-stays-dead');
56
- expect(r).toBeDefined();
57
- expect(r!.pass, r!.detail).toBe(true);
58
- });
59
- });
@@ -1,100 +0,0 @@
1
- import { describe, it, expect, beforeAll } from 'vitest';
2
- import path from 'path';
3
- import {
4
- hasContainerRuntime,
5
- runNode,
6
- parseResults,
7
- type TestResult,
8
- } from './scripts/redis-test-helpers';
9
-
10
- const describeOrSkip = hasContainerRuntime() ? describe : describe.skip;
11
-
12
- describeOrSkip('issue #86: subscriber outage recovery', () => {
13
- let results: Map<string, TestResult>;
14
-
15
- beforeAll(async () => {
16
- const script = path.join(
17
- __dirname,
18
- 'scripts',
19
- 'redis-subscriber-outage.ts',
20
- );
21
- const res = await runNode(script, 300_000);
22
- // Script always exits 0 (it records pass/fail per sub-test)
23
- if (res.code !== 0) {
24
- throw new Error(
25
- `Script exited with code ${res.code}\nstdout:\n${res.stdout}\nstderr:\n${res.stderr}`,
26
- );
27
- }
28
- results = parseResults(res.stdout);
29
- }, 300_000);
30
-
31
- // --- Tests that should PASS (verify correct behavior before outage) ---
32
-
33
- it('PubSub sync works before outage', () => {
34
- const r = results.get('pubsub-initial-strings');
35
- expect(r).toBeDefined();
36
- expect(r!.pass, r!.detail).toBe(true);
37
- });
38
-
39
- it('subscriber clients have error listeners before outage', () => {
40
- const r = results.get('error-listener-initial');
41
- expect(r).toBeDefined();
42
- expect(r!.pass, r!.detail).toBe(true);
43
- });
44
-
45
- // --- Tests that verify symptoms during outage ---
46
-
47
- it('get() returns null during outage', () => {
48
- const r = results.get('get-during-outage');
49
- expect(r).toBeDefined();
50
- expect(r!.pass, r!.detail).toBe(true);
51
- });
52
-
53
- it('heap does not grow excessively during 50 get() calls in outage', () => {
54
- const r = results.get('heap-growth-during-outage');
55
- expect(r).toBeDefined();
56
- expect(r!.pass, r!.detail).toBe(true);
57
- });
58
-
59
- it('main client reconnects after Redis restart', () => {
60
- const r = results.get('main-client-reconnects');
61
- expect(r).toBeDefined();
62
- expect(r!.pass, r!.detail).toBe(true);
63
- });
64
-
65
- it('get() works after Redis restart (main client path)', () => {
66
- const r = results.get('get-after-outage');
67
- expect(r).toBeDefined();
68
- expect(r!.pass, r!.detail).toBe(true);
69
- });
70
-
71
- // --- Tests that reproduce the bug (EXPECTED TO FAIL with current code) ---
72
-
73
- it('PubSub sync works after outage (issue #86 — subscriber never recovers)', () => {
74
- const r = results.get('pubsub-after-outage-strings');
75
- expect(r).toBeDefined();
76
- expect(r!.pass, r!.detail).toBe(true);
77
- });
78
-
79
- it('subscriber error listener state after outage (informational)', () => {
80
- const r = results.get('error-listener-after-outage');
81
- expect(r).toBeDefined();
82
- // Informational — always passes. Detail shows whether the listener
83
- // survived and whether the subscriber client object was replaced.
84
- expect(r!.pass, r!.detail).toBe(true);
85
- });
86
-
87
- it('duplicate() call count during outage (informational)', () => {
88
- const r = results.get('duplicate-count-during-outage');
89
- expect(r).toBeDefined();
90
- // Informational — always passes. Detail has the actual count.
91
- expect(r!.pass, r!.detail).toBe(true);
92
- });
93
-
94
- it('total duplicate() call count (informational)', () => {
95
- const r = results.get('duplicate-count-total');
96
- expect(r).toBeDefined();
97
- // Informational — always passes. Detail has the actual count.
98
- expect(r!.pass, r!.detail).toBe(true);
99
- });
100
- });
@@ -1,243 +0,0 @@
1
- import { spawnSync } from 'child_process';
2
-
3
- type ContainerRuntime = 'podman' | 'docker';
4
-
5
- function sh(cmd: string, args: string[], opts: { timeoutMs?: number } = {}) {
6
- const res = spawnSync(cmd, args, {
7
- encoding: 'utf8',
8
- timeout: opts.timeoutMs ?? 30_000,
9
- });
10
- if (res.error) throw res.error;
11
- return {
12
- code: res.status ?? -1,
13
- stdout: res.stdout ?? '',
14
- stderr: res.stderr ?? '',
15
- };
16
- }
17
-
18
- function canRun(cmd: string) {
19
- const res = spawnSync(cmd, ['--version'], {
20
- encoding: 'utf8',
21
- timeout: 3_000,
22
- });
23
- return !res.error && (res.status ?? 1) === 0;
24
- }
25
-
26
- function detectContainerRuntime(): ContainerRuntime {
27
- if (process.env.CONTAINER_RUNTIME === 'podman') return 'podman';
28
- if (process.env.CONTAINER_RUNTIME === 'docker') return 'docker';
29
-
30
- if (canRun('podman')) return 'podman';
31
- if (canRun('docker')) return 'docker';
32
-
33
- throw new Error(
34
- 'Neither podman nor docker is available. Install one of them or set CONTAINER_RUNTIME.',
35
- );
36
- }
37
-
38
- async function sleep(ms: number) {
39
- await new Promise((r) => setTimeout(r, ms));
40
- }
41
-
42
- async function waitUntil(fn: () => Promise<boolean>, timeoutMs = 20_000) {
43
- const start = Date.now();
44
- // eslint-disable-next-line no-constant-condition
45
- while (true) {
46
- if (await fn()) return;
47
- if (Date.now() - start > timeoutMs) throw new Error('waitUntil timeout');
48
- await sleep(200);
49
- }
50
- }
51
-
52
- async function main() {
53
- const runtime = detectContainerRuntime();
54
- const name = `redis-e2e-${Math.random().toString(36).slice(2, 8)}`;
55
-
56
- // Pick a free localhost port
57
- const net = await import('net');
58
- const port: number = await new Promise((resolve, reject) => {
59
- const srv = net.createServer();
60
- srv.on('error', reject);
61
- srv.listen(0, '127.0.0.1', () => {
62
- const addr = srv.address();
63
- if (!addr || typeof addr === 'string')
64
- return reject(new Error('bad addr'));
65
- const p = addr.port;
66
- srv.close(() => resolve(p));
67
- });
68
- });
69
-
70
- // cleanup
71
- sh(runtime, ['rm', '-f', name]);
72
-
73
- // Start redis with keyspace notifications enabled (required by SyncedMap)
74
- {
75
- const r = sh(
76
- runtime,
77
- [
78
- 'run',
79
- '-d',
80
- '--rm',
81
- '--name',
82
- name,
83
- '-p',
84
- `${port}:6379`,
85
- 'docker.io/redis:7-alpine',
86
- 'redis-server',
87
- '--notify-keyspace-events',
88
- 'Exe',
89
- ],
90
- { timeoutMs: 60_000 },
91
- );
92
- if (r.code !== 0) throw new Error(`podman run failed: ${r.stderr}`);
93
- }
94
-
95
- // Set env BEFORE importing in case any module-level code reads REDIS_URL.
96
- // (With lazy init the singleton is no longer created at import time, but
97
- // setting env first is still good practice.)
98
- process.env.REDIS_URL = `redis://127.0.0.1:${port}`;
99
- process.env.VERCEL_URL = `e2e-${name}-`;
100
-
101
- const { getRedisCacheComponentsHandler } = await import(
102
- '../../../../../src/CacheComponentsHandler'
103
- );
104
- const { default: RedisStringsHandler } = await import(
105
- '../../../../../src/RedisStringsHandler'
106
- );
107
-
108
- // Cache Components handler
109
- const cacheComponentsHandler = getRedisCacheComponentsHandler({
110
- redisUrl: `redis://127.0.0.1:${port}`,
111
- socketOptions: {
112
- connectTimeout: 2_000,
113
- // allow redis client to reconnect; this scenario is about stability under restart
114
- reconnectStrategy: (retries) => Math.min(50 + retries * 50, 500),
115
- },
116
- });
117
-
118
- const cacheComponentsClient = (cacheComponentsHandler as any).client as {
119
- ping: () => Promise<string>;
120
- isReady: boolean;
121
- };
122
-
123
- await waitUntil(async () => cacheComponentsClient.isReady, 20_000);
124
- if ((await cacheComponentsClient.ping()) !== 'PONG') {
125
- throw new Error('expected PONG (cache components)');
126
- }
127
-
128
- // Regular handler
129
- const regularHandler = new RedisStringsHandler({
130
- redisUrl: `redis://127.0.0.1:${port}`,
131
- socketOptions: {
132
- connectTimeout: 2_000,
133
- reconnectStrategy: (retries: number) => Math.min(50 + retries * 50, 500),
134
- },
135
- // Keep default offline queue behavior here; SyncedMap does initial sync during construction.
136
- keyPrefix: `e2e-${name}-regular-`,
137
- } as any);
138
-
139
- const regularClient = (regularHandler as any).client as {
140
- ping: () => Promise<string>;
141
- isReady: boolean;
142
- };
143
-
144
- await waitUntil(async () => regularClient.isReady, 20_000);
145
- if ((await regularClient.ping()) !== 'PONG') {
146
- throw new Error('expected PONG (regular handler)');
147
- }
148
-
149
- // Seed a small entry so we know regular handler does real ops
150
- await regularHandler.set(
151
- 'e2e-key',
152
- {
153
- kind: 'FETCH',
154
- data: {
155
- headers: {},
156
- body: Buffer.from('hello').toString('base64'),
157
- status: 200,
158
- url: 'https://example.com/e2e',
159
- },
160
- revalidate: 10,
161
- },
162
- { isRoutePPREnabled: false, isFallback: false, tags: ['e2e'] },
163
- );
164
-
165
- const got = await regularHandler.get('e2e-key', {
166
- kind: 'FETCH',
167
- revalidate: 10,
168
- fetchUrl: 'https://example.com/e2e',
169
- fetchIdx: 0,
170
- tags: ['e2e'],
171
- softTags: [],
172
- isFallback: false,
173
- });
174
- if (!got) throw new Error('expected cache hit (regular handler)');
175
-
176
- // Kill redis
177
- sh(runtime, ['stop', '-t', '0', name], { timeoutMs: 30_000 });
178
-
179
- // Wait a bit, then start again
180
- await sleep(500);
181
-
182
- {
183
- const r = sh(
184
- runtime,
185
- [
186
- 'run',
187
- '-d',
188
- '--rm',
189
- '--name',
190
- name,
191
- '-p',
192
- `${port}:6379`,
193
- 'docker.io/redis:7-alpine',
194
- 'redis-server',
195
- '--notify-keyspace-events',
196
- 'Exe',
197
- ],
198
- { timeoutMs: 60_000 },
199
- );
200
- if (r.code !== 0) throw new Error(`${runtime} run2 failed: ${r.stderr}`);
201
- }
202
-
203
- // Should recover (both)
204
- await waitUntil(async () => {
205
- try {
206
- return (await cacheComponentsClient.ping()) === 'PONG';
207
- } catch {
208
- return false;
209
- }
210
- }, 30_000);
211
-
212
- await waitUntil(async () => {
213
- try {
214
- return (await regularClient.ping()) === 'PONG';
215
- } catch {
216
- return false;
217
- }
218
- }, 30_000);
219
-
220
- // Shut down clients before cleaning up Redis, otherwise reconnect loops keep the process alive.
221
- try {
222
- await (cacheComponentsHandler as any).client?.quit?.();
223
- } catch {}
224
- try {
225
- await (regularHandler as any).client?.quit?.();
226
- } catch {}
227
-
228
- // cleanup best effort
229
- sh(runtime, ['rm', '-f', name]);
230
-
231
- // If we reach here without crashing due to Socket already opened, we're good.
232
- // (Vitest will check stdout for this line.)
233
- process.stdout.write('OK\n');
234
-
235
- // Ensure we exit even if background reconnect timers exist.
236
- process.exit(0);
237
- }
238
-
239
- main().catch((err) => {
240
- // Ensure we don't hide the key error string
241
- console.error(err);
242
- process.exit(1);
243
- });
@@ -1,113 +0,0 @@
1
- /**
2
- * Demonstrates the difference between quit() and disconnect() on a
3
- * node-redis subscriber client while Redis is down.
4
- *
5
- * Scenario:
6
- * 1. Start Redis.
7
- * 2. Connect two subscriber clients and subscribe them.
8
- * 3. Kill Redis and wait for the reconnect loop to start.
9
- * 4. Subscriber A: call quit(). It should NOT resolve quickly while Redis is down.
10
- * 5. Subscriber B: call disconnect(). It must close the socket immediately.
11
- * 6. Restart Redis and verify that the disconnected subscriber stays dead.
12
- *
13
- * This shows why disconnect() is the correct teardown method during an outage.
14
- */
15
-
16
- import { createClient } from 'redis';
17
- import {
18
- detectContainerRuntime,
19
- getFreePort,
20
- record,
21
- sh,
22
- sleep,
23
- startRedis,
24
- } from './redis-test-helpers';
25
-
26
- async function createSubscriber(url: string) {
27
- const client = createClient({
28
- url,
29
- socket: {
30
- connectTimeout: 2_000,
31
- reconnectStrategy: (retries: number) => Math.min(50 + retries * 50, 500),
32
- },
33
- });
34
- client.on('error', () => {});
35
- await client.connect();
36
- await client.subscribe('test-channel', () => {});
37
- return client;
38
- }
39
-
40
- async function main() {
41
- const runtime = detectContainerRuntime();
42
- const name = `redis-quit-disc-${Math.random().toString(36).slice(2, 8)}`;
43
- const port = await getFreePort();
44
-
45
- sh(runtime, ['rm', '-f', name]);
46
- startRedis(runtime, name, port);
47
-
48
- const url = `redis://127.0.0.1:${port}`;
49
-
50
- const quitSubscriber = await createSubscriber(url);
51
- const disconnectSubscriber = await createSubscriber(url);
52
-
53
- record(
54
- 'subscribers-ready',
55
- quitSubscriber.isReady && disconnectSubscriber.isReady,
56
- `quitSub isReady=${quitSubscriber.isReady}, disconnectSub isReady=${disconnectSubscriber.isReady}`,
57
- );
58
-
59
- // Kill Redis and let both clients enter their reconnect loops
60
- sh(runtime, ['stop', '-t', '0', name], { timeoutMs: 30_000 });
61
- await sleep(1_500);
62
-
63
- // Test quit(): it should not resolve while Redis is unreachable.
64
- const quitResult = await Promise.race([
65
- quitSubscriber
66
- .quit()
67
- .then(() => 'resolved')
68
- .catch((e) => `error:${e.message ?? e}`),
69
- sleep(3_000).then(() => 'timeout'),
70
- ]);
71
-
72
- record(
73
- 'quit-does-not-immediately-close',
74
- quitResult !== 'resolved',
75
- `quit() result during outage: ${quitResult}`,
76
- );
77
-
78
- // Test disconnect(): it must close the client immediately, even during outage.
79
- const disconnectStart = Date.now();
80
- try {
81
- await disconnectSubscriber.disconnect();
82
- } catch (e: any) {
83
- // If the client is already closed, that's still a successful teardown.
84
- }
85
- const disconnectDuration = Date.now() - disconnectStart;
86
-
87
- record(
88
- 'disconnect-closes-immediately',
89
- !disconnectSubscriber.isOpen && !disconnectSubscriber.isReady,
90
- `disconnect() took ${disconnectDuration}ms, isOpen=${disconnectSubscriber.isOpen}, isReady=${disconnectSubscriber.isReady}`,
91
- );
92
-
93
- // Restart Redis and make sure the disconnected client does not come back.
94
- startRedis(runtime, name, port);
95
- await sleep(1_500);
96
-
97
- record(
98
- 'disconnected-client-stays-dead',
99
- !disconnectSubscriber.isOpen && !disconnectSubscriber.isReady,
100
- `after restart: isOpen=${disconnectSubscriber.isOpen}, isReady=${disconnectSubscriber.isReady}`,
101
- );
102
-
103
- // Also clean up the quit subscriber so we don't leak the process.
104
- try {
105
- await quitSubscriber.disconnect();
106
- } catch {}
107
- sh(runtime, ['rm', '-f', name]);
108
- }
109
-
110
- main().catch((err) => {
111
- process.stderr.write(String(err) + '\n');
112
- process.exit(1);
113
- });