stratal 0.0.21 → 0.0.22

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 (246) hide show
  1. package/README.md +1 -1
  2. package/dist/{base-email.provider-CfQCA08m.mjs → base-email.provider-BWZHIjt8.mjs} +1 -1
  3. package/dist/{base-email.provider-CfQCA08m.mjs.map → base-email.provider-BWZHIjt8.mjs.map} +1 -1
  4. package/dist/bin/quarry.mjs +46 -109
  5. package/dist/bin/quarry.mjs.map +1 -1
  6. package/dist/cache/index.d.mts +6 -46
  7. package/dist/cache/index.d.mts.map +1 -1
  8. package/dist/cache/index.mjs +20 -67
  9. package/dist/cache/index.mjs.map +1 -1
  10. package/dist/{cache.service-DsnKuNyO.d.mts → cache.service-e34gV6tz.d.mts} +8 -8
  11. package/dist/{cache.service-DsnKuNyO.d.mts.map → cache.service-e34gV6tz.d.mts.map} +1 -1
  12. package/dist/{cache.tokens-B7Rw1C9Q.mjs → cache.tokens-ovi_c52J.mjs} +1 -1
  13. package/dist/{cache.tokens-B7Rw1C9Q.mjs.map → cache.tokens-ovi_c52J.mjs.map} +1 -1
  14. package/dist/{colors-DJaRDXoS.mjs → colors-axmupKdp.mjs} +1 -1
  15. package/dist/{colors-DJaRDXoS.mjs.map → colors-axmupKdp.mjs.map} +1 -1
  16. package/dist/{command-BgSlsS4M.mjs → command-BU4ApTo5.mjs} +2 -3
  17. package/dist/command-BU4ApTo5.mjs.map +1 -0
  18. package/dist/{command-Cmmf0oHX.d.mts → command-wXfvHbBZ.d.mts} +3 -2
  19. package/dist/command-wXfvHbBZ.d.mts.map +1 -0
  20. package/dist/config/index.d.mts +24 -11
  21. package/dist/config/index.d.mts.map +1 -1
  22. package/dist/config/index.mjs +31 -57
  23. package/dist/config/index.mjs.map +1 -1
  24. package/dist/{consumer-registry-B7yUNh0q.d.mts → consumer-registry-DHQtypr1.d.mts} +1 -1
  25. package/dist/{consumer-registry-B7yUNh0q.d.mts.map → consumer-registry-DHQtypr1.d.mts.map} +1 -1
  26. package/dist/container-storage-GpNNz79X.mjs +52 -0
  27. package/dist/container-storage-GpNNz79X.mjs.map +1 -0
  28. package/dist/{controller.decorator-B9vwn0zK.mjs → controller.decorator-DIUazNU7.mjs} +8 -8
  29. package/dist/controller.decorator-DIUazNU7.mjs.map +1 -0
  30. package/dist/cron/index.d.mts +26 -5
  31. package/dist/cron/index.d.mts.map +1 -1
  32. package/dist/cron/index.mjs +1 -1
  33. package/dist/{cron-manager-DQSK8uoV.mjs → cron-manager-9bpN9bu4.mjs} +35 -15
  34. package/dist/cron-manager-9bpN9bu4.mjs.map +1 -0
  35. package/dist/{cron-manager-CmTimEjf.d.mts → cron-manager-CSTIBPcM.d.mts} +6 -13
  36. package/dist/cron-manager-CSTIBPcM.d.mts.map +1 -0
  37. package/dist/decorate-HgTKAYK8.mjs +16 -0
  38. package/dist/deep-merge-C8NgcXw4.mjs +18 -0
  39. package/dist/deep-merge-C8NgcXw4.mjs.map +1 -0
  40. package/dist/di/index.d.mts +2 -2
  41. package/dist/di/index.mjs +4 -3
  42. package/dist/di-BO1QIb5H.mjs +415 -0
  43. package/dist/di-BO1QIb5H.mjs.map +1 -0
  44. package/dist/email/index.d.mts +14 -89
  45. package/dist/email/index.d.mts.map +1 -1
  46. package/dist/email/index.mjs +25 -125
  47. package/dist/email/index.mjs.map +1 -1
  48. package/dist/en-BPP6h6y5.mjs +202 -0
  49. package/dist/en-BPP6h6y5.mjs.map +1 -0
  50. package/dist/{env-D1rcZ8_r.d.mts → env-DKSbuBi5.d.mts} +1 -1
  51. package/dist/env-DKSbuBi5.d.mts.map +1 -0
  52. package/dist/errors/index.d.mts +2 -2
  53. package/dist/errors/index.mjs +4 -2
  54. package/dist/errors-BBZTnjdq.mjs +333 -0
  55. package/dist/errors-BBZTnjdq.mjs.map +1 -0
  56. package/dist/events/index.d.mts +2 -2
  57. package/dist/events/index.d.mts.map +1 -1
  58. package/dist/events/index.mjs +1 -1
  59. package/dist/{events-CzCV8jI8.mjs → events-D1KdDaiP.mjs} +11 -11
  60. package/dist/events-D1KdDaiP.mjs.map +1 -0
  61. package/dist/exception-context-B4kM-M53.mjs +429 -0
  62. package/dist/exception-context-B4kM-M53.mjs.map +1 -0
  63. package/dist/{gateway-context-CXmXtaUP.mjs → gateway-context-CFe6a9gz.mjs} +19 -31
  64. package/dist/gateway-context-CFe6a9gz.mjs.map +1 -0
  65. package/dist/guards/index.d.mts +3 -3
  66. package/dist/guards/index.d.mts.map +1 -1
  67. package/dist/guards/index.mjs +1 -1
  68. package/dist/{guards-DU1_J9YA.mjs → guards-Ced-uNIF.mjs} +6 -5
  69. package/dist/guards-Ced-uNIF.mjs.map +1 -0
  70. package/dist/{http-method.decorator-BrgHMdLQ.mjs → http-method.decorator-CdjKFJZZ.mjs} +7 -6
  71. package/dist/http-method.decorator-CdjKFJZZ.mjs.map +1 -0
  72. package/dist/i18n/index.d.mts +238 -3
  73. package/dist/i18n/index.d.mts.map +1 -0
  74. package/dist/i18n/index.mjs +39 -3
  75. package/dist/i18n/index.mjs.map +1 -0
  76. package/dist/i18n/messages/en/index.d.mts +2 -2
  77. package/dist/i18n/messages/en/index.mjs +2 -2
  78. package/dist/i18n/utils/index.d.mts +4 -26
  79. package/dist/i18n/utils/index.d.mts.map +1 -1
  80. package/dist/i18n/utils/index.mjs +2 -2
  81. package/dist/i18n/validation/index.d.mts +3 -2
  82. package/dist/i18n/validation/index.mjs +4 -2
  83. package/dist/i18n.module-BlXrtAlV.mjs +219 -0
  84. package/dist/i18n.module-BlXrtAlV.mjs.map +1 -0
  85. package/dist/i18n.tokens-hwRpmjRq.mjs +19 -0
  86. package/dist/i18n.tokens-hwRpmjRq.mjs.map +1 -0
  87. package/dist/{index-7-hU3GTV.d.mts → index-B4UBK-2T.d.mts} +1 -1
  88. package/dist/{index-7-hU3GTV.d.mts.map → index-B4UBK-2T.d.mts.map} +1 -1
  89. package/dist/index-BtlE9RuO.d.mts +124 -0
  90. package/dist/index-BtlE9RuO.d.mts.map +1 -0
  91. package/dist/{index-DUzWs0z7.d.mts → index-CW1YHSft.d.mts} +71 -167
  92. package/dist/index-CW1YHSft.d.mts.map +1 -0
  93. package/dist/{index-ByOyTmqf.d.mts → index-DEncMcC6.d.mts} +554 -2237
  94. package/dist/index-DEncMcC6.d.mts.map +1 -0
  95. package/dist/index-Dj5IMwtr.d.mts +44 -0
  96. package/dist/index-Dj5IMwtr.d.mts.map +1 -0
  97. package/dist/{index-C1KvMncZ.d.mts → index-KMgSCSM7.d.mts} +3 -108
  98. package/dist/index-KMgSCSM7.d.mts.map +1 -0
  99. package/dist/index.d.mts +5 -43
  100. package/dist/index.mjs +1 -1
  101. package/dist/{is-command-C6a7WTPw.mjs → is-command-CX5rAfZW.mjs} +2 -2
  102. package/dist/{is-command-C6a7WTPw.mjs.map → is-command-CX5rAfZW.mjs.map} +1 -1
  103. package/dist/{is-seeder-CebjZCDn.mjs → is-seeder-CYCtELlm.mjs} +1 -1
  104. package/dist/{is-seeder-CebjZCDn.mjs.map → is-seeder-CYCtELlm.mjs.map} +1 -1
  105. package/dist/logger/index.d.mts +2 -2
  106. package/dist/logger/index.mjs +170 -2
  107. package/dist/logger/index.mjs.map +1 -0
  108. package/dist/macroable/index.d.mts +1 -1
  109. package/dist/macroable/index.mjs +1 -1
  110. package/dist/{macroable-BmufBshB.mjs → macroable-DzlfzT50.mjs} +1 -1
  111. package/dist/{macroable-BmufBshB.mjs.map → macroable-DzlfzT50.mjs.map} +1 -1
  112. package/dist/metadata-BVkc4aUu.mjs +39 -0
  113. package/dist/metadata-BVkc4aUu.mjs.map +1 -0
  114. package/dist/module/index.d.mts +6 -24
  115. package/dist/module/index.d.mts.map +1 -1
  116. package/dist/module/index.mjs +2 -2
  117. package/dist/module-xYoHba6B.mjs +422 -0
  118. package/dist/module-xYoHba6B.mjs.map +1 -0
  119. package/dist/openapi/index.d.mts +3 -3
  120. package/dist/openapi/index.d.mts.map +1 -1
  121. package/dist/openapi/index.mjs +1 -2
  122. package/dist/openapi-C6lm0RmV.mjs +483 -0
  123. package/dist/openapi-C6lm0RmV.mjs.map +1 -0
  124. package/dist/{openapi.service-Bt9bCIrd.d.mts → openapi.service-CrLlsXAd.d.mts} +3 -3
  125. package/dist/openapi.service-CrLlsXAd.d.mts.map +1 -0
  126. package/dist/quarry/index.d.mts +5 -163
  127. package/dist/quarry/index.d.mts.map +1 -1
  128. package/dist/quarry/index.mjs +5 -5
  129. package/dist/quarry/runner.d.mts +184 -0
  130. package/dist/quarry/runner.d.mts.map +1 -0
  131. package/dist/quarry/runner.mjs +775 -0
  132. package/dist/quarry/runner.mjs.map +1 -0
  133. package/dist/quarry-registry-D4hIGScf.d.mts +69 -0
  134. package/dist/quarry-registry-D4hIGScf.d.mts.map +1 -0
  135. package/dist/quarry-registry-DkraZNwn.mjs +311 -0
  136. package/dist/quarry-registry-DkraZNwn.mjs.map +1 -0
  137. package/dist/queue/index.d.mts +3 -3
  138. package/dist/queue/index.mjs +26 -28
  139. package/dist/queue/index.mjs.map +1 -1
  140. package/dist/{queue.module-BhCjZp6H.mjs → queue.module-DeWJ0tQM.mjs} +59 -113
  141. package/dist/queue.module-DeWJ0tQM.mjs.map +1 -0
  142. package/dist/{r2-storage.provider-DuonKeYm.mjs → r2-storage.provider-Hfm6LdZQ.mjs} +5 -5
  143. package/dist/r2-storage.provider-Hfm6LdZQ.mjs.map +1 -0
  144. package/dist/{rate-limit.decorator-6qzNcSOt.mjs → rate-limit.decorator-D69zdZbp.mjs} +6 -11
  145. package/dist/rate-limit.decorator-D69zdZbp.mjs.map +1 -0
  146. package/dist/rate-limiter/index.d.mts +11 -50
  147. package/dist/rate-limiter/index.d.mts.map +1 -1
  148. package/dist/rate-limiter/index.mjs +16 -30
  149. package/dist/rate-limiter/index.mjs.map +1 -1
  150. package/dist/{resend.provider-DB4IlFjG.mjs → resend.provider-Ur6tU7fK.mjs} +7 -7
  151. package/dist/resend.provider-Ur6tU7fK.mjs.map +1 -0
  152. package/dist/router/index.d.mts +2 -2
  153. package/dist/router/index.mjs +8 -7
  154. package/dist/{i18n.module-CzXLW9Hy.mjs → router-Cy6DjkvP.mjs} +171 -851
  155. package/dist/router-Cy6DjkvP.mjs.map +1 -0
  156. package/dist/seeder/index.d.mts +6 -11
  157. package/dist/seeder/index.d.mts.map +1 -1
  158. package/dist/seeder/index.mjs +3 -3
  159. package/dist/{seeder-zoEfEw9i.mjs → seeder-BADTig4n.mjs} +14 -22
  160. package/dist/seeder-BADTig4n.mjs.map +1 -0
  161. package/dist/{signed-url-BQPbv2In.mjs → signed-url-BqUqt5dF.mjs} +1 -1
  162. package/dist/{signed-url-BQPbv2In.mjs.map → signed-url-BqUqt5dF.mjs.map} +1 -1
  163. package/dist/{smtp.provider-B6D7zuWX.mjs → smtp.provider-C129sNBT.mjs} +6 -6
  164. package/dist/smtp.provider-C129sNBT.mjs.map +1 -0
  165. package/dist/storage/index.d.mts +15 -39
  166. package/dist/storage/index.d.mts.map +1 -1
  167. package/dist/storage/index.mjs +3 -3
  168. package/dist/storage/providers/index.d.mts +2 -2
  169. package/dist/storage/providers/index.mjs +1 -1
  170. package/dist/{storage-D8CBP72Z.mjs → storage-BA3ppVYM.mjs} +65 -59
  171. package/dist/storage-BA3ppVYM.mjs.map +1 -0
  172. package/dist/{storage-provider.interface-Bd6vA4ak.d.mts → storage-provider.interface-DQMtT42e.d.mts} +2 -3
  173. package/dist/storage-provider.interface-DQMtT42e.d.mts.map +1 -0
  174. package/dist/storage.error-C6FY037a.mjs +8 -0
  175. package/dist/storage.error-C6FY037a.mjs.map +1 -0
  176. package/dist/{stratal-CNwpbSZl.mjs → stratal-Bdq4IdB3.mjs} +31 -185
  177. package/dist/stratal-Bdq4IdB3.mjs.map +1 -0
  178. package/dist/stratal-BsKmvP6J.d.mts +43 -0
  179. package/dist/stratal-BsKmvP6J.d.mts.map +1 -0
  180. package/dist/{types-cySNS_lp.d.mts → types-BaeHi67f.d.mts} +1 -1
  181. package/dist/types-BaeHi67f.d.mts.map +1 -0
  182. package/dist/{usage-generator-BUdlhnCK.mjs → usage-generator-DTqaUMR9.mjs} +6 -3
  183. package/dist/usage-generator-DTqaUMR9.mjs.map +1 -0
  184. package/dist/validation-DUzcjb8Q.mjs +49 -0
  185. package/dist/validation-DUzcjb8Q.mjs.map +1 -0
  186. package/dist/validation.context-XTysWJ3b.mjs +117 -0
  187. package/dist/validation.context-XTysWJ3b.mjs.map +1 -0
  188. package/dist/websocket/index.d.mts +7 -14
  189. package/dist/websocket/index.d.mts.map +1 -1
  190. package/dist/websocket/index.mjs +2 -2
  191. package/dist/workers/index.d.mts +2 -2
  192. package/dist/workers/index.mjs +3 -2
  193. package/dist/workers/index.mjs.map +1 -1
  194. package/dist/{index-Bnpfq6uk.d.mts → zod-DvWTfRpI.d.mts} +58 -133
  195. package/dist/zod-DvWTfRpI.d.mts.map +1 -0
  196. package/dist/zod-hMa3rSHV.mjs +72 -0
  197. package/dist/zod-hMa3rSHV.mjs.map +1 -0
  198. package/package.json +10 -10
  199. package/dist/command-BgSlsS4M.mjs.map +0 -1
  200. package/dist/command-Cmmf0oHX.d.mts.map +0 -1
  201. package/dist/controller.decorator-B9vwn0zK.mjs.map +0 -1
  202. package/dist/cron-manager-CmTimEjf.d.mts.map +0 -1
  203. package/dist/cron-manager-DQSK8uoV.mjs.map +0 -1
  204. package/dist/en-DSH_bhh6.mjs +0 -308
  205. package/dist/en-DSH_bhh6.mjs.map +0 -1
  206. package/dist/env-D1rcZ8_r.d.mts.map +0 -1
  207. package/dist/errors-COW9-Mar.mjs +0 -1739
  208. package/dist/errors-COW9-Mar.mjs.map +0 -1
  209. package/dist/errors-ORxu1-Bb.mjs +0 -74
  210. package/dist/errors-ORxu1-Bb.mjs.map +0 -1
  211. package/dist/events-CzCV8jI8.mjs.map +0 -1
  212. package/dist/gateway-context-CXmXtaUP.mjs.map +0 -1
  213. package/dist/guards-DU1_J9YA.mjs.map +0 -1
  214. package/dist/http-method.decorator-BrgHMdLQ.mjs.map +0 -1
  215. package/dist/i18n.module-CzXLW9Hy.mjs.map +0 -1
  216. package/dist/index-Bnpfq6uk.d.mts.map +0 -1
  217. package/dist/index-ByOyTmqf.d.mts.map +0 -1
  218. package/dist/index-C1KvMncZ.d.mts.map +0 -1
  219. package/dist/index-DBd_2wv8.d.mts +0 -263
  220. package/dist/index-DBd_2wv8.d.mts.map +0 -1
  221. package/dist/index-DUzWs0z7.d.mts.map +0 -1
  222. package/dist/index.d.mts.map +0 -1
  223. package/dist/logger-DlV7NtvD.mjs +0 -440
  224. package/dist/logger-DlV7NtvD.mjs.map +0 -1
  225. package/dist/module-BzLg57FK.mjs +0 -866
  226. package/dist/module-BzLg57FK.mjs.map +0 -1
  227. package/dist/openapi-tools.service-Zs-Ewv7F.mjs +0 -200
  228. package/dist/openapi-tools.service-Zs-Ewv7F.mjs.map +0 -1
  229. package/dist/openapi.service-Bt9bCIrd.d.mts.map +0 -1
  230. package/dist/quarry-registry-BwY2hOxm.mjs +0 -699
  231. package/dist/quarry-registry-BwY2hOxm.mjs.map +0 -1
  232. package/dist/queue.module-BhCjZp6H.mjs.map +0 -1
  233. package/dist/r2-storage.provider-DuonKeYm.mjs.map +0 -1
  234. package/dist/rate-limit.decorator-6qzNcSOt.mjs.map +0 -1
  235. package/dist/resend.provider-DB4IlFjG.mjs.map +0 -1
  236. package/dist/seeder-zoEfEw9i.mjs.map +0 -1
  237. package/dist/setup-CefZKV_e.mjs +0 -37
  238. package/dist/setup-CefZKV_e.mjs.map +0 -1
  239. package/dist/smtp.provider-B6D7zuWX.mjs.map +0 -1
  240. package/dist/storage-D8CBP72Z.mjs.map +0 -1
  241. package/dist/storage-provider.interface-Bd6vA4ak.d.mts.map +0 -1
  242. package/dist/stratal-CNwpbSZl.mjs.map +0 -1
  243. package/dist/types-cySNS_lp.d.mts.map +0 -1
  244. package/dist/usage-generator-BUdlhnCK.mjs.map +0 -1
  245. package/dist/validation-DtJwAv7O.mjs +0 -248
  246. package/dist/validation-DtJwAv7O.mjs.map +0 -1
@@ -1,22 +1,16 @@
1
- import { a as __decorate, f as DI_TOKENS, o as __decorateParam, p as Transient, s as __decorateMetadata } from "../logger-DlV7NtvD.mjs";
2
- import { a as QueueProviderNotSupportedError, c as QUEUE_TOKENS, i as CloudflareQueueProvider, l as QueueSender, n as QueueProviderFactory, o as QueueBindingNotFoundError, r as SyncQueueProvider, s as QueueRegistry, t as QueueModule, u as ConsumerRegistry } from "../queue.module-BhCjZp6H.mjs";
3
- import { inject } from "tsyringe";
1
+ import { c as DI_TOKENS, d as Transient, m as inject } from "../di-BO1QIb5H.mjs";
2
+ import { n as __decorateParam, t as __decorate } from "../decorate-HgTKAYK8.mjs";
3
+ import { LOGGER_TOKENS } from "../logger/index.mjs";
4
+ import { a as QueueError, c as QueueSender, i as CloudflareQueueProvider, l as ConsumerRegistry, n as QueueProviderFactory, o as QueueRegistry, r as SyncQueueProvider, s as QUEUE_TOKENS, t as QueueModule } from "../queue.module-DeWJ0tQM.mjs";
4
5
  //#region src/queue/queue-manager.ts
5
6
  let QueueManager = class QueueManager {
6
7
  registry;
7
- constructor(registry) {
8
+ logger;
9
+ constructor(registry, logger) {
8
10
  this.registry = registry;
11
+ this.logger = logger;
9
12
  }
10
- /**
11
- * Process a batch of queue messages
12
- *
13
- * Routes messages to registered consumers based on message type.
14
- * Uses ConsumerRegistry to find matching consumers.
15
- *
16
- * @param _queueName - Name of the queue (for logging, not used for routing)
17
- * @param batch - Batch of messages from Cloudflare Queue
18
- */
19
- async processBatch(_queueName, batch) {
13
+ async processBatch(queueName, batch) {
20
14
  for (const message of batch.messages) {
21
15
  const queueMessage = message.body;
22
16
  const consumers = this.registry.getConsumers(queueMessage.type);
@@ -25,6 +19,10 @@ let QueueManager = class QueueManager {
25
19
  message.ack();
26
20
  } catch (error) {
27
21
  const errorInstance = error instanceof Error ? error : new Error(String(error));
22
+ this.logger.error("Queue message processing failed", errorInstance, {
23
+ type: queueMessage.type,
24
+ queue: queueName
25
+ });
28
26
  if (consumer.onError) await consumer.onError(errorInstance, queueMessage);
29
27
  message.retry();
30
28
  }
@@ -34,27 +32,27 @@ let QueueManager = class QueueManager {
34
32
  QueueManager = __decorate([
35
33
  Transient(DI_TOKENS.Queue),
36
34
  __decorateParam(0, inject(DI_TOKENS.ConsumerRegistry)),
37
- __decorateMetadata("design:paramtypes", [Object])
35
+ __decorateParam(1, inject(LOGGER_TOKENS.LoggerService))
38
36
  ], QueueManager);
39
37
  //#endregion
40
38
  //#region src/queue/decorators/inject-queue.decorator.ts
41
39
  /**
42
- * Inject a queue sender by name.
40
+ * Inject a queue sender by binding name.
43
41
  *
44
- * Queue names are typed via module augmentation of QueueNames interface.
45
- * The queue name serves as both the identifier and the DI injection token.
42
+ * The binding name matches the `binding` field declared under `queues.producers`
43
+ * in `wrangler.jsonc` (e.g. `BACKGROUND_QUEUE`). Stratal looks the binding up
44
+ * directly on the worker's `env`; the underlying Cloudflare queue can be any
45
+ * env-specific name (e.g. `background-queue-dev`) without affecting code.
46
46
  *
47
- * @param name - Queue name (typed with autocomplete if QueueNames is augmented)
47
+ * @param binding - Queue binding identifier (typed against `StratalEnv`).
48
48
  * @returns Parameter decorator for constructor injection
49
49
  *
50
50
  * @example
51
51
  * ```typescript
52
- * // Direct injection by queue name
53
52
  * constructor(
54
- * @InjectQueue('notifications-queue') private queue: IQueueSender
53
+ * @InjectQueue('NOTIFICATIONS_QUEUE') private queue: IQueueSender
55
54
  * ) {}
56
55
  *
57
- * // Usage
58
56
  * await this.queue.dispatch({
59
57
  * type: 'email.send',
60
58
  * payload: { to: 'user@example.com', subject: 'Hello' }
@@ -62,14 +60,14 @@ QueueManager = __decorate([
62
60
  * ```
63
61
  *
64
62
  * @remarks
65
- * The queue must be registered via `QueueModule.registerQueue(name)` before injection.
66
- * For module-internal queue bindings (e.g., EmailModule), use `@inject(TOKEN)` with
67
- * `useExisting` provider binding instead.
63
+ * The binding must be registered via `QueueModule.registerQueue(binding)`
64
+ * before injection. For module-internal bindings (e.g. EmailModule),
65
+ * use `@inject(TOKEN)` with `useExisting` provider binding instead.
68
66
  */
69
- function InjectQueue(name) {
70
- return inject(name);
67
+ function InjectQueue(binding) {
68
+ return inject(binding);
71
69
  }
72
70
  //#endregion
73
- export { CloudflareQueueProvider, ConsumerRegistry, InjectQueue, QUEUE_TOKENS, QueueBindingNotFoundError, QueueManager, QueueModule, QueueProviderFactory, QueueProviderNotSupportedError, QueueRegistry, QueueSender, SyncQueueProvider };
71
+ export { CloudflareQueueProvider, ConsumerRegistry, InjectQueue, QUEUE_TOKENS, QueueError, QueueManager, QueueModule, QueueProviderFactory, QueueRegistry, QueueSender, SyncQueueProvider };
74
72
 
75
73
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/queue/queue-manager.ts","../../src/queue/decorators/inject-queue.decorator.ts"],"sourcesContent":["import { inject } from 'tsyringe'\nimport { Transient } from '../di/decorators'\nimport { DI_TOKENS } from '../di/tokens'\nimport { type ConsumerRegistry } from './consumer-registry'\nimport type { QueueMessage } from './queue-consumer'\n\n/**\n * Queue Manager\n *\n * Singleton service for processing queue message batches.\n * Routes messages to consumers based on message type.\n *\n * **Message Routing:**\n * - Consumers declare message types they handle (e.g., ['email.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 *\n * **Note:** For sending messages to queues, use IQueueSender instances\n * obtained via @InjectQueue('queue-name') or module bindings.\n *\n * @example Processing a queue batch\n * ```typescript\n * // In Cloudflare Worker queue handler\n * await queueManager.processBatch('notifications-queue', batch)\n * ```\n */\n@Transient(DI_TOKENS.Queue)\nexport class QueueManager {\n constructor(\n @inject(DI_TOKENS.ConsumerRegistry) private readonly registry: ConsumerRegistry\n ) {}\n\n /**\n * Process a batch of queue messages\n *\n * Routes messages to registered consumers based on message type.\n * Uses ConsumerRegistry to find matching consumers.\n *\n * @param _queueName - Name of the queue (for logging, not used for routing)\n * @param batch - Batch of messages from Cloudflare Queue\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\n // Find consumers by message type (not by queue name)\n const consumers = this.registry.getConsumers(queueMessage.type)\n\n for (const consumer of consumers) {\n try {\n await consumer.handle(queueMessage)\n message.ack()\n } catch (error) {\n const errorInstance = error instanceof Error\n ? error\n : new Error(String(error))\n\n if (consumer.onError) {\n await consumer.onError(errorInstance, queueMessage)\n }\n message.retry()\n }\n }\n }\n }\n}\n","import { inject } from 'tsyringe'\nimport type { QueueName } from '../queue-name'\n\n/**\n * Inject a queue sender by name.\n *\n * Queue names are typed via module augmentation of QueueNames interface.\n * The queue name serves as both the identifier and the DI injection token.\n *\n * @param name - Queue name (typed with autocomplete if QueueNames is augmented)\n * @returns Parameter decorator for constructor injection\n *\n * @example\n * ```typescript\n * // Direct injection by queue name\n * constructor(\n * @InjectQueue('notifications-queue') private queue: IQueueSender\n * ) {}\n *\n * // Usage\n * await this.queue.dispatch({\n * type: 'email.send',\n * payload: { to: 'user@example.com', subject: 'Hello' }\n * })\n * ```\n *\n * @remarks\n * The queue must be registered via `QueueModule.registerQueue(name)` before injection.\n * For module-internal queue bindings (e.g., EmailModule), use `@inject(TOKEN)` with\n * `useExisting` provider binding instead.\n */\nexport function InjectQueue(name: QueueName): ParameterDecorator {\n return inject(name)\n}\n"],"mappings":";;;;AA2BO,IAAA,eAAA,MAAM,aAAa;CAE+B;CADvD,YACE,UACA;EADqD,KAAA,WAAA;;;;;;;;;;;CAYvD,MAAM,aAAa,YAAoB,OAAoC;EACzE,KAAK,MAAM,WAAW,MAAM,UAAU;GACpC,MAAM,eAAe,QAAQ;GAG7B,MAAM,YAAY,KAAK,SAAS,aAAa,aAAa,KAAK;GAE/D,KAAK,MAAM,YAAY,WACrB,IAAI;IACF,MAAM,SAAS,OAAO,aAAa;IACnC,QAAQ,KAAK;YACN,OAAO;IACd,MAAM,gBAAgB,iBAAiB,QACnC,QACA,IAAI,MAAM,OAAO,MAAM,CAAC;IAE5B,IAAI,SAAS,SACX,MAAM,SAAS,QAAQ,eAAe,aAAa;IAErD,QAAQ,OAAO;;;;;;CAlCxB,UAAU,UAAU,MAAM;oBAGtB,OAAO,UAAU,iBAAiB,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACEvC,SAAgB,YAAY,MAAqC;CAC/D,OAAO,OAAO,KAAK"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/queue/queue-manager.ts","../../src/queue/decorators/inject-queue.decorator.ts"],"sourcesContent":["import { inject } from '../di';\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 { QueueMessage } from './queue-consumer';\n\n@Transient(DI_TOKENS.Queue)\nexport class QueueManager {\n constructor(\n @inject(DI_TOKENS.ConsumerRegistry) private readonly registry: ConsumerRegistry,\n @inject(LOGGER_TOKENS.LoggerService) private readonly logger: LoggerService,\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\n const consumers = this.registry.getConsumers(queueMessage.type)\n\n for (const consumer of consumers) {\n try {\n await consumer.handle(queueMessage)\n message.ack()\n } catch (error) {\n const errorInstance = error instanceof Error\n ? error\n : new Error(String(error))\n\n this.logger.error('Queue message processing failed', errorInstance, {\n type: queueMessage.type,\n queue: queueName,\n })\n\n if (consumer.onError) {\n await consumer.onError(errorInstance, queueMessage)\n }\n message.retry()\n }\n }\n }\n }\n}\n","import { inject } from '../../di'\nimport type { QueueBinding } from '../queue-binding'\n\n/**\n * Inject a queue sender by binding name.\n *\n * The binding name matches the `binding` field declared under `queues.producers`\n * in `wrangler.jsonc` (e.g. `BACKGROUND_QUEUE`). Stratal looks the binding up\n * directly on the worker's `env`; the underlying Cloudflare queue can be any\n * env-specific name (e.g. `background-queue-dev`) without affecting code.\n *\n * @param binding - Queue binding identifier (typed against `StratalEnv`).\n * @returns Parameter decorator for constructor injection\n *\n * @example\n * ```typescript\n * constructor(\n * @InjectQueue('NOTIFICATIONS_QUEUE') private queue: IQueueSender\n * ) {}\n *\n * await this.queue.dispatch({\n * type: 'email.send',\n * payload: { to: 'user@example.com', subject: 'Hello' }\n * })\n * ```\n *\n * @remarks\n * The binding must be registered via `QueueModule.registerQueue(binding)`\n * before injection. For module-internal bindings (e.g. EmailModule),\n * use `@inject(TOKEN)` with `useExisting` provider binding instead.\n */\nexport function InjectQueue(binding: QueueBinding): ParameterDecorator {\n return inject(binding)\n}\n"],"mappings":";;;;;AAQO,IAAA,eAAA,MAAM,aAAa;CAE+B;CACC;CAFxD,YACE,UACA,QACA;EAFqD,KAAA,WAAA;EACC,KAAA,SAAA;;CAGxD,MAAM,aAAa,WAAmB,OAAoC;EACxE,KAAK,MAAM,WAAW,MAAM,UAAU;GACpC,MAAM,eAAe,QAAQ;GAE7B,MAAM,YAAY,KAAK,SAAS,aAAa,aAAa,KAAK;GAE/D,KAAK,MAAM,YAAY,WACrB,IAAI;IACF,MAAM,SAAS,OAAO,aAAa;IACnC,QAAQ,KAAK;YACN,OAAO;IACd,MAAM,gBAAgB,iBAAiB,QACnC,QACA,IAAI,MAAM,OAAO,MAAM,CAAC;IAE5B,KAAK,OAAO,MAAM,mCAAmC,eAAe;KAClE,MAAM,aAAa;KACnB,OAAO;KACR,CAAC;IAEF,IAAI,SAAS,SACX,MAAM,SAAS,QAAQ,eAAe,aAAa;IAErD,QAAQ,OAAO;;;;;;CA9BxB,UAAU,UAAU,MAAM;oBAGtB,OAAO,UAAU,iBAAiB,CAAA;oBAClC,OAAO,cAAc,cAAc,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACoBxC,SAAgB,YAAY,SAA2C;CACrE,OAAO,OAAO,QAAQ"}
@@ -1,7 +1,9 @@
1
- import { A as Scope, H as ApplicationError, k as ERROR_CODES, w as I18N_TOKENS } from "./errors-COW9-Mar.mjs";
2
- import { a as __decorate, f as DI_TOKENS, o as __decorateParam, p as Transient, s as __decorateMetadata } from "./logger-DlV7NtvD.mjs";
3
- import { k as Module } from "./module-BzLg57FK.mjs";
4
- import { inject } from "tsyringe";
1
+ import { a as ApplicationError } from "./container-storage-GpNNz79X.mjs";
2
+ import { c as DI_TOKENS, d as Transient, l as Request, m as inject, u as Singleton } from "./di-BO1QIb5H.mjs";
3
+ import { n as __decorateParam, t as __decorate } from "./decorate-HgTKAYK8.mjs";
4
+ import "./errors-BBZTnjdq.mjs";
5
+ import { f as Module } from "./module-xYoHba6B.mjs";
6
+ import { t as I18N_TOKENS } from "./i18n.tokens-hwRpmjRq.mjs";
5
7
  //#region src/queue/consumer-registry.ts
6
8
  let ConsumerRegistry = class ConsumerRegistry {
7
9
  /** Map from message type to consumers handling that type */
@@ -66,14 +68,14 @@ let ConsumerRegistry = class ConsumerRegistry {
66
68
  return Array.from(this.allConsumers);
67
69
  }
68
70
  };
69
- ConsumerRegistry = __decorate([Transient(DI_TOKENS.ConsumerRegistry)], ConsumerRegistry);
71
+ ConsumerRegistry = __decorate([Singleton(DI_TOKENS.ConsumerRegistry)], ConsumerRegistry);
70
72
  //#endregion
71
73
  //#region src/queue/queue-sender.ts
72
74
  /**
73
75
  * Queue Sender
74
76
  *
75
- * Implementation of IQueueSender bound to a specific queue name.
76
- * Created by QueueRegistry for each registered queue.
77
+ * Implementation of IQueueSender bound to a specific queue binding.
78
+ * Created by QueueRegistry for each registered binding.
77
79
  *
78
80
  * Automatically enriches messages with:
79
81
  * - `id`: UUID generated via crypto.randomUUID()
@@ -83,7 +85,7 @@ ConsumerRegistry = __decorate([Transient(DI_TOKENS.ConsumerRegistry)], ConsumerR
83
85
  * @example
84
86
  * ```typescript
85
87
  * // Created by QueueRegistry, not directly instantiated
86
- * const sender = registry.getQueue('notifications-queue')
88
+ * const sender = registry.getQueue('NOTIFICATIONS_QUEUE')
87
89
  *
88
90
  * await sender.dispatch({
89
91
  * type: 'email.send',
@@ -92,11 +94,11 @@ ConsumerRegistry = __decorate([Transient(DI_TOKENS.ConsumerRegistry)], ConsumerR
92
94
  * ```
93
95
  */
94
96
  var QueueSender = class {
95
- queueName;
97
+ binding;
96
98
  provider;
97
99
  i18n;
98
- constructor(queueName, provider, i18n) {
99
- this.queueName = queueName;
100
+ constructor(binding, provider, i18n) {
101
+ this.binding = binding;
100
102
  this.provider = provider;
101
103
  this.i18n = i18n;
102
104
  }
@@ -117,7 +119,7 @@ var QueueSender = class {
117
119
  ...message,
118
120
  metadata: Object.keys(metadata).length > 0 ? metadata : void 0
119
121
  };
120
- await this.provider.send(this.queueName, fullMessage);
122
+ await this.provider.send(this.binding, fullMessage);
121
123
  }
122
124
  };
123
125
  //#endregion
@@ -138,60 +140,30 @@ let QueueRegistry = class QueueRegistry {
138
140
  this.provider = providerFactory.create();
139
141
  }
140
142
  /**
141
- * Get or create a QueueSender for the specified queue name.
143
+ * Get or create a QueueSender for the specified binding.
142
144
  *
143
- * Senders are cached per queue name within the request scope.
145
+ * Senders are cached per binding within the request scope.
144
146
  *
145
- * @param queueName - The queue name to get a sender for
146
- * @returns QueueSender bound to the specified queue
147
+ * @param binding - The queue binding to get a sender for
148
+ * @returns QueueSender bound to the specified binding
147
149
  */
148
- getQueue(queueName) {
149
- let sender = this.senders.get(queueName);
150
+ getQueue(binding) {
151
+ let sender = this.senders.get(binding);
150
152
  if (!sender) {
151
- sender = new QueueSender(queueName, this.provider, this.i18n);
152
- this.senders.set(queueName, sender);
153
+ sender = new QueueSender(binding, this.provider, this.i18n);
154
+ this.senders.set(binding, sender);
153
155
  }
154
156
  return sender;
155
157
  }
156
158
  };
157
159
  QueueRegistry = __decorate([
158
- Transient(QUEUE_TOKENS.QueueRegistry),
160
+ Request(QUEUE_TOKENS.QueueRegistry),
159
161
  __decorateParam(0, inject(QUEUE_TOKENS.QueueProviderFactory)),
160
- __decorateParam(1, inject(I18N_TOKENS.I18nService)),
161
- __decorateMetadata("design:paramtypes", [Object, Object])
162
+ __decorateParam(1, inject(I18N_TOKENS.I18nService))
162
163
  ], QueueRegistry);
163
164
  //#endregion
164
- //#region src/queue/errors/queue-binding-not-found.error.ts
165
- /**
166
- * QueueBindingNotFoundError
167
- *
168
- * Thrown when attempting to access a Cloudflare Queue binding that hasn't been configured.
169
- * This typically indicates that the queue binding is missing from wrangler.jsonc
170
- * or the environment variables are not properly set.
171
- */
172
- var QueueBindingNotFoundError = class extends ApplicationError {
173
- constructor(queueName, bindingName) {
174
- super("errors.queueBindingNotFound", ERROR_CODES.SYSTEM.QUEUE_BINDING_NOT_FOUND, {
175
- queueName,
176
- bindingName
177
- });
178
- }
179
- };
180
- //#endregion
181
- //#region src/queue/errors/queue-provider-not-supported.error.ts
182
- /**
183
- * QueueProviderNotSupportedError
184
- *
185
- * Thrown when attempting to use a queue provider that is not supported.
186
- * Valid providers are: 'cloudflare', 'sync'
187
- *
188
- * This typically indicates an invalid QUEUE_PROVIDER environment variable.
189
- */
190
- var QueueProviderNotSupportedError = class extends ApplicationError {
191
- constructor(provider) {
192
- super("errors.queueProviderNotSupported", ERROR_CODES.SYSTEM.QUEUE_PROVIDER_NOT_SUPPORTED, { provider });
193
- }
194
- };
165
+ //#region src/queue/queue.error.ts
166
+ var QueueError = class extends ApplicationError {};
195
167
  //#endregion
196
168
  //#region src/queue/providers/cloudflare-queue.provider.ts
197
169
  let CloudflareQueueProvider = class CloudflareQueueProvider {
@@ -202,34 +174,17 @@ let CloudflareQueueProvider = class CloudflareQueueProvider {
202
174
  /**
203
175
  * Send a message to a Cloudflare Queue
204
176
  *
205
- * @param queueName - Queue name (e.g., 'notifications-queue')
177
+ * @param binding - Queue binding identifier (e.g., 'NOTIFICATIONS_QUEUE')
206
178
  * @param message - Complete message with id, timestamp, and payload
207
- * @throws {QueueBindingNotFoundError} If queue binding is not configured
179
+ * @throws {QueueError} If the binding is not configured on env
208
180
  */
209
- async send(queueName, message) {
210
- await this.getQueue(queueName).send(message);
211
- }
212
- /**
213
- * Resolve queue binding from Cloudflare environment
214
- *
215
- * Converts kebab-case queue name to UPPER_SNAKE_CASE binding name.
216
- *
217
- * @param queueName - Queue name (e.g., 'notifications-queue')
218
- * @returns Cloudflare Queue binding
219
- * @throws {QueueBindingNotFoundError} If binding not found in env
220
- */
221
- getQueue(queueName) {
222
- const bindingName = queueName.toUpperCase().replace(/-/g, "_");
223
- const queue = this.env[bindingName];
224
- if (!queue) throw new QueueBindingNotFoundError(queueName, bindingName);
225
- return queue;
181
+ async send(binding, message) {
182
+ const queue = this.env[binding];
183
+ if (!queue) throw new QueueError(`Queue binding "${binding}" was not found in the environment`);
184
+ await queue.send(message);
226
185
  }
227
186
  };
228
- CloudflareQueueProvider = __decorate([
229
- Transient(),
230
- __decorateParam(0, inject(DI_TOKENS.CloudflareEnv)),
231
- __decorateMetadata("design:paramtypes", [Object])
232
- ], CloudflareQueueProvider);
187
+ CloudflareQueueProvider = __decorate([Transient(), __decorateParam(0, inject(DI_TOKENS.CloudflareEnv))], CloudflareQueueProvider);
233
188
  //#endregion
234
189
  //#region src/queue/providers/sync-queue.provider.ts
235
190
  let SyncQueueProvider = class SyncQueueProvider {
@@ -243,11 +198,11 @@ let SyncQueueProvider = class SyncQueueProvider {
243
198
  * Finds all matching consumers by message type and calls their handle() method.
244
199
  * If any consumer throws, onError() is called and the error is re-thrown.
245
200
  *
246
- * @param _queueName - Queue name (not used for routing, consumers match by message type)
201
+ * @param _binding - Queue binding (not used for routing, consumers match by message type)
247
202
  * @param message - Complete message with id, timestamp, and payload
248
203
  * @throws Re-throws any error from consumer.handle() after calling onError()
249
204
  */
250
- async send(_queueName, message) {
205
+ async send(_binding, message) {
251
206
  const consumers = this.registry.getConsumers(message.type);
252
207
  for (const consumer of consumers) try {
253
208
  await consumer.handle(message);
@@ -258,11 +213,7 @@ let SyncQueueProvider = class SyncQueueProvider {
258
213
  }
259
214
  }
260
215
  };
261
- SyncQueueProvider = __decorate([
262
- Transient(),
263
- __decorateParam(0, inject(DI_TOKENS.ConsumerRegistry)),
264
- __decorateMetadata("design:paramtypes", [Object])
265
- ], SyncQueueProvider);
216
+ SyncQueueProvider = __decorate([Transient(), __decorateParam(0, inject(DI_TOKENS.ConsumerRegistry))], SyncQueueProvider);
266
217
  //#endregion
267
218
  //#region src/queue/services/queue-provider-factory.ts
268
219
  let QueueProviderFactory = class QueueProviderFactory {
@@ -278,14 +229,14 @@ let QueueProviderFactory = class QueueProviderFactory {
278
229
  * Create a queue provider based on module configuration
279
230
  *
280
231
  * @returns Queue provider instance
281
- * @throws {QueueProviderNotSupportedError} If provider type is not supported
232
+ * @throws {QueueError} If provider type is not supported
282
233
  */
283
234
  create() {
284
235
  const providerType = this.options?.provider ?? "cloudflare";
285
236
  switch (providerType) {
286
237
  case "cloudflare": return new CloudflareQueueProvider(this.env);
287
238
  case "sync": return new SyncQueueProvider(this.registry);
288
- default: throw new QueueProviderNotSupportedError(providerType);
239
+ default: throw new QueueError(`Queue provider "${String(providerType)}" is not supported`);
289
240
  }
290
241
  }
291
242
  };
@@ -293,12 +244,7 @@ QueueProviderFactory = __decorate([
293
244
  Transient(QUEUE_TOKENS.QueueProviderFactory),
294
245
  __decorateParam(0, inject(DI_TOKENS.CloudflareEnv)),
295
246
  __decorateParam(1, inject(DI_TOKENS.ConsumerRegistry)),
296
- __decorateParam(2, inject(QUEUE_TOKENS.QueueModuleOptions, { isOptional: true })),
297
- __decorateMetadata("design:paramtypes", [
298
- Object,
299
- Object,
300
- Object
301
- ])
247
+ __decorateParam(2, inject(QUEUE_TOKENS.QueueModuleOptions, { isOptional: true }))
302
248
  ], QueueProviderFactory);
303
249
  //#endregion
304
250
  //#region src/queue/queue.module.ts
@@ -315,12 +261,12 @@ QueueProviderFactory = __decorate([
315
261
  * useFactory: (config) => ({ provider: config.get('queue').provider })
316
262
  * })
317
263
  *
318
- * // 2. Register queues (queue name IS the injection token)
319
- * QueueModule.registerQueue('notifications-queue')
320
- * QueueModule.registerQueue('batch-notifications-queue')
264
+ * // 2. Register queue bindings (the binding IS the injection token)
265
+ * QueueModule.registerQueue('NOTIFICATIONS_QUEUE')
266
+ * QueueModule.registerQueue('BACKGROUND_QUEUE')
321
267
  *
322
268
  * // 3. Inject and use
323
- * constructor(@InjectQueue('notifications-queue') private queue: IQueueSender) {}
269
+ * constructor(@InjectQueue('NOTIFICATIONS_QUEUE') private queue: IQueueSender) {}
324
270
  * await this.queue.dispatch({ type: 'email.send', payload: {...} })
325
271
  * ```
326
272
  *
@@ -359,29 +305,31 @@ let QueueModule = _QueueModule = class QueueModule {
359
305
  };
360
306
  }
361
307
  /**
362
- * Register a queue for injection.
308
+ * Register a queue binding for injection.
363
309
  *
364
- * The queue name serves as both the identifier and the DI injection token.
365
- * Queue names are typed via module augmentation of QueueNames interface.
310
+ * The binding name doubles as the DI injection token and the
311
+ * `env`-lookup key. Binding names are typed against `StratalEnv`
312
+ * (autocomplete works once an app augments `StratalEnv` with its
313
+ * Cloudflare bindings).
366
314
  *
367
- * @param name - Queue name (typed with autocomplete if QueueNames is augmented)
315
+ * @param binding - Queue binding identifier (e.g. `NOTIFICATIONS_QUEUE`).
368
316
  * @returns Dynamic module that provides the queue sender
369
317
  *
370
318
  * @example
371
319
  * ```typescript
372
320
  * // In AppModule imports
373
- * QueueModule.registerQueue('notifications-queue')
321
+ * QueueModule.registerQueue('NOTIFICATIONS_QUEUE')
374
322
  *
375
- * // Then inject using the queue name
376
- * constructor(@InjectQueue('notifications-queue') private queue: IQueueSender) {}
323
+ * // Then inject using the binding name
324
+ * constructor(@InjectQueue('NOTIFICATIONS_QUEUE') private queue: IQueueSender) {}
377
325
  * ```
378
326
  */
379
- static registerQueue(name) {
327
+ static registerQueue(binding) {
380
328
  return {
381
329
  module: _QueueModule,
382
330
  providers: [{
383
- provide: name,
384
- useFactory: (registry) => registry.getQueue(name),
331
+ provide: binding,
332
+ useFactory: (registry) => registry.getQueue(binding),
385
333
  inject: [QUEUE_TOKENS.QueueRegistry]
386
334
  }]
387
335
  };
@@ -390,13 +338,11 @@ let QueueModule = _QueueModule = class QueueModule {
390
338
  QueueModule = _QueueModule = __decorate([Module({ providers: [
391
339
  {
392
340
  provide: DI_TOKENS.ConsumerRegistry,
393
- useClass: ConsumerRegistry,
394
- scope: Scope.Singleton
341
+ useClass: ConsumerRegistry
395
342
  },
396
343
  {
397
344
  provide: QUEUE_TOKENS.QueueProviderFactory,
398
- useClass: QueueProviderFactory,
399
- scope: Scope.Singleton
345
+ useClass: QueueProviderFactory
400
346
  },
401
347
  {
402
348
  provide: QUEUE_TOKENS.QueueRegistry,
@@ -404,6 +350,6 @@ QueueModule = _QueueModule = __decorate([Module({ providers: [
404
350
  }
405
351
  ] })], QueueModule);
406
352
  //#endregion
407
- export { QueueProviderNotSupportedError as a, QUEUE_TOKENS as c, CloudflareQueueProvider as i, QueueSender as l, QueueProviderFactory as n, QueueBindingNotFoundError as o, SyncQueueProvider as r, QueueRegistry as s, QueueModule as t, ConsumerRegistry as u };
353
+ export { QueueError as a, QueueSender as c, CloudflareQueueProvider as i, ConsumerRegistry as l, QueueProviderFactory as n, QueueRegistry as o, SyncQueueProvider as r, QUEUE_TOKENS as s, QueueModule as t };
408
354
 
409
- //# sourceMappingURL=queue.module-BhCjZp6H.mjs.map
355
+ //# sourceMappingURL=queue.module-DeWJ0tQM.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.module-DeWJ0tQM.mjs","names":[],"sources":["../src/queue/consumer-registry.ts","../src/queue/queue-sender.ts","../src/queue/queue.tokens.ts","../src/queue/queue-registry.ts","../src/queue/queue.error.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 { Singleton } from '../di/decorators'\nimport { DI_TOKENS } from '../di/tokens'\nimport type { IQueueConsumer } from './queue-consumer'\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 consumers handling that type */\n private consumersByType = new Map<string, IQueueConsumer[]>()\n\n /** Set of all registered consumers (for iteration) */\n private allConsumers = new Set<IQueueConsumer>()\n\n /**\n * Register a queue consumer\n *\n * Indexes the consumer by each of its declared message types.\n *\n * @param consumer - Queue consumer to register\n */\n register(consumer: IQueueConsumer): void {\n if (this.allConsumers.has(consumer)) {\n return // Already registered\n }\n\n this.allConsumers.add(consumer)\n\n for (const messageType of consumer.messageTypes) {\n const existing = this.consumersByType.get(messageType) ?? []\n existing.push(consumer)\n this.consumersByType.set(messageType, existing)\n }\n }\n\n /**\n * Get all consumers that can handle a specific message type\n *\n * Returns consumers that either:\n * - Explicitly declare the message type\n * - Use '*' wildcard to handle all types\n *\n * @param messageType - The message type to find consumers for\n * @returns Array of consumers that can handle this message type\n */\n getConsumers(messageType: string): IQueueConsumer[] {\n const exactMatch = this.consumersByType.get(messageType) ?? []\n const wildcardMatch = this.consumersByType.get('*') ?? []\n\n // Combine and dedupe\n const combined = new Set([...exactMatch, ...wildcardMatch])\n return Array.from(combined)\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.getConsumers(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.consumersByType.keys())\n }\n\n /**\n * Get all registered consumers\n *\n * @returns Array of all registered consumers\n */\n getAllConsumers(): IQueueConsumer[] {\n return Array.from(this.allConsumers)\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 * - `timestamp`: Current time in milliseconds\n * - `metadata.locale`: Current locale from I18n context\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/timestamp)\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 const fullMessage: QueueMessage<T> = {\n id: crypto.randomUUID(),\n timestamp: Date.now(),\n ...message,\n metadata: Object.keys(metadata).length > 0 ? metadata : undefined\n }\n\n await this.provider.send(this.binding, fullMessage)\n }\n}\n","export const QUEUE_TOKENS = {\n QueueProviderFactory: Symbol.for('stratal:queue:provider:factory'),\n QueueRegistry: Symbol.for('stratal:queue:registry'),\n QueueModuleOptions: Symbol.for('stratal:queue:options'),\n} as const\n\nexport type QueueToken = (typeof QUEUE_TOKENS)[keyof typeof QUEUE_TOKENS]\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 { ApplicationError } from '../errors'\n\nexport class QueueError extends ApplicationError {}\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, timestamp, 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 { inject } from '../../di'\nimport { Transient } from '../../di/decorators'\nimport { DI_TOKENS } from '../../di/tokens'\nimport { type ConsumerRegistry } from '../consumer-registry'\nimport type { 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 * @example Testing with sync provider\n * ```typescript\n * const provider = new SyncQueueProvider(registry)\n * await provider.send('NOTIFICATIONS_QUEUE', {\n * id: '123',\n * timestamp: Date.now(),\n * type: 'email.send',\n * payload: { to: 'test@example.com' }\n * })\n * // Consumer's handle() is called immediately!\n * ```\n */\n@Transient()\nexport class SyncQueueProvider implements IQueueProvider {\n constructor(\n @inject(DI_TOKENS.ConsumerRegistry) private readonly registry: ConsumerRegistry\n ) {}\n\n /**\n * Process a message synchronously\n *\n * Finds all matching consumers by message type and calls their handle() method.\n * If any consumer throws, onError() is called and the error is re-thrown.\n *\n * @param _binding - Queue binding (not used for routing, consumers match by message type)\n * @param message - Complete message with id, timestamp, 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 // Consumers are matched by message type, not queue name\n const consumers = this.registry.getConsumers(message.type)\n\n // Process synchronously - call each matching consumer\n for (const consumer of consumers) {\n try {\n await consumer.handle(message)\n } catch (error) {\n const errorInstance = error instanceof Error\n ? error\n : 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 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 { 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(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)\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 { DI_TOKENS } from '../di/tokens'\nimport { Module } from '../module'\nimport type { AsyncModuleOptions, DynamicModule, InjectionToken } from '../module/types'\nimport { ConsumerRegistry } from './consumer-registry'\nimport type { QueueBinding } from './queue-binding'\nimport { QueueRegistry } from './queue-registry'\nimport type { IQueueSender } from './queue-sender.interface'\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@Module({\n providers: [\n { provide: DI_TOKENS.ConsumerRegistry, useClass: ConsumerRegistry },\n { provide: QUEUE_TOKENS.QueueProviderFactory, useClass: QueueProviderFactory },\n { provide: QUEUE_TOKENS.QueueRegistry, useClass: QueueRegistry },\n ],\n})\nexport class QueueModule {\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":";;;;;;;AAoCO,IAAA,mBAAA,MAAM,iBAAiB;;CAE5B,kCAA0B,IAAI,KAA+B;;CAG7D,+BAAuB,IAAI,KAAqB;;;;;;;;CAShD,SAAS,UAAgC;EACvC,IAAI,KAAK,aAAa,IAAI,SAAS,EACjC;EAGF,KAAK,aAAa,IAAI,SAAS;EAE/B,KAAK,MAAM,eAAe,SAAS,cAAc;GAC/C,MAAM,WAAW,KAAK,gBAAgB,IAAI,YAAY,IAAI,EAAE;GAC5D,SAAS,KAAK,SAAS;GACvB,KAAK,gBAAgB,IAAI,aAAa,SAAS;;;;;;;;;;;;;CAcnD,aAAa,aAAuC;EAClD,MAAM,aAAa,KAAK,gBAAgB,IAAI,YAAY,IAAI,EAAE;EAC9D,MAAM,gBAAgB,KAAK,gBAAgB,IAAI,IAAI,IAAI,EAAE;EAGzD,MAAM,WAAW,IAAI,IAAI,CAAC,GAAG,YAAY,GAAG,cAAc,CAAC;EAC3D,OAAO,MAAM,KAAK,SAAS;;;;;;;;CAS7B,aAAa,aAA8B;EACzC,OAAO,KAAK,aAAa,YAAY,CAAC,SAAS;;;;;;;CAQjD,kBAA4B;EAC1B,OAAO,MAAM,KAAK,KAAK,gBAAgB,MAAM,CAAC;;;;;;;CAQhD,kBAAoC;EAClC,OAAO,MAAM,KAAK,KAAK,aAAa;;;+BAzEvC,UAAU,UAAU,iBAAiB,CAAA,EAAA,iBAAA;;;;;;;;;;;;;;;;;;;;;;;;;ACRtC,IAAa,cAAb,MAAiD;CAE5B;CACA;CACA;CAHnB,YACE,SACA,UACA,MACA;EAHiB,KAAA,UAAA;EACA,KAAA,WAAA;EACA,KAAA,OAAA;;;;;;;CAQnB,MAAM,SAAY,SAA4C;EAC5D,MAAM,WAAW,EAAE,GAAG,QAAQ,UAAU;EAExC,IAAI,CAAC,SAAS,QAAQ;GACpB,MAAM,SAAS,KAAK,KAAK,WAAW;GACpC,IAAI,QACF,SAAS,SAAS;;EAItB,MAAM,cAA+B;GACnC,IAAI,OAAO,YAAY;GACvB,WAAW,KAAK,KAAK;GACrB,GAAG;GACH,UAAU,OAAO,KAAK,SAAS,CAAC,SAAS,IAAI,WAAW,KAAA;GACzD;EAED,MAAM,KAAK,SAAS,KAAK,KAAK,SAAS,YAAY;;;;;ACxDvD,MAAa,eAAe;CAC1B,sBAAsB,OAAO,IAAI,iCAAiC;CAClE,eAAe,OAAO,IAAI,yBAAyB;CACnD,oBAAoB,OAAO,IAAI,wBAAwB;CACxD;;;ACkCM,IAAA,gBAAA,MAAM,cAAc;CAM2B;CALpD;CACA,0BAA2B,IAAI,KAA2B;CAE1D,YACE,iBACA,MACA;EADkD,KAAA,OAAA;EAElD,KAAK,WAAW,gBAAgB,QAAQ;;;;;;;;;;CAW1C,SAAS,SAA+B;EACtC,IAAI,SAAS,KAAK,QAAQ,IAAI,QAAQ;EAEtC,IAAI,CAAC,QAAQ;GACX,SAAS,IAAI,YAAY,SAAS,KAAK,UAAU,KAAK,KAAK;GAC3D,KAAK,QAAQ,IAAI,SAAS,OAAO;;EAGnC,OAAO;;;;CA5BV,QAAQ,aAAa,cAAc;oBAM/B,OAAO,aAAa,qBAAqB,CAAA;oBACzC,OAAO,YAAY,YAAY,CAAA;;;;AC1CpC,IAAa,aAAb,cAAgC,iBAAiB;;;ACoB1C,IAAA,0BAAA,MAAM,wBAAkD;CAET;CADpD,YACE,KACA;EADkD,KAAA,MAAA;;;;;;;;;CAUpD,MAAM,KAAQ,SAAiB,SAAyC;EACtE,MAAM,QAAS,KAAK,IAA2C;EAE/D,IAAI,CAAC,OACH,MAAM,IAAI,WAAW,kBAAkB,QAAQ,oCAAoC;EAGrF,MAAM,MAAM,KAAK,QAAQ;;;sCApB5B,WAAW,EAAA,gBAAA,GAGP,OAAO,UAAU,cAAc,CAAA,CAAA,EAAA,wBAAA;;;ACa7B,IAAA,oBAAA,MAAM,kBAA4C;CAEA;CADvD,YACE,UACA;EADqD,KAAA,WAAA;;;;;;;;;;;;CAavD,MAAM,KAAQ,UAAkB,SAAyC;EAEvE,MAAM,YAAY,KAAK,SAAS,aAAa,QAAQ,KAAK;EAG1D,KAAK,MAAM,YAAY,WACrB,IAAI;GACF,MAAM,SAAS,OAAO,QAAQ;WACvB,OAAO;GACd,MAAM,gBAAgB,iBAAiB,QACnC,QACA,IAAI,MAAM,OAAO,MAAM,CAAC;GAG5B,IAAI,SAAS,SACX,MAAM,SAAS,QAAQ,eAAe,QAAQ;GAIhD,MAAM;;;;gCAnCb,WAAW,EAAA,gBAAA,GAGP,OAAO,UAAU,iBAAiB,CAAA,CAAA,EAAA,kBAAA;;;ACLhC,IAAA,uBAAA,MAAM,qBAAqB;CAEoB;CACG;CAC2B;CAHlF,YACE,KACA,UACA,SACA;EAHkD,KAAA,MAAA;EACG,KAAA,WAAA;EAC2B,KAAA,UAAA;;;;;;;;CASlF,SAAyB;EACvB,MAAM,eAAe,KAAK,SAAS,YAAY;EAE/C,QAAQ,cAAR;GACE,KAAK,cACH,OAAO,IAAI,wBAAwB,KAAK,IAAI;GAE9C,KAAK,QACH,OAAO,IAAI,kBAAkB,KAAK,SAAS;GAE7C,SACE,MAAM,IAAI,WAAW,mBAAmB,OAAO,aAAa,CAAC,oBAAoB;;;;;CAzBxF,UAAU,aAAa,qBAAqB;oBAGxC,OAAO,UAAU,cAAc,CAAA;oBAC/B,OAAO,UAAU,iBAAiB,CAAA;oBAClC,OAAO,aAAa,oBAAoB,EAAE,YAAY,MAAM,CAAC,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACkB3D,IAAA,cAAA,eAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;CAmBvB,OAAO,aAAa,SAAgE;EAClF,OAAO;GACL,QAAA;GACA,WAAW,CACT;IACE,SAAS,aAAa;IACtB,YAAY,QAAQ;IACpB,QAAQ,QAAQ;IACjB,CACF;GACF;;;;;;;;;;;;;;;;;;;;;;CAuBH,OAAO,cAAc,SAAsC;EACzD,OAAO;GACL,QAAA;GACA,WAAW,CACT;IACE,SAAS;IACT,aAAa,aAA4B,SAAS,SAAS,QAAQ;IACnE,QAAQ,CAAC,aAAa,cAAc;IACrC,CACF;GACF;;;yCArEJ,OAAO,EACN,WAAW;CACT;EAAE,SAAS,UAAU;EAAkB,UAAU;EAAkB;CACnE;EAAE,SAAS,aAAa;EAAsB,UAAU;EAAsB;CAC9E;EAAE,SAAS,aAAa;EAAe,UAAU;EAAe;CACjE,EACF,CAAC,CAAA,EAAA,YAAA"}
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./chunk-D1SwGrFN.mjs";
2
- import { t as signUrl } from "./signed-url-BQPbv2In.mjs";
3
- import { n as R2PresignedUrlSecretMissingError, t as StorageResponseBodyMissingError } from "./errors-ORxu1-Bb.mjs";
2
+ import { t as signUrl } from "./signed-url-BqUqt5dF.mjs";
3
+ import { t as StorageError } from "./storage.error-C6FY037a.mjs";
4
4
  //#region src/storage/providers/r2-storage.provider.ts
5
5
  var r2_storage_provider_exports = /* @__PURE__ */ __exportAll({ R2StorageProvider: () => R2StorageProvider });
6
6
  const MIN_PART_SIZE = 5 * 1024 * 1024;
@@ -43,7 +43,7 @@ var R2StorageProvider = class {
43
43
  }
44
44
  async download(path) {
45
45
  const obj = await this.bucket.get(path);
46
- if (!obj) throw new StorageResponseBodyMissingError(path);
46
+ if (!obj) throw new StorageError(`Storage object not found at path "${path}"`);
47
47
  const objBody = obj;
48
48
  return {
49
49
  toStream: () => objBody.body,
@@ -62,7 +62,7 @@ var R2StorageProvider = class {
62
62
  }
63
63
  async getPresignedUrl(path, method, expiresIn) {
64
64
  const secret = this.env.APP_SECRET;
65
- if (!secret) throw new R2PresignedUrlSecretMissingError();
65
+ if (!secret) throw new StorageError("APP_SECRET is required for generating presigned URLs");
66
66
  return {
67
67
  url: await signUrl(`${`${this.routeBasePath}/${this.config.disk}/${path}`}?method=${method}`, secret, { expiresIn }),
68
68
  expiresIn,
@@ -244,4 +244,4 @@ var R2StorageProvider = class {
244
244
  //#endregion
245
245
  export { r2_storage_provider_exports as n, R2StorageProvider as t };
246
246
 
247
- //# sourceMappingURL=r2-storage.provider-DuonKeYm.mjs.map
247
+ //# sourceMappingURL=r2-storage.provider-Hfm6LdZQ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"r2-storage.provider-Hfm6LdZQ.mjs","names":[],"sources":["../src/storage/providers/r2-storage.provider.ts"],"sourcesContent":["import { type StratalEnv } from '../../env'\nimport { signUrl } from '../../router/signed-url'\nimport type { DownloadResult, PresignedUrlResult, UploadOptions, UploadResult } 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 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 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 as ArrayBuffer | ArrayBufferView | string | Blob)\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"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- import { f as ROUTE_METADATA_KEYS } from "./errors-COW9-Mar.mjs";
1
+ import { n as getMetadata, t as defineMetadata } from "./metadata-BVkc4aUu.mjs";
2
+ import { u as ROUTE_METADATA_KEYS } from "./exception-context-B4kM-M53.mjs";
2
3
  //#region src/rate-limiter/decorators/rate-limit.decorator.ts
3
4
  const KEY = ROUTE_METADATA_KEYS.RATE_LIMIT;
4
5
  /**
@@ -29,13 +30,8 @@ const KEY = ROUTE_METADATA_KEYS.RATE_LIMIT;
29
30
  */
30
31
  function RateLimit(name) {
31
32
  return (target, propertyKey) => {
32
- if (propertyKey === void 0) {
33
- const existing = Reflect.getOwnMetadata(KEY, target) ?? [];
34
- Reflect.defineMetadata(KEY, [...existing, name], target);
35
- } else {
36
- const existing = Reflect.getOwnMetadata(KEY, target, propertyKey) ?? [];
37
- Reflect.defineMetadata(KEY, [...existing, name], target, propertyKey);
38
- }
33
+ if (propertyKey === void 0) defineMetadata(KEY, [...getMetadata(KEY, target) ?? [], name], target);
34
+ else defineMetadata(KEY, [...getMetadata(KEY, target, propertyKey) ?? [], name], target, propertyKey);
39
35
  };
40
36
  }
41
37
  /**
@@ -46,10 +42,9 @@ function RateLimit(name) {
46
42
  * For method metadata, pass `Controller.prototype` and the method name.
47
43
  */
48
44
  function getRateLimits(target, propertyKey) {
49
- const meta = propertyKey === void 0 ? Reflect.getMetadata(KEY, target) : Reflect.getMetadata(KEY, target, propertyKey);
50
- return Array.isArray(meta) ? meta : [];
45
+ return (propertyKey === void 0 ? getMetadata(KEY, target) : getMetadata(KEY, target, propertyKey)) ?? [];
51
46
  }
52
47
  //#endregion
53
48
  export { getRateLimits as n, RateLimit as t };
54
49
 
55
- //# sourceMappingURL=rate-limit.decorator-6qzNcSOt.mjs.map
50
+ //# sourceMappingURL=rate-limit.decorator-D69zdZbp.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.decorator-D69zdZbp.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,OAAO,IAAI,EAAE,EACvB,KAAK,EAAE,OAAO;OAGhD,eAAe,KAAK,CAAC,GADJ,YAAsB,KAAK,QAAQ,YAAsB,IAAI,EAAE,EAC9C,KAAK,EAAE,QAAQ,YAAsB;;;;;;;;;;AAY7E,SAAgB,cAAc,QAAgB,aAAgC;CAI5E,QAHa,gBAAgB,KAAA,IACzB,YAAsB,KAAK,OAAO,GAClC,YAAsB,KAAK,QAAQ,YAAY,KACpC,EAAE"}