stratal 0.0.27 → 0.1.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 (284) hide show
  1. package/CHANGELOG.md +912 -0
  2. package/README.md +96 -14
  3. package/dist/bin/cloudflare-workers-loader.mjs +18 -3
  4. package/dist/bin/cloudflare-workers-loader.mjs.map +1 -1
  5. package/dist/bin/quarry.mjs +73 -12
  6. package/dist/bin/quarry.mjs.map +1 -1
  7. package/dist/cache/index.d.mts +8 -9
  8. package/dist/cache/index.d.mts.map +1 -1
  9. package/dist/cache/index.mjs +56 -17
  10. package/dist/cache/index.mjs.map +1 -1
  11. package/dist/{cache.service-uElmBtdS.d.mts → cache.service-Dg0Hj0gp.d.mts} +27 -12
  12. package/dist/cache.service-Dg0Hj0gp.d.mts.map +1 -0
  13. package/dist/{command-DoBD2Cwl.d.mts → command-BXMccV_9.d.mts} +6 -3
  14. package/dist/command-BXMccV_9.d.mts.map +1 -0
  15. package/dist/{command-BvmUAPPQ.mjs → command-Bb820B6W.mjs} +36 -13
  16. package/dist/command-Bb820B6W.mjs.map +1 -0
  17. package/dist/common.schemas-BigQCwYB.mjs +89 -0
  18. package/dist/common.schemas-BigQCwYB.mjs.map +1 -0
  19. package/dist/config/index.d.mts +15 -15
  20. package/dist/config/index.d.mts.map +1 -1
  21. package/dist/config/index.mjs +4 -6
  22. package/dist/config/index.mjs.map +1 -1
  23. package/dist/{consumer-registry-D3iMTSdy.d.mts → consumer-registry-i9oCdarv.d.mts} +2 -3
  24. package/dist/consumer-registry-i9oCdarv.d.mts.map +1 -0
  25. package/dist/{container-storage-BmOJ4_Na.mjs → container-storage-DfF7SXdV.mjs} +11 -1
  26. package/dist/container-storage-DfF7SXdV.mjs.map +1 -0
  27. package/dist/{controller.decorator-YSTPQntu.mjs → controller.decorator-BB0JcwMg.mjs} +18 -9
  28. package/dist/controller.decorator-BB0JcwMg.mjs.map +1 -0
  29. package/dist/cron/index.d.mts +6 -7
  30. package/dist/cron/index.d.mts.map +1 -1
  31. package/dist/cron/index.mjs +1 -1
  32. package/dist/{cron-job-NesZRk8F.d.mts → cron-job-DAgNw4Bn.d.mts} +1 -2
  33. package/dist/cron-job-DAgNw4Bn.d.mts.map +1 -0
  34. package/dist/{cron.module-C81HTzR7.mjs → cron.module-Dnyl9sFk.mjs} +6 -7
  35. package/dist/{cron.module-C81HTzR7.mjs.map → cron.module-Dnyl9sFk.mjs.map} +1 -1
  36. package/dist/{decorate-CuAoSZvs.mjs → decorate-C79yt3Wt.mjs} +2 -2
  37. package/dist/di/index.d.mts +1 -1
  38. package/dist/di/index.mjs +2 -3
  39. package/dist/{di-D7qmrAir.mjs → di-DQ3xbjgf.mjs} +48 -6
  40. package/dist/di-DQ3xbjgf.mjs.map +1 -0
  41. package/dist/email/index.d.mts +158 -133
  42. package/dist/email/index.d.mts.map +1 -1
  43. package/dist/email/index.mjs +66 -78
  44. package/dist/email/index.mjs.map +1 -1
  45. package/dist/{en-CDZBMcc1.mjs → en-CCL84z4k.mjs} +2 -2
  46. package/dist/{en-CDZBMcc1.mjs.map → en-CCL84z4k.mjs.map} +1 -1
  47. package/dist/env-ug22bJj7.d.mts.map +1 -1
  48. package/dist/errors/index.d.mts +1 -1
  49. package/dist/errors/index.mjs +3 -3
  50. package/dist/{errors-C01O2T-n.mjs → errors-CrOXI_y5.mjs} +6 -8
  51. package/dist/errors-CrOXI_y5.mjs.map +1 -0
  52. package/dist/events/index.d.mts +8 -9
  53. package/dist/events/index.d.mts.map +1 -1
  54. package/dist/events/index.mjs +1 -1
  55. package/dist/{events-BhEQuT1X.mjs → events-GtwGEHyT.mjs} +5 -6
  56. package/dist/events-GtwGEHyT.mjs.map +1 -0
  57. package/dist/{exception-context-D-kvney-.mjs → exception-context-CdnGTQNF.mjs} +13 -7
  58. package/dist/exception-context-CdnGTQNF.mjs.map +1 -0
  59. package/dist/{gateway-context-m7kEzRa2.mjs → gateway-context-Cmc1NRS0.mjs} +7 -7
  60. package/dist/{gateway-context-m7kEzRa2.mjs.map → gateway-context-Cmc1NRS0.mjs.map} +1 -1
  61. package/dist/gateway-mode-0rUaeW3g.mjs +53 -0
  62. package/dist/gateway-mode-0rUaeW3g.mjs.map +1 -0
  63. package/dist/guards/index.d.mts +38 -20
  64. package/dist/guards/index.d.mts.map +1 -1
  65. package/dist/guards/index.mjs +2 -2
  66. package/dist/{guards-DALPXy3_.mjs → guards-DOGly9IF.mjs} +34 -11
  67. package/dist/guards-DOGly9IF.mjs.map +1 -0
  68. package/dist/hono-app-IhdqL0Fk.mjs +399 -0
  69. package/dist/hono-app-IhdqL0Fk.mjs.map +1 -0
  70. package/dist/{http-method.decorator-BljM8BDj.mjs → http-method.decorator-BDi9LTWj.mjs} +9 -8
  71. package/dist/http-method.decorator-BDi9LTWj.mjs.map +1 -0
  72. package/dist/i18n/index.d.mts +26 -137
  73. package/dist/i18n/index.d.mts.map +1 -1
  74. package/dist/i18n/index.mjs +4 -5
  75. package/dist/i18n/index.mjs.map +1 -1
  76. package/dist/i18n/messages/en/index.d.mts +1 -1
  77. package/dist/i18n/messages/en/index.mjs +1 -1
  78. package/dist/i18n/utils/index.d.mts +1 -2
  79. package/dist/i18n/utils/index.d.mts.map +1 -1
  80. package/dist/i18n/validation/index.d.mts +80 -5
  81. package/dist/i18n/validation/index.d.mts.map +1 -0
  82. package/dist/i18n/validation/index.mjs +4 -5
  83. package/dist/{i18n.module-B2DvWUPa.mjs → i18n.module-Bm3xlViA.mjs} +28 -13
  84. package/dist/i18n.module-Bm3xlViA.mjs.map +1 -0
  85. package/dist/index-0ItCjaqw.d.mts.map +1 -1
  86. package/dist/{index-CNuFQSNj.d.mts → index-3HJfhLlE.d.mts} +9 -11
  87. package/dist/index-3HJfhLlE.d.mts.map +1 -0
  88. package/dist/{index-uybm0bhQ.d.mts → index-B2WtEagR.d.mts} +1187 -234
  89. package/dist/index-B2WtEagR.d.mts.map +1 -0
  90. package/dist/index-BUt92sAE.d.mts.map +1 -1
  91. package/dist/index-CAHNCpvr.d.mts +203 -0
  92. package/dist/index-CAHNCpvr.d.mts.map +1 -0
  93. package/dist/index.d.mts +4 -4
  94. package/dist/index.mjs +2 -2
  95. package/dist/{is-command-CEPO9n8c.mjs → is-command-CSVv14wh.mjs} +2 -2
  96. package/dist/{is-command-CEPO9n8c.mjs.map → is-command-CSVv14wh.mjs.map} +1 -1
  97. package/dist/{is-seeder-Gvh_AM71.mjs → is-seeder-gLgXYbqC.mjs} +2 -2
  98. package/dist/is-seeder-gLgXYbqC.mjs.map +1 -0
  99. package/dist/{lazy-module-loader-M6YKudNL.d.mts → lazy-module-loader-C3OHoUnR.d.mts} +4 -5
  100. package/dist/lazy-module-loader-C3OHoUnR.d.mts.map +1 -0
  101. package/dist/limit-BE0HNAF6.mjs +116 -0
  102. package/dist/limit-BE0HNAF6.mjs.map +1 -0
  103. package/dist/locale-path.service-BqzDIrgy.mjs +213 -0
  104. package/dist/locale-path.service-BqzDIrgy.mjs.map +1 -0
  105. package/dist/locale-url-nZrZxqJP.mjs.map +1 -1
  106. package/dist/{locale-url.service-6bgia24_.mjs → locale-url.service-H8PH6GGh.mjs} +15 -5
  107. package/dist/{locale-url.service-6bgia24_.mjs.map → locale-url.service-H8PH6GGh.mjs.map} +1 -1
  108. package/dist/logger/index.mjs +3 -5
  109. package/dist/logger/index.mjs.map +1 -1
  110. package/dist/macroable-cvDTFZ_A.mjs.map +1 -1
  111. package/dist/metadata-Ba8Fxs1F.mjs +48 -0
  112. package/dist/metadata-Ba8Fxs1F.mjs.map +1 -0
  113. package/dist/module/index.d.mts +8 -9
  114. package/dist/module/index.d.mts.map +1 -1
  115. package/dist/module/index.mjs +3 -4
  116. package/dist/module/index.mjs.map +1 -1
  117. package/dist/{module-registry-NxX5O0Qk.mjs → module-registry-lkQPDuQe.mjs} +17 -15
  118. package/dist/module-registry-lkQPDuQe.mjs.map +1 -0
  119. package/dist/{module.decorator-CYHY6pG5.mjs → module.decorator-CYbrOlX9.mjs} +2 -2
  120. package/dist/{module.decorator-CYHY6pG5.mjs.map → module.decorator-CYbrOlX9.mjs.map} +1 -1
  121. package/dist/openapi/index.d.mts +19 -16
  122. package/dist/openapi/index.d.mts.map +1 -1
  123. package/dist/openapi/index.mjs +2 -2
  124. package/dist/{openapi-CMwuCp31.mjs → openapi-DBfBa5Qh.mjs} +41 -65
  125. package/dist/openapi-DBfBa5Qh.mjs.map +1 -0
  126. package/dist/openapi-generator-CCwiu8AU.mjs +134 -0
  127. package/dist/openapi-generator-CCwiu8AU.mjs.map +1 -0
  128. package/dist/{openapi-tools.service-BC5EC3R3.mjs → openapi-tools.service-VKZJlTIX.mjs} +12 -4
  129. package/dist/openapi-tools.service-VKZJlTIX.mjs.map +1 -0
  130. package/dist/openapi.service-Ds-R1tih.d.mts +43 -0
  131. package/dist/openapi.service-Ds-R1tih.d.mts.map +1 -0
  132. package/dist/quarry/index.d.mts +10 -11
  133. package/dist/quarry/index.d.mts.map +1 -1
  134. package/dist/quarry/index.mjs +4 -4
  135. package/dist/quarry/runner.d.mts +19 -20
  136. package/dist/quarry/runner.d.mts.map +1 -1
  137. package/dist/quarry/runner.mjs +16 -13
  138. package/dist/quarry/runner.mjs.map +1 -1
  139. package/dist/{quarry-registry-DRnV-DDa.d.mts → quarry-registry-Ci4k58FZ.d.mts} +3 -4
  140. package/dist/quarry-registry-Ci4k58FZ.d.mts.map +1 -0
  141. package/dist/{quarry.module-CcGxU2dJ.mjs → quarry.module-BssRR8f0.mjs} +20 -37
  142. package/dist/quarry.module-BssRR8f0.mjs.map +1 -0
  143. package/dist/queue/index.d.mts +2 -2
  144. package/dist/queue/index.mjs +3 -3
  145. package/dist/queue/index.mjs.map +1 -1
  146. package/dist/{queue.module-CEs4_kEM.mjs → queue.module-DnAdSbR-.mjs} +40 -41
  147. package/dist/queue.module-DnAdSbR-.mjs.map +1 -0
  148. package/dist/{r2-storage.provider-BoZmR6Ut.mjs → r2-storage.provider-wU_6api4.mjs} +45 -6
  149. package/dist/r2-storage.provider-wU_6api4.mjs.map +1 -0
  150. package/dist/{rate-limit.decorator-Djs4oYDB.mjs → rate-limit.decorator-PYQ9EYqs.mjs} +10 -5
  151. package/dist/{rate-limit.decorator-Djs4oYDB.mjs.map → rate-limit.decorator-PYQ9EYqs.mjs.map} +1 -1
  152. package/dist/rate-limiter/index.d.mts +64 -19
  153. package/dist/rate-limiter/index.d.mts.map +1 -1
  154. package/dist/rate-limiter/index.mjs +67 -83
  155. package/dist/rate-limiter/index.mjs.map +1 -1
  156. package/dist/response-cache/index.d.mts +326 -0
  157. package/dist/response-cache/index.d.mts.map +1 -0
  158. package/dist/response-cache/index.mjs +511 -0
  159. package/dist/response-cache/index.mjs.map +1 -0
  160. package/dist/response-cache.error-B4D0zy9W.mjs +24 -0
  161. package/dist/response-cache.error-B4D0zy9W.mjs.map +1 -0
  162. package/dist/response-headers-BqA57S_X.mjs +158 -0
  163. package/dist/response-headers-BqA57S_X.mjs.map +1 -0
  164. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  165. package/dist/route-cache-binding-CrLh8tB6.mjs +390 -0
  166. package/dist/route-cache-binding-CrLh8tB6.mjs.map +1 -0
  167. package/dist/route-metadata-QQhbed1i.mjs +17 -0
  168. package/dist/route-metadata-QQhbed1i.mjs.map +1 -0
  169. package/dist/{route-name-DGoBOfPg.mjs → route-name-BslSbmMP.mjs} +15 -11
  170. package/dist/route-name-BslSbmMP.mjs.map +1 -0
  171. package/dist/route-registration.service-COKBZTsn.mjs +1265 -0
  172. package/dist/route-registration.service-COKBZTsn.mjs.map +1 -0
  173. package/dist/{route-registry-BvLJisvK.mjs → route-registry-DFz9_-N6.mjs} +6 -6
  174. package/dist/{route-registry-BvLJisvK.mjs.map → route-registry-DFz9_-N6.mjs.map} +1 -1
  175. package/dist/router/index.d.mts +2 -2
  176. package/dist/router/index.mjs +16 -15
  177. package/dist/{router-resolver-sUV_jTrU.mjs → router-resolver-B-dMSMIG.mjs} +7 -3
  178. package/dist/router-resolver-B-dMSMIG.mjs.map +1 -0
  179. package/dist/{router-DwyqEXgf.mjs → router-wlsVkGkM.mjs} +5 -14
  180. package/dist/{router-DwyqEXgf.mjs.map → router-wlsVkGkM.mjs.map} +1 -1
  181. package/dist/seeder/index.d.mts +12 -13
  182. package/dist/seeder/index.d.mts.map +1 -1
  183. package/dist/seeder/index.mjs +4 -4
  184. package/dist/{seeder-BPGY5rUb.mjs → seeder-BrAk7XIj.mjs} +5 -6
  185. package/dist/{seeder-BPGY5rUb.mjs.map → seeder-BrAk7XIj.mjs.map} +1 -1
  186. package/dist/{seeder-registry-DEvCycsT.mjs → seeder-registry-Bo5BgLbC.mjs} +5 -6
  187. package/dist/{seeder-registry-DEvCycsT.mjs.map → seeder-registry-Bo5BgLbC.mjs.map} +1 -1
  188. package/dist/{seeder.module-CIwQbdN4.mjs → seeder.module-DOoVgTsK.mjs} +5 -5
  189. package/dist/{seeder.module-CIwQbdN4.mjs.map → seeder.module-DOoVgTsK.mjs.map} +1 -1
  190. package/dist/signed-url-DIU0sK_6.mjs.map +1 -1
  191. package/dist/storage/index.d.mts +63 -13
  192. package/dist/storage/index.d.mts.map +1 -1
  193. package/dist/storage/index.mjs +2 -2
  194. package/dist/storage/providers/index.d.mts +18 -16
  195. package/dist/storage/providers/index.d.mts.map +1 -1
  196. package/dist/storage/providers/index.mjs +1 -1
  197. package/dist/{storage-C30X81CS.mjs → storage-DBWVYMnn.mjs} +167 -49
  198. package/dist/storage-DBWVYMnn.mjs.map +1 -0
  199. package/dist/storage-provider.interface-CinHpjl1.d.mts +315 -0
  200. package/dist/storage-provider.interface-CinHpjl1.d.mts.map +1 -0
  201. package/dist/storage.error-Ao38Qdjx.mjs +7 -0
  202. package/dist/storage.error-Ao38Qdjx.mjs.map +1 -0
  203. package/dist/{stratal-D5j_I14G.d.mts → stratal-Bs1_E8GQ.d.mts} +13 -3
  204. package/dist/stratal-Bs1_E8GQ.d.mts.map +1 -0
  205. package/dist/{stratal-BL6FKUM_.mjs → stratal-CiwNpWy2.mjs} +56 -23
  206. package/dist/stratal-CiwNpWy2.mjs.map +1 -0
  207. package/dist/{tiered-cache.service-Dv3BhxxE.d.mts → tiered-cache.service-Bz8bbvyb.d.mts} +16 -3
  208. package/dist/tiered-cache.service-Bz8bbvyb.d.mts.map +1 -0
  209. package/dist/trailing-slash-2SctvePW.mjs.map +1 -1
  210. package/dist/types-CmV_9xBD.d.mts.map +1 -1
  211. package/dist/{uri-iwofWJ_T.mjs → uri-BrOq148R.mjs} +13 -9
  212. package/dist/{uri-iwofWJ_T.mjs.map → uri-BrOq148R.mjs.map} +1 -1
  213. package/dist/{usage-generator-DAWYasuP.mjs → usage-generator-BW7EcO-C.mjs} +3 -3
  214. package/dist/{usage-generator-DAWYasuP.mjs.map → usage-generator-BW7EcO-C.mjs.map} +1 -1
  215. package/dist/{zod-eKqqhZ5_.mjs → validation-CZ_B0aXY.mjs} +38 -17
  216. package/dist/validation-CZ_B0aXY.mjs.map +1 -0
  217. package/dist/{validation.context-CRvmrhq7.mjs → validation.context-CdbwQWIr.mjs} +2 -2
  218. package/dist/{validation.context-CRvmrhq7.mjs.map → validation.context-CdbwQWIr.mjs.map} +1 -1
  219. package/dist/{versioning.service-CCa2oYMJ.mjs → versioning.service-DnF_4Lnd.mjs} +5 -5
  220. package/dist/{versioning.service-CCa2oYMJ.mjs.map → versioning.service-DnF_4Lnd.mjs.map} +1 -1
  221. package/dist/websocket/index.d.mts +12 -13
  222. package/dist/websocket/index.d.mts.map +1 -1
  223. package/dist/websocket/index.mjs +1 -1
  224. package/dist/workers/index.d.mts +70 -7
  225. package/dist/workers/index.d.mts.map +1 -1
  226. package/dist/workers/index.mjs +72 -3
  227. package/dist/workers/index.mjs.map +1 -1
  228. package/dist/{zod-wecrEVAs.d.mts → zod-BGisqLs3.d.mts} +9 -16
  229. package/dist/zod-BGisqLs3.d.mts.map +1 -0
  230. package/package.json +33 -25
  231. package/dist/cache.service-uElmBtdS.d.mts.map +0 -1
  232. package/dist/chunk-BBjsoOtd.mjs +0 -27
  233. package/dist/command-BvmUAPPQ.mjs.map +0 -1
  234. package/dist/command-DoBD2Cwl.d.mts.map +0 -1
  235. package/dist/consumer-registry-D3iMTSdy.d.mts.map +0 -1
  236. package/dist/container-storage-BmOJ4_Na.mjs.map +0 -1
  237. package/dist/controller.decorator-YSTPQntu.mjs.map +0 -1
  238. package/dist/cron-job-NesZRk8F.d.mts.map +0 -1
  239. package/dist/di-D7qmrAir.mjs.map +0 -1
  240. package/dist/errors-C01O2T-n.mjs.map +0 -1
  241. package/dist/events-BhEQuT1X.mjs.map +0 -1
  242. package/dist/exception-context-D-kvney-.mjs.map +0 -1
  243. package/dist/guards-DALPXy3_.mjs.map +0 -1
  244. package/dist/hono-app-COAgmutc.mjs +0 -168
  245. package/dist/hono-app-COAgmutc.mjs.map +0 -1
  246. package/dist/http-method.decorator-BljM8BDj.mjs.map +0 -1
  247. package/dist/i18n.module-B2DvWUPa.mjs.map +0 -1
  248. package/dist/index-CNuFQSNj.d.mts.map +0 -1
  249. package/dist/index-DtBNIFuP.d.mts +0 -42
  250. package/dist/index-DtBNIFuP.d.mts.map +0 -1
  251. package/dist/index-HgOLNruQ.d.mts +0 -203
  252. package/dist/index-HgOLNruQ.d.mts.map +0 -1
  253. package/dist/index-uybm0bhQ.d.mts.map +0 -1
  254. package/dist/is-seeder-Gvh_AM71.mjs.map +0 -1
  255. package/dist/lazy-module-loader-M6YKudNL.d.mts.map +0 -1
  256. package/dist/locale-path.service-CH0CaxwH.mjs +0 -165
  257. package/dist/locale-path.service-CH0CaxwH.mjs.map +0 -1
  258. package/dist/metadata-DzzprcID.mjs +0 -39
  259. package/dist/metadata-DzzprcID.mjs.map +0 -1
  260. package/dist/module-registry-NxX5O0Qk.mjs.map +0 -1
  261. package/dist/openapi-CMwuCp31.mjs.map +0 -1
  262. package/dist/openapi-tools.service-BC5EC3R3.mjs.map +0 -1
  263. package/dist/openapi.service-2rvJBCEg.d.mts +0 -50
  264. package/dist/openapi.service-2rvJBCEg.d.mts.map +0 -1
  265. package/dist/quarry-registry-DRnV-DDa.d.mts.map +0 -1
  266. package/dist/quarry.module-CcGxU2dJ.mjs.map +0 -1
  267. package/dist/queue.module-CEs4_kEM.mjs.map +0 -1
  268. package/dist/r2-storage.provider-BoZmR6Ut.mjs.map +0 -1
  269. package/dist/route-name-DGoBOfPg.mjs.map +0 -1
  270. package/dist/route-registration.service-CDPQKpm4.mjs +0 -918
  271. package/dist/route-registration.service-CDPQKpm4.mjs.map +0 -1
  272. package/dist/router-resolver-sUV_jTrU.mjs.map +0 -1
  273. package/dist/storage-C30X81CS.mjs.map +0 -1
  274. package/dist/storage-provider.interface-ClUwxz4S.d.mts +0 -201
  275. package/dist/storage-provider.interface-ClUwxz4S.d.mts.map +0 -1
  276. package/dist/storage.error-BStXPmO4.mjs +0 -8
  277. package/dist/storage.error-BStXPmO4.mjs.map +0 -1
  278. package/dist/stratal-BL6FKUM_.mjs.map +0 -1
  279. package/dist/stratal-D5j_I14G.d.mts.map +0 -1
  280. package/dist/tiered-cache.service-Dv3BhxxE.d.mts.map +0 -1
  281. package/dist/validation-CpOjviyT.mjs +0 -49
  282. package/dist/validation-CpOjviyT.mjs.map +0 -1
  283. package/dist/zod-eKqqhZ5_.mjs.map +0 -1
  284. package/dist/zod-wecrEVAs.d.mts.map +0 -1
@@ -1,11 +1,9 @@
1
- import { t as __exportAll } from "./chunk-BBjsoOtd.mjs";
2
- import { c as Request, l as Singleton, p as inject, r as DI_TOKENS, u as Transient } from "./di-D7qmrAir.mjs";
3
- import { a as ApplicationError, n as getContainer, t as containerStorage } from "./container-storage-BmOJ4_Na.mjs";
4
- import { n as __decorateParam, t as __decorate } from "./decorate-CuAoSZvs.mjs";
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { c as Request, l as Singleton, p as inject, r as DI_TOKENS, u as Transient } from "./di-DQ3xbjgf.mjs";
3
+ import { a as ApplicationError, n as getContainer, t as containerStorage } from "./container-storage-DfF7SXdV.mjs";
4
+ import { n as __decorateParam, t as __decorate } from "./decorate-C79yt3Wt.mjs";
5
5
  import { LOGGER_TOKENS } from "./logger/index.mjs";
6
- import "./errors-C01O2T-n.mjs";
7
- import { n as Module } from "./module.decorator-CYHY6pG5.mjs";
8
- import "./module/index.mjs";
6
+ import { n as Module } from "./module.decorator-CYbrOlX9.mjs";
9
7
  import { CACHE_TOKENS, CacheModule } from "./cache/index.mjs";
10
8
  import { t as QUEUE_TOKENS } from "./queue.tokens-DjHnFmre.mjs";
11
9
  import { t as I18N_TOKENS } from "./i18n.tokens-CZ_v8oyS.mjs";
@@ -54,36 +52,37 @@ let QueueManager = class QueueManager {
54
52
  failedConsumer = consumer.constructor.name;
55
53
  }
56
54
  }
57
- if (lastError !== void 0) if (message.attempts > this.maxRetries) {
58
- try {
59
- const binding = queueMessage.metadata?.binding;
60
- if (!binding) throw new QueueError(`Queue message ${queueMessage.id} has no binding metadata and cannot be recorded for retry. Messages must be dispatched through a Stratal queue sender (@InjectQueue).`);
61
- const failedJob = {
62
- id: queueMessage.id,
63
- queue: queueName,
64
- binding,
65
- type: queueMessage.type,
66
- message: queueMessage,
67
- error: {
68
- name: lastError.name,
69
- message: lastError.message,
70
- stack: lastError.stack
71
- },
72
- consumer: failedConsumer,
73
- attempts: message.attempts,
74
- failedAt: (/* @__PURE__ */ new Date()).toISOString()
75
- };
76
- await this.store.storeFailedJob(failedJob);
77
- } catch (error) {
78
- this.logger.error("Failed to persist failed queue job", error instanceof Error ? error : new Error(String(error)), {
79
- queue: queueName,
80
- messageId: queueMessage.id,
81
- type: queueMessage.type
82
- });
83
- }
84
- message.ack();
85
- } else message.retry();
86
- else {
55
+ if (lastError !== void 0) {
56
+ if (message.attempts > this.maxRetries) {
57
+ try {
58
+ const binding = queueMessage.metadata?.binding;
59
+ if (!binding) throw new QueueError(`Queue message ${queueMessage.id} has no binding metadata and cannot be recorded for retry. Messages must be dispatched through a Stratal queue sender (@InjectQueue).`);
60
+ const failedJob = {
61
+ id: queueMessage.id,
62
+ queue: queueName,
63
+ binding,
64
+ type: queueMessage.type,
65
+ message: queueMessage,
66
+ error: {
67
+ name: lastError.name,
68
+ message: lastError.message,
69
+ stack: lastError.stack
70
+ },
71
+ consumer: failedConsumer,
72
+ attempts: message.attempts,
73
+ failedAt: (/* @__PURE__ */ new Date()).toISOString()
74
+ };
75
+ await this.store.storeFailedJob(failedJob);
76
+ } catch (error) {
77
+ this.logger.error("Failed to persist failed queue job", error instanceof Error ? error : new Error(String(error)), {
78
+ queue: queueName,
79
+ messageId: queueMessage.id,
80
+ type: queueMessage.type
81
+ });
82
+ }
83
+ message.ack();
84
+ } else message.retry();
85
+ } else {
87
86
  await this.store.markProcessed(idempotencyKey);
88
87
  message.ack();
89
88
  }
@@ -143,7 +142,7 @@ let ConsumerRegistry = class ConsumerRegistry {
143
142
  getConsumerClasses(messageType) {
144
143
  const exactMatch = this.classesByType.get(messageType) ?? [];
145
144
  const wildcardMatch = this.classesByType.get("*") ?? [];
146
- return Array.from(new Set([...exactMatch, ...wildcardMatch]));
145
+ return Array.from(/* @__PURE__ */ new Set([...exactMatch, ...wildcardMatch]));
147
146
  }
148
147
  /**
149
148
  * Check if any consumers can handle a message type
@@ -297,7 +296,7 @@ let QueueStore = class QueueStore {
297
296
  return await this.cache.get(`${IDEM_PREFIX}${key}`) !== null;
298
297
  }
299
298
  async markProcessed(key) {
300
- await this.cache.put(`${IDEM_PREFIX}${key}`, "1", { expirationTtl: this.idempotencyTtl });
299
+ await this.cache.putDurable(`${IDEM_PREFIX}${key}`, "1", { expirationTtl: this.idempotencyTtl });
301
300
  }
302
301
  async storeFailedJob(job) {
303
302
  const metadata = {
@@ -308,7 +307,7 @@ let QueueStore = class QueueStore {
308
307
  attempts: job.attempts,
309
308
  failedAt: job.failedAt
310
309
  };
311
- await this.cache.put(`${FAILED_PREFIX}${job.id}`, JSON.stringify(job), { metadata });
310
+ await this.cache.putDurable(`${FAILED_PREFIX}${job.id}`, JSON.stringify(job), { metadata });
312
311
  }
313
312
  async getFailedJob(messageId) {
314
313
  return this.cache.get(`${FAILED_PREFIX}${messageId}`, "json");
@@ -617,4 +616,4 @@ QueueModule = _QueueModule = __decorate([Module({
617
616
  //#endregion
618
617
  export { CloudflareQueueProvider as a, QueueRegistry as c, QueueManager as d, QueueError as f, SyncQueueProvider as i, QueueSender as l, queue_module_exports as n, DEFAULT_STORE_BINDING as o, QueueProviderFactory as r, QueueStore as s, QueueModule as t, ConsumerRegistry as u };
619
618
 
620
- //# sourceMappingURL=queue.module-CEs4_kEM.mjs.map
619
+ //# sourceMappingURL=queue.module-DnAdSbR-.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.module-DnAdSbR-.mjs","names":[],"sources":["../src/queue/queue.error.ts","../src/queue/queue-manager.ts","../src/queue/consumer-registry.ts","../src/queue/queue-sender.ts","../src/queue/queue-registry.ts","../src/queue/queue-store.ts","../src/queue/providers/cloudflare-queue.provider.ts","../src/queue/providers/sync-queue.provider.ts","../src/queue/services/queue-provider-factory.ts","../src/queue/queue.module.ts"],"sourcesContent":["import { ApplicationError } from '../errors'\n\nexport class QueueError extends ApplicationError {}\n","import { inject } from '../di'\nimport { getContainer } from '../di/container-storage'\nimport { Transient } from '../di/decorators'\nimport { DI_TOKENS } from '../di/tokens'\nimport { LOGGER_TOKENS, type LoggerService } from '../logger'\nimport { type ConsumerRegistry } from './consumer-registry'\nimport type { FailedJob } from './failed-job'\nimport type { IQueueConsumer, QueueMessage } from './queue-consumer'\nimport { QueueError } from './queue.error'\nimport type { QueueModuleOptions } from './queue.module'\nimport type { QueueStore } from './queue-store'\nimport { QUEUE_TOKENS } from './queue.tokens'\n\nconst DEFAULT_MAX_RETRIES = 3\n\n@Transient(DI_TOKENS.Queue)\nexport class QueueManager {\n private readonly maxRetries: number\n\n constructor(\n @inject(DI_TOKENS.ConsumerRegistry) private readonly registry: ConsumerRegistry,\n @inject(LOGGER_TOKENS.LoggerService) private readonly logger: LoggerService,\n @inject(QUEUE_TOKENS.QueueStore) private readonly store: QueueStore,\n @inject(QUEUE_TOKENS.QueueModuleOptions) options: QueueModuleOptions,\n ) {\n this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES\n }\n\n async processBatch(queueName: string, batch: MessageBatch): Promise<void> {\n for (const message of batch.messages) {\n const queueMessage = message.body as QueueMessage\n const idempotencyKey = queueMessage.metadata?.idempotencyKey ?? queueMessage.id\n\n if (await this.store.isProcessed(idempotencyKey)) {\n message.ack()\n continue\n }\n\n // Resolve a fresh consumer instance per message from the active request\n // scope (handleQueue runs processBatch inside runInRequestScope), so\n // request-scoped consumer dependencies bind to this message's scope.\n const container = getContainer()\n const consumers = this.registry\n .getConsumerClasses(queueMessage.type)\n .map((ConsumerClass) => container.resolve<IQueueConsumer>(ConsumerClass))\n\n const results = await Promise.allSettled(\n consumers.map((consumer) => consumer.handle(queueMessage)),\n )\n\n let lastError: Error | undefined\n let failedConsumer: string | undefined\n\n for (let i = 0; i < results.length; i++) {\n const result = results[i]\n if (result.status === 'rejected') {\n const consumer = consumers[i]\n const errorInstance = result.reason instanceof Error\n ? result.reason\n : new Error(String(result.reason))\n\n this.logger.error('Queue message processing failed', errorInstance, {\n type: queueMessage.type,\n queue: queueName,\n messageId: queueMessage.id,\n idempotencyKey,\n })\n\n if (consumer.onError) {\n await consumer.onError(errorInstance, queueMessage)\n }\n\n lastError = errorInstance\n failedConsumer = consumer.constructor.name\n }\n }\n\n const failed = lastError !== undefined\n\n if (failed) {\n // `message.attempts` is 1-based (the first delivery is attempt 1), so a\n // message is only out of retries once it has been delivered more than\n // `maxRetries` times. `maxRetries: 3` therefore means 3 retries after the\n // initial delivery (4 total attempts). This must be <= the consumer's\n // `max_retries` in wrangler.jsonc, otherwise Cloudflare dead-letters the\n // message before this branch ever runs and it never reaches the store.\n if (message.attempts > this.maxRetries) {\n // A KV failure (or a message dispatched outside Stratal, with no\n // binding to retry through) while persisting the failed job must not\n // abort the rest of the batch — log and ack so the message isn't\n // redelivered forever.\n try {\n const binding = queueMessage.metadata?.binding\n if (!binding) {\n throw new QueueError(\n `Queue message ${queueMessage.id} has no binding metadata and cannot be recorded for retry. ` +\n `Messages must be dispatched through a Stratal queue sender (@InjectQueue).`,\n )\n }\n\n const failedJob: FailedJob = {\n id: queueMessage.id,\n queue: queueName,\n binding,\n type: queueMessage.type,\n message: queueMessage,\n error: {\n name: lastError!.name,\n message: lastError!.message,\n stack: lastError!.stack,\n },\n consumer: failedConsumer!,\n attempts: message.attempts,\n failedAt: new Date().toISOString(),\n }\n\n await this.store.storeFailedJob(failedJob)\n } catch (error) {\n this.logger.error(\n 'Failed to persist failed queue job',\n error instanceof Error ? error : new Error(String(error)),\n { queue: queueName, messageId: queueMessage.id, type: queueMessage.type },\n )\n }\n message.ack()\n } else {\n message.retry()\n }\n } else {\n await this.store.markProcessed(idempotencyKey)\n message.ack()\n }\n }\n }\n}\n","import { Singleton } from '../di/decorators'\nimport { DI_TOKENS } from '../di/tokens'\nimport type { Constructor } from '../types'\nimport type { IQueueConsumer } from './queue-consumer'\n\n/** A registered consumer class together with the message types it handles. */\nexport interface ConsumerRegistration {\n consumerClass: Constructor<IQueueConsumer>\n messageTypes: string[]\n}\n\n/**\n * Consumer Registry\n *\n * Singleton service that holds all registered queue consumers indexed by message type.\n * Consumers declare the message types they handle, and this registry routes messages\n * to the appropriate consumers based on their types.\n *\n * **Message-Type Routing:**\n * - Consumers declare `messageTypes` array (e.g., `['email.send', 'email.batch.send']`)\n * - When a message arrives, consumers matching the message type are invoked\n * - A consumer can handle messages from ANY queue (routing is by type, not queue)\n * - Use `'*'` as a wildcard to handle all message types\n *\n * @example Consumer registration\n * ```typescript\n * // In consumer.ts\n * @Transient()\n * export class EmailConsumer implements IQueueConsumer {\n * readonly messageTypes = ['email.send', 'email.batch.send']\n * // ...\n * }\n *\n * // In module.ts\n * @Module({\n * consumers: [EmailConsumer]\n * })\n *\n * // Application auto-registers via ConsumerRegistry\n * this.consumerRegistry.register(consumer)\n * ```\n */\n@Singleton(DI_TOKENS.ConsumerRegistry)\nexport class ConsumerRegistry {\n /** Map from message type to consumer classes handling that type */\n private classesByType = new Map<string, Constructor<IQueueConsumer>[]>()\n\n /** All registrations (for iteration / listing) */\n private registrations: ConsumerRegistration[] = []\n\n /** Registered consumer classes (dedupe) */\n private registeredClasses = new Set<Constructor<IQueueConsumer>>()\n\n /**\n * Register a queue consumer class.\n *\n * Indexes the class by each of its declared message types. A fresh instance\n * is resolved per message from the request-scoped container at dispatch time —\n * consumers are never held as long-lived singletons, so they may safely inject\n * request-scoped providers (`@InjectQueue`, i18n, auth context, …).\n *\n * @param consumerClass - Queue consumer class to register\n * @param messageTypes - Message types the consumer handles\n */\n register(consumerClass: Constructor<IQueueConsumer>, messageTypes: string[]): void {\n if (this.registeredClasses.has(consumerClass)) {\n return // Already registered\n }\n\n this.registeredClasses.add(consumerClass)\n this.registrations.push({ consumerClass, messageTypes })\n\n for (const messageType of messageTypes) {\n const existing = this.classesByType.get(messageType) ?? []\n existing.push(consumerClass)\n this.classesByType.set(messageType, existing)\n }\n }\n\n /**\n * Get all consumer classes that can handle a specific message type.\n *\n * Returns classes that either declare the message type explicitly or use the\n * `'*'` wildcard. Callers resolve a fresh instance per message from the\n * request-scoped container.\n *\n * @param messageType - The message type to find consumers for\n * @returns Array of consumer classes that can handle this message type\n */\n getConsumerClasses(messageType: string): Constructor<IQueueConsumer>[] {\n const exactMatch = this.classesByType.get(messageType) ?? []\n const wildcardMatch = this.classesByType.get('*') ?? []\n\n // Combine and dedupe\n return Array.from(new Set([...exactMatch, ...wildcardMatch]))\n }\n\n /**\n * Check if any consumers can handle a message type\n *\n * @param messageType - The message type to check\n * @returns true if at least one consumer can handle this type\n */\n hasConsumers(messageType: string): boolean {\n return this.getConsumerClasses(messageType).length > 0\n }\n\n /**\n * Get all registered message types\n *\n * @returns Array of message types with registered consumers\n */\n getMessageTypes(): string[] {\n return Array.from(this.classesByType.keys())\n }\n\n /**\n * Get all consumer registrations (class + message types) for listing.\n */\n getRegistrations(): readonly ConsumerRegistration[] {\n return this.registrations\n }\n}\n","import type { II18nService } from '../i18n/i18n.types';\nimport type { IQueueProvider } from './providers';\nimport type { QueueMessage } from './queue-consumer';\nimport type { DispatchMessage, IQueueSender } from './queue-sender.interface';\n\n/**\n * Queue Sender\n *\n * Implementation of IQueueSender bound to a specific queue binding.\n * Created by QueueRegistry for each registered binding.\n *\n * Automatically enriches messages with:\n * - `id`: UUID generated via crypto.randomUUID()\n * - `metadata.locale`: Current locale from I18n context\n * - `metadata.idempotencyKey`: Deterministic SHA-256 hash of type + payload (if not provided)\n *\n * @example\n * ```typescript\n * // Created by QueueRegistry, not directly instantiated\n * const sender = registry.getQueue('NOTIFICATIONS_QUEUE')\n *\n * await sender.dispatch({\n * type: 'email.send',\n * payload: { to: 'user@example.com', subject: 'Hello' }\n * })\n * ```\n */\nexport class QueueSender implements IQueueSender {\n constructor(\n private readonly binding: string,\n private readonly provider: IQueueProvider,\n private readonly i18n: II18nService\n ) {}\n\n /**\n * Dispatch a message to this queue.\n *\n * @param message - Message to dispatch (without id)\n */\n async dispatch<T>(message: DispatchMessage<T>): Promise<void> {\n const metadata = { ...message.metadata }\n\n if (!metadata.locale) {\n const locale = this.i18n.getLocale()\n if (locale) {\n metadata.locale = locale\n }\n }\n\n metadata.idempotencyKey ??= await this.generateIdempotencyKey(message.type, message.payload);\n\n // Stamp the producer binding so a failed job can be retried to the right\n // Cloudflare binding. Consumers only see the queue *name*, which is not a\n // valid producer binding key.\n metadata.binding = this.binding\n\n const fullMessage: QueueMessage<T> = {\n id: crypto.randomUUID(),\n ...message,\n metadata,\n }\n\n await this.provider.send(this.binding, fullMessage)\n }\n\n private async generateIdempotencyKey(type: string, payload: unknown): Promise<string> {\n // Use a stable, key-sorted serialization: `JSON.stringify` preserves\n // insertion order, so two semantically identical payloads with differently\n // ordered keys would otherwise hash differently and defeat deduplication.\n const data = new TextEncoder().encode(stableStringify({ type, payload }))\n const hash = await crypto.subtle.digest('SHA-256', data)\n return `queue:${Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, '0')).join('')}`\n }\n}\n\n/**\n * Deterministic JSON serialization: object keys are emitted in sorted order at\n * every level so the output depends only on the data, not on key insertion\n * order. Arrays keep their order (order is significant). Used to derive a stable\n * idempotency hash from a message's `type` + `payload`.\n */\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== 'object') {\n return JSON.stringify(value) ?? 'null'\n }\n if (Array.isArray(value)) {\n return `[${value.map(stableStringify).join(',')}]`\n }\n const entries = Object.keys(value as Record<string, unknown>)\n .sort()\n .map((key) => {\n const v = (value as Record<string, unknown>)[key]\n if (v === undefined) return undefined\n return `${JSON.stringify(key)}:${stableStringify(v)}`\n })\n .filter((entry): entry is string => entry !== undefined)\n return `{${entries.join(',')}}`\n}\n","import { inject } from '../di'\nimport { Request } from '../di/decorators'\nimport { I18N_TOKENS } from '../i18n/i18n.tokens'\nimport type { II18nService } from '../i18n/i18n.types'\nimport type { IQueueProvider } from './providers'\nimport type { IQueueSender } from './queue-sender.interface'\nimport { QueueSender } from './queue-sender'\nimport { QUEUE_TOKENS } from './queue.tokens'\nimport { type QueueProviderFactory } from './services'\n\n/**\n * Queue Registry\n *\n * Request-scoped factory service for creating QueueSender instances.\n * Caches senders per binding within the request scope.\n *\n * This service is used internally by QueueModule.registerQueue() to provide\n * IQueueSender instances for each registered binding.\n *\n * **Why request-scoped?**\n * - Needs access to I18nService for locale-aware message metadata\n * - Provider is created once per request for consistency\n * - Queue senders are cached per request to avoid recreating them\n *\n * @example\n * ```typescript\n * // Used internally by QueueModule.registerQueue()\n * QueueModule.registerQueue('NOTIFICATIONS_QUEUE')\n *\n * // The module creates a factory provider:\n * {\n * provide: 'NOTIFICATIONS_QUEUE',\n * useFactory: (registry: QueueRegistry) => registry.getQueue('NOTIFICATIONS_QUEUE'),\n * inject: [QUEUE_TOKENS.QueueRegistry],\n * }\n * ```\n */\n@Request(QUEUE_TOKENS.QueueRegistry)\nexport class QueueRegistry {\n private readonly provider: IQueueProvider\n private readonly senders = new Map<string, IQueueSender>()\n\n constructor(\n @inject(QUEUE_TOKENS.QueueProviderFactory) providerFactory: QueueProviderFactory,\n @inject(I18N_TOKENS.I18nService) private readonly i18n: II18nService\n ) {\n this.provider = providerFactory.create()\n }\n\n /**\n * Get or create a QueueSender for the specified binding.\n *\n * Senders are cached per binding within the request scope.\n *\n * @param binding - The queue binding to get a sender for\n * @returns QueueSender bound to the specified binding\n */\n getQueue(binding: string): IQueueSender {\n let sender = this.senders.get(binding)\n\n if (!sender) {\n sender = new QueueSender(binding, this.provider, this.i18n)\n this.senders.set(binding, sender)\n }\n\n return sender\n }\n}\n","import { inject } from '../di';\nimport { Transient } from '../di/decorators';\nimport { CACHE_TOKENS } from '../cache/cache.tokens';\nimport type { TieredCacheService } from '../cache/services/tiered-cache.service';\nimport type { FailedJob, FailedJobMetadata } from './failed-job';\nimport type { QueueModuleOptions } from './queue.module';\nimport { QUEUE_TOKENS } from './queue.tokens';\n\nconst IDEM_PREFIX = 'queue:idem:'\nconst FAILED_PREFIX = 'queue:failed:'\nconst DEFAULT_IDEMPOTENCY_TTL = 86400\n\n/** Default KV binding name used for queue state when none is configured. */\nexport const DEFAULT_STORE_BINDING = 'CACHE'\n\n/**\n * Persists queue idempotency claims and failed jobs.\n *\n * Backed by {@link TieredCacheService} (isolate-local L1 + KV). The L1 is what\n * makes `markProcessed` → `isProcessed` reliable within an isolate: a claim\n * written on this isolate is read back from memory, so a message redelivered to\n * the same warm isolate is de-duplicated even inside KV's eventual-consistency\n * window. Idempotency markers are set-once, the pattern the L1 tier is designed\n * for. Cross-isolate duplicates still rely on KV (eventually consistent) —\n * delivery remains at-least-once with best-effort de-duplication, not\n * exactly-once.\n */\n@Transient(QUEUE_TOKENS.QueueStore)\nexport class QueueStore {\n private readonly cache: TieredCacheService\n private readonly idempotencyTtl: number\n\n constructor(\n @inject(CACHE_TOKENS.TieredCacheService) cache: TieredCacheService,\n @inject(QUEUE_TOKENS.QueueModuleOptions) options: QueueModuleOptions,\n ) {\n // Bind to the configured KV namespace via the tiered cache so the L1 lives\n // on its singleton (one per isolate) and persists across messages. The\n // binding is validated at module init (QueueModule.onInitialize);\n // `binding()` also throws if it is somehow absent here.\n this.cache = cache.binding(options.store?.binding ?? DEFAULT_STORE_BINDING)\n this.idempotencyTtl = options.idempotency?.ttl ?? DEFAULT_IDEMPOTENCY_TTL\n }\n\n async isProcessed(key: string): Promise<boolean> {\n return (await this.cache.get(`${IDEM_PREFIX}${key}`)) !== null\n }\n\n async markProcessed(key: string): Promise<void> {\n // Durable, awaited write: the idempotency claim gates message redelivery, so\n // it must be committed (or fail loudly so the handler retries) before the\n // consumer acks — never deferred. See TieredCacheService.putDurable.\n await this.cache.putDurable(`${IDEM_PREFIX}${key}`, '1', {\n expirationTtl: this.idempotencyTtl,\n })\n }\n\n async storeFailedJob(job: FailedJob): Promise<void> {\n const metadata: FailedJobMetadata = {\n queue: job.queue,\n binding: job.binding,\n type: job.type,\n consumer: job.consumer,\n attempts: job.attempts,\n failedAt: job.failedAt,\n }\n\n // Durable, awaited write: a failed-job record that only lands in L1 is\n // invisible to listFailedJobs (KV-direct) and lost on any other isolate, so\n // it must be committed or fail loudly — never deferred.\n await this.cache.putDurable(`${FAILED_PREFIX}${job.id}`, JSON.stringify(job), {\n metadata,\n })\n }\n\n async getFailedJob(messageId: string): Promise<FailedJob | null> {\n return this.cache.get<FailedJob>(`${FAILED_PREFIX}${messageId}`, 'json')\n }\n\n async removeFailedJob(messageId: string): Promise<void> {\n await this.cache.delete(`${FAILED_PREFIX}${messageId}`)\n }\n\n async listFailedJobs(options?: {\n limit?: number\n cursor?: string\n }): Promise<{ keys: { id: string; metadata: FailedJobMetadata }[]; cursor?: string }> {\n const result = await this.cache.list<FailedJobMetadata>({\n prefix: FAILED_PREFIX,\n limit: options?.limit ?? 50,\n cursor: options?.cursor,\n })\n\n // Skip keys without metadata: a partially-written entry (or one written by\n // an older code path) is unusable for listing and must not crash the page.\n const keys = result.keys\n .filter((key): key is typeof key & { metadata: FailedJobMetadata } => key.metadata != null)\n .map((key) => ({\n id: key.name.slice(FAILED_PREFIX.length),\n metadata: key.metadata,\n }))\n\n return {\n keys,\n cursor: result.list_complete ? undefined : result.cursor,\n }\n }\n\n async purgeFailedJobs(): Promise<void> {\n let cursor: string | undefined\n\n do {\n const result = await this.cache.list({ prefix: FAILED_PREFIX, cursor })\n await Promise.all(result.keys.map((key) => this.cache.delete(key.name)))\n cursor = result.list_complete ? undefined : result.cursor\n } while (cursor)\n }\n\n /**\n * Delete failed jobs older than `retentionSeconds` (by their `failedAt`\n * timestamp). Returns the number removed. Backs the opt-in\n * {@link FailedJobCleanupJob} cron — failed jobs otherwise persist\n * indefinitely until retried or purged.\n */\n async purgeFailedJobsOlderThan(retentionSeconds: number): Promise<number> {\n const cutoff = Date.now() - retentionSeconds * 1000\n let cursor: string | undefined\n let removed = 0\n\n do {\n const result = await this.cache.list<FailedJobMetadata>({ prefix: FAILED_PREFIX, cursor })\n const expired = result.keys.filter((key) => {\n const failedAt = key.metadata?.failedAt\n return failedAt !== undefined && Date.parse(failedAt) < cutoff\n })\n await Promise.all(expired.map((key) => this.cache.delete(key.name)))\n removed += expired.length\n cursor = result.list_complete ? undefined : result.cursor\n } while (cursor)\n\n return removed\n }\n}\n","import { inject } from '../../di'\nimport { type StratalEnv } from '../../env'\nimport { Transient } from '../../di/decorators'\nimport { DI_TOKENS } from '../../di/tokens'\nimport { QueueError } from '../queue.error'\nimport type { QueueMessage } from '../queue-consumer'\nimport type { IQueueProvider } from './queue-provider.interface'\n\n/**\n * Cloudflare Queue Provider\n *\n * Sends messages to Cloudflare Queues by resolving the binding directly on\n * the worker's `env`. Used in production environments where Cloudflare Workers\n * handle queue processing.\n *\n * @example\n * ```typescript\n * const provider = new CloudflareQueueProvider(env)\n * await provider.send('NOTIFICATIONS_QUEUE', message)\n * ```\n */\n@Transient()\nexport class CloudflareQueueProvider implements IQueueProvider {\n constructor(\n @inject(DI_TOKENS.CloudflareEnv) private readonly env: StratalEnv\n ) { }\n\n /**\n * Send a message to a Cloudflare Queue\n *\n * @param binding - Queue binding identifier (e.g., 'NOTIFICATIONS_QUEUE')\n * @param message - Complete message with id and payload\n * @throws {QueueError} If the binding is not configured on env\n */\n async send<T>(binding: string, message: QueueMessage<T>): Promise<void> {\n const queue = (this.env as unknown as Record<string, unknown>)[binding] as Queue | undefined\n\n if (!queue) {\n throw new QueueError(`Queue binding \"${binding}\" was not found in the environment`)\n }\n\n await queue.send(message)\n }\n}\n","import type { Application } from '../../application'\nimport type { Container } from '../../di/container'\nimport { containerStorage } from '../../di/container-storage'\nimport { inject } from '../../di'\nimport { Transient } from '../../di/decorators'\nimport { DI_TOKENS } from '../../di/tokens'\nimport type { RouterContext } from '../../router/router-context'\nimport { type ConsumerRegistry } from '../consumer-registry'\nimport type { IQueueConsumer, QueueMessage } from '../queue-consumer'\nimport type { IQueueProvider } from './queue-provider.interface'\n\n/**\n * Sync Queue Provider\n *\n * Processes messages immediately by finding matching consumers and calling\n * their handle() method directly. Used for testing and development where\n * real queue infrastructure is not available.\n *\n * **Behavior:**\n * - Messages are processed synchronously when send() is called\n * - Matching consumers are found via ConsumerRegistry by message type\n * - All matching consumers are called sequentially\n * - Errors are re-thrown after onError() is called (fail-fast for testing)\n *\n * **Consumer Matching:**\n * - Consumers are matched by message type, not queue name\n * - Wildcard ('*') matches all message types\n */\n@Transient()\nexport class SyncQueueProvider implements IQueueProvider {\n constructor(\n @inject(DI_TOKENS.ConsumerRegistry) private readonly registry: ConsumerRegistry,\n @inject(DI_TOKENS.Container) private readonly root: Container,\n @inject(DI_TOKENS.Application) private readonly app: Application,\n ) {}\n\n /**\n * Process a message synchronously.\n *\n * Runs inside the active request scope when dispatch happens within one (an\n * HTTP request, or `runInScope` for queues/cron/commands). When dispatched\n * with no ambient scope — e.g. a service invoked directly in a test — it\n * establishes its own request scope (mirroring the production queue handler)\n * so consumers and their request-scoped dependencies resolve correctly.\n *\n * @param _binding - Queue binding (not used for routing, consumers match by message type)\n * @param message - Complete message with id and payload\n * @throws Re-throws any error from consumer.handle() after calling onError()\n */\n async send<T>(_binding: string, message: QueueMessage<T>): Promise<void> {\n // The fetch path deliberately skips queue init (a Cloudflare-queue worker\n // never processes consumers inline) — but the sync provider IS the inline\n // consumer path, so populate the consumer registry before matching message\n // types. Memoized inside the application; a no-op after the first dispatch.\n await this.app.ensureScopedHandlers()\n\n const ambient = containerStorage.getStore()\n if (ambient) {\n await this.process(ambient, message)\n return\n }\n\n const locale = message.metadata?.locale ?? 'en'\n const context = {\n getLocale: () => locale,\n setLocale: () => { /* no-op */ },\n getContainer: () => containerStorage.getStore() ?? this.root,\n } as unknown as RouterContext\n\n await this.root.runInRequestScope(context, (container) => this.process(container, message))\n }\n\n /**\n * Resolve a fresh consumer per message from `container` (matched by type) and\n * invoke each sequentially, fail-fast on the first error after `onError`.\n */\n private async process<T>(container: Container, message: QueueMessage<T>): Promise<void> {\n const consumers = this.registry\n .getConsumerClasses(message.type)\n .map((ConsumerClass) => container.resolve<IQueueConsumer>(ConsumerClass))\n\n for (const consumer of consumers) {\n try {\n await consumer.handle(message)\n } catch (error) {\n const errorInstance = error instanceof Error ? error : new Error(String(error))\n\n // Call onError hook if defined\n if (consumer.onError) {\n await consumer.onError(errorInstance, message)\n }\n\n // Re-throw for fail-fast behavior in tests\n throw errorInstance\n }\n }\n }\n}\n","import { inject } from '../../di'\nimport type { Container } from '../../di/container'\nimport { type StratalEnv } from '../../env'\nimport { Transient } from '../../di/decorators'\nimport { DI_TOKENS } from '../../di/tokens'\nimport { type ConsumerRegistry } from '../consumer-registry'\nimport { QueueError } from '../queue.error'\nimport type { Application } from '../../application'\nimport { CloudflareQueueProvider, SyncQueueProvider, type IQueueProvider } from '../providers'\nimport type { QueueModuleOptions } from '../queue.module'\nimport { QUEUE_TOKENS } from '../queue.tokens'\n\n/**\n * Queue Provider Factory\n *\n * Creates the appropriate queue provider based on configuration provided\n * via QueueModule.forRootAsync().\n *\n * **Provider Selection:**\n * - `cloudflare`: Production provider using Cloudflare Queue bindings\n * - `sync`: Testing provider that processes messages immediately\n *\n * @example\n * ```typescript\n * // Configuration via QueueModule.forRootAsync()\n * QueueModule.forRootAsync({\n * inject: [CONFIG_TOKENS.ConfigService],\n * useFactory: (config) => ({ provider: config.get('queue').provider })\n * })\n *\n * // Factory usage (internal)\n * const factory = container.resolve(QueueProviderFactory)\n * const provider = factory.create()\n * ```\n */\n@Transient(QUEUE_TOKENS.QueueProviderFactory)\nexport class QueueProviderFactory {\n constructor(\n @inject(DI_TOKENS.CloudflareEnv) private readonly env: StratalEnv,\n @inject(DI_TOKENS.ConsumerRegistry) private readonly registry: ConsumerRegistry,\n @inject(DI_TOKENS.Container) private readonly container: Container,\n @inject(DI_TOKENS.Application) private readonly app: Application,\n @inject(QUEUE_TOKENS.QueueModuleOptions, { isOptional: true }) private readonly options?: QueueModuleOptions,\n ) { }\n\n /**\n * Create a queue provider based on module configuration\n *\n * @returns Queue provider instance\n * @throws {QueueError} If provider type is not supported\n */\n create(): IQueueProvider {\n const providerType = this.options?.provider ?? 'cloudflare'\n\n switch (providerType) {\n case 'cloudflare':\n return new CloudflareQueueProvider(this.env)\n\n case 'sync':\n return new SyncQueueProvider(this.registry, this.container, this.app)\n\n default:\n throw new QueueError(`Queue provider \"${String(providerType)}\" is not supported`)\n }\n }\n}\n","/**\n * Queue Module\n *\n * Provides declarative queue infrastructure with provider abstraction.\n *\n * **Usage:**\n * ```typescript\n * // 1. Configure provider (once, in app root)\n * QueueModule.forRootAsync({\n * inject: [CONFIG_TOKENS.ConfigService],\n * useFactory: (config) => ({ provider: config.get('queue').provider })\n * })\n *\n * // 2. Register queue bindings (the binding IS the injection token)\n * QueueModule.registerQueue('NOTIFICATIONS_QUEUE')\n * QueueModule.registerQueue('BACKGROUND_QUEUE')\n *\n * // 3. Inject and use\n * constructor(@InjectQueue('NOTIFICATIONS_QUEUE') private queue: IQueueSender) {}\n * await this.queue.dispatch({ type: 'email.send', payload: {...} })\n * ```\n *\n * **Providers:**\n * - `cloudflare`: Production provider using Cloudflare Queue bindings\n * - `sync`: Testing provider that processes messages immediately\n */\n\nimport { CacheModule } from '../cache';\nimport { DI_TOKENS } from '../di/tokens';\nimport { type StratalEnv } from '../env';\nimport { Module } from '../module';\nimport type {\n AsyncModuleOptions,\n DynamicModule,\n InjectionToken,\n ModuleContext,\n OnInitialize,\n} from '../module/types';\nimport { ConsumerRegistry } from './consumer-registry';\nimport type { QueueBinding } from './queue-binding';\nimport { QueueManager } from './queue-manager';\nimport { QueueRegistry } from './queue-registry';\nimport type { IQueueSender } from './queue-sender.interface';\nimport { QueueError } from './queue.error';\nimport { DEFAULT_STORE_BINDING, QueueStore } from './queue-store';\nimport { QUEUE_TOKENS } from './queue.tokens';\nimport { QueueProviderFactory } from './services';\n\n/**\n * Queue module configuration options\n */\nexport interface QueueModuleOptions {\n /**\n * Queue provider type\n * - 'cloudflare': Production provider using Cloudflare Queue bindings\n * - 'sync': Testing provider that processes messages immediately\n */\n provider: 'cloudflare' | 'sync'\n\n /**\n * KV binding for queue state (idempotency keys + failed jobs).\n * Defaults to the `CACHE` binding if omitted.\n */\n store?: {\n /**\n * KV namespace binding name.\n * @default CACHE\n */\n binding?: string\n }\n\n /**\n * Idempotency configuration.\n *\n * Delivery is **at-least-once with best-effort de-duplication**, not\n * exactly-once. Every dispatch carries an idempotency key (an explicit\n * `metadata.idempotencyKey`, otherwise a deterministic SHA-256 hash of `type`\n * + `payload`), and a message already recorded as processed is skipped. The\n * processed marker is written only after a handler succeeds, and KV `get`/`put`\n * are eventually consistent — so a message redelivered concurrently (or after\n * a crash before the marker was durable) can still run more than once. Make\n * handlers idempotent; don't rely on this as a hard exactly-once guarantee.\n * `ttl` bounds how long processed keys are remembered.\n */\n idempotency?: {\n /** TTL in seconds for processed idempotency keys. Default: 86400 (24h) */\n ttl?: number\n }\n\n /**\n * Failed-job configuration.\n *\n * Failed jobs persist indefinitely until retried or purged. To bound growth,\n * register the opt-in `FailedJobCleanupJob` cron in a module's `jobs` array;\n * it deletes failed jobs older than `retention`.\n */\n failedJobs?: {\n /**\n * Age in seconds beyond which `FailedJobCleanupJob` deletes a failed job.\n * Default: 604800 (7d). Has no effect unless the cron is registered.\n */\n retention?: number\n }\n\n /** Max retry attempts before storing as failed job. Default: 3 */\n maxRetries?: number\n}\n\n@Module({\n // QueueStore persists idempotency claims and failed jobs through CacheService\n // (isolate-local L1 + KV), so the cache must be available wherever queues are.\n imports: [CacheModule],\n providers: [\n { provide: DI_TOKENS.ConsumerRegistry, useClass: ConsumerRegistry },\n QueueManager,\n { provide: QUEUE_TOKENS.QueueProviderFactory, useClass: QueueProviderFactory },\n { provide: QUEUE_TOKENS.QueueRegistry, useClass: QueueRegistry },\n { provide: QUEUE_TOKENS.QueueStore, useClass: QueueStore },\n ],\n})\nexport class QueueModule implements OnInitialize {\n /**\n * Fail fast at boot if the configured KV store binding is missing, rather\n * than letting every queue invocation hard-fail lazily. The binding backs\n * idempotency claims and failed-job storage, so without it the queue\n * subsystem cannot function.\n */\n onInitialize({ container }: ModuleContext): void {\n const options = container.resolve<QueueModuleOptions>(QUEUE_TOKENS.QueueModuleOptions)\n\n // Only the `cloudflare` provider persists idempotency claims and failed jobs\n // to KV. The `sync` provider (dev/CLI) processes inline and never touches the\n // store, so it must not require the binding.\n if (options.provider !== 'cloudflare') return\n\n const binding = options.store?.binding ?? DEFAULT_STORE_BINDING\n const env = container.resolve<StratalEnv>(DI_TOKENS.CloudflareEnv)\n const kv = (env as unknown as Record<string, unknown>)[binding]\n\n if (!kv) {\n throw new QueueError(\n `Queue KV store binding \"${binding}\" was not found in the environment. ` +\n `The queue subsystem persists idempotency claims and failed jobs to KV. ` +\n `Add a kv_namespaces entry for \"${binding}\" in wrangler.jsonc, or set ` +\n `QueueModule.forRootAsync({ ..., store: { binding: 'YOUR_KV' } }) to point at an existing namespace.`,\n )\n }\n }\n\n /**\n * Configure queue infrastructure with async factory.\n *\n * Use when provider configuration depends on other services like ConfigService.\n *\n * @param options - Async configuration with factory and inject tokens\n * @returns Dynamic module with queue infrastructure\n *\n * @example\n * ```typescript\n * QueueModule.forRootAsync({\n * inject: [CONFIG_TOKENS.ConfigService],\n * useFactory: (config: IConfigService) => ({\n * provider: config.get('queue').provider\n * })\n * })\n * ```\n */\n static forRootAsync(options: AsyncModuleOptions<QueueModuleOptions>): DynamicModule {\n return {\n module: QueueModule,\n providers: [\n {\n provide: QUEUE_TOKENS.QueueModuleOptions,\n useFactory: options.useFactory,\n inject: options.inject,\n },\n ],\n }\n }\n\n /**\n * Register a queue binding for injection.\n *\n * The binding name doubles as the DI injection token and the\n * `env`-lookup key. Binding names are typed against `StratalEnv`\n * (autocomplete works once an app augments `StratalEnv` with its\n * Cloudflare bindings).\n *\n * @param binding - Queue binding identifier (e.g. `NOTIFICATIONS_QUEUE`).\n * @returns Dynamic module that provides the queue sender\n *\n * @example\n * ```typescript\n * // In AppModule imports\n * QueueModule.registerQueue('NOTIFICATIONS_QUEUE')\n *\n * // Then inject using the binding name\n * constructor(@InjectQueue('NOTIFICATIONS_QUEUE') private queue: IQueueSender) {}\n * ```\n */\n static registerQueue(binding: QueueBinding): DynamicModule {\n return {\n module: QueueModule,\n providers: [\n {\n provide: binding as InjectionToken<IQueueSender>,\n useFactory: (registry: QueueRegistry) => registry.getQueue(binding),\n inject: [QUEUE_TOKENS.QueueRegistry],\n },\n ],\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAEA,IAAa,aAAb,cAAgC,iBAAiB,CAAC;;;ACWlD,MAAM,sBAAsB;AAGrB,IAAM,eAAN,MAAM,aAAa;CAI+B;CACC;CACJ;CALpD;CAEA,YACE,UACA,QACA,OACA,SACA;EAJqD,KAAA,WAAA;EACC,KAAA,SAAA;EACJ,KAAA,QAAA;EAGlD,KAAK,aAAa,QAAQ,cAAc;CAC1C;CAEA,MAAM,aAAa,WAAmB,OAAoC;EACxE,KAAK,MAAM,WAAW,MAAM,UAAU;GACpC,MAAM,eAAe,QAAQ;GAC7B,MAAM,iBAAiB,aAAa,UAAU,kBAAkB,aAAa;GAE7E,IAAI,MAAM,KAAK,MAAM,YAAY,cAAc,GAAG;IAChD,QAAQ,IAAI;IACZ;GACF;GAKA,MAAM,YAAY,aAAa;GAC/B,MAAM,YAAY,KAAK,SACpB,mBAAmB,aAAa,IAAI,CAAC,CACrC,KAAK,kBAAkB,UAAU,QAAwB,aAAa,CAAC;GAE1E,MAAM,UAAU,MAAM,QAAQ,WAC5B,UAAU,KAAK,aAAa,SAAS,OAAO,YAAY,CAAC,CAC3D;GAEA,IAAI;GACJ,IAAI;GAEJ,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;IACvC,MAAM,SAAS,QAAQ;IACvB,IAAI,OAAO,WAAW,YAAY;KAChC,MAAM,WAAW,UAAU;KAC3B,MAAM,gBAAgB,OAAO,kBAAkB,QAC3C,OAAO,SACP,IAAI,MAAM,OAAO,OAAO,MAAM,CAAC;KAEnC,KAAK,OAAO,MAAM,mCAAmC,eAAe;MAClE,MAAM,aAAa;MACnB,OAAO;MACP,WAAW,aAAa;MACxB;KACF,CAAC;KAED,IAAI,SAAS,SACX,MAAM,SAAS,QAAQ,eAAe,YAAY;KAGpD,YAAY;KACZ,iBAAiB,SAAS,YAAY;IACxC;GACF;GAIA,IAFe,cAAc,KAAA,GAEjB;IAOV,IAAI,QAAQ,WAAW,KAAK,YAAY;KAKtC,IAAI;MACF,MAAM,UAAU,aAAa,UAAU;MACvC,IAAI,CAAC,SACH,MAAM,IAAI,WACR,iBAAiB,aAAa,GAAG,sIAEnC;MAGF,MAAM,YAAuB;OAC3B,IAAI,aAAa;OACjB,OAAO;OACP;OACA,MAAM,aAAa;OACnB,SAAS;OACT,OAAO;QACL,MAAM,UAAW;QACjB,SAAS,UAAW;QACpB,OAAO,UAAW;OACpB;OACA,UAAU;OACV,UAAU,QAAQ;OAClB,2BAAU,IAAI,KAAK,EAAA,CAAE,YAAY;MACnC;MAEA,MAAM,KAAK,MAAM,eAAe,SAAS;KAC3C,SAAS,OAAO;MACd,KAAK,OAAO,MACV,sCACA,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,GACxD;OAAE,OAAO;OAAW,WAAW,aAAa;OAAI,MAAM,aAAa;MAAK,CAC1E;KACF;KACA,QAAQ,IAAI;IACd,OACE,QAAQ,MAAM;GAElB,OAAO;IACL,MAAM,KAAK,MAAM,cAAc,cAAc;IAC7C,QAAQ,IAAI;GACd;EACF;CACF;AACF;;CAvHC,UAAU,UAAU,KAAK;CAKrB,gBAAA,GAAA,OAAO,UAAU,gBAAgB,CAAA;CACjC,gBAAA,GAAA,OAAO,cAAc,aAAa,CAAA;CAClC,gBAAA,GAAA,OAAO,aAAa,UAAU,CAAA;CAC9B,gBAAA,GAAA,OAAO,aAAa,kBAAkB,CAAA;;;;ACoBpC,IAAM,mBAAN,MAAM,iBAAiB;;CAE5B,gCAAwB,IAAI,IAA2C;;CAGvE,gBAAgD,CAAC;;CAGjD,oCAA4B,IAAI,IAAiC;;;;;;;;;;;;CAajE,SAAS,eAA4C,cAA8B;EACjF,IAAI,KAAK,kBAAkB,IAAI,aAAa,GAC1C;EAGF,KAAK,kBAAkB,IAAI,aAAa;EACxC,KAAK,cAAc,KAAK;GAAE;GAAe;EAAa,CAAC;EAEvD,KAAK,MAAM,eAAe,cAAc;GACtC,MAAM,WAAW,KAAK,cAAc,IAAI,WAAW,KAAK,CAAC;GACzD,SAAS,KAAK,aAAa;GAC3B,KAAK,cAAc,IAAI,aAAa,QAAQ;EAC9C;CACF;;;;;;;;;;;CAYA,mBAAmB,aAAoD;EACrE,MAAM,aAAa,KAAK,cAAc,IAAI,WAAW,KAAK,CAAC;EAC3D,MAAM,gBAAgB,KAAK,cAAc,IAAI,GAAG,KAAK,CAAC;EAGtD,OAAO,MAAM,qBAAK,IAAI,IAAI,CAAC,GAAG,YAAY,GAAG,aAAa,CAAC,CAAC;CAC9D;;;;;;;CAQA,aAAa,aAA8B;EACzC,OAAO,KAAK,mBAAmB,WAAW,CAAC,CAAC,SAAS;CACvD;;;;;;CAOA,kBAA4B;EAC1B,OAAO,MAAM,KAAK,KAAK,cAAc,KAAK,CAAC;CAC7C;;;;CAKA,mBAAoD;EAClD,OAAO,KAAK;CACd;AACF;AAhFC,mBAAA,WAAA,CAAA,UAAU,UAAU,gBAAgB,CAAA,GAAA,gBAAA;;;;;;;;;;;;;;;;;;;;;;;;;ACfrC,IAAa,cAAb,MAAiD;CAE5B;CACA;CACA;CAHnB,YACE,SACA,UACA,MACA;EAHiB,KAAA,UAAA;EACA,KAAA,WAAA;EACA,KAAA,OAAA;CAChB;;;;;;CAOH,MAAM,SAAY,SAA4C;EAC5D,MAAM,WAAW,EAAE,GAAG,QAAQ,SAAS;EAEvC,IAAI,CAAC,SAAS,QAAQ;GACpB,MAAM,SAAS,KAAK,KAAK,UAAU;GACnC,IAAI,QACF,SAAS,SAAS;EAEtB;EAEA,SAAS,mBAAmB,MAAM,KAAK,uBAAuB,QAAQ,MAAM,QAAQ,OAAO;EAK3F,SAAS,UAAU,KAAK;EAExB,MAAM,cAA+B;GACnC,IAAI,OAAO,WAAW;GACtB,GAAG;GACH;EACF;EAEA,MAAM,KAAK,SAAS,KAAK,KAAK,SAAS,WAAW;CACpD;CAEA,MAAc,uBAAuB,MAAc,SAAmC;EAIpF,MAAM,OAAO,IAAI,YAAY,CAAC,CAAC,OAAO,gBAAgB;GAAE;GAAM;EAAQ,CAAC,CAAC;EACxE,MAAM,OAAO,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI;EACvD,OAAO,SAAS,MAAM,KAAK,IAAI,WAAW,IAAI,CAAC,CAAC,CAAC,KAAI,MAAK,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE;CACpG;AACF;;;;;;;AAQA,SAAS,gBAAgB,OAAwB;CAC/C,IAAI,UAAU,QAAQ,OAAO,UAAU,UACrC,OAAO,KAAK,UAAU,KAAK,KAAK;CAElC,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,IAAI,MAAM,IAAI,eAAe,CAAC,CAAC,KAAK,GAAG,EAAE;CAUlD,OAAO,IARS,OAAO,KAAK,KAAgC,CAAC,CAC1D,KAAK,CAAC,CACN,KAAK,QAAQ;EACZ,MAAM,IAAK,MAAkC;EAC7C,IAAI,MAAM,KAAA,GAAW,OAAO,KAAA;EAC5B,OAAO,GAAG,KAAK,UAAU,GAAG,EAAE,GAAG,gBAAgB,CAAC;CACpD,CAAC,CAAC,CACD,QAAQ,UAA2B,UAAU,KAAA,CAC/B,CAAC,CAAC,KAAK,GAAG,EAAE;AAC/B;;;AC3DO,IAAM,gBAAN,MAAM,cAAc;CAM2B;CALpD;CACA,0BAA2B,IAAI,IAA0B;CAEzD,YACE,iBACA,MACA;EADkD,KAAA,OAAA;EAElD,KAAK,WAAW,gBAAgB,OAAO;CACzC;;;;;;;;;CAUA,SAAS,SAA+B;EACtC,IAAI,SAAS,KAAK,QAAQ,IAAI,OAAO;EAErC,IAAI,CAAC,QAAQ;GACX,SAAS,IAAI,YAAY,SAAS,KAAK,UAAU,KAAK,IAAI;GAC1D,KAAK,QAAQ,IAAI,SAAS,MAAM;EAClC;EAEA,OAAO;CACT;AACF;;CA9BC,QAAQ,aAAa,aAAa;CAM9B,gBAAA,GAAA,OAAO,aAAa,oBAAoB,CAAA;CACxC,gBAAA,GAAA,OAAO,YAAY,WAAW,CAAA;;;;ACpCnC,MAAM,cAAc;AACpB,MAAM,gBAAgB;AACtB,MAAM,0BAA0B;;AAGhC,MAAa,wBAAwB;AAe9B,IAAM,aAAN,MAAM,WAAW;CACtB;CACA;CAEA,YACE,OACA,SACA;EAKA,KAAK,QAAQ,MAAM,QAAQ,QAAQ,OAAO,WAAA,OAAgC;EAC1E,KAAK,iBAAiB,QAAQ,aAAa,OAAO;CACpD;CAEA,MAAM,YAAY,KAA+B;EAC/C,OAAQ,MAAM,KAAK,MAAM,IAAI,GAAG,cAAc,KAAK,MAAO;CAC5D;CAEA,MAAM,cAAc,KAA4B;EAI9C,MAAM,KAAK,MAAM,WAAW,GAAG,cAAc,OAAO,KAAK,EACvD,eAAe,KAAK,eACtB,CAAC;CACH;CAEA,MAAM,eAAe,KAA+B;EAClD,MAAM,WAA8B;GAClC,OAAO,IAAI;GACX,SAAS,IAAI;GACb,MAAM,IAAI;GACV,UAAU,IAAI;GACd,UAAU,IAAI;GACd,UAAU,IAAI;EAChB;EAKA,MAAM,KAAK,MAAM,WAAW,GAAG,gBAAgB,IAAI,MAAM,KAAK,UAAU,GAAG,GAAG,EAC5E,SACF,CAAC;CACH;CAEA,MAAM,aAAa,WAA8C;EAC/D,OAAO,KAAK,MAAM,IAAe,GAAG,gBAAgB,aAAa,MAAM;CACzE;CAEA,MAAM,gBAAgB,WAAkC;EACtD,MAAM,KAAK,MAAM,OAAO,GAAG,gBAAgB,WAAW;CACxD;CAEA,MAAM,eAAe,SAGiE;EACpF,MAAM,SAAS,MAAM,KAAK,MAAM,KAAwB;GACtD,QAAQ;GACR,OAAO,SAAS,SAAS;GACzB,QAAQ,SAAS;EACnB,CAAC;EAWD,OAAO;GACL,MARW,OAAO,KACjB,QAAQ,QAA6D,IAAI,YAAY,IAAI,CAAC,CAC1F,KAAK,SAAS;IACb,IAAI,IAAI,KAAK,MAAM,EAAoB;IACvC,UAAU,IAAI;GAChB,EAGG;GACH,QAAQ,OAAO,gBAAgB,KAAA,IAAY,OAAO;EACpD;CACF;CAEA,MAAM,kBAAiC;EACrC,IAAI;EAEJ,GAAG;GACD,MAAM,SAAS,MAAM,KAAK,MAAM,KAAK;IAAE,QAAQ;IAAe;GAAO,CAAC;GACtE,MAAM,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,CAAC;GACvE,SAAS,OAAO,gBAAgB,KAAA,IAAY,OAAO;EACrD,SAAS;CACX;;;;;;;CAQA,MAAM,yBAAyB,kBAA2C;EACxE,MAAM,SAAS,KAAK,IAAI,IAAI,mBAAmB;EAC/C,IAAI;EACJ,IAAI,UAAU;EAEd,GAAG;GACD,MAAM,SAAS,MAAM,KAAK,MAAM,KAAwB;IAAE,QAAQ;IAAe;GAAO,CAAC;GACzF,MAAM,UAAU,OAAO,KAAK,QAAQ,QAAQ;IAC1C,MAAM,WAAW,IAAI,UAAU;IAC/B,OAAO,aAAa,KAAA,KAAa,KAAK,MAAM,QAAQ,IAAI;GAC1D,CAAC;GACD,MAAM,QAAQ,IAAI,QAAQ,KAAK,QAAQ,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,CAAC;GACnE,WAAW,QAAQ;GACnB,SAAS,OAAO,gBAAgB,KAAA,IAAY,OAAO;EACrD,SAAS;EAET,OAAO;CACT;AACF;;CAnHC,UAAU,aAAa,UAAU;CAM7B,gBAAA,GAAA,OAAO,aAAa,kBAAkB,CAAA;CACtC,gBAAA,GAAA,OAAO,aAAa,kBAAkB,CAAA;;;;ACZpC,IAAM,0BAAN,MAAM,wBAAkD;CAET;CADpD,YACE,KACA;EADkD,KAAA,MAAA;CAChD;;;;;;;;CASJ,MAAM,KAAQ,SAAiB,SAAyC;EACtE,MAAM,QAAS,KAAK,IAA2C;EAE/D,IAAI,CAAC,OACH,MAAM,IAAI,WAAW,kBAAkB,QAAQ,mCAAmC;EAGpF,MAAM,MAAM,KAAK,OAAO;CAC1B;AACF;AAtBC,0BAAA,WAAA,CAAA,UAAU,GAAA,gBAAA,GAGN,OAAO,UAAU,aAAa,CAAA,CAAA,GAAA,uBAAA;;;ACK5B,IAAM,oBAAN,MAAM,kBAA4C;CAEA;CACP;CACE;CAHlD,YACE,UACA,MACA,KACA;EAHqD,KAAA,WAAA;EACP,KAAA,OAAA;EACE,KAAA,MAAA;CAC/C;;;;;;;;;;;;;;CAeH,MAAM,KAAQ,UAAkB,SAAyC;EAKvE,MAAM,KAAK,IAAI,qBAAqB;EAEpC,MAAM,UAAU,iBAAiB,SAAS;EAC1C,IAAI,SAAS;GACX,MAAM,KAAK,QAAQ,SAAS,OAAO;GACnC;EACF;EAEA,MAAM,SAAS,QAAQ,UAAU,UAAU;EAO3C,MAAM,KAAK,KAAK,kBAAkB;GALhC,iBAAiB;GACjB,iBAAiB,CAAc;GAC/B,oBAAoB,iBAAiB,SAAS,KAAK,KAAK;EAGlB,IAAI,cAAc,KAAK,QAAQ,WAAW,OAAO,CAAC;CAC5F;;;;;CAMA,MAAc,QAAW,WAAsB,SAAyC;EACtF,MAAM,YAAY,KAAK,SACpB,mBAAmB,QAAQ,IAAI,CAAC,CAChC,KAAK,kBAAkB,UAAU,QAAwB,aAAa,CAAC;EAE1E,KAAK,MAAM,YAAY,WACrB,IAAI;GACF,MAAM,SAAS,OAAO,OAAO;EAC/B,SAAS,OAAO;GACd,MAAM,gBAAgB,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;GAG9E,IAAI,SAAS,SACX,MAAM,SAAS,QAAQ,eAAe,OAAO;GAI/C,MAAM;EACR;CAEJ;AACF;;CArEC,UAAU;CAGN,gBAAA,GAAA,OAAO,UAAU,gBAAgB,CAAA;CACjC,gBAAA,GAAA,OAAO,UAAU,SAAS,CAAA;CAC1B,gBAAA,GAAA,OAAO,UAAU,WAAW,CAAA;;;;ACG1B,IAAM,uBAAN,MAAM,qBAAqB;CAEoB;CACG;CACP;CACE;CACgC;CALlF,YACE,KACA,UACA,WACA,KACA,SACA;EALkD,KAAA,MAAA;EACG,KAAA,WAAA;EACP,KAAA,YAAA;EACE,KAAA,MAAA;EACgC,KAAA,UAAA;CAC9E;;;;;;;CAQJ,SAAyB;EACvB,MAAM,eAAe,KAAK,SAAS,YAAY;EAE/C,QAAQ,cAAR;GACE,KAAK,cACH,OAAO,IAAI,wBAAwB,KAAK,GAAG;GAE7C,KAAK,QACH,OAAO,IAAI,kBAAkB,KAAK,UAAU,KAAK,WAAW,KAAK,GAAG;GAEtE,SACE,MAAM,IAAI,WAAW,mBAAmB,OAAO,YAAY,EAAE,mBAAmB;EACpF;CACF;AACF;;CA9BC,UAAU,aAAa,oBAAoB;CAGvC,gBAAA,GAAA,OAAO,UAAU,aAAa,CAAA;CAC9B,gBAAA,GAAA,OAAO,UAAU,gBAAgB,CAAA;CACjC,gBAAA,GAAA,OAAO,UAAU,SAAS,CAAA;CAC1B,gBAAA,GAAA,OAAO,UAAU,WAAW,CAAA;CAC5B,gBAAA,GAAA,OAAO,aAAa,oBAAoB,EAAE,YAAY,KAAK,CAAC,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC8E1D,IAAM,cAAA,eAAN,MAAM,YAAoC;;;;;;;CAO/C,aAAa,EAAE,aAAkC;EAC/C,MAAM,UAAU,UAAU,QAA4B,aAAa,kBAAkB;EAKrF,IAAI,QAAQ,aAAa,cAAc;EAEvC,MAAM,UAAU,QAAQ,OAAO,WAAA;EAI/B,IAAI,CAHQ,UAAU,QAAoB,UAAU,aACtC,CAAC,CAAwC,UAGrD,MAAM,IAAI,WACR,2BAA2B,QAAQ,4IAEC,QAAQ,gIAE9C;CAEJ;;;;;;;;;;;;;;;;;;;CAoBA,OAAO,aAAa,SAAgE;EAClF,OAAO;GACL,QAAA;GACA,WAAW,CACT;IACE,SAAS,aAAa;IACtB,YAAY,QAAQ;IACpB,QAAQ,QAAQ;GAClB,CACF;EACF;CACF;;;;;;;;;;;;;;;;;;;;;CAsBA,OAAO,cAAc,SAAsC;EACzD,OAAO;GACL,QAAA;GACA,WAAW,CACT;IACE,SAAS;IACT,aAAa,aAA4B,SAAS,SAAS,OAAO;IAClE,QAAQ,CAAC,aAAa,aAAa;GACrC,CACF;EACF;CACF;AACF;AAxGC,cAAA,eAAA,WAAA,CAAA,OAAO;CAGN,SAAS,CAAC,WAAW;CACrB,WAAW;EACT;GAAE,SAAS,UAAU;GAAkB,UAAU;EAAiB;EAClE;EACA;GAAE,SAAS,aAAa;GAAsB,UAAU;EAAqB;EAC7E;GAAE,SAAS,aAAa;GAAe,UAAU;EAAc;EAC/D;GAAE,SAAS,aAAa;GAAY,UAAU;EAAW;CAC3D;AACF,CAAC,CAAA,GAAA,WAAA"}
@@ -1,9 +1,10 @@
1
- import { t as __exportAll } from "./chunk-BBjsoOtd.mjs";
2
- import { t as StorageError } from "./storage.error-BStXPmO4.mjs";
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { t as StorageError } from "./storage.error-Ao38Qdjx.mjs";
3
3
  import { t as signUrl } from "./signed-url-DIU0sK_6.mjs";
4
4
  //#region src/storage/providers/r2-storage.provider.ts
5
5
  var r2_storage_provider_exports = /* @__PURE__ */ __exportAll({ R2StorageProvider: () => R2StorageProvider });
6
- const MIN_PART_SIZE = 5 * 1024 * 1024;
6
+ const MIN_PART_SIZE = 5242880;
7
+ const R2_BULK_DELETE_LIMIT = 1e3;
7
8
  const MULTIPART_PREFIX = "__multipart/";
8
9
  const PARTS_PREFIX = "__parts/";
9
10
  /**
@@ -60,11 +61,33 @@ var R2StorageProvider = class {
60
61
  async exists(path) {
61
62
  return await this.bucket.head(path) !== null;
62
63
  }
64
+ async deleteMany(paths) {
65
+ for (let i = 0; i < paths.length; i += R2_BULK_DELETE_LIMIT) await this.bucket.delete(paths.slice(i, i + R2_BULK_DELETE_LIMIT));
66
+ }
67
+ async head(path) {
68
+ const obj = await this.bucket.head(path);
69
+ if (obj === null) return null;
70
+ return toHeadResult(obj);
71
+ }
72
+ async list(options) {
73
+ const listed = await this.bucket.list({
74
+ prefix: options.prefix,
75
+ cursor: options.cursor,
76
+ limit: options.limit,
77
+ include: options.includeMetadata ? ["httpMetadata", "customMetadata"] : []
78
+ });
79
+ return {
80
+ objects: listed.objects.map((obj) => toHeadResult(obj)),
81
+ truncated: listed.truncated,
82
+ cursor: listed.truncated ? listed.cursor : void 0
83
+ };
84
+ }
63
85
  async getPresignedUrl(path, method, expiresIn) {
64
86
  const secret = this.env.APP_SECRET;
65
87
  if (!secret) throw new StorageError("APP_SECRET is required for generating presigned URLs");
88
+ const urlWithMethod = `${`${this.routeBasePath}/${this.config.disk}/${path}`}?method=${method}`;
66
89
  return {
67
- url: await signUrl(`${`${this.routeBasePath}/${this.config.disk}/${path}`}?method=${method}`, secret, { expiresIn }),
90
+ url: await signUrl(urlWithMethod, secret, { expiresIn }),
68
91
  expiresIn,
69
92
  expiresAt: new Date(Date.now() + expiresIn * 1e3),
70
93
  method
@@ -79,7 +102,7 @@ var R2StorageProvider = class {
79
102
  try {
80
103
  if (body instanceof ReadableStream) {
81
104
  const reader = body.getReader();
82
- let buffer = new Uint8Array(0);
105
+ let buffer = /* @__PURE__ */ new Uint8Array(0);
83
106
  let partNumber = 1;
84
107
  while (true) {
85
108
  const { done, value } = await reader.read();
@@ -241,7 +264,23 @@ var R2StorageProvider = class {
241
264
  if (keysToDelete.length > 0) await this.bucket.delete(keysToDelete);
242
265
  }
243
266
  };
267
+ /**
268
+ * Narrows an R2 object to the provider-agnostic shape.
269
+ *
270
+ * Takes `R2Object` rather than the `R2ObjectBody` `get` returns, so it works for both `head` and
271
+ * `list` — neither of which carries a body.
272
+ */
273
+ function toHeadResult(obj) {
274
+ return {
275
+ path: obj.key,
276
+ size: obj.size,
277
+ contentType: obj.httpMetadata?.contentType,
278
+ etag: obj.etag,
279
+ uploadedAt: obj.uploaded,
280
+ metadata: obj.customMetadata
281
+ };
282
+ }
244
283
  //#endregion
245
284
  export { r2_storage_provider_exports as n, R2StorageProvider as t };
246
285
 
247
- //# sourceMappingURL=r2-storage.provider-BoZmR6Ut.mjs.map
286
+ //# sourceMappingURL=r2-storage.provider-wU_6api4.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"r2-storage.provider-wU_6api4.mjs","names":[],"sources":["../src/storage/providers/r2-storage.provider.ts"],"sourcesContent":["import { type StratalEnv } from '../../env'\nimport { signUrl } from '../../router/signed-url'\nimport type {\n DownloadResult,\n HeadResult,\n ListOptions,\n ListResult,\n PresignedUrlResult,\n UploadOptions,\n UploadResult,\n} from '../contracts'\nimport { StorageError } from '../storage.error'\nimport type { StorageEntry, StorageRouteConfig } from '../types'\nimport type {\n CompletedPart,\n CompleteMultipartResult,\n CreateMultipartOptions,\n CreateMultipartResult,\n DeleteObjectsResult,\n HeadObjectResult,\n IMultipartProvider,\n ListMultipartUploadsResult,\n ListPartsResult,\n UploadPartResult,\n} from './multipart-provider.interface'\nimport type { StreamingBlobPayloadInputTypes } from './storage-provider.interface'\n\nconst MIN_PART_SIZE = 5 * 1024 * 1024 // 5MB\nconst R2_BULK_DELETE_LIMIT = 1000\nconst MULTIPART_PREFIX = '__multipart/'\nconst PARTS_PREFIX = '__parts/'\n\n/**\n * R2 Storage Provider\n * Implements storage operations using native Cloudflare R2 bindings\n *\n * Implements IMultipartProvider for multipart upload support needed by TUS.\n * Tracks multipart upload metadata using companion R2 objects so that\n * listParts/listMultipartUploads work in all environments (local, test, prod).\n */\nexport class R2StorageProvider implements IMultipartProvider {\n private readonly routeBasePath: string\n\n constructor(\n private readonly config: StorageEntry,\n private readonly bucket: R2Bucket,\n private readonly env: StratalEnv,\n routeConfig?: StorageRouteConfig\n ) {\n this.routeBasePath = routeConfig?.basePath ?? '/storage'\n }\n\n async upload(\n body: StreamingBlobPayloadInputTypes,\n path: string,\n options: UploadOptions\n ): Promise<UploadResult> {\n const customMetadata: Record<string, string> = { ...options.metadata }\n if (options.tagging) {\n customMetadata['x-tags'] = options.tagging\n }\n\n await this.bucket.put(path, body as ReadableStream | ArrayBuffer | ArrayBufferView | string | Blob | null, {\n httpMetadata: {\n contentType: options.mimeType,\n },\n customMetadata,\n })\n\n return {\n path,\n disk: this.config.disk,\n fullPath: path,\n size: options.size,\n mimeType: options.mimeType ?? 'application/octet-stream',\n uploadedAt: new Date(),\n }\n }\n\n async download(path: string): Promise<DownloadResult> {\n const obj = await this.bucket.get(path)\n\n if (!obj) {\n throw new StorageError(`Storage object not found at path \"${path}\"`)\n }\n\n // R2ObjectBody has body, text(), arrayBuffer(), etc.\n const objBody = obj\n\n return {\n toStream: () => objBody.body as ReadableStream<Uint8Array>,\n contentType: obj.httpMetadata?.contentType ?? 'application/octet-stream',\n size: obj.size,\n metadata: obj.customMetadata,\n toString: () => objBody.text(),\n toArrayBuffer: () => objBody.bytes(),\n }\n }\n\n async delete(path: string): Promise<void> {\n await this.bucket.delete(path)\n }\n\n async exists(path: string): Promise<boolean> {\n const head = await this.bucket.head(path)\n return head !== null\n }\n\n async deleteMany(paths: string[]): Promise<void> {\n // Chunked here rather than by the caller: R2 accepts 1000 keys per call, and unlike a\n // listing there is no partial answer to misread — every key given is deleted before this\n // resolves.\n for (let i = 0; i < paths.length; i += R2_BULK_DELETE_LIMIT) {\n await this.bucket.delete(paths.slice(i, i + R2_BULK_DELETE_LIMIT))\n }\n }\n\n async head(path: string): Promise<HeadResult | null> {\n const obj = await this.bucket.head(path)\n if (obj === null) {\n return null\n }\n\n return toHeadResult(obj)\n }\n\n async list(options: ListOptions): Promise<ListResult> {\n const listed = await this.bucket.list({\n prefix: options.prefix,\n cursor: options.cursor,\n limit: options.limit,\n // R2 omits both unless asked (`r2_list_honor_include`, default since 2022-08-04), and\n // including them makes it return fewer objects per page — so this stays off unless the\n // caller says it reads those fields.\n include: options.includeMetadata ? ['httpMetadata', 'customMetadata'] : [],\n })\n\n return {\n objects: listed.objects.map((obj) => toHeadResult(obj)),\n // Passed through rather than resolved here. R2 caps a single list at 1000 keys whatever\n // `limit` asks for, so `truncated` is the only thing that tells a caller its aggregate is\n // complete — and looping internally would turn one await into an unbounded number of\n // subrequests, which on Workers is a budget the caller has to own.\n truncated: listed.truncated,\n cursor: listed.truncated ? listed.cursor : undefined,\n }\n }\n\n async getPresignedUrl(\n path: string,\n method: 'GET' | 'PUT' | 'DELETE' | 'HEAD',\n expiresIn: number\n ): Promise<PresignedUrlResult> {\n const secret = this.env.APP_SECRET;\n if (!secret) {\n throw new StorageError('APP_SECRET is required for generating presigned URLs')\n }\n\n // Build a relative URL pointing to the StorageController route\n const routePath = `${this.routeBasePath}/${this.config.disk}/${path}`\n const urlWithMethod = `${routePath}?method=${method}`\n const url = await signUrl(urlWithMethod, secret, { expiresIn })\n\n return {\n url,\n expiresIn,\n expiresAt: new Date(Date.now() + expiresIn * 1000),\n method,\n }\n }\n\n async chunkedUpload(\n body: StreamingBlobPayloadInputTypes,\n path: string,\n options: Omit<UploadOptions, 'size'> & { size?: number }\n ): Promise<UploadResult> {\n const multipartUpload = await this.bucket.createMultipartUpload(path, {\n httpMetadata: {\n contentType: options.mimeType,\n },\n customMetadata: options.metadata,\n })\n\n const parts: R2UploadedPart[] = []\n\n try {\n if (body instanceof ReadableStream) {\n const reader = (body as ReadableStream<Uint8Array>).getReader()\n let buffer = new Uint8Array(0)\n let partNumber = 1\n\n while (true) {\n const { done, value } = await reader.read()\n\n if (value) {\n const newBuffer = new Uint8Array(buffer.length + value.length)\n newBuffer.set(buffer, 0)\n newBuffer.set(value, buffer.length)\n buffer = newBuffer\n }\n\n while (buffer.length >= MIN_PART_SIZE) {\n const partData = buffer.slice(0, MIN_PART_SIZE)\n buffer = buffer.slice(MIN_PART_SIZE)\n const part = await multipartUpload.uploadPart(partNumber, partData)\n parts.push(part)\n partNumber++\n }\n\n if (done) {\n // Upload remaining buffer as final part\n if (buffer.length > 0) {\n const part = await multipartUpload.uploadPart(partNumber, buffer)\n parts.push(part)\n }\n break\n }\n }\n } else if (body !== null && body !== undefined) {\n // Non-stream body: upload as single part\n const part = await multipartUpload.uploadPart(1, body)\n parts.push(part)\n }\n\n await multipartUpload.complete(parts)\n } catch (error) {\n await multipartUpload.abort()\n throw error\n }\n\n // Get actual size via head\n const headResult = await this.bucket.head(path)\n\n return {\n path,\n disk: this.config.disk,\n fullPath: path,\n size: headResult?.size ?? options.size ?? 0,\n mimeType: options.mimeType ?? 'application/octet-stream',\n uploadedAt: new Date(),\n }\n }\n\n // ============================================\n // IMultipartProvider - Multipart upload methods\n // ============================================\n\n getBucket(): string {\n return this.config.binding\n }\n\n async headObject(key: string): Promise<HeadObjectResult | null> {\n const obj = await this.bucket.head(key)\n if (!obj) {\n return null\n }\n return {\n size: obj.size,\n contentType: obj.httpMetadata?.contentType,\n metadata: obj.customMetadata,\n }\n }\n\n async deleteObjects(keys: string[]): Promise<DeleteObjectsResult> {\n if (keys.length === 0) {\n return { deleted: 0, errors: [] }\n }\n\n await this.bucket.delete(keys)\n\n return {\n deleted: keys.length,\n errors: [],\n }\n }\n\n async createMultipartUpload(\n key: string,\n options?: CreateMultipartOptions\n ): Promise<CreateMultipartResult> {\n const upload = await this.bucket.createMultipartUpload(key, {\n httpMetadata: {\n contentType: options?.contentType,\n cacheControl: options?.cacheControl,\n },\n customMetadata: options?.metadata,\n })\n\n // Track upload for listMultipartUploads\n await this.bucket.put(\n `${MULTIPART_PREFIX}${upload.uploadId}.json`,\n JSON.stringify({ key: upload.key, uploadId: upload.uploadId, initiated: new Date().toISOString() }),\n { httpMetadata: { contentType: 'application/json' } }\n )\n\n return {\n uploadId: upload.uploadId,\n key: upload.key,\n }\n }\n\n async uploadPart(\n key: string,\n uploadId: string,\n partNumber: number,\n body: Uint8Array\n ): Promise<UploadPartResult> {\n const upload = this.bucket.resumeMultipartUpload(key, uploadId)\n const part = await upload.uploadPart(partNumber, body)\n\n // Track part for listParts\n await this.bucket.put(\n `${PARTS_PREFIX}${uploadId}/${partNumber}`,\n JSON.stringify({ partNumber: part.partNumber, etag: part.etag, size: body.length }),\n { httpMetadata: { contentType: 'application/json' } }\n )\n\n return {\n etag: part.etag,\n partNumber: part.partNumber,\n }\n }\n\n async completeMultipartUpload(\n key: string,\n uploadId: string,\n parts: CompletedPart[]\n ): Promise<CompleteMultipartResult> {\n const upload = this.bucket.resumeMultipartUpload(key, uploadId)\n const result = await upload.complete(\n parts.map((p) => ({\n partNumber: p.partNumber,\n etag: p.etag,\n }))\n )\n\n await this.cleanupTrackingObjects(uploadId)\n\n return {\n key: result.key,\n }\n }\n\n async abortMultipartUpload(key: string, uploadId: string): Promise<void> {\n const upload = this.bucket.resumeMultipartUpload(key, uploadId)\n await upload.abort()\n\n await this.cleanupTrackingObjects(uploadId)\n }\n\n async listParts(\n _key: string,\n uploadId: string,\n partNumberMarker?: string\n ): Promise<ListPartsResult> {\n const prefix = `${PARTS_PREFIX}${uploadId}/`\n const listed = await this.bucket.list({\n prefix,\n cursor: partNumberMarker,\n })\n\n const parts = await Promise.all(\n listed.objects.map(async (obj) => {\n const data = await this.bucket.get(obj.key)\n if (!data) {\n return null\n }\n return JSON.parse(await data.text()) as { partNumber: number; etag: string; size: number }\n })\n )\n\n const validParts = parts\n .filter((p): p is NonNullable<typeof p> => p !== null)\n .sort((a, b) => a.partNumber - b.partNumber)\n\n return {\n parts: validParts,\n isTruncated: listed.truncated,\n nextPartNumberMarker: listed.truncated ? listed.cursor : undefined,\n }\n }\n\n async listMultipartUploads(\n keyMarker?: string,\n _uploadIdMarker?: string\n ): Promise<ListMultipartUploadsResult> {\n const listed = await this.bucket.list({\n prefix: MULTIPART_PREFIX,\n cursor: keyMarker,\n })\n\n const uploads = await Promise.all(\n listed.objects.map(async (obj) => {\n const data = await this.bucket.get(obj.key)\n if (!data) {\n return null\n }\n const parsed = JSON.parse(await data.text()) as { key: string; uploadId: string; initiated: string }\n return {\n key: parsed.key,\n uploadId: parsed.uploadId,\n initiated: new Date(parsed.initiated),\n }\n })\n )\n\n return {\n uploads: uploads.filter((u): u is NonNullable<typeof u> => u !== null),\n isTruncated: listed.truncated,\n nextKeyMarker: listed.truncated ? listed.cursor : undefined,\n nextUploadIdMarker: undefined,\n }\n }\n\n // ============================================\n // Private helpers\n // ============================================\n\n private async cleanupTrackingObjects(uploadId: string): Promise<void> {\n // Delete part tracking objects\n const partsPrefix = `${PARTS_PREFIX}${uploadId}/`\n let cursor: string | undefined\n const keysToDelete: string[] = []\n\n do {\n const listed = await this.bucket.list({ prefix: partsPrefix, cursor })\n for (const obj of listed.objects) {\n keysToDelete.push(obj.key)\n }\n cursor = listed.truncated ? listed.cursor : undefined\n } while (cursor)\n\n // Delete multipart tracking object\n keysToDelete.push(`${MULTIPART_PREFIX}${uploadId}.json`)\n\n if (keysToDelete.length > 0) {\n await this.bucket.delete(keysToDelete)\n }\n }\n}\n\n/**\n * Narrows an R2 object to the provider-agnostic shape.\n *\n * Takes `R2Object` rather than the `R2ObjectBody` `get` returns, so it works for both `head` and\n * `list` — neither of which carries a body.\n */\nfunction toHeadResult(obj: R2Object): HeadResult {\n return {\n path: obj.key,\n size: obj.size,\n contentType: obj.httpMetadata?.contentType,\n etag: obj.etag,\n uploadedAt: obj.uploaded,\n metadata: obj.customMetadata,\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { n as getMetadata, t as defineMetadata } from "./metadata-DzzprcID.mjs";
2
- import { u as ROUTE_METADATA_KEYS } from "./exception-context-D-kvney-.mjs";
1
+ import { _ as defineMetadata, v as getMetadata } from "./di-DQ3xbjgf.mjs";
2
+ import { u as ROUTE_METADATA_KEYS } from "./exception-context-CdnGTQNF.mjs";
3
3
  //#region src/rate-limiter/decorators/rate-limit.decorator.ts
4
4
  const KEY = ROUTE_METADATA_KEYS.RATE_LIMIT;
5
5
  /**
@@ -30,8 +30,13 @@ const KEY = ROUTE_METADATA_KEYS.RATE_LIMIT;
30
30
  */
31
31
  function RateLimit(name) {
32
32
  return (target, propertyKey) => {
33
- if (propertyKey === void 0) defineMetadata(KEY, [...getMetadata(KEY, target) ?? [], name], target);
34
- else defineMetadata(KEY, [...getMetadata(KEY, target, propertyKey) ?? [], name], target, propertyKey);
33
+ if (propertyKey === void 0) {
34
+ const existing = getMetadata(KEY, target) ?? [];
35
+ defineMetadata(KEY, [...existing, name], target);
36
+ } else {
37
+ const existing = getMetadata(KEY, target, propertyKey) ?? [];
38
+ defineMetadata(KEY, [...existing, name], target, propertyKey);
39
+ }
35
40
  };
36
41
  }
37
42
  /**
@@ -47,4 +52,4 @@ function getRateLimits(target, propertyKey) {
47
52
  //#endregion
48
53
  export { getRateLimits as n, RateLimit as t };
49
54
 
50
- //# sourceMappingURL=rate-limit.decorator-Djs4oYDB.mjs.map
55
+ //# sourceMappingURL=rate-limit.decorator-PYQ9EYqs.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.decorator-Djs4oYDB.mjs","names":[],"sources":["../src/rate-limiter/decorators/rate-limit.decorator.ts"],"sourcesContent":["import { defineMetadata, getMetadata } from '../../di/metadata'\nimport { ROUTE_METADATA_KEYS } from '../../router/constants'\n\nconst KEY = ROUTE_METADATA_KEYS.RATE_LIMIT\n\n/**\n * Apply a named rate limiter to a controller class or a single route method.\n *\n * Stacks: multiple `@RateLimit` decorators on the same target push onto\n * the metadata array — every named limiter is enforced. Class-level limits\n * run before method-level limits in the resulting middleware chain.\n *\n * The named limiter must be registered separately via\n * `RateLimiterRegistry.for('name', resolver)` (typically inside a\n * module's `onInitialize` hook) and the user must import\n * `RateLimiterModule.forRoot({ store: ... })` in their AppModule.\n *\n * @example\n * ```typescript\n * @Controller('/api/v1/users')\n * @RateLimit('api')\n * export class UsersController {\n * @Get('/')\n * list(ctx: RouterContext) { ... }\n *\n * @Post('/')\n * @RateLimit('writes') // stacks with class-level 'api'\n * create(ctx: RouterContext) { ... }\n * }\n * ```\n */\nexport function RateLimit(name: string): ClassDecorator & MethodDecorator {\n return (target: object, propertyKey?: string | symbol) => {\n if (propertyKey === undefined) {\n const existing = getMetadata<string[]>(KEY, target) ?? []\n defineMetadata(KEY, [...existing, name], target)\n } else {\n const existing = getMetadata<string[]>(KEY, target, propertyKey as string) ?? []\n defineMetadata(KEY, [...existing, name], target, propertyKey as string)\n }\n }\n}\n\n/**\n * Read the rate-limit names attached to a class or method via `@RateLimit`.\n * Returns an empty array when no decorator was applied.\n *\n * @param target - For class metadata, pass the controller constructor.\n * For method metadata, pass `Controller.prototype` and the method name.\n */\nexport function getRateLimits(target: object, propertyKey?: string): string[] {\n const meta = propertyKey === undefined\n ? getMetadata<string[]>(KEY, target)\n : getMetadata<string[]>(KEY, target, propertyKey)\n return meta ?? []\n}\n"],"mappings":";;;AAGA,MAAM,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BhC,SAAgB,UAAU,MAAgD;CACxE,QAAQ,QAAgB,gBAAkC;EACxD,IAAI,gBAAgB,KAAA,GAElB,eAAe,KAAK,CAAC,GADJ,YAAsB,KAAK,MAAM,KAAK,CAAC,GACtB,IAAI,GAAG,MAAM;OAG/C,eAAe,KAAK,CAAC,GADJ,YAAsB,KAAK,QAAQ,WAAqB,KAAK,CAAC,GAC7C,IAAI,GAAG,QAAQ,WAAqB;CAE1E;AACF;;;;;;;;AASA,SAAgB,cAAc,QAAgB,aAAgC;CAI5E,QAHa,gBAAgB,KAAA,IACzB,YAAsB,KAAK,MAAM,IACjC,YAAsB,KAAK,QAAQ,WAAW,MACnC,CAAC;AAClB"}
1
+ {"version":3,"file":"rate-limit.decorator-PYQ9EYqs.mjs","names":[],"sources":["../src/rate-limiter/decorators/rate-limit.decorator.ts"],"sourcesContent":["import { defineMetadata, getMetadata } from '../../di/metadata'\nimport { ROUTE_METADATA_KEYS } from '../../router/constants'\n\nconst KEY = ROUTE_METADATA_KEYS.RATE_LIMIT\n\n/**\n * Apply a named rate limiter to a controller class or a single route method.\n *\n * Stacks: multiple `@RateLimit` decorators on the same target push onto\n * the metadata array — every named limiter is enforced. Class-level limits\n * run before method-level limits in the resulting middleware chain.\n *\n * The named limiter must be registered separately via\n * `RateLimiterRegistry.for('name', resolver)` (typically inside a\n * module's `onInitialize` hook) and the user must import\n * `RateLimiterModule.forRoot({ store: ... })` in their AppModule.\n *\n * @example\n * ```typescript\n * @Controller('/api/v1/users')\n * @RateLimit('api')\n * export class UsersController {\n * @Get('/')\n * list(ctx: RouterContext) { ... }\n *\n * @Post('/')\n * @RateLimit('writes') // stacks with class-level 'api'\n * create(ctx: RouterContext) { ... }\n * }\n * ```\n */\nexport function RateLimit(name: string): ClassDecorator & MethodDecorator {\n return (target: object, propertyKey?: string | symbol) => {\n if (propertyKey === undefined) {\n const existing = getMetadata<string[]>(KEY, target) ?? []\n defineMetadata(KEY, [...existing, name], target)\n } else {\n const existing = getMetadata<string[]>(KEY, target, propertyKey) ?? []\n defineMetadata(KEY, [...existing, name], target, propertyKey)\n }\n }\n}\n\n/**\n * Read the rate-limit names attached to a class or method via `@RateLimit`.\n * Returns an empty array when no decorator was applied.\n *\n * @param target - For class metadata, pass the controller constructor.\n * For method metadata, pass `Controller.prototype` and the method name.\n */\nexport function getRateLimits(target: object, propertyKey?: string): string[] {\n const meta = propertyKey === undefined\n ? getMetadata<string[]>(KEY, target)\n : getMetadata<string[]>(KEY, target, propertyKey)\n return meta ?? []\n}\n"],"mappings":";;;AAGA,MAAM,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BhC,SAAgB,UAAU,MAAgD;CACxE,QAAQ,QAAgB,gBAAkC;EACxD,IAAI,gBAAgB,KAAA,GAAW;GAC7B,MAAM,WAAW,YAAsB,KAAK,MAAM,KAAK,CAAC;GACxD,eAAe,KAAK,CAAC,GAAG,UAAU,IAAI,GAAG,MAAM;EACjD,OAAO;GACL,MAAM,WAAW,YAAsB,KAAK,QAAQ,WAAW,KAAK,CAAC;GACrE,eAAe,KAAK,CAAC,GAAG,UAAU,IAAI,GAAG,QAAQ,WAAW;EAC9D;CACF;AACF;;;;;;;;AASA,SAAgB,cAAc,QAAgB,aAAgC;CAI5E,QAHa,gBAAgB,KAAA,IACzB,YAAsB,KAAK,MAAM,IACjC,YAAsB,KAAK,QAAQ,WAAW,MACnC,CAAC;AAClB"}
@@ -1,9 +1,8 @@
1
- import { $ as Container, Cn as Middleware, Fn as DynamicModule, Hn as OnInitialize, Ir as ApplicationError, Nn as AsyncModuleOptions, Vn as OnException, f as ExceptionHandler, l as HttpException, st as RouterContext, wn as Next, zn as ModuleContext } from "../index-uybm0bhQ.mjs";
1
+ import { $ as Container, Jn as AsyncModuleOptions, Xn as DynamicModule, Xr as Middleware, Zr as Next, er as ModuleContext, f as ExceptionHandler, l as HttpException, li as ApplicationError, nr as OnException, rr as OnInitialize, st as RouterContext } from "../index-B2WtEagR.mjs";
2
2
  import { t as Macroable } from "../index-0ItCjaqw.mjs";
3
3
  import { t as StratalEnv } from "../env-ug22bJj7.mjs";
4
4
  import { t as Constructor } from "../types-CmV_9xBD.mjs";
5
- import { t as CacheService } from "../cache.service-uElmBtdS.mjs";
6
-
5
+ import { t as CacheService } from "../cache.service-Dg0Hj0gp.mjs";
7
6
  //#region src/rate-limiter/errors.d.ts
8
7
  /**
9
8
  * Thrown when a request exceeds a configured rate limit.
@@ -15,7 +14,7 @@ import { t as CacheService } from "../cache.service-uElmBtdS.mjs";
15
14
  * headers (`Retry-After`, `X-RateLimit-*`) are injected by the
16
15
  * `respond()` callback registered via `RateLimiterModule.onException`.
17
16
  */
18
- declare class TooManyRequestsError extends HttpException {
17
+ export declare class TooManyRequestsError extends HttpException {
19
18
  readonly info: {
20
19
  retryAfter: number;
21
20
  limit: number;
@@ -27,7 +26,7 @@ declare class TooManyRequestsError extends HttpException {
27
26
  resetAt: number;
28
27
  });
29
28
  }
30
- declare class RateLimiterError extends ApplicationError {}
29
+ export declare class RateLimiterError extends ApplicationError {}
31
30
  //#endregion
32
31
  //#region src/rate-limiter/limit.d.ts
33
32
  /**
@@ -64,12 +63,13 @@ type RateLimitResponseHandler = (ctx: RouterContext, headers: RateLimitHeaders)
64
63
  * Limit.none() // bypass
65
64
  * ```
66
65
  */
67
- declare class Limit {
66
+ export declare class Limit {
68
67
  readonly windowSeconds: number;
69
68
  readonly max: number;
70
69
  readonly disabled: boolean;
71
70
  private _key?;
72
71
  private _customResponse?;
72
+ private _distinctValue?;
73
73
  private constructor();
74
74
  static perSecond(max: number): Limit;
75
75
  static perSeconds(seconds: number, max: number): Limit;
@@ -87,8 +87,30 @@ declare class Limit {
87
87
  * them as it sees fit.
88
88
  */
89
89
  response(handler: RateLimitResponseHandler): this;
90
+ /**
91
+ * Count DISTINCT values of `value` in the window instead of counting requests.
92
+ *
93
+ * `Limit.perDay(10).distinctBy(courseCode).by(userId)` admits a user touching up to ten
94
+ * different courses a day, however many requests each takes — and keeps admitting a course
95
+ * already counted once the budget is full, which a request counter cannot express.
96
+ *
97
+ * A new value past the cap is refused without being stored, so the stored set never exceeds
98
+ * `max`.
99
+ *
100
+ * Two consequences worth knowing before reaching for this:
101
+ *
102
+ * - `X-RateLimit-Remaining` counts unused DISTINCT slots, not remaining requests. A user who
103
+ * has spent every slot gets `0` on each *successful* request for a value already counted, so
104
+ * a client that throttles itself on the header will stop issuing requests that would succeed.
105
+ * - The cap is a soft ceiling on an eventually-consistent store (KV): concurrent requests
106
+ * carrying different new values can lose each other's writes, and a lost set member is gone
107
+ * for the rest of the window. Use small caps — tens, not thousands — and a
108
+ * strongly-consistent store (a Durable Object) if the cap must be hard.
109
+ */
110
+ distinctBy(value: string | number): this;
90
111
  get key(): string | undefined;
91
112
  get customResponse(): RateLimitResponseHandler | undefined;
113
+ get distinctValue(): string | undefined;
92
114
  }
93
115
  //#endregion
94
116
  //#region src/rate-limiter/rate-limiter-registry.d.ts
@@ -117,7 +139,7 @@ type LimitResolver = (ctx: RouterContext) => Limit | Limit[] | Promise<Limit | L
117
139
  * can attach extra registration methods such as `forPath()` for better-auth
118
140
  * `customRules` interop.
119
141
  */
120
- declare class RateLimiterRegistry extends Macroable {
142
+ export declare class RateLimiterRegistry extends Macroable {
121
143
  private readonly container;
122
144
  private readonly resolvers;
123
145
  constructor(container: Container);
@@ -149,6 +171,31 @@ declare class RateLimiterRegistry extends Macroable {
149
171
  * concurrent edge requests on KV — see `KvRateLimiterStore`'s caveat.
150
172
  */
151
173
  private hit;
174
+ /**
175
+ * A window that counts DISTINCT values rather than requests.
176
+ *
177
+ * Uses the same typed-KV store as {@link hit} — the store persists arbitrary values, so a
178
+ * cardinality window needs no store-interface change — but a key namespace of its own, since
179
+ * the two shapes are not interchangeable and a shared key would have each read the other's.
180
+ *
181
+ * A value already present is admitted WITHOUT a write, which both keeps the count stable and
182
+ * leaves the TTL alone: re-touching something already counted must not slide the window. The
183
+ * stored set therefore never exceeds `max`.
184
+ *
185
+ * The cap is a SOFT ceiling under an eventually-consistent store. This is a read-modify-write
186
+ * of the whole array, so N concurrent requests carrying N different values all read the same
187
+ * set and last-write-wins keeps one of them: a burst can overshoot `max`, and unlike a lost
188
+ * counter increment (which the next request re-adds) a lost set member is gone for the rest of
189
+ * the window. Intended for small caps — tens, not thousands; nothing bounds `max` itself, and
190
+ * the whole set is read and rewritten on every new value. A hard cap needs a
191
+ * strongly-consistent store, i.e. a Durable Object.
192
+ */
193
+ private hitDistinct;
194
+ /**
195
+ * Distinct windows carry a `d:` segment: they store `StoredDistinct` where a plain window
196
+ * stores `StoredHit`, and one limiter name can legitimately declare both on the same window
197
+ * and actor. Sharing a key would have each branch read the other's shape and throw.
198
+ */
152
199
  private makeKey;
153
200
  private makeHeaders;
154
201
  }
@@ -217,7 +264,7 @@ type RateLimiterModuleOptions = {
217
264
  useClass: Constructor<IRateLimiterStore>;
218
265
  };
219
266
  };
220
- declare class RateLimiterStoreFactory {
267
+ export declare class RateLimiterStoreFactory {
221
268
  private readonly env;
222
269
  private readonly cache;
223
270
  private readonly container;
@@ -251,7 +298,7 @@ declare class RateLimiterStoreFactory {
251
298
  * on every {@link TooManyRequestsError} response, regardless of whether
252
299
  * the body was rendered as JSON, HTML, or via Inertia.
253
300
  */
254
- declare class RateLimiterModule implements OnInitialize, OnException {
301
+ export declare class RateLimiterModule implements OnInitialize, OnException {
255
302
  /**
256
303
  * Configure the rate limiter with a store choice.
257
304
  *
@@ -271,9 +318,7 @@ declare class RateLimiterModule implements OnInitialize, OnException {
271
318
  * indistinguishable at the DI layer because @Module's `registry()` call
272
319
  * binds providers globally at decoration time).
273
320
  */
274
- onInitialize({
275
- container
276
- }: ModuleContext): void;
321
+ onInitialize({ container }: ModuleContext): void;
277
322
  /**
278
323
  * Inject `Retry-After` + `X-RateLimit-*` headers on every 429 response.
279
324
  * The body itself is rendered by `ExceptionHandler.defaultRender` —
@@ -284,7 +329,7 @@ declare class RateLimiterModule implements OnInitialize, OnException {
284
329
  }
285
330
  //#endregion
286
331
  //#region src/rate-limiter/rate-limiter.tokens.d.ts
287
- declare const RATE_LIMITER_TOKENS: {
332
+ export declare const RATE_LIMITER_TOKENS: {
288
333
  readonly Registry: symbol;
289
334
  readonly Store: symbol;
290
335
  readonly StoreFactory: symbol;
@@ -307,7 +352,7 @@ type RateLimiterToken = (typeof RATE_LIMITER_TOKENS)[keyof typeof RATE_LIMITER_T
307
352
  * registered by `RateLimiterModule.onInitialize`. We check
308
353
  * `isRegistered(marker)` at request time before resolving Registry.
309
354
  */
310
- declare function createThrottleMiddleware(name: string): Constructor<Middleware>;
355
+ export declare function createThrottleMiddleware(name: string): Constructor<Middleware>;
311
356
  //#endregion
312
357
  //#region src/rate-limiter/decorators/rate-limit.decorator.d.ts
313
358
  /**
@@ -336,7 +381,7 @@ declare function createThrottleMiddleware(name: string): Constructor<Middleware>
336
381
  * }
337
382
  * ```
338
383
  */
339
- declare function RateLimit(name: string): ClassDecorator & MethodDecorator;
384
+ export declare function RateLimit(name: string): ClassDecorator & MethodDecorator;
340
385
  /**
341
386
  * Read the rate-limit names attached to a class or method via `@RateLimit`.
342
387
  * Returns an empty array when no decorator was applied.
@@ -344,7 +389,7 @@ declare function RateLimit(name: string): ClassDecorator & MethodDecorator;
344
389
  * @param target - For class metadata, pass the controller constructor.
345
390
  * For method metadata, pass `Controller.prototype` and the method name.
346
391
  */
347
- declare function getRateLimits(target: object, propertyKey?: string): string[];
392
+ export declare function getRateLimits(target: object, propertyKey?: string): string[];
348
393
  //#endregion
349
394
  //#region src/rate-limiter/stores/kv-store.d.ts
350
395
  /**
@@ -359,7 +404,7 @@ declare function getRateLimits(target: object, propertyKey?: string): string[];
359
404
  * KV tradeoff — pick `{ useClass: MyDurableObjectStore }` for strict
360
405
  * accuracy across edges.
361
406
  */
362
- declare class KvRateLimiterStore implements IRateLimiterStore {
407
+ export declare class KvRateLimiterStore implements IRateLimiterStore {
363
408
  private readonly cache;
364
409
  constructor(cache: CacheService);
365
410
  get<T>(key: string): Promise<T | null>;
@@ -377,12 +422,12 @@ declare class KvRateLimiterStore implements IRateLimiterStore {
377
422
  * for production. Expiry is lazy: stale entries are dropped on the next
378
423
  * `get`.
379
424
  */
380
- declare class InMemoryRateLimiterStore implements IRateLimiterStore {
425
+ export declare class InMemoryRateLimiterStore implements IRateLimiterStore {
381
426
  private readonly entries;
382
427
  get<T>(key: string): Promise<T | null>;
383
428
  set<T>(key: string, value: T, ttlSeconds: number): Promise<void>;
384
429
  delete(key: string): Promise<void>;
385
430
  }
386
431
  //#endregion
387
- export { type IRateLimiterStore, InMemoryRateLimiterStore, KvRateLimiterStore, Limit, type LimitResolver, RATE_LIMITER_TOKENS, RateLimit, type RateLimitHeaders, type RateLimitHit, type RateLimitResponseHandler, RateLimiterError, RateLimiterModule, type RateLimiterModuleOptions, RateLimiterRegistry, RateLimiterStoreFactory, type RateLimiterToken, TooManyRequestsError, createThrottleMiddleware, getRateLimits };
432
+ export type { IRateLimiterStore, LimitResolver, RateLimitHeaders, RateLimitHit, RateLimitResponseHandler, RateLimiterModuleOptions, RateLimiterToken };
388
433
  //# sourceMappingURL=index.d.mts.map