@geekmidas/constructs 10.0.0-alpha.38 → 10.0.0-alpha.39

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 (257) hide show
  1. package/README.md +156 -155
  2. package/dist/{Construct-Detz65RA.mjs → Construct-BO1X-kt9.mjs} +3 -6
  3. package/dist/Construct-BO1X-kt9.mjs.map +1 -0
  4. package/dist/Construct-BQALFCh2.d.cts +230 -0
  5. package/dist/Construct-BQALFCh2.d.cts.map +1 -0
  6. package/dist/{Construct-yG9Sw-5p.cjs → Construct-DmvXceVY.cjs} +3 -6
  7. package/dist/Construct-DmvXceVY.cjs.map +1 -0
  8. package/dist/Construct-QXnl3VqT.d.mts +230 -0
  9. package/dist/Construct-QXnl3VqT.d.mts.map +1 -0
  10. package/dist/{Cron-Brs_n_vQ.d.cts → Cron-CMEGfRs8.d.cts} +7 -5
  11. package/dist/Cron-CMEGfRs8.d.cts.map +1 -0
  12. package/dist/{Cron-C18-2Lgb.d.mts → Cron-D6X4-xjB.d.mts} +7 -5
  13. package/dist/Cron-D6X4-xjB.d.mts.map +1 -0
  14. package/dist/{CronBuilder-4WnWKR40.d.cts → CronBuilder-CEK7aOqD.d.cts} +21 -16
  15. package/dist/CronBuilder-CEK7aOqD.d.cts.map +1 -0
  16. package/dist/{CronBuilder-BbnT7tqo.cjs → CronBuilder-DB35T5dL.cjs} +13 -11
  17. package/dist/CronBuilder-DB35T5dL.cjs.map +1 -0
  18. package/dist/{CronBuilder-BRkzUOAs.mjs → CronBuilder-Dv0IlZs0.mjs} +14 -12
  19. package/dist/CronBuilder-Dv0IlZs0.mjs.map +1 -0
  20. package/dist/{CronBuilder-CGv51EoE.d.mts → CronBuilder-O_gvButp.d.mts} +21 -16
  21. package/dist/CronBuilder-O_gvButp.d.mts.map +1 -0
  22. package/dist/{Endpoint-DO332Dnm.mjs → Endpoint-CQAeh1Rh.mjs} +5 -5
  23. package/dist/Endpoint-CQAeh1Rh.mjs.map +1 -0
  24. package/dist/{Endpoint-QKuFg5gs.cjs → Endpoint-D1L9qu5C.cjs} +5 -5
  25. package/dist/Endpoint-D1L9qu5C.cjs.map +1 -0
  26. package/dist/{Endpoint-BkhPHCuX.d.cts → Endpoint-Dp5AKIXl.d.cts} +10 -13
  27. package/dist/Endpoint-Dp5AKIXl.d.cts.map +1 -0
  28. package/dist/{Endpoint-DkiD0GJs.d.mts → Endpoint-DtI7heFm.d.mts} +10 -13
  29. package/dist/Endpoint-DtI7heFm.d.mts.map +1 -0
  30. package/dist/{EndpointFactory-B9HAjDAP.mjs → EndpointFactory-BBdNeaGJ.mjs} +23 -52
  31. package/dist/EndpointFactory-BBdNeaGJ.mjs.map +1 -0
  32. package/dist/{EndpointFactory-BmO0WnXE.cjs → EndpointFactory-BZOCOaK6.cjs} +22 -51
  33. package/dist/EndpointFactory-BZOCOaK6.cjs.map +1 -0
  34. package/dist/{EndpointFactory-WJzZfkTW.d.mts → EndpointFactory-CgMDvERK.d.mts} +58 -48
  35. package/dist/EndpointFactory-CgMDvERK.d.mts.map +1 -0
  36. package/dist/{EndpointFactory-n96CT_Ys.d.cts → EndpointFactory-FeFRmlDZ.d.cts} +58 -48
  37. package/dist/EndpointFactory-FeFRmlDZ.d.cts.map +1 -0
  38. package/dist/{HonoEndpointAdaptor-CiqAZ5Mt.mjs → HonoEndpointAdaptor-Aui-x9Lg.mjs} +4 -3
  39. package/dist/HonoEndpointAdaptor-Aui-x9Lg.mjs.map +1 -0
  40. package/dist/{HonoEndpointAdaptor-DoEQdC3r.cjs → HonoEndpointAdaptor-Cq99r9H7.cjs} +5 -4
  41. package/dist/HonoEndpointAdaptor-Cq99r9H7.cjs.map +1 -0
  42. package/dist/{Queue-DpKTLl6E.mjs → Queue-C1uDgjxt.mjs} +29 -48
  43. package/dist/Queue-C1uDgjxt.mjs.map +1 -0
  44. package/dist/{Queue-B7yRXQmL.cjs → Queue-Ckmudcm8.cjs} +29 -48
  45. package/dist/Queue-Ckmudcm8.cjs.map +1 -0
  46. package/dist/{Queue-Bhzi2WQC.d.cts → Queue-CpQIGB_2.d.cts} +22 -33
  47. package/dist/Queue-CpQIGB_2.d.cts.map +1 -0
  48. package/dist/{Queue-BoUA-e83.d.mts → Queue-YmvN2vEP.d.mts} +21 -32
  49. package/dist/Queue-YmvN2vEP.d.mts.map +1 -0
  50. package/dist/QueueBuilder-CJNcv4dy.cjs +112 -0
  51. package/dist/QueueBuilder-CJNcv4dy.cjs.map +1 -0
  52. package/dist/QueueBuilder-CM0WUxfw.d.mts +62 -0
  53. package/dist/QueueBuilder-CM0WUxfw.d.mts.map +1 -0
  54. package/dist/QueueBuilder-XEnnOWGe.d.cts +62 -0
  55. package/dist/QueueBuilder-XEnnOWGe.d.cts.map +1 -0
  56. package/dist/QueueBuilder-XuPKZwKH.mjs +95 -0
  57. package/dist/QueueBuilder-XuPKZwKH.mjs.map +1 -0
  58. package/dist/{Subscriber-DQCaiOXF.d.cts → Subscriber-B91YSkZt.d.cts} +12 -13
  59. package/dist/Subscriber-B91YSkZt.d.cts.map +1 -0
  60. package/dist/{Subscriber-CIO3pwji.d.mts → Subscriber-BjHYe5hK.d.mts} +11 -12
  61. package/dist/Subscriber-BjHYe5hK.d.mts.map +1 -0
  62. package/dist/{SubscriberBuilder-CIXTozBi.d.cts → SubscriberBuilder-BSpQX1JT.d.cts} +19 -23
  63. package/dist/SubscriberBuilder-BSpQX1JT.d.cts.map +1 -0
  64. package/dist/{SubscriberBuilder-CMBzPdRX.d.mts → SubscriberBuilder-BhMphk4z.d.mts} +18 -22
  65. package/dist/SubscriberBuilder-BhMphk4z.d.mts.map +1 -0
  66. package/dist/{SubscriberBuilder-C2bCOuPT.cjs → SubscriberBuilder-Duk028pD.cjs} +11 -19
  67. package/dist/SubscriberBuilder-Duk028pD.cjs.map +1 -0
  68. package/dist/{SubscriberBuilder-B4YRTBng.mjs → SubscriberBuilder-DzQUWE08.mjs} +11 -19
  69. package/dist/SubscriberBuilder-DzQUWE08.mjs.map +1 -0
  70. package/dist/{Topic-D5rlM9wS.cjs → Topic-BBVXC3H4.cjs} +46 -51
  71. package/dist/Topic-BBVXC3H4.cjs.map +1 -0
  72. package/dist/Topic-DOw051ch.mjs +98 -0
  73. package/dist/Topic-DOw051ch.mjs.map +1 -0
  74. package/dist/adaptors/aws.cjs +5 -5
  75. package/dist/adaptors/aws.cjs.map +1 -1
  76. package/dist/adaptors/aws.d.cts +26 -26
  77. package/dist/adaptors/aws.d.cts.map +1 -1
  78. package/dist/adaptors/aws.d.mts +25 -25
  79. package/dist/adaptors/aws.d.mts.map +1 -1
  80. package/dist/adaptors/aws.mjs +5 -5
  81. package/dist/adaptors/aws.mjs.map +1 -1
  82. package/dist/adaptors/hono.cjs +1 -1
  83. package/dist/adaptors/hono.d.cts +4 -5
  84. package/dist/adaptors/hono.d.cts.map +1 -1
  85. package/dist/adaptors/hono.d.mts +4 -5
  86. package/dist/adaptors/hono.d.mts.map +1 -1
  87. package/dist/adaptors/hono.mjs +1 -1
  88. package/dist/adaptors/testing.cjs +15 -17
  89. package/dist/adaptors/testing.cjs.map +1 -1
  90. package/dist/adaptors/testing.d.cts +26 -28
  91. package/dist/adaptors/testing.d.cts.map +1 -1
  92. package/dist/adaptors/testing.d.mts +26 -28
  93. package/dist/adaptors/testing.d.mts.map +1 -1
  94. package/dist/adaptors/testing.mjs +15 -17
  95. package/dist/adaptors/testing.mjs.map +1 -1
  96. package/dist/{auth-6Cdx5amw.d.cts → auth-CMwml9QN.d.mts} +4 -4
  97. package/dist/{auth-6Cdx5amw.d.cts.map → auth-CMwml9QN.d.mts.map} +1 -1
  98. package/dist/{auth-B_c9b54v.d.mts → auth-rVRDjIw_.d.cts} +4 -4
  99. package/dist/{auth-B_c9b54v.d.mts.map → auth-rVRDjIw_.d.cts.map} +1 -1
  100. package/dist/auth.d.cts +1 -1
  101. package/dist/auth.d.mts +1 -1
  102. package/dist/{cache-CSJHbgwS.d.cts → cache-C9a_hVCc.d.cts} +2 -2
  103. package/dist/{cache-CSJHbgwS.d.cts.map → cache-C9a_hVCc.d.cts.map} +1 -1
  104. package/dist/{cache-HHg5No4L.d.mts → cache-CeTMI8LR.d.mts} +2 -2
  105. package/dist/{cache-HHg5No4L.d.mts.map → cache-CeTMI8LR.d.mts.map} +1 -1
  106. package/dist/cache.d.cts +1 -1
  107. package/dist/cache.d.mts +1 -1
  108. package/dist/{construct-interface-BLG_BEH5.d.mts → construct-interface-_3zgxzXl.d.mts} +9 -18
  109. package/dist/{construct-interface-BLG_BEH5.d.mts.map → construct-interface-_3zgxzXl.d.mts.map} +1 -1
  110. package/dist/{construct-interface-DcNaRKoC.d.cts → construct-interface-_xi4BPN8.d.cts} +9 -18
  111. package/dist/{construct-interface-DcNaRKoC.d.cts.map → construct-interface-_xi4BPN8.d.cts.map} +1 -1
  112. package/dist/construct-interface.cjs +1 -30
  113. package/dist/construct-interface.cjs.map +1 -1
  114. package/dist/construct-interface.d.cts +2 -2
  115. package/dist/construct-interface.d.mts +2 -2
  116. package/dist/construct-interface.mjs +2 -29
  117. package/dist/construct-interface.mjs.map +1 -1
  118. package/dist/credential.d.cts +1 -1
  119. package/dist/credential.d.mts +1 -1
  120. package/dist/crons/index.cjs +1 -1
  121. package/dist/crons/index.d.cts +2 -2
  122. package/dist/crons/index.d.mts +2 -2
  123. package/dist/crons/index.mjs +1 -1
  124. package/dist/database/kysely.d.cts +1 -1
  125. package/dist/database/kysely.d.mts +1 -1
  126. package/dist/email.d.cts +1 -1
  127. package/dist/email.d.mts +1 -1
  128. package/dist/endpoints/index.cjs +5 -4
  129. package/dist/endpoints/index.d.cts +4 -13
  130. package/dist/endpoints/index.d.cts.map +1 -1
  131. package/dist/endpoints/index.d.mts +4 -13
  132. package/dist/endpoints/index.d.mts.map +1 -1
  133. package/dist/endpoints/index.mjs +4 -3
  134. package/dist/{external-api-CZHev1Oh.d.mts → external-api-DS3mujT5.d.mts} +2 -2
  135. package/dist/{external-api-BKOb2jSC.d.cts.map → external-api-DS3mujT5.d.mts.map} +1 -1
  136. package/dist/{external-api-BKOb2jSC.d.cts → external-api-LBfE-q3g.d.cts} +2 -2
  137. package/dist/{external-api-CZHev1Oh.d.mts.map → external-api-LBfE-q3g.d.cts.map} +1 -1
  138. package/dist/external-api.d.cts +1 -1
  139. package/dist/external-api.d.mts +1 -1
  140. package/dist/file-server.d.cts +1 -1
  141. package/dist/file-server.d.mts +1 -1
  142. package/dist/functions/index.cjs +1 -1
  143. package/dist/functions/index.d.cts +1 -1
  144. package/dist/functions/index.d.mts +1 -1
  145. package/dist/functions/index.mjs +1 -1
  146. package/dist/{functions-DlFm8HmH.mjs → functions-DCIfCRR-.mjs} +94 -23
  147. package/dist/functions-DCIfCRR-.mjs.map +1 -0
  148. package/dist/{functions-DKN6aQWJ.cjs → functions-ZH60FE7H.cjs} +99 -19
  149. package/dist/functions-ZH60FE7H.cjs.map +1 -0
  150. package/dist/{index-Dzkdm-IE.d.cts → index-B0kxstwf.d.cts} +36 -24
  151. package/dist/index-B0kxstwf.d.cts.map +1 -0
  152. package/dist/{index-DwveWr3c.d.mts → index-CjVEjOw0.d.mts} +36 -24
  153. package/dist/index-CjVEjOw0.d.mts.map +1 -0
  154. package/dist/index.cjs +1 -1
  155. package/dist/index.d.cts +1 -1
  156. package/dist/index.d.mts +1 -1
  157. package/dist/index.mjs +1 -1
  158. package/dist/{kysely-Dy8nNgvk.d.cts → kysely-BJ8IQ_1P.d.mts} +4 -4
  159. package/dist/{kysely-Dy8nNgvk.d.cts.map → kysely-BJ8IQ_1P.d.mts.map} +1 -1
  160. package/dist/{kysely-QLoPapbM.d.mts → kysely-DVF8wOT4.d.cts} +4 -4
  161. package/dist/{kysely-QLoPapbM.d.mts.map → kysely-DVF8wOT4.d.cts.map} +1 -1
  162. package/dist/{lazyAccessors-BHtcqgz2.mjs → lazyAccessors-CFanua0O.mjs} +2 -39
  163. package/dist/lazyAccessors-CFanua0O.mjs.map +1 -0
  164. package/dist/{lazyAccessors-Dx9NLwJZ.cjs → lazyAccessors-hJKGv8Ay.cjs} +1 -50
  165. package/dist/lazyAccessors-hJKGv8Ay.cjs.map +1 -0
  166. package/dist/mobile-app.d.cts +1 -1
  167. package/dist/mobile-app.d.mts +1 -1
  168. package/dist/object-storage.d.cts +1 -1
  169. package/dist/object-storage.d.mts +1 -1
  170. package/dist/processAudits-BpRlPQMW.mjs.map +1 -1
  171. package/dist/processAudits-D_2W5x8k.cjs.map +1 -1
  172. package/dist/queue/index.cjs +4 -78
  173. package/dist/queue/index.d.cts +3 -53
  174. package/dist/queue/index.d.mts +3 -53
  175. package/dist/queue/index.mjs +3 -77
  176. package/dist/rest-api.cjs +2 -4
  177. package/dist/rest-api.cjs.map +1 -1
  178. package/dist/rest-api.d.cts +4 -5
  179. package/dist/rest-api.d.cts.map +1 -1
  180. package/dist/rest-api.d.mts +4 -5
  181. package/dist/rest-api.d.mts.map +1 -1
  182. package/dist/rest-api.mjs +2 -4
  183. package/dist/rest-api.mjs.map +1 -1
  184. package/dist/site.d.cts +1 -1
  185. package/dist/site.d.mts +1 -1
  186. package/dist/subscribers/index.cjs +1 -1
  187. package/dist/subscribers/index.d.cts +2 -2
  188. package/dist/subscribers/index.d.mts +2 -2
  189. package/dist/subscribers/index.mjs +1 -1
  190. package/dist/topic/index.cjs +1 -36
  191. package/dist/topic/index.d.cts +2 -3
  192. package/dist/topic/index.d.mts +2 -3
  193. package/dist/topic/index.mjs +2 -35
  194. package/dist/worker.cjs +26 -17
  195. package/dist/worker.cjs.map +1 -1
  196. package/dist/worker.d.cts +37 -29
  197. package/dist/worker.d.cts.map +1 -1
  198. package/dist/worker.d.mts +37 -29
  199. package/dist/worker.d.mts.map +1 -1
  200. package/dist/worker.mjs +26 -17
  201. package/dist/worker.mjs.map +1 -1
  202. package/package.json +31 -31
  203. package/dist/Construct-DTqYvkPG.d.cts +0 -115
  204. package/dist/Construct-DTqYvkPG.d.cts.map +0 -1
  205. package/dist/Construct-Detz65RA.mjs.map +0 -1
  206. package/dist/Construct-pVadmURD.d.mts +0 -115
  207. package/dist/Construct-pVadmURD.d.mts.map +0 -1
  208. package/dist/Construct-yG9Sw-5p.cjs.map +0 -1
  209. package/dist/Cron-Brs_n_vQ.d.cts.map +0 -1
  210. package/dist/Cron-C18-2Lgb.d.mts.map +0 -1
  211. package/dist/CronBuilder-4WnWKR40.d.cts.map +0 -1
  212. package/dist/CronBuilder-BRkzUOAs.mjs.map +0 -1
  213. package/dist/CronBuilder-BbnT7tqo.cjs.map +0 -1
  214. package/dist/CronBuilder-CGv51EoE.d.mts.map +0 -1
  215. package/dist/Endpoint-BkhPHCuX.d.cts.map +0 -1
  216. package/dist/Endpoint-DO332Dnm.mjs.map +0 -1
  217. package/dist/Endpoint-DkiD0GJs.d.mts.map +0 -1
  218. package/dist/Endpoint-QKuFg5gs.cjs.map +0 -1
  219. package/dist/EndpointFactory-B9HAjDAP.mjs.map +0 -1
  220. package/dist/EndpointFactory-BmO0WnXE.cjs.map +0 -1
  221. package/dist/EndpointFactory-WJzZfkTW.d.mts.map +0 -1
  222. package/dist/EndpointFactory-n96CT_Ys.d.cts.map +0 -1
  223. package/dist/HonoEndpointAdaptor-CiqAZ5Mt.mjs.map +0 -1
  224. package/dist/HonoEndpointAdaptor-DoEQdC3r.cjs.map +0 -1
  225. package/dist/Queue-B7yRXQmL.cjs.map +0 -1
  226. package/dist/Queue-Bhzi2WQC.d.cts.map +0 -1
  227. package/dist/Queue-BoUA-e83.d.mts.map +0 -1
  228. package/dist/Queue-DpKTLl6E.mjs.map +0 -1
  229. package/dist/Subscriber-CIO3pwji.d.mts.map +0 -1
  230. package/dist/Subscriber-DQCaiOXF.d.cts.map +0 -1
  231. package/dist/SubscriberBuilder-B4YRTBng.mjs.map +0 -1
  232. package/dist/SubscriberBuilder-C2bCOuPT.cjs.map +0 -1
  233. package/dist/SubscriberBuilder-CIXTozBi.d.cts.map +0 -1
  234. package/dist/SubscriberBuilder-CMBzPdRX.d.mts.map +0 -1
  235. package/dist/Topic-Ct3Le8Wt.mjs +0 -103
  236. package/dist/Topic-Ct3Le8Wt.mjs.map +0 -1
  237. package/dist/Topic-D5rlM9wS.cjs.map +0 -1
  238. package/dist/Topic-D6blllEn.d.cts +0 -89
  239. package/dist/Topic-D6blllEn.d.cts.map +0 -1
  240. package/dist/Topic-DVvheJ_-.d.mts +0 -89
  241. package/dist/Topic-DVvheJ_-.d.mts.map +0 -1
  242. package/dist/functions-DKN6aQWJ.cjs.map +0 -1
  243. package/dist/functions-DlFm8HmH.mjs.map +0 -1
  244. package/dist/index-DwveWr3c.d.mts.map +0 -1
  245. package/dist/index-Dzkdm-IE.d.cts.map +0 -1
  246. package/dist/index-fnpjXiA-.d.mts +0 -26
  247. package/dist/index-fnpjXiA-.d.mts.map +0 -1
  248. package/dist/index-tE_119SX.d.cts +0 -26
  249. package/dist/index-tE_119SX.d.cts.map +0 -1
  250. package/dist/lazyAccessors-BHtcqgz2.mjs.map +0 -1
  251. package/dist/lazyAccessors-Dx9NLwJZ.cjs.map +0 -1
  252. package/dist/queue/index.cjs.map +0 -1
  253. package/dist/queue/index.d.cts.map +0 -1
  254. package/dist/queue/index.d.mts.map +0 -1
  255. package/dist/queue/index.mjs.map +0 -1
  256. package/dist/topic/index.cjs.map +0 -1
  257. package/dist/topic/index.mjs.map +0 -1
package/README.md CHANGED
@@ -33,14 +33,13 @@ import { RestApi } from '@geekmidas/constructs/rest-api';
33
33
  // returns. There is no free-standing `e` — an endpoint belongs to a surface.
34
34
  import { EndpointBuilder, EndpointFactory } from '@geekmidas/constructs/endpoints';
35
35
 
36
- // Functions
37
- import { f, FunctionBuilder } from '@geekmidas/constructs/functions';
36
+ // The process with no port. Crons, topic subscribers, queues and functions
37
+ // are built from it — `worker.cron(…)`, `worker.topic(…)`, `worker.queue(…)`,
38
+ // `worker.input(…)` — and carry its logger.
39
+ import { Worker } from '@geekmidas/constructs/worker';
38
40
 
39
- // Crons
40
- import { c, CronBuilder } from '@geekmidas/constructs/crons';
41
-
42
- // Subscribers
43
- import { s, SubscriberBuilder } from '@geekmidas/constructs/subscribers';
41
+ // A declared event contract. Its publisher is its `service`.
42
+ import { Topic } from '@geekmidas/constructs/topic';
44
43
  ```
45
44
 
46
45
  ## Quick Start
@@ -126,15 +125,31 @@ export const searchUsers = api
126
125
  });
127
126
  ```
128
127
 
128
+ ### The Worker
129
+
130
+ Everything that is not an HTTP route is built from a `Worker`: the process
131
+ that runs a schedule, drains a queue or consumes a topic. It takes no
132
+ authorizer, because nothing reaches it from outside.
133
+
134
+ ```typescript
135
+ // constructs/worker.ts
136
+ import { Worker } from '@geekmidas/constructs/worker';
137
+ import { database } from './database';
138
+ import { logger } from './logger';
139
+
140
+ // `.database(…)` is where a server target keeps cron schedules.
141
+ export const worker = new Worker('Jobs', { logger }).database(database);
142
+ ```
143
+
129
144
  ### Cloud Functions
130
145
 
131
- Create serverless functions with input/output validation:
146
+ Create functions with input/output validation:
132
147
 
133
148
  ```typescript
134
- import { f } from '@geekmidas/constructs/functions';
149
+ import { worker } from '../constructs/worker';
135
150
  import { z } from 'zod';
136
151
 
137
- export const processOrder = f
152
+ export const processOrder = worker
138
153
  .input(z.object({
139
154
  orderId: z.string(),
140
155
  items: z.array(z.object({
@@ -145,13 +160,12 @@ export const processOrder = f
145
160
  .output(z.object({
146
161
  orderId: z.string(),
147
162
  status: z.enum(['processing', 'completed', 'failed']),
148
- processedAt: z.string().datetime()
163
+ processedAt: z.iso.datetime()
149
164
  }))
150
165
  .timeout(300000) // 5 minutes
151
166
  .handle(async ({ input, logger }) => {
152
167
  logger.info(`Processing order ${input.orderId}`);
153
168
 
154
- // Process order logic
155
169
  for (const item of input.items) {
156
170
  logger.info(`Processing item ${item.id}, quantity: ${item.quantity}`);
157
171
  }
@@ -166,87 +180,99 @@ export const processOrder = f
166
180
 
167
181
  ### Scheduled Tasks (Crons)
168
182
 
169
- Define cron jobs with schedules:
170
-
171
183
  ```typescript
172
- import { cron } from '@geekmidas/constructs/crons';
184
+ import { worker } from '../constructs/worker';
173
185
 
174
186
  // Daily report at 9 AM UTC
175
- export const dailyReport = cron
176
- .schedule('cron(0 9 * * ? *)')
187
+ export const dailyReport = worker
188
+ .cron('cron(0 9 * * ? *)')
177
189
  .timeout(600000) // 10 minutes
178
190
  .handle(async ({ logger }) => {
179
- logger.info('Generating daily report');
180
-
181
- const reportDate = new Date().toISOString().split('T')[0];
182
191
  const reportData = {
183
- date: reportDate,
192
+ date: new Date().toISOString().split('T')[0],
184
193
  totalOrders: 150,
185
- totalRevenue: 12500.00
186
194
  };
187
195
 
188
- logger.info('Daily report generated', reportData);
196
+ logger.info(reportData, 'Daily report generated');
189
197
  return reportData;
190
198
  });
191
199
 
192
200
  // Hourly cleanup
193
- export const hourlyCleanup = cron
194
- .schedule('rate(1 hour)')
195
- .timeout(300000) // 5 minutes
196
- .handle(async ({ logger }) => {
197
- logger.info('Running hourly cleanup');
198
-
199
- const itemsCleaned = 42;
200
- logger.info(`Cleaned ${itemsCleaned} items`);
201
+ export const hourlyCleanup = worker
202
+ .cron('rate(1 hour)')
203
+ .dependsOn([database])
204
+ .handle(async ({ services, logger }) => {
205
+ const result = await services.database
206
+ .deleteFrom('sessions')
207
+ .where('expires_at', '<', new Date())
208
+ .executeTakeFirst();
201
209
 
202
- return { itemsCleaned };
210
+ logger.info({ deleted: Number(result.numDeletedRows) }, 'Cleaned up');
203
211
  });
204
212
  ```
205
213
 
206
- ### Event Subscribers
214
+ On AWS a cron is an EventBridge rule; on a server the worker schedules it in
215
+ the database it was given.
216
+
217
+ ### Topics and Subscribers
207
218
 
208
- Handle events with type-safe message processing:
219
+ A topic declares the event contract once. A subscriber binds to it from the
220
+ worker and is typed by it:
209
221
 
210
222
  ```typescript
211
- import { SubscriberBuilder } from '@geekmidas/constructs/subscribers';
223
+ // constructs/topics.ts
224
+ import { Topic } from '@geekmidas/constructs/topic';
212
225
  import { z } from 'zod';
213
226
 
214
- export const userEventsSubscriber = new SubscriberBuilder()
215
- .subscribe(['user.created', 'user.updated', 'user.deleted'])
216
- .timeout(30000)
217
- .output(z.object({
218
- processed: z.number(),
219
- success: z.boolean()
220
- }))
221
- .handle(async ({ events, logger }) => {
222
- logger.info(
223
- { eventCount: events.length },
224
- 'Processing user events'
225
- );
227
+ export const users = new Topic('Users', {
228
+ events: {
229
+ 'user.created': z.object({ userId: z.string(), email: z.email() }),
230
+ 'user.updated': z.object({ userId: z.string(), changes: z.array(z.string()) }),
231
+ },
232
+ });
226
233
 
234
+ // subscribers/userEvents.ts
235
+ export const userEventsSubscriber = worker
236
+ .topic(users)
237
+ .subscribe(['user.created', 'user.updated'])
238
+ .handle(async ({ events, logger }) => {
227
239
  for (const event of events) {
228
- try {
229
- switch (event.type) {
230
- case 'user.created':
231
- logger.info({ userId: event.data.userId }, 'User created');
232
- break;
233
- case 'user.updated':
234
- logger.info({ userId: event.data.userId }, 'User updated');
235
- break;
236
- case 'user.deleted':
237
- logger.info({ userId: event.data.userId }, 'User deleted');
238
- break;
239
- }
240
- } catch (error) {
241
- logger.error({ error, event }, 'Failed to process event');
242
- throw error;
243
- }
240
+ // event.type narrows event.payload to that event's schema
241
+ logger.info({ type: event.type, userId: event.payload.userId }, 'User event');
244
242
  }
243
+ });
244
+ ```
245
245
 
246
- return {
247
- processed: events.length,
248
- success: true
249
- };
246
+ A subscriber is bound, not handed a publisher. One that emits follow-ups
247
+ depends on the topic it emits to: `.dependsOn([notifications])`.
248
+
249
+ ### Queues
250
+
251
+ A queue and its one consumer are one construct. Producers send by depending on
252
+ it:
253
+
254
+ ```typescript
255
+ // queues/emails.ts
256
+ export const emails = worker
257
+ .queue('Emails')
258
+ .message(z.object({ to: z.email(), template: z.enum(['welcome']) }))
259
+ .dependsOn([mail])
260
+ .handle(async ({ messages, services }) => {
261
+ for (const { to, template } of messages) {
262
+ await services.mail.sendTemplate(template, { to, subject: 'Welcome', props: {} });
263
+ }
264
+ });
265
+
266
+ // endpoints/invites.ts
267
+ export const invite = api
268
+ .post('/invites')
269
+ .body(z.object({ email: z.email() }))
270
+ .dependsOn([emails])
271
+ .handle(async ({ body, services }) => {
272
+ // `type` is the queue's name exactly as written
273
+ await services.emails.publish([
274
+ { type: 'Emails', payload: { to: body.email, template: 'welcome' } },
275
+ ]);
250
276
  });
251
277
  ```
252
278
 
@@ -344,99 +370,69 @@ export const getHealth = router
344
370
  .handle(async () => ({ status: 'ok' }));
345
371
  ```
346
372
 
347
- ### Service Discovery
373
+ ### Dependencies
348
374
 
349
- Inject services into your constructs:
375
+ `.dependsOn([...])` takes constructs. Each one's client lands in the handler's
376
+ `services` under the construct's own id, and the edge is what the manifest
377
+ records — so the environment, the runtime client and the cloud access all come
378
+ from the one line:
350
379
 
351
380
  ```typescript
352
381
  import { api } from '../constructs/api';
353
- import type { Service } from '@geekmidas/services';
354
- import type { EnvironmentParser } from '@geekmidas/envkit';
355
- import { Kysely } from 'kysely';
382
+ import { database } from '../constructs/database';
356
383
  import { z } from 'zod';
357
384
 
358
- // Define a database service
359
- const databaseService = {
360
- serviceName: 'database' as const,
361
- async register(envParser: EnvironmentParser<{}>) {
362
- const config = envParser.create((get) => ({
363
- url: get('DATABASE_URL').string()
364
- })).parse();
365
-
366
- const db = new Kysely({ /* config */ });
367
- return db;
368
- }
369
- } satisfies Service<'database', Kysely<Database>>;
370
-
371
- // Use service in endpoint
372
385
  export const getUserFromDb = api
373
386
  .get('/users/:id')
374
387
  .params(z.object({ id: z.string() }))
375
- .services([databaseService])
388
+ .dependsOn([database])
376
389
  .handle(async ({ params, services }) => {
377
- // services.database is fully typed
378
- const user = await services.database
390
+ // services.database is a Kysely client typed by the construct's schema
391
+ return services.database
379
392
  .selectFrom('users')
380
393
  .where('id', '=', params.id)
381
394
  .selectAll()
382
395
  .executeTakeFirst();
383
-
384
- return user;
385
396
  });
386
397
  ```
387
398
 
399
+ A hand-written `Service` still works through `.services()` on every builder,
400
+ for code that has not moved to a construct yet. It records no edge, so a deploy
401
+ target cannot grant it anything.
402
+
388
403
  ### Event Publishing
389
404
 
390
- Publish events from any construct:
405
+ An endpoint, function or cron publishes to a topic with `.event(topic, …)`,
406
+ once its handler has succeeded:
391
407
 
392
408
  ```typescript
393
- import { api } from '../constructs/api';
394
- import type { Service } from '@geekmidas/services';
395
- import type { EventPublisher, PublishableMessage } from '@geekmidas/events';
396
- import { z } from 'zod';
409
+ import { users } from '../constructs/topics';
397
410
 
398
- // Define event types
399
- type UserEvents =
400
- | PublishableMessage<'user.created', { userId: string; email: string }>
401
- | PublishableMessage<'user.updated', { userId: string }>;
402
-
403
- // Create event publisher service
404
- const userEventPublisher = {
405
- serviceName: 'userEventPublisher' as const,
406
- async register(envParser: EnvironmentParser<{}>) {
407
- const config = envParser.create((get) => ({
408
- publisherUrl: get('EVENT_PUBLISHER_URL').string()
409
- })).parse();
410
-
411
- const { Publisher } = await import('@geekmidas/events');
412
- return Publisher.fromConnectionString<UserEvents>(config.publisherUrl);
413
- }
414
- } satisfies Service<'userEventPublisher', EventPublisher<UserEvents>>;
415
-
416
- // Use in endpoint with event publishing
417
- export const createUser = api
411
+ export const createUser = router
418
412
  .post('/users')
419
- .body(z.object({
420
- name: z.string(),
421
- email: z.email()
422
- }))
423
- .publisher(userEventPublisher)
424
- .event('user.created', (body, result) => ({
425
- userId: result.id,
426
- email: body.email
427
- }))
428
- .handle(async ({ body, publish }) => {
429
- const user = {
430
- id: crypto.randomUUID(),
431
- name: body.name,
432
- email: body.email
433
- };
434
-
435
- // Events are automatically published after successful execution
436
- return user;
437
- });
413
+ .body(z.object({ name: z.string(), email: z.email() }))
414
+ .output(z.object({ id: z.string(), name: z.string(), email: z.email() }))
415
+ .event(users, {
416
+ type: 'user.created',
417
+ payload: (user) => ({ userId: user.id, email: user.email }),
418
+ when: (user) => !user.email.endsWith('@example.com'), // optional
419
+ })
420
+ .handle(async ({ body }) => ({ id: crypto.randomUUID(), ...body }));
438
421
  ```
439
422
 
423
+ - `type` is checked against the topic's events, and `payload` against that
424
+ event's schema.
425
+ - `.event()` is repeatable across topics. Two calls to two topics publish to
426
+ each, every event through its own topic's publisher.
427
+ - It also puts the topic's publisher in the handler as `services.users`, as
428
+ `.dependsOn([users])` would, for events the handler decides on:
429
+ `await services.users.publish([{ type: 'user.updated', payload }])`.
430
+ - A failed publish is logged, never thrown — the handler already succeeded.
431
+
432
+ The publisher reads `USERS_PUBLISHER_CONNECTION_STRING`, which only constructs
433
+ that publish to the topic are given, and picks its transport from the protocol:
434
+ `pgboss://` locally, `sns://` deployed.
435
+
440
436
  ### Database Context
441
437
 
442
438
  Inject a database instance directly into the handler context using `.database()`:
@@ -633,10 +629,11 @@ For complex scenarios, use `ctx.auditor` to record audits manually within your h
633
629
 
634
630
  ```typescript
635
631
  export const processOrder = api
636
- .post('/orders')
637
- .database(databaseService)
638
- .services([paymentService])
632
+ .database(database)
639
633
  .auditor(auditStorageService)
634
+ .post('/orders')
635
+ // `payments` is an ExternalApi whose client has `charge()`
636
+ .dependsOn([payments])
640
637
  .actor(({ session }) => ({ id: session.userId, type: 'user' }))
641
638
  .handle(async ({ body, db, services, auditor }) => {
642
639
  // db is automatically the transaction when auditor uses KyselyAuditStorage
@@ -647,7 +644,7 @@ export const processOrder = api
647
644
  .executeTakeFirstOrThrow();
648
645
 
649
646
  // Manual audit for payment (external service call)
650
- const payment = await services.payment.charge(order.total);
647
+ const payment = await services.payments.charge(order.total);
651
648
  auditor.audit('payment.processed', {
652
649
  orderId: order.id,
653
650
  amount: order.total,
@@ -1097,19 +1094,19 @@ Every construct has an async `getEnvironment()` method that returns the environm
1097
1094
 
1098
1095
  ```typescript
1099
1096
  import { api } from '../constructs/api';
1100
- import { databaseService } from './services/database';
1101
- import { cacheService } from './services/cache';
1097
+ import { cache } from '../constructs/cache';
1098
+ import { database } from '../constructs/database';
1102
1099
 
1103
1100
  const endpoint = api
1104
1101
  .get('/users')
1105
- .services([databaseService, cacheService])
1102
+ .dependsOn([database, cache])
1106
1103
  .handle(async ({ services }) => {
1107
1104
  // Implementation
1108
1105
  });
1109
1106
 
1110
1107
  // Detect required environment variables
1111
1108
  const envVars = await endpoint.getEnvironment();
1112
- // Returns: ['CACHE_URL', 'DATABASE_URL'] (sorted alphabetically)
1109
+ // Returns: ['DATABASE_URL', 'SESSIONS_URL'] (sorted alphabetically)
1113
1110
  ```
1114
1111
 
1115
1112
  ### How It Works
@@ -1197,33 +1194,37 @@ This manifest can then be used by infrastructure-as-code tools (Terraform, CDK,
1197
1194
  - **Deduplication**: Each variable listed once even if used by multiple services
1198
1195
  - **Sorted Output**: Variables always returned in alphabetical order
1199
1196
  - **Error Resilient**: Parse failures don't affect detection
1200
- - **Publisher Support**: Detects variables from `.publisher()` services
1197
+ - **Topic Support**: A topic named by `.event(topic, …)` contributes its `<ID>_PUBLISHER_CONNECTION_STRING`
1201
1198
 
1202
1199
  ### Example with Multiple Services
1203
1200
 
1204
1201
  ```typescript
1205
1202
  import { api } from '../constructs/api';
1206
- import { databaseService } from './services/database';
1207
- import { cacheService } from './services/cache';
1208
- import { emailService } from './services/email';
1203
+ import { cache } from '../constructs/cache';
1204
+ import { database } from '../constructs/database';
1205
+ import { mail } from '../constructs/email';
1206
+ import { users } from '../constructs/topics';
1209
1207
 
1210
1208
  const endpoint = api
1211
1209
  .post('/users')
1212
- .services([databaseService, cacheService, emailService])
1210
+ .dependsOn([database, cache, mail])
1211
+ .output(userSchema)
1212
+ .event(users, {
1213
+ type: 'user.created',
1214
+ payload: (user) => ({ userId: user.id, email: user.email }),
1215
+ })
1213
1216
  .handle(async ({ services }) => {
1214
1217
  // Create user
1215
1218
  });
1216
1219
 
1217
- // Automatically detects all variables from all services
1220
+ // Detects the keys of every construct it names
1218
1221
  const envVars = await endpoint.getEnvironment();
1219
1222
  // Returns: [
1220
- // 'CACHE_URL',
1221
1223
  // 'DATABASE_URL',
1222
- // 'DATABASE_PORT',
1223
- // 'SMTP_HOST',
1224
- // 'SMTP_PORT',
1225
- // 'SMTP_USER',
1226
- // 'SMTP_PASS'
1224
+ // 'MAIL_FROM',
1225
+ // 'MAIL_URL',
1226
+ // 'SESSIONS_URL',
1227
+ // 'USERS_PUBLISHER_CONNECTION_STRING'
1227
1228
  // ]
1228
1229
  ```
1229
1230
 
@@ -88,7 +88,6 @@ var Construct = class {
88
88
  logger;
89
89
  services;
90
90
  events;
91
- publisherService;
92
91
  outputSchema;
93
92
  timeout;
94
93
  memorySize;
@@ -109,12 +108,11 @@ var Construct = class {
109
108
  * still how `c`, `s` and `f` work.
110
109
  */
111
110
  owner;
112
- constructor(type, logger, services, events = [], publisherService, outputSchema, timeout, memorySize, auditorStorageService, constructs = []) {
111
+ constructor(type, logger, services, events = [], outputSchema, timeout, memorySize, auditorStorageService, constructs = []) {
113
112
  this.type = type;
114
113
  this.logger = logger;
115
114
  this.services = services;
116
115
  this.events = events;
117
- this.publisherService = publisherService;
118
116
  this.outputSchema = outputSchema;
119
117
  this.timeout = timeout;
120
118
  this.memorySize = memorySize;
@@ -150,8 +148,7 @@ var Construct = class {
150
148
  const optionalVars = /* @__PURE__ */ new Set();
151
149
  const services = (0, import_lodash_compact.default)([
152
150
  ...this.services,
153
- this.publisherService,
154
- this.publisherService,
151
+ ...this.events.map(({ topic }) => topic),
155
152
  this.auditorStorageService,
156
153
  this.databaseService
157
154
  ]);
@@ -221,4 +218,4 @@ async function sniffService(service) {
221
218
  //#endregion
222
219
  export { __commonJSMin as a, snifferContext as i, ConstructType as n, __toESM as o, sniffService as r, Construct as t };
223
220
 
224
- //# sourceMappingURL=Construct-Detz65RA.mjs.map
221
+ //# sourceMappingURL=Construct-BO1X-kt9.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Construct-BO1X-kt9.mjs","names":["compact"],"sources":["../../../node_modules/.pnpm/lodash.compact@3.0.1/node_modules/lodash.compact/index.js","../src/Construct.ts"],"sourcesContent":["/**\n * lodash 3.0.1 (Custom Build) <https://lodash.com/>\n * Build: `lodash modularize exports=\"npm\" -o ./`\n * Copyright 2012-2016 The Dojo Foundation <http://dojofoundation.org/>\n * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>\n * Copyright 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors\n * Available under MIT license <https://lodash.com/license>\n */\n\n/**\n * Creates an array with all falsey values removed. The values `false`, `null`,\n * `0`, `\"\"`, `undefined`, and `NaN` are falsey.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to compact.\n * @returns {Array} Returns the new array of filtered values.\n * @example\n *\n * _.compact([0, 1, false, 2, '', 3]);\n * // => [1, 2, 3]\n */\nfunction compact(array) {\n var index = -1,\n length = array ? array.length : 0,\n resIndex = 0,\n result = [];\n\n while (++index < length) {\n var value = array[index];\n if (value) {\n result[resIndex++] = value;\n }\n }\n return result;\n}\n\nmodule.exports = compact;\n","import type { AuditStorage } from '@geekmidas/audit';\nimport {\n\tSnifferEnvironmentParser,\n\ttype SniffResult,\n\tsniffWithFireAndForget,\n} from '@geekmidas/envkit/sniffer';\nimport type { Logger } from '@geekmidas/logger';\nimport type { Service, ServiceContext } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport compact from 'lodash.compact';\nimport type { TopicEvent } from './publisher';\n\n// Cache for service environment variables to handle singleton services\n// Stores both the full list and the optional subset so the same sniff result\n// can satisfy both getEnvironment() and getEnvironment({ markOptional: true }).\nconst serviceEnvCache = new Map<\n\tService,\n\t{ envVars: string[]; optionalEnvVars: string[] }\n>();\n\n/**\n * Noop context for environment sniffing.\n * Used when calling service.register() to detect env vars without a real request.\n * Returns dummy values since services should only use context in instance methods,\n * not during registration.\n */\nconst snifferContext: ServiceContext = {\n\tgetLogger() {\n\t\t// Return a noop logger for sniffing - services shouldn't log during registration\n\t\treturn {\n\t\t\tdebug: () => {},\n\t\t\tinfo: () => {},\n\t\t\twarn: () => {},\n\t\t\terror: () => {},\n\t\t\tchild: () => snifferContext.getLogger(),\n\t\t} as unknown as Logger;\n\t},\n\tgetRequestId() {\n\t\treturn 'sniffer-context';\n\t},\n\tgetRequestStartTime() {\n\t\treturn Date.now();\n\t},\n\thasContext() {\n\t\treturn false;\n\t},\n};\n\nexport abstract class Construct<\n\tTLogger extends Logger = Logger,\n\tOutSchema extends StandardSchemaV1 | undefined = undefined,\n\tTServices extends Service[] = [],\n\tTAuditStorageServiceName extends string = string,\n\tTAuditStorage extends AuditStorage | undefined = undefined,\n\tTDatabaseServiceName extends string = string,\n\tTDatabase = unknown,\n> {\n\t/** Database service for this construct (used by Endpoint for RLS/audit context) */\n\tpublic databaseService?: Service<TDatabaseServiceName, TDatabase>;\n\n\t/**\n\t * The deploy unit that runs this, by construct id.\n\t *\n\t * What an endpoint's `surface` is to it, and for the same reason: something\n\t * has to say which process a runnable belongs to, and until this existed the\n\t * only thing that said so was the directory its file sat in. Set by the\n\t * factory it was built from — `worker.crons`, `api.get(…)` — so it is\n\t * carried rather than configured.\n\t *\n\t * Undefined for a construct built from a free-standing builder, which is\n\t * still how `c`, `s` and `f` work.\n\t */\n\tpublic owner?: string;\n\n\tconstructor(\n\t\tpublic readonly type: ConstructType,\n\t\tpublic readonly logger: TLogger,\n\t\tpublic readonly services: TServices,\n\t\t/**\n\t\t * What this construct publishes once its handler succeeds — each event\n\t\t * bound to its own topic, so a construct publishes to as many as it names.\n\t\t */\n\t\tpublic readonly events: TopicEvent[] = [],\n\t\tpublic outputSchema?: OutSchema,\n\t\tpublic readonly timeout?: number,\n\t\tpublic readonly memorySize?: number,\n\t\tpublic readonly auditorStorageService?: Service<\n\t\t\tTAuditStorageServiceName,\n\t\t\tTAuditStorage\n\t\t>,\n\t\t/**\n\t\t * The constructs this one declared an edge to, by id.\n\t\t *\n\t\t * What the manifest records. `services` is the same information in the\n\t\t * form a handler runs with — clients, keyed by name — and an id cannot be\n\t\t * recovered from one of those reliably, so both are kept.\n\t\t *\n\t\t * Last in the list because every other parameter is positional and already\n\t\t * spoken for; a new one anywhere else silently rebinds existing callers.\n\t\t */\n\t\tpublic readonly constructs: string[] = [],\n\t) {}\n\n\t/**\n\t * Returns an array of environment variable names required by this construct's services.\n\t * This is determined by running a \"sniffer\" EnvironmentParser through each service's\n\t * register method to track which environment variables are accessed.\n\t *\n\t * Results are cached per service class to handle singleton patterns where\n\t * subsequent register() calls may short-circuit and not access env vars.\n\t *\n\t * @param markOptional - When true, optional vars (those accessed via `.optional()` or\n\t * `.default()`) are suffixed with `?` in the returned array (e.g. `'PORT?'`).\n\t * @returns Promise that resolves to array of environment variable names, sorted alphabetically\n\t *\n\t * @example\n\t * ```typescript\n\t * const endpoint = e\n\t * .services([databaseService, authService])\n\t * .get('/users')\n\t * .handle(async () => []);\n\t *\n\t * await endpoint.getEnvironment(); // ['AUTH_SECRET', 'DATABASE_URL']\n\t * await endpoint.getEnvironment({ markOptional: true }); // ['AUTH_SECRET', 'DATABASE_URL', 'PORT?']\n\t * ```\n\t */\n\tasync getEnvironment(\n\t\topts: { markOptional?: boolean } = {},\n\t): Promise<string[]> {\n\t\tconst { markOptional = false } = opts;\n\t\tconst envVars = new Set<string>();\n\t\tconst optionalVars = new Set<string>();\n\t\tconst services: Service[] = compact([\n\t\t\t...this.services,\n\t\t\t// Each topic an event names is a dependency like any other.\n\t\t\t...this.events.map(({ topic }) => topic),\n\t\t\tthis.auditorStorageService,\n\t\t\tthis.databaseService,\n\t\t]);\n\n\t\ttry {\n\t\t\tfor (const service of services) {\n\t\t\t\t// Check cache first - handles singleton services that short-circuit\n\t\t\t\tif (serviceEnvCache.has(service)) {\n\t\t\t\t\tconst cached = serviceEnvCache.get(service)!;\n\t\t\t\t\tcached.envVars.forEach((v) => envVars.add(v));\n\t\t\t\t\tcached.optionalEnvVars.forEach((v) => optionalVars.add(v));\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Use sniffService to properly handle all error scenarios\n\t\t\t\tconst result = await sniffService(service);\n\n\t\t\t\t// Log any issues for debugging\n\t\t\t\tif (result.error) {\n\t\t\t\t\tthis.logger.warn(\n\t\t\t\t\t\t{ error: result.error.message, service: service.serviceName },\n\t\t\t\t\t\t'Service threw error during env sniffing (env vars still captured)',\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (result.unhandledRejections.length > 0) {\n\t\t\t\t\tthis.logger.warn(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\terrors: result.unhandledRejections.map((e) => e.message),\n\t\t\t\t\t\t\tservice: service.serviceName,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t'Fire-and-forget rejections during env sniffing (suppressed)',\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Cache and collect env vars (both required and optional)\n\t\t\t\tserviceEnvCache.set(service, {\n\t\t\t\t\tenvVars: result.envVars,\n\t\t\t\t\toptionalEnvVars: result.optionalEnvVars,\n\t\t\t\t});\n\t\t\t\tresult.envVars.forEach((v) => envVars.add(v));\n\t\t\t\tresult.optionalEnvVars.forEach((v) => optionalVars.add(v));\n\t\t\t}\n\n\t\t\tconst sorted = Array.from(envVars).sort();\n\t\t\tif (!markOptional || optionalVars.size === 0) return sorted;\n\t\t\treturn sorted.map((v) => (optionalVars.has(v) ? `${v}?` : v));\n\t\t} catch (error) {\n\t\t\tthis.logger.error(\n\t\t\t\t{ error },\n\t\t\t\t'Error determining environment variables for construct',\n\t\t\t);\n\n\t\t\treturn [];\n\t\t}\n\t}\n}\n\nexport enum ConstructType {\n\tCron = 'dev.geekmidas.function.cron',\n\tEndpoint = 'dev.geekmidas.function.endpoint',\n\tFunction = 'dev.geekmidas.function.function',\n\tSubscriber = 'dev.geekmidas.function.subscriber',\n\tQueue = 'dev.geekmidas.function.queue',\n\tTopic = 'dev.geekmidas.resource.topic',\n}\n\n/**\n * Utility to test sniffing a service for environment variables.\n * Useful for debugging services that throw errors during sniffing.\n *\n * @example\n * ```typescript\n * import { sniffService } from '@geekmidas/constructs';\n * import { authService } from './services/AuthService';\n *\n * const result = await sniffService(authService);\n * console.log('Env vars:', result.envVars);\n * console.log('Error:', result.error);\n * ```\n */\nexport async function sniffService(service: Service): Promise<SniffResult> {\n\tconst sniffer = new SnifferEnvironmentParser();\n\n\treturn sniffWithFireAndForget(sniffer, () =>\n\t\tservice.register({\n\t\t\tenvParser: sniffer as any,\n\t\t\tcontext: snifferContext,\n\t\t}),\n\t);\n}\n\n// Export for testing\nexport { snifferContext };\n"],"x_google_ignoreList":[0],"mappings":"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"}