@pattern-stack/codegen 0.15.1 → 0.15.2

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 (521) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/chunk-24CWKBK5.js +94 -0
  3. package/dist/chunk-24CWKBK5.js.map +1 -0
  4. package/dist/chunk-2E224ZSN.js +20 -0
  5. package/dist/chunk-2E224ZSN.js.map +1 -0
  6. package/dist/chunk-2FTZLDBP.js +179 -0
  7. package/dist/chunk-2FTZLDBP.js.map +1 -0
  8. package/dist/chunk-2N4UG4VD.js +20 -0
  9. package/dist/chunk-2N4UG4VD.js.map +1 -0
  10. package/dist/chunk-2TVVBC53.js +92 -0
  11. package/dist/chunk-2TVVBC53.js.map +1 -0
  12. package/dist/chunk-2VHZ7EKC.js +37 -0
  13. package/dist/chunk-2VHZ7EKC.js.map +1 -0
  14. package/dist/chunk-32BMMV4H.js +109 -0
  15. package/dist/chunk-32BMMV4H.js.map +1 -0
  16. package/dist/chunk-32DOFN3T.js +4042 -0
  17. package/dist/chunk-32DOFN3T.js.map +1 -0
  18. package/dist/chunk-36U5UGIO.js +107 -0
  19. package/dist/chunk-36U5UGIO.js.map +1 -0
  20. package/dist/chunk-3CJFPU6Q.js +14 -0
  21. package/dist/chunk-3CJFPU6Q.js.map +1 -0
  22. package/dist/chunk-3NMCDN7L.js +90 -0
  23. package/dist/chunk-3NMCDN7L.js.map +1 -0
  24. package/dist/chunk-3SZFUTXE.js +62 -0
  25. package/dist/chunk-3SZFUTXE.js.map +1 -0
  26. package/dist/chunk-4DOJBQTP.js +117 -0
  27. package/dist/chunk-4DOJBQTP.js.map +1 -0
  28. package/dist/chunk-4JLJYWJC.js +308 -0
  29. package/dist/chunk-4JLJYWJC.js.map +1 -0
  30. package/dist/chunk-4KNXX6TI.js +29 -0
  31. package/dist/chunk-4KNXX6TI.js.map +1 -0
  32. package/dist/chunk-4LH67P4U.js +17 -0
  33. package/dist/chunk-4LH67P4U.js.map +1 -0
  34. package/dist/chunk-4MVGAMUA.js +40 -0
  35. package/dist/chunk-4MVGAMUA.js.map +1 -0
  36. package/dist/chunk-4OMHBMZJ.js +75 -0
  37. package/dist/chunk-4OMHBMZJ.js.map +1 -0
  38. package/dist/chunk-4RFHUZXU.js +635 -0
  39. package/dist/chunk-4RFHUZXU.js.map +1 -0
  40. package/dist/chunk-5A432NZJ.js +7 -0
  41. package/dist/chunk-5A432NZJ.js.map +1 -0
  42. package/dist/chunk-5Y7W3XR6.js +356 -0
  43. package/dist/chunk-5Y7W3XR6.js.map +1 -0
  44. package/dist/chunk-6DWFJNIK.js +15 -0
  45. package/dist/chunk-6DWFJNIK.js.map +1 -0
  46. package/dist/chunk-6I7ULIN6.js +15 -0
  47. package/dist/chunk-6I7ULIN6.js.map +1 -0
  48. package/dist/chunk-6XY6ZMMD.js +25 -0
  49. package/dist/chunk-6XY6ZMMD.js.map +1 -0
  50. package/dist/chunk-7B3RYX45.js +63 -0
  51. package/dist/chunk-7B3RYX45.js.map +1 -0
  52. package/dist/chunk-7C3FOSDI.js +1 -0
  53. package/dist/chunk-7C3FOSDI.js.map +1 -0
  54. package/dist/chunk-7KOW6PU6.js +59 -0
  55. package/dist/chunk-7KOW6PU6.js.map +1 -0
  56. package/dist/chunk-7LKAMLV4.js +92 -0
  57. package/dist/chunk-7LKAMLV4.js.map +1 -0
  58. package/dist/chunk-7RELQJIN.js +22 -0
  59. package/dist/chunk-7RELQJIN.js.map +1 -0
  60. package/dist/chunk-AHV4GDYM.js +63 -0
  61. package/dist/chunk-AHV4GDYM.js.map +1 -0
  62. package/dist/chunk-AQFQ4BYM.js +81 -0
  63. package/dist/chunk-AQFQ4BYM.js.map +1 -0
  64. package/dist/chunk-AS3NAZB6.js +14 -0
  65. package/dist/chunk-AS3NAZB6.js.map +1 -0
  66. package/dist/chunk-BGULBWKJ.js +88 -0
  67. package/dist/chunk-BGULBWKJ.js.map +1 -0
  68. package/dist/chunk-BIO6F7YI.js +17 -0
  69. package/dist/chunk-BIO6F7YI.js.map +1 -0
  70. package/dist/chunk-BOPZWRJK.js +36 -0
  71. package/dist/chunk-BOPZWRJK.js.map +1 -0
  72. package/dist/chunk-BPARRK6F.js +14 -0
  73. package/dist/chunk-BPARRK6F.js.map +1 -0
  74. package/dist/chunk-CO6LUM72.js +59 -0
  75. package/dist/chunk-CO6LUM72.js.map +1 -0
  76. package/dist/chunk-COGHTKXY.js +84 -0
  77. package/dist/chunk-COGHTKXY.js.map +1 -0
  78. package/dist/chunk-DCCZB4UC.js +100 -0
  79. package/dist/chunk-DCCZB4UC.js.map +1 -0
  80. package/dist/chunk-DKKFTHHI.js +53 -0
  81. package/dist/chunk-DKKFTHHI.js.map +1 -0
  82. package/dist/chunk-DV4RV2DC.js +59 -0
  83. package/dist/chunk-DV4RV2DC.js.map +1 -0
  84. package/dist/chunk-EDKJU5BO.js +11 -0
  85. package/dist/chunk-EDKJU5BO.js.map +1 -0
  86. package/dist/chunk-EO2QPOKH.js +116 -0
  87. package/dist/chunk-EO2QPOKH.js.map +1 -0
  88. package/dist/chunk-EOLLMEAH.js +155 -0
  89. package/dist/chunk-EOLLMEAH.js.map +1 -0
  90. package/dist/chunk-EWYCWP4H.js +14 -0
  91. package/dist/chunk-EWYCWP4H.js.map +1 -0
  92. package/dist/chunk-EXVDJMIY.js +33 -0
  93. package/dist/chunk-EXVDJMIY.js.map +1 -0
  94. package/dist/chunk-FASRXRX5.js +19 -0
  95. package/dist/chunk-FASRXRX5.js.map +1 -0
  96. package/dist/chunk-FI34KYZ5.js +1 -0
  97. package/dist/chunk-FI34KYZ5.js.map +1 -0
  98. package/dist/chunk-FN2PYDPP.js +1 -0
  99. package/dist/chunk-FN2PYDPP.js.map +1 -0
  100. package/dist/chunk-GM3RMJIJ.js +92 -0
  101. package/dist/chunk-GM3RMJIJ.js.map +1 -0
  102. package/dist/chunk-GYGNEQSC.js +9 -0
  103. package/dist/chunk-GYGNEQSC.js.map +1 -0
  104. package/dist/chunk-H5NH7KPE.js +21 -0
  105. package/dist/chunk-H5NH7KPE.js.map +1 -0
  106. package/dist/chunk-HNWZFNKP.js +168 -0
  107. package/dist/chunk-HNWZFNKP.js.map +1 -0
  108. package/dist/chunk-HUH73XGI.js +1 -0
  109. package/dist/chunk-HUH73XGI.js.map +1 -0
  110. package/dist/chunk-I6MG4M3F.js +201 -0
  111. package/dist/chunk-I6MG4M3F.js.map +1 -0
  112. package/dist/chunk-I6MVCB5A.js +39 -0
  113. package/dist/chunk-I6MVCB5A.js.map +1 -0
  114. package/dist/chunk-IBGER4YK.js +12 -0
  115. package/dist/chunk-IBGER4YK.js.map +1 -0
  116. package/dist/chunk-IF5I3DAA.js +92 -0
  117. package/dist/chunk-IF5I3DAA.js.map +1 -0
  118. package/dist/chunk-IP4OO26U.js +54 -0
  119. package/dist/chunk-IP4OO26U.js.map +1 -0
  120. package/dist/chunk-IWAOY6KC.js +1 -0
  121. package/dist/chunk-IWAOY6KC.js.map +1 -0
  122. package/dist/chunk-J37YWU7Y.js +19 -0
  123. package/dist/chunk-J37YWU7Y.js.map +1 -0
  124. package/dist/chunk-J6KZS54B.js +269 -0
  125. package/dist/chunk-J6KZS54B.js.map +1 -0
  126. package/dist/chunk-J6MN42LG.js +19 -0
  127. package/dist/chunk-J6MN42LG.js.map +1 -0
  128. package/dist/chunk-JRQO2IOF.js +65 -0
  129. package/dist/chunk-JRQO2IOF.js.map +1 -0
  130. package/dist/chunk-JRVNVKN6.js +212 -0
  131. package/dist/chunk-JRVNVKN6.js.map +1 -0
  132. package/dist/chunk-JWNHNUYL.js +96 -0
  133. package/dist/chunk-JWNHNUYL.js.map +1 -0
  134. package/dist/chunk-K2I6XIK5.js +122 -0
  135. package/dist/chunk-K2I6XIK5.js.map +1 -0
  136. package/dist/chunk-KMZCQASO.js +111 -0
  137. package/dist/chunk-KMZCQASO.js.map +1 -0
  138. package/dist/chunk-KVOWSC5S.js +1 -0
  139. package/dist/chunk-KVOWSC5S.js.map +1 -0
  140. package/dist/chunk-KYR3B3OW.js +79 -0
  141. package/dist/chunk-KYR3B3OW.js.map +1 -0
  142. package/dist/chunk-L3LZWWSX.js +61 -0
  143. package/dist/chunk-L3LZWWSX.js.map +1 -0
  144. package/dist/chunk-L4SDDEEU.js +1 -0
  145. package/dist/chunk-L4SDDEEU.js.map +1 -0
  146. package/dist/chunk-L6FTY45T.js +13 -0
  147. package/dist/chunk-L6FTY45T.js.map +1 -0
  148. package/dist/chunk-L7BNNRGI.js +134 -0
  149. package/dist/chunk-L7BNNRGI.js.map +1 -0
  150. package/dist/chunk-LG57S2SC.js +150 -0
  151. package/dist/chunk-LG57S2SC.js.map +1 -0
  152. package/dist/chunk-M6QLSLPO.js +97 -0
  153. package/dist/chunk-M6QLSLPO.js.map +1 -0
  154. package/dist/chunk-MZ6GV4YF.js +21 -0
  155. package/dist/chunk-MZ6GV4YF.js.map +1 -0
  156. package/dist/chunk-N5OTOWTP.js +55 -0
  157. package/dist/chunk-N5OTOWTP.js.map +1 -0
  158. package/dist/chunk-NN7XZEGF.js +14 -0
  159. package/dist/chunk-NN7XZEGF.js.map +1 -0
  160. package/dist/chunk-NPFPZ2HO.js +13 -0
  161. package/dist/chunk-NPFPZ2HO.js.map +1 -0
  162. package/dist/chunk-NXXDZ6ZF.js +42 -0
  163. package/dist/chunk-NXXDZ6ZF.js.map +1 -0
  164. package/dist/chunk-NYBCQZC7.js +11 -0
  165. package/dist/chunk-NYBCQZC7.js.map +1 -0
  166. package/dist/chunk-OFRRBC7M.js +78 -0
  167. package/dist/chunk-OFRRBC7M.js.map +1 -0
  168. package/dist/chunk-OGIZXGPY.js +222 -0
  169. package/dist/chunk-OGIZXGPY.js.map +1 -0
  170. package/dist/chunk-OKXZ63IA.js +168 -0
  171. package/dist/chunk-OKXZ63IA.js.map +1 -0
  172. package/dist/chunk-OSQRXVG2.js +58 -0
  173. package/dist/chunk-OSQRXVG2.js.map +1 -0
  174. package/dist/chunk-OTDN3OUQ.js +215 -0
  175. package/dist/chunk-OTDN3OUQ.js.map +1 -0
  176. package/dist/chunk-OZZJDRGW.js +122 -0
  177. package/dist/chunk-OZZJDRGW.js.map +1 -0
  178. package/dist/chunk-PNZSGAB2.js +114 -0
  179. package/dist/chunk-PNZSGAB2.js.map +1 -0
  180. package/dist/chunk-PRWIX6UW.js +21 -0
  181. package/dist/chunk-PRWIX6UW.js.map +1 -0
  182. package/dist/chunk-PSXUNOVU.js +7 -0
  183. package/dist/chunk-PSXUNOVU.js.map +1 -0
  184. package/dist/chunk-QLTJSCE6.js +44 -0
  185. package/dist/chunk-QLTJSCE6.js.map +1 -0
  186. package/dist/chunk-RC23QROE.js +447 -0
  187. package/dist/chunk-RC23QROE.js.map +1 -0
  188. package/dist/chunk-RFH7N6EP.js +36 -0
  189. package/dist/chunk-RFH7N6EP.js.map +1 -0
  190. package/dist/chunk-RHVN6NA7.js +134 -0
  191. package/dist/chunk-RHVN6NA7.js.map +1 -0
  192. package/dist/chunk-S7C6TIIF.js +21 -0
  193. package/dist/chunk-S7C6TIIF.js.map +1 -0
  194. package/dist/chunk-SNQ3TOWP.js +20 -0
  195. package/dist/chunk-SNQ3TOWP.js.map +1 -0
  196. package/dist/chunk-SOVM2VEK.js +14 -0
  197. package/dist/chunk-SOVM2VEK.js.map +1 -0
  198. package/dist/chunk-SQDOBLBP.js +13 -0
  199. package/dist/chunk-SQDOBLBP.js.map +1 -0
  200. package/dist/chunk-SR7F3TJY.js +130 -0
  201. package/dist/chunk-SR7F3TJY.js.map +1 -0
  202. package/dist/chunk-SZVPIHWE.js +129 -0
  203. package/dist/chunk-SZVPIHWE.js.map +1 -0
  204. package/dist/chunk-T4BIIU5E.js +89 -0
  205. package/dist/chunk-T4BIIU5E.js.map +1 -0
  206. package/dist/chunk-T6C4LFLC.js +112 -0
  207. package/dist/chunk-T6C4LFLC.js.map +1 -0
  208. package/dist/chunk-TNXH7BJS.js +48 -0
  209. package/dist/chunk-TNXH7BJS.js.map +1 -0
  210. package/dist/chunk-U64T4YZE.js +9 -0
  211. package/dist/chunk-U64T4YZE.js.map +1 -0
  212. package/dist/chunk-UQ5EHOH2.js +39 -0
  213. package/dist/chunk-UQ5EHOH2.js.map +1 -0
  214. package/dist/chunk-UTN4GBPQ.js +1 -0
  215. package/dist/chunk-UTN4GBPQ.js.map +1 -0
  216. package/dist/chunk-V4AF6DI4.js +16 -0
  217. package/dist/chunk-V4AF6DI4.js.map +1 -0
  218. package/dist/chunk-W72PRNJY.js +126 -0
  219. package/dist/chunk-W72PRNJY.js.map +1 -0
  220. package/dist/chunk-WEVWJKOW.js +81 -0
  221. package/dist/chunk-WEVWJKOW.js.map +1 -0
  222. package/dist/chunk-WL67FZGF.js +21 -0
  223. package/dist/chunk-WL67FZGF.js.map +1 -0
  224. package/dist/chunk-WPXNN6QS.js +290 -0
  225. package/dist/chunk-WPXNN6QS.js.map +1 -0
  226. package/dist/chunk-WRUUSZDJ.js +29 -0
  227. package/dist/chunk-WRUUSZDJ.js.map +1 -0
  228. package/dist/chunk-X2GMTYPA.js +50 -0
  229. package/dist/chunk-X2GMTYPA.js.map +1 -0
  230. package/dist/chunk-XCEI7NUH.js +41 -0
  231. package/dist/chunk-XCEI7NUH.js.map +1 -0
  232. package/dist/chunk-Y7GDG744.js +88 -0
  233. package/dist/chunk-Y7GDG744.js.map +1 -0
  234. package/dist/chunk-Y7RRSEOC.js +9 -0
  235. package/dist/chunk-Y7RRSEOC.js.map +1 -0
  236. package/dist/chunk-YPWODKD5.js +184 -0
  237. package/dist/chunk-YPWODKD5.js.map +1 -0
  238. package/dist/chunk-YSLTTQLC.js +25 -0
  239. package/dist/chunk-YSLTTQLC.js.map +1 -0
  240. package/dist/chunk-YTN6BKWA.js +121 -0
  241. package/dist/chunk-YTN6BKWA.js.map +1 -0
  242. package/dist/chunk-Z7PQCAVK.js +200 -0
  243. package/dist/chunk-Z7PQCAVK.js.map +1 -0
  244. package/dist/chunk-ZUKFQL6E.js +47 -0
  245. package/dist/chunk-ZUKFQL6E.js.map +1 -0
  246. package/dist/chunk-ZUMULSEQ.js +1 -0
  247. package/dist/chunk-ZUMULSEQ.js.map +1 -0
  248. package/dist/runtime/analytics/index.js +8 -41
  249. package/dist/runtime/analytics/index.js.map +1 -1
  250. package/dist/runtime/analytics/types.js +8 -41
  251. package/dist/runtime/analytics/types.js.map +1 -1
  252. package/dist/runtime/base-classes/activity-entity-repository.js +6 -312
  253. package/dist/runtime/base-classes/activity-entity-repository.js.map +1 -1
  254. package/dist/runtime/base-classes/activity-entity-service.js +6 -212
  255. package/dist/runtime/base-classes/activity-entity-service.js.map +1 -1
  256. package/dist/runtime/base-classes/base-read-use-cases.js +5 -27
  257. package/dist/runtime/base-classes/base-read-use-cases.js.map +1 -1
  258. package/dist/runtime/base-classes/base-repository.js +5 -277
  259. package/dist/runtime/base-classes/base-repository.js.map +1 -1
  260. package/dist/runtime/base-classes/base-service.js +5 -184
  261. package/dist/runtime/base-classes/base-service.js.map +1 -1
  262. package/dist/runtime/base-classes/index.js +59 -1076
  263. package/dist/runtime/base-classes/index.js.map +1 -1
  264. package/dist/runtime/base-classes/integrated-entity-repository.js +6 -486
  265. package/dist/runtime/base-classes/integrated-entity-repository.js.map +1 -1
  266. package/dist/runtime/base-classes/integrated-entity-service.js +6 -213
  267. package/dist/runtime/base-classes/integrated-entity-service.js.map +1 -1
  268. package/dist/runtime/base-classes/junction-integration-repository.js +8 -448
  269. package/dist/runtime/base-classes/junction-integration-repository.js.map +1 -1
  270. package/dist/runtime/base-classes/knowledge-entity-repository.js +6 -283
  271. package/dist/runtime/base-classes/knowledge-entity-repository.js.map +1 -1
  272. package/dist/runtime/base-classes/knowledge-entity-service.js +6 -190
  273. package/dist/runtime/base-classes/knowledge-entity-service.js.map +1 -1
  274. package/dist/runtime/base-classes/lifecycle-events.js +8 -70
  275. package/dist/runtime/base-classes/lifecycle-events.js.map +1 -1
  276. package/dist/runtime/base-classes/metadata-entity-repository.js +6 -330
  277. package/dist/runtime/base-classes/metadata-entity-repository.js.map +1 -1
  278. package/dist/runtime/base-classes/metadata-entity-service.js +6 -212
  279. package/dist/runtime/base-classes/metadata-entity-service.js.map +1 -1
  280. package/dist/runtime/base-classes/tenant-context.js +10 -36
  281. package/dist/runtime/base-classes/tenant-context.js.map +1 -1
  282. package/dist/runtime/base-classes/with-analytics.js +4 -7
  283. package/dist/runtime/base-classes/with-analytics.js.map +1 -1
  284. package/dist/runtime/constants/tokens.js +5 -3
  285. package/dist/runtime/constants/tokens.js.map +1 -1
  286. package/dist/runtime/eav-helpers.js +2 -0
  287. package/dist/runtime/eav-helpers.js.map +1 -1
  288. package/dist/runtime/pipes/zod-validation.pipe.js +3 -10
  289. package/dist/runtime/pipes/zod-validation.pipe.js.map +1 -1
  290. package/dist/runtime/shared/openapi/error-response.dto.js +5 -8
  291. package/dist/runtime/shared/openapi/error-response.dto.js.map +1 -1
  292. package/dist/runtime/shared/openapi/errors.js +5 -19
  293. package/dist/runtime/shared/openapi/errors.js.map +1 -1
  294. package/dist/runtime/shared/openapi/index.js +15 -106
  295. package/dist/runtime/shared/openapi/index.js.map +1 -1
  296. package/dist/runtime/shared/openapi/registry.js +6 -103
  297. package/dist/runtime/shared/openapi/registry.js.map +1 -1
  298. package/dist/runtime/shared/openapi/registry.tokens.js +4 -2
  299. package/dist/runtime/shared/openapi/registry.tokens.js.map +1 -1
  300. package/dist/runtime/subsystems/analytics/analytics.module.js +8 -117
  301. package/dist/runtime/subsystems/analytics/analytics.module.js.map +1 -1
  302. package/dist/runtime/subsystems/analytics/analytics.tokens.js +7 -8
  303. package/dist/runtime/subsystems/analytics/analytics.tokens.js.map +1 -1
  304. package/dist/runtime/subsystems/analytics/cube-backend.js +6 -71
  305. package/dist/runtime/subsystems/analytics/cube-backend.js.map +1 -1
  306. package/dist/runtime/subsystems/analytics/index.js +16 -117
  307. package/dist/runtime/subsystems/analytics/index.js.map +1 -1
  308. package/dist/runtime/subsystems/analytics/noop-backend.js +4 -21
  309. package/dist/runtime/subsystems/analytics/noop-backend.js.map +1 -1
  310. package/dist/runtime/subsystems/auth/auth-oauth-state.schema.js +4 -8
  311. package/dist/runtime/subsystems/auth/auth-oauth-state.schema.js.map +1 -1
  312. package/dist/runtime/subsystems/auth/auth.module.js +12 -359
  313. package/dist/runtime/subsystems/auth/auth.module.js.map +1 -1
  314. package/dist/runtime/subsystems/auth/auth.tokens.js +12 -13
  315. package/dist/runtime/subsystems/auth/auth.tokens.js.map +1 -1
  316. package/dist/runtime/subsystems/auth/backends/encryption-key/env.js +4 -49
  317. package/dist/runtime/subsystems/auth/backends/encryption-key/env.js.map +1 -1
  318. package/dist/runtime/subsystems/auth/backends/state-store.drizzle-backend.js +6 -64
  319. package/dist/runtime/subsystems/auth/backends/state-store.drizzle-backend.js.map +1 -1
  320. package/dist/runtime/subsystems/auth/backends/state-store.memory-backend.js +5 -47
  321. package/dist/runtime/subsystems/auth/backends/state-store.memory-backend.js.map +1 -1
  322. package/dist/runtime/subsystems/auth/controllers/auth.controller.js +5 -139
  323. package/dist/runtime/subsystems/auth/controllers/auth.controller.js.map +1 -1
  324. package/dist/runtime/subsystems/auth/index.js +53 -542
  325. package/dist/runtime/subsystems/auth/index.js.map +1 -1
  326. package/dist/runtime/subsystems/auth/middleware/requester-context.js +9 -65
  327. package/dist/runtime/subsystems/auth/middleware/requester-context.js.map +1 -1
  328. package/dist/runtime/subsystems/auth/protocols/oauth-state-store.js +4 -9
  329. package/dist/runtime/subsystems/auth/protocols/oauth-state-store.js.map +1 -1
  330. package/dist/runtime/subsystems/auth/runtime/connection-broken.error.js +4 -15
  331. package/dist/runtime/subsystems/auth/runtime/connection-broken.error.js.map +1 -1
  332. package/dist/runtime/subsystems/auth/runtime/oauth2-refresh.strategy.js +5 -104
  333. package/dist/runtime/subsystems/auth/runtime/oauth2-refresh.strategy.js.map +1 -1
  334. package/dist/runtime/subsystems/auth/runtime/session-expired.error.js +5 -16
  335. package/dist/runtime/subsystems/auth/runtime/session-expired.error.js.map +1 -1
  336. package/dist/runtime/subsystems/auth/runtime/with-auth-retry.js +5 -29
  337. package/dist/runtime/subsystems/auth/runtime/with-auth-retry.js.map +1 -1
  338. package/dist/runtime/subsystems/bridge/assert-tenant-id.js +5 -18
  339. package/dist/runtime/subsystems/bridge/assert-tenant-id.js.map +1 -1
  340. package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js +12 -184
  341. package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js.map +1 -1
  342. package/dist/runtime/subsystems/bridge/bridge-delivery.drizzle-backend.js +10 -448
  343. package/dist/runtime/subsystems/bridge/bridge-delivery.drizzle-backend.js.map +1 -1
  344. package/dist/runtime/subsystems/bridge/bridge-delivery.memory-backend.js +5 -126
  345. package/dist/runtime/subsystems/bridge/bridge-delivery.memory-backend.js.map +1 -1
  346. package/dist/runtime/subsystems/bridge/bridge-delivery.schema.js +6 -308
  347. package/dist/runtime/subsystems/bridge/bridge-delivery.schema.js.map +1 -1
  348. package/dist/runtime/subsystems/bridge/bridge-errors.js +6 -35
  349. package/dist/runtime/subsystems/bridge/bridge-errors.js.map +1 -1
  350. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js +14 -606
  351. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js.map +1 -1
  352. package/dist/runtime/subsystems/bridge/bridge.module.js +35 -3476
  353. package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
  354. package/dist/runtime/subsystems/bridge/bridge.tokens.js +9 -7
  355. package/dist/runtime/subsystems/bridge/bridge.tokens.js.map +1 -1
  356. package/dist/runtime/subsystems/bridge/event-flow.service.js +11 -137
  357. package/dist/runtime/subsystems/bridge/event-flow.service.js.map +1 -1
  358. package/dist/runtime/subsystems/bridge/generated/registry.js +4 -2
  359. package/dist/runtime/subsystems/bridge/generated/registry.js.map +1 -1
  360. package/dist/runtime/subsystems/bridge/index.js +60 -3470
  361. package/dist/runtime/subsystems/bridge/index.js.map +1 -1
  362. package/dist/runtime/subsystems/bridge/reserved-pools.js +4 -6
  363. package/dist/runtime/subsystems/bridge/reserved-pools.js.map +1 -1
  364. package/dist/runtime/subsystems/cache/cache.drizzle-backend.js +10 -133
  365. package/dist/runtime/subsystems/cache/cache.drizzle-backend.js.map +1 -1
  366. package/dist/runtime/subsystems/cache/cache.memory-backend.js +6 -101
  367. package/dist/runtime/subsystems/cache/cache.memory-backend.js.map +1 -1
  368. package/dist/runtime/subsystems/cache/cache.module.js +10 -278
  369. package/dist/runtime/subsystems/cache/cache.module.js.map +1 -1
  370. package/dist/runtime/subsystems/cache/cache.schema.js +4 -14
  371. package/dist/runtime/subsystems/cache/cache.schema.js.map +1 -1
  372. package/dist/runtime/subsystems/cache/cache.tokens.js +6 -7
  373. package/dist/runtime/subsystems/cache/cache.tokens.js.map +1 -1
  374. package/dist/runtime/subsystems/cache/index.js +20 -278
  375. package/dist/runtime/subsystems/cache/index.js.map +1 -1
  376. package/dist/runtime/subsystems/events/domain-events.schema.js +3 -72
  377. package/dist/runtime/subsystems/events/domain-events.schema.js.map +1 -1
  378. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +9 -413
  379. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -1
  380. package/dist/runtime/subsystems/events/event-bus.memory-backend.js +7 -235
  381. package/dist/runtime/subsystems/events/event-bus.memory-backend.js.map +1 -1
  382. package/dist/runtime/subsystems/events/event-bus.redis-backend.js +8 -20
  383. package/dist/runtime/subsystems/events/event-bus.redis-backend.js.map +1 -1
  384. package/dist/runtime/subsystems/events/event-keyset-cursor.js +8 -30
  385. package/dist/runtime/subsystems/events/event-keyset-cursor.js.map +1 -1
  386. package/dist/runtime/subsystems/events/event-read.protocol.js +2 -0
  387. package/dist/runtime/subsystems/events/event-read.protocol.js.map +1 -1
  388. package/dist/runtime/subsystems/events/events-errors.js +4 -11
  389. package/dist/runtime/subsystems/events/events-errors.js.map +1 -1
  390. package/dist/runtime/subsystems/events/events.module.js +15 -949
  391. package/dist/runtime/subsystems/events/events.module.js.map +1 -1
  392. package/dist/runtime/subsystems/events/events.tokens.js +10 -11
  393. package/dist/runtime/subsystems/events/events.tokens.js.map +1 -1
  394. package/dist/runtime/subsystems/events/generated/bus.js +9 -240
  395. package/dist/runtime/subsystems/events/generated/bus.js.map +1 -1
  396. package/dist/runtime/subsystems/events/generated/index.js +23 -240
  397. package/dist/runtime/subsystems/events/generated/index.js.map +1 -1
  398. package/dist/runtime/subsystems/events/generated/registry.js +5 -82
  399. package/dist/runtime/subsystems/events/generated/registry.js.map +1 -1
  400. package/dist/runtime/subsystems/events/generated/schemas.js +12 -52
  401. package/dist/runtime/subsystems/events/generated/schemas.js.map +1 -1
  402. package/dist/runtime/subsystems/events/generated/types.js +1 -0
  403. package/dist/runtime/subsystems/events/index.js +32 -949
  404. package/dist/runtime/subsystems/events/index.js.map +1 -1
  405. package/dist/runtime/subsystems/index.js +171 -5912
  406. package/dist/runtime/subsystems/index.js.map +1 -1
  407. package/dist/runtime/subsystems/integration/build-change-source.js +6 -178
  408. package/dist/runtime/subsystems/integration/build-change-source.js.map +1 -1
  409. package/dist/runtime/subsystems/integration/deep-equal.differ.js +4 -109
  410. package/dist/runtime/subsystems/integration/deep-equal.differ.js.map +1 -1
  411. package/dist/runtime/subsystems/integration/detection-config.schema.js +11 -78
  412. package/dist/runtime/subsystems/integration/detection-config.schema.js.map +1 -1
  413. package/dist/runtime/subsystems/integration/entity-change-source-registry.memory.js +5 -30
  414. package/dist/runtime/subsystems/integration/entity-change-source-registry.memory.js.map +1 -1
  415. package/dist/runtime/subsystems/integration/entity-change-source-registry.protocol.js +4 -9
  416. package/dist/runtime/subsystems/integration/entity-change-source-registry.protocol.js.map +1 -1
  417. package/dist/runtime/subsystems/integration/execute-integration.use-case.js +6 -239
  418. package/dist/runtime/subsystems/integration/execute-integration.use-case.js.map +1 -1
  419. package/dist/runtime/subsystems/integration/incremental-read.js +5 -144
  420. package/dist/runtime/subsystems/integration/incremental-read.js.map +1 -1
  421. package/dist/runtime/subsystems/integration/index.js +83 -1352
  422. package/dist/runtime/subsystems/integration/index.js.map +1 -1
  423. package/dist/runtime/subsystems/integration/integration-audit.schema.js +10 -155
  424. package/dist/runtime/subsystems/integration/integration-audit.schema.js.map +1 -1
  425. package/dist/runtime/subsystems/integration/integration-cursor-store.drizzle-backend.js +7 -270
  426. package/dist/runtime/subsystems/integration/integration-cursor-store.drizzle-backend.js.map +1 -1
  427. package/dist/runtime/subsystems/integration/integration-cursor-store.memory-backend.js +4 -65
  428. package/dist/runtime/subsystems/integration/integration-cursor-store.memory-backend.js.map +1 -1
  429. package/dist/runtime/subsystems/integration/integration-errors.js +5 -15
  430. package/dist/runtime/subsystems/integration/integration-errors.js.map +1 -1
  431. package/dist/runtime/subsystems/integration/integration-field-diff.protocol.js +5 -7
  432. package/dist/runtime/subsystems/integration/integration-field-diff.protocol.js.map +1 -1
  433. package/dist/runtime/subsystems/integration/integration-run-recorder.drizzle-backend.js +8 -303
  434. package/dist/runtime/subsystems/integration/integration-run-recorder.drizzle-backend.js.map +1 -1
  435. package/dist/runtime/subsystems/integration/integration-run-recorder.memory-backend.js +5 -125
  436. package/dist/runtime/subsystems/integration/integration-run-recorder.memory-backend.js.map +1 -1
  437. package/dist/runtime/subsystems/integration/integration.module.js +13 -700
  438. package/dist/runtime/subsystems/integration/integration.module.js.map +1 -1
  439. package/dist/runtime/subsystems/integration/integration.tokens.js +11 -9
  440. package/dist/runtime/subsystems/integration/integration.tokens.js.map +1 -1
  441. package/dist/runtime/subsystems/integration/loopback.middleware.js +4 -16
  442. package/dist/runtime/subsystems/integration/loopback.middleware.js.map +1 -1
  443. package/dist/runtime/subsystems/integration/poll-change-source.js +4 -89
  444. package/dist/runtime/subsystems/integration/poll-change-source.js.map +1 -1
  445. package/dist/runtime/subsystems/integration/webhook-change-source.js +4 -70
  446. package/dist/runtime/subsystems/integration/webhook-change-source.js.map +1 -1
  447. package/dist/runtime/subsystems/jobs/bullmq.config.js +9 -140
  448. package/dist/runtime/subsystems/jobs/bullmq.config.js.map +1 -1
  449. package/dist/runtime/subsystems/jobs/index.js +88 -2691
  450. package/dist/runtime/subsystems/jobs/index.js.map +1 -1
  451. package/dist/runtime/subsystems/jobs/job-handler.base.js +10 -49
  452. package/dist/runtime/subsystems/jobs/job-handler.base.js.map +1 -1
  453. package/dist/runtime/subsystems/jobs/job-orchestration.schema.js +13 -152
  454. package/dist/runtime/subsystems/jobs/job-orchestration.schema.js.map +1 -1
  455. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +36 -699
  456. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -1
  457. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js +10 -564
  458. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js.map +1 -1
  459. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js +10 -824
  460. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js.map +1 -1
  461. package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.js +9 -51
  462. package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.js.map +1 -1
  463. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js +9 -416
  464. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js.map +1 -1
  465. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +9 -290
  466. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js.map +1 -1
  467. package/dist/runtime/subsystems/jobs/job-step-service.drizzle-backend.js +5 -213
  468. package/dist/runtime/subsystems/jobs/job-step-service.drizzle-backend.js.map +1 -1
  469. package/dist/runtime/subsystems/jobs/job-step-service.memory-backend.js +5 -131
  470. package/dist/runtime/subsystems/jobs/job-step-service.memory-backend.js.map +1 -1
  471. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +9 -175
  472. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js.map +1 -1
  473. package/dist/runtime/subsystems/jobs/job-worker.js +14 -613
  474. package/dist/runtime/subsystems/jobs/job-worker.js.map +1 -1
  475. package/dist/runtime/subsystems/jobs/job-worker.module.js +23 -2647
  476. package/dist/runtime/subsystems/jobs/job-worker.module.js.map +1 -1
  477. package/dist/runtime/subsystems/jobs/jobs-domain.module.js +19 -1897
  478. package/dist/runtime/subsystems/jobs/jobs-domain.module.js.map +1 -1
  479. package/dist/runtime/subsystems/jobs/jobs-domain.tokens.js +8 -9
  480. package/dist/runtime/subsystems/jobs/jobs-domain.tokens.js.map +1 -1
  481. package/dist/runtime/subsystems/jobs/jobs-errors.js +10 -78
  482. package/dist/runtime/subsystems/jobs/jobs-errors.js.map +1 -1
  483. package/dist/runtime/subsystems/jobs/memory-job-store.js +4 -15
  484. package/dist/runtime/subsystems/jobs/memory-job-store.js.map +1 -1
  485. package/dist/runtime/subsystems/jobs/pool-config.loader.js +9 -124
  486. package/dist/runtime/subsystems/jobs/pool-config.loader.js.map +1 -1
  487. package/dist/runtime/subsystems/observability/index.js +21 -310
  488. package/dist/runtime/subsystems/observability/index.js.map +1 -1
  489. package/dist/runtime/subsystems/observability/observability-errors.js +4 -9
  490. package/dist/runtime/subsystems/observability/observability-errors.js.map +1 -1
  491. package/dist/runtime/subsystems/observability/observability.module.js +11 -300
  492. package/dist/runtime/subsystems/observability/observability.module.js.map +1 -1
  493. package/dist/runtime/subsystems/observability/observability.service.js +9 -197
  494. package/dist/runtime/subsystems/observability/observability.service.js.map +1 -1
  495. package/dist/runtime/subsystems/observability/observability.tokens.js +5 -3
  496. package/dist/runtime/subsystems/observability/observability.tokens.js.map +1 -1
  497. package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.js +4 -84
  498. package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.js.map +1 -1
  499. package/dist/runtime/subsystems/observability/reporters/index.js +5 -84
  500. package/dist/runtime/subsystems/observability/reporters/index.js.map +1 -1
  501. package/dist/runtime/subsystems/storage/index.js +15 -200
  502. package/dist/runtime/subsystems/storage/index.js.map +1 -1
  503. package/dist/runtime/subsystems/storage/storage.local-backend.js +4 -103
  504. package/dist/runtime/subsystems/storage/storage.local-backend.js.map +1 -1
  505. package/dist/runtime/subsystems/storage/storage.memory-backend.js +5 -68
  506. package/dist/runtime/subsystems/storage/storage.memory-backend.js.map +1 -1
  507. package/dist/runtime/subsystems/storage/storage.module.js +8 -200
  508. package/dist/runtime/subsystems/storage/storage.module.js.map +1 -1
  509. package/dist/runtime/subsystems/storage/storage.tokens.js +5 -6
  510. package/dist/runtime/subsystems/storage/storage.tokens.js.map +1 -1
  511. package/dist/runtime/subsystems/storage/storage.utils.js +4 -14
  512. package/dist/runtime/subsystems/storage/storage.utils.js.map +1 -1
  513. package/dist/runtime/subsystems/token-key.js +5 -3
  514. package/dist/runtime/subsystems/token-key.js.map +1 -1
  515. package/dist/src/cli/index.js +637 -5454
  516. package/dist/src/cli/index.js.map +1 -1
  517. package/dist/src/index.js +68 -4170
  518. package/dist/src/index.js.map +1 -1
  519. package/package.json +1 -1
  520. package/runtime/subsystems/bridge/bridge-outbox-drain-hook.ts +44 -21
  521. package/runtime/subsystems/jobs/job-worker.ts +17 -11
@@ -1,3478 +1,68 @@
1
- var __defProp = Object.defineProperty;
2
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
- var __decorateClass = (decorators, target, key2, kind) => {
4
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key2) : target;
5
- for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
- if (decorator = decorators[i])
7
- result = (kind ? decorator(target, key2, result) : decorator(result)) || result;
8
- if (kind && result) __defProp(target, key2, result);
9
- return result;
10
- };
11
- var __decorateParam = (index4, decorator) => (target, key2) => decorator(target, key2, index4);
12
-
13
- // runtime/subsystems/bridge/bridge-delivery.schema.ts
14
1
  import {
15
- index as index3,
16
- jsonb as jsonb3,
17
- pgEnum as pgEnum2,
18
- pgTable as pgTable3,
19
- text as text3,
20
- timestamp as timestamp3,
21
- unique,
22
- uuid as uuid3
23
- } from "drizzle-orm/pg-core";
24
- import { sql as sql3 } from "drizzle-orm";
25
-
26
- // runtime/subsystems/events/domain-events.schema.ts
2
+ BridgeModule
3
+ } from "../../../chunk-OZZJDRGW.js";
4
+ import "../../../chunk-5A432NZJ.js";
27
5
  import {
28
- check,
29
- index,
30
- jsonb,
31
- pgTable,
32
- text,
33
- timestamp,
34
- uuid
35
- } from "drizzle-orm/pg-core";
36
- import { sql } from "drizzle-orm";
37
- var domainEvents = pgTable(
38
- "domain_events",
39
- {
40
- id: uuid("id").primaryKey(),
41
- type: text("type").notNull(),
42
- aggregateId: text("aggregate_id").notNull(),
43
- aggregateType: text("aggregate_type").notNull(),
44
- payload: jsonb("payload").notNull().$type(),
45
- occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull(),
46
- processedAt: timestamp("processed_at", { withTimezone: true }),
47
- /** Lifecycle status: pending | processed | failed */
48
- status: text("status").notNull().default("pending"),
49
- /** Error message from the last failed dispatch attempt. */
50
- error: text("error"),
51
- metadata: jsonb("metadata").$type(),
52
- /** Routing pool (e.g. `events_inbound`, `events_change`, `events_outbound`). Populated by DrizzleEventBus.publish() in EVT-4. NULL when `tier='audit'`. */
53
- pool: text("pool"),
54
- /** Routing direction: `inbound` | `change` | `outbound`. Populated by DrizzleEventBus.publish() in EVT-4. NULL when `tier='audit'`. */
55
- direction: text("direction"),
56
- /**
57
- * Event tier: `'domain'` (default) or `'audit'`. Audit-tier rows are
58
- * observability-only and have null `pool`/`direction` by construction —
59
- * enforced by the `domain_events_tier_routing_check` CHECK constraint
60
- * declared below. (AUDIT-1)
61
- */
62
- tier: text("tier").notNull().default("domain"),
63
- // conditional: emitted only when events.multi_tenant: true
64
- tenantId: text("tenant_id")
65
- },
66
- (t) => ({
67
- /** Polling drain filter (existing — promoted from comment to declaration in EVT-1). */
68
- idxDomainEventsStatusOccurredAt: index("idx_domain_events_status_occurred_at").on(
69
- t.status,
70
- t.occurredAt
71
- ),
72
- /** Event replay per aggregate (existing — promoted from comment to declaration in EVT-1). */
73
- idxDomainEventsAggregate: index("idx_domain_events_aggregate").on(
74
- t.aggregateId,
75
- t.aggregateType
76
- ),
77
- /** Per-pool drain filter (EVT-1). Enables DrizzleEventBus to drain a single pool without scanning all events. */
78
- idxDomainEventsPoolStatusOccurredAt: index(
79
- "idx_domain_events_pool_status_occurred_at"
80
- ).on(t.pool, t.status, t.occurredAt),
81
- /** Per-tier filter (AUDIT-1). Backs the observability viewer's tier toggle. */
82
- idxDomainEventsTierStatusOccurredAt: index(
83
- "idx_domain_events_tier_status_occurred_at"
84
- ).on(t.tier, t.status, t.occurredAt),
85
- /**
86
- * Tier ↔ routing-fields invariant (AUDIT-1):
87
- * - `tier` is one of `'domain' | 'audit'`.
88
- * - `tier='audit'` ⇔ `pool IS NULL AND direction IS NULL`.
89
- * - `tier='domain'` ⇒ `pool` and `direction` are populated (the
90
- * DrizzleEventBus inserts always supply them; the bus stamps them
91
- * in AUDIT-3).
92
- */
93
- tierRoutingCheck: check(
94
- "domain_events_tier_routing_check",
95
- sql`${t.tier} in ('domain','audit') AND ((${t.tier} = 'audit') = (${t.pool} is null and ${t.direction} is null))`
96
- )
97
- })
98
- );
99
-
100
- // runtime/subsystems/jobs/job-orchestration.schema.ts
6
+ BridgeOutboxDrainHook
7
+ } from "../../../chunk-L7BNNRGI.js";
101
8
  import {
102
- pgEnum,
103
- pgTable as pgTable2,
104
- uuid as uuid2,
105
- text as text2,
106
- jsonb as jsonb2,
107
- integer,
108
- timestamp as timestamp2,
109
- index as index2,
110
- uniqueIndex
111
- } from "drizzle-orm/pg-core";
112
- import { sql as sql2 } from "drizzle-orm";
113
- var jobRunStatusEnum = pgEnum("job_run_status", [
114
- "pending",
115
- "running",
116
- "waiting",
117
- "completed",
118
- "failed",
119
- "timed_out",
120
- "canceled"
121
- ]);
122
- var jobStepKindEnum = pgEnum("job_step_kind", ["task"]);
123
- var jobStepStatusEnum = pgEnum("job_step_status", [
124
- "pending",
125
- "running",
126
- "completed",
127
- "failed",
128
- "skipped"
129
- ]);
130
- var collisionModeEnum = pgEnum("job_collision_mode", [
131
- "queue",
132
- "reject",
133
- "replace"
134
- ]);
135
- var replayFromEnum = pgEnum("job_replay_from", [
136
- "scratch",
137
- "last_step",
138
- "last_checkpoint"
139
- ]);
140
- var parentClosePolicyEnum = pgEnum("job_parent_close_policy", [
141
- "terminate",
142
- "cancel",
143
- "abandon"
144
- ]);
145
- var waitKindEnum = pgEnum("job_wait_kind", ["signal"]);
146
- var triggerSourceEnum = pgEnum("job_trigger_source", [
147
- "manual",
148
- "schedule",
149
- "event",
150
- "parent"
151
- ]);
152
- var jobs = pgTable2("job", {
153
- type: text2("type").primaryKey(),
154
- version: integer("version").notNull().default(1),
155
- pool: text2("pool").notNull(),
156
- scopeEntityType: text2("scope_entity_type"),
157
- retryPolicy: jsonb2("retry_policy").notNull().$type(),
158
- timeoutMs: integer("timeout_ms"),
159
- concurrencyKeyTemplate: text2("concurrency_key_template"),
160
- collisionMode: collisionModeEnum("collision_mode").notNull().default("queue"),
161
- dedupeKeyTemplate: text2("dedupe_key_template"),
162
- dedupeWindowMs: integer("dedupe_window_ms"),
163
- priorityDefault: integer("priority_default").notNull().default(0),
164
- replayFrom: replayFromEnum("replay_from").notNull().default("last_checkpoint"),
165
- createdAt: timestamp2("created_at", { withTimezone: true }).notNull().defaultNow(),
166
- updatedAt: timestamp2("updated_at", { withTimezone: true }).notNull().defaultNow()
167
- });
168
- var jobRuns = pgTable2(
169
- "job_run",
170
- {
171
- id: uuid2("id").primaryKey().defaultRandom(),
172
- jobType: text2("job_type").notNull().references(() => jobs.type),
173
- jobVersion: integer("job_version").notNull(),
174
- parentRunId: uuid2("parent_run_id").references(() => jobRuns.id),
175
- /**
176
- * Service generates `id` client-side via randomUUID() and sets
177
- * root_run_id = id for root runs (single INSERT, no self-FK race).
178
- */
179
- rootRunId: uuid2("root_run_id").notNull(),
180
- parentClosePolicy: parentClosePolicyEnum("parent_close_policy").notNull().default("terminate"),
181
- scopeEntityType: text2("scope_entity_type"),
182
- scopeEntityId: text2("scope_entity_id"),
183
- tenantId: text2("tenant_id"),
184
- tags: jsonb2("tags").notNull().default({}).$type(),
185
- pool: text2("pool").notNull(),
186
- priority: integer("priority").notNull().default(0),
187
- concurrencyKey: text2("concurrency_key"),
188
- dedupeKey: text2("dedupe_key"),
189
- status: jobRunStatusEnum("status").notNull().default("pending"),
190
- input: jsonb2("input").notNull().$type(),
191
- output: jsonb2("output").$type(),
192
- error: jsonb2("error").$type(),
193
- triggerSource: triggerSourceEnum("trigger_source").notNull(),
194
- triggerRef: text2("trigger_ref"),
195
- runAt: timestamp2("run_at", { withTimezone: true }).notNull().defaultNow(),
196
- startedAt: timestamp2("started_at", { withTimezone: true }),
197
- finishedAt: timestamp2("finished_at", { withTimezone: true }),
198
- claimedAt: timestamp2("claimed_at", { withTimezone: true }),
199
- attempts: integer("attempts").notNull().default(0),
200
- // Phase 3 placeholder — see ADR-025
201
- waitKind: waitKindEnum("wait_kind"),
202
- // Phase 3 placeholder — see ADR-025
203
- resumeToken: text2("resume_token"),
204
- // Phase 3 placeholder — see ADR-025
205
- waitDeadline: timestamp2("wait_deadline", { withTimezone: true }),
206
- createdAt: timestamp2("created_at", { withTimezone: true }).notNull().defaultNow(),
207
- updatedAt: timestamp2("updated_at", { withTimezone: true }).notNull().defaultNow()
208
- },
209
- (t) => ({
210
- /** Claim query: ORDER BY priority DESC, run_at ASC. */
211
- idxJobRunClaim: index2("idx_job_run_claim").on(t.status, t.pool, t.runAt),
212
- /** Tree traversal / cascade cancel. */
213
- idxJobRunRoot: index2("idx_job_run_root").on(t.rootRunId),
214
- /** listForScope query. */
215
- idxJobRunScope: index2("idx_job_run_scope").on(t.scopeEntityType, t.scopeEntityId),
216
- /** Idempotency collapse — partial index. */
217
- idxJobRunDedupe: index2("idx_job_run_dedupe").on(t.jobType, t.dedupeKey).where(sql2`${t.dedupeKey} IS NOT NULL`),
218
- /** Collision check — partial index. */
219
- idxJobRunConcurrency: index2("idx_job_run_concurrency").on(t.concurrencyKey).where(
220
- sql2`${t.concurrencyKey} IS NOT NULL AND ${t.status} IN ('pending','running')`
221
- )
222
- })
223
- );
224
- var jobSteps = pgTable2(
225
- "job_step",
226
- {
227
- id: uuid2("id").primaryKey().defaultRandom(),
228
- jobRunId: uuid2("job_run_id").notNull().references(() => jobRuns.id),
229
- stepId: text2("step_id").notNull(),
230
- kind: jobStepKindEnum("kind").notNull().default("task"),
231
- /**
232
- * Monotonic within run. integer (max ~2B per run) is sufficient —
233
- * downgraded from ADR-022's bigint; revisit only if a single run
234
- * ever exceeds 2 billion steps.
235
- */
236
- seq: integer("seq").notNull(),
237
- status: jobStepStatusEnum("status").notNull().default("pending"),
238
- input: jsonb2("input").$type(),
239
- /** Memoised on success for replay. */
240
- output: jsonb2("output").$type(),
241
- error: jsonb2("error").$type(),
242
- attempts: integer("attempts").notNull().default(0),
243
- startedAt: timestamp2("started_at", { withTimezone: true }),
244
- finishedAt: timestamp2("finished_at", { withTimezone: true })
245
- },
246
- (t) => ({
247
- /** No duplicate step IDs per run. */
248
- idxJobStepRunStep: uniqueIndex("idx_job_step_run_step").on(t.jobRunId, t.stepId),
249
- /** Ordered timeline reads. */
250
- idxJobStepTimeline: index2("idx_job_step_timeline").on(t.jobRunId, t.seq)
251
- })
252
- );
253
-
254
- // runtime/subsystems/bridge/bridge-delivery.schema.ts
255
- var bridgeDeliveryStatusEnum = pgEnum2("bridge_delivery_status", [
256
- "pending",
257
- "delivered",
258
- "skipped",
259
- "failed"
260
- ]);
261
- var bridgeDelivery = pgTable3(
262
- "bridge_delivery",
263
- {
264
- id: uuid3("id").primaryKey().defaultRandom(),
265
- /** FK to the source event in the outbox. */
266
- eventId: uuid3("event_id").notNull().references(() => domainEvents.id),
267
- /**
268
- * Stable codegen-emitted identifier for the (job, trigger) pair, of the
269
- * form `<job_type>#<triggerIndex>` (BRIDGE-6). Forms the second half of
270
- * the UNIQUE idempotency key.
271
- */
272
- triggerId: text3("trigger_id").notNull(),
273
- /**
274
- * Wrapper `job_run.id` (the framework `@framework/bridge_delivery` run
275
- * that drove this delivery). Nullable: the facade-eager path
276
- * (`publishAndStart` Case B) pre-writes a delivered row with no wrapper.
277
- */
278
- wrapperRunId: uuid3("wrapper_run_id").references(() => jobRuns.id),
279
- /**
280
- * Spawned user `job_run.id`. Null until status is `delivered`; remains
281
- * null for `skipped` and `failed` deliveries.
282
- */
283
- userRunId: uuid3("user_run_id").references(() => jobRuns.id),
284
- status: bridgeDeliveryStatusEnum("status").notNull().default("pending"),
285
- /** Populated when status=`skipped` (e.g. `'when_returned_false'`, `'trigger_unregistered'`). */
286
- skipReason: text3("skip_reason"),
287
- /** Populated when status=`failed`. Mirrors `job_run.error` shape. */
288
- error: jsonb3("error").$type(),
289
- /**
290
- * Emitted unconditionally and nullable (JOB-8 / SYNC-6 precedent).
291
- * Enforcement gated on `BRIDGE_MULTI_TENANT` at the service layer
292
- * (BRIDGE-8); no DB constraint.
293
- */
294
- tenantId: text3("tenant_id"),
295
- attemptedAt: timestamp3("attempted_at", { withTimezone: true }).notNull().defaultNow(),
296
- deliveredAt: timestamp3("delivered_at", { withTimezone: true })
297
- },
298
- (t) => ({
299
- /**
300
- * Idempotency ledger. Outbox replays and facade-vs-drain collisions both
301
- * dedup through this constraint.
302
- */
303
- uqBridgeDeliveryEventTrigger: unique("uq_bridge_delivery_event_trigger").on(
304
- t.eventId,
305
- t.triggerId
306
- ),
307
- /** Lookup all deliveries for an event (fanout report, debugging). */
308
- idxBridgeDeliveryEvent: index3("idx_bridge_delivery_event").on(t.eventId),
309
- /**
310
- * Ops dashboard filter — only the actionable states. Partial index keeps
311
- * it small at scale (the bulk of rows will be `delivered`).
312
- */
313
- idxBridgeDeliveryStatus: index3("idx_bridge_delivery_status").on(t.status).where(sql3`${t.status} IN ('pending','failed')`),
314
- /**
315
- * Reverse lookup from a spawned user run back to its delivery row.
316
- * Partial — most rows in the bridge ledger but only successful
317
- * deliveries have a `user_run_id`.
318
- */
319
- idxBridgeDeliveryUserRun: index3("idx_bridge_delivery_user_run").on(t.userRunId).where(sql3`${t.userRunId} IS NOT NULL`)
320
- })
321
- );
322
-
323
- // runtime/subsystems/bridge/bridge.tokens.ts
324
- var BRIDGE_DELIVERY_REPO = "BRIDGE_DELIVERY_REPO";
325
- var EVENT_FLOW = "EVENT_FLOW";
326
- var BRIDGE_MULTI_TENANT = "BRIDGE_MULTI_TENANT";
327
- var BRIDGE_MODULE_OPTIONS = "BRIDGE_MODULE_OPTIONS";
328
- var BRIDGE_REGISTRY = "BRIDGE_REGISTRY";
329
- var BRIDGE_OUTBOX_DRAIN_HOOK = "BRIDGE_OUTBOX_DRAIN_HOOK";
330
-
331
- // runtime/subsystems/bridge/bridge-errors.ts
332
- var MissingTenantIdError = class extends Error {
333
- constructor(callSite) {
334
- super(
335
- `MissingTenantIdError: BridgeModule was configured with multiTenant=true but ${callSite} was called without tenantId (undefined). Pass an explicit tenantId, or pass null for cross-tenant work.`
336
- );
337
- this.callSite = callSite;
338
- }
339
- callSite;
340
- name = "MissingTenantIdError";
341
- };
342
- var BridgeReservedPoolsNotPolledError = class extends Error {
343
- constructor(missingPools) {
344
- super(
345
- `BridgeModule loaded but JobWorkerModule is not polling reserved pool '${missingPools[0]}'. Add ...BRIDGE_RESERVED_POOLS to your JobWorkerModule.forRoot({ pools }) configuration. Missing pools: ${missingPools.join(", ")}. (Bridge-fanout wrappers will sit pending forever without these pollers.)`
346
- );
347
- this.missingPools = missingPools;
348
- }
349
- missingPools;
350
- name = "BridgeReservedPoolsNotPolledError";
351
- };
352
- var UniqueConstraintError = class extends Error {
353
- constructor(constraint, eventId, triggerId) {
354
- super(
355
- `UniqueConstraintError: duplicate insert into bridge_delivery for (event_id='${eventId}', trigger_id='${triggerId}') \u2014 violates constraint '${constraint}'.`
356
- );
357
- this.constraint = constraint;
358
- this.eventId = eventId;
359
- this.triggerId = triggerId;
360
- }
361
- constraint;
362
- eventId;
363
- triggerId;
364
- name = "UniqueConstraintError";
365
- };
366
-
367
- // runtime/subsystems/bridge/assert-tenant-id.ts
368
- function assertTenantId(site, multiTenant, tenantId) {
369
- if (multiTenant && tenantId === void 0) {
370
- throw new MissingTenantIdError(site);
371
- }
372
- }
373
-
374
- // runtime/subsystems/bridge/reserved-pools.ts
375
- var BRIDGE_RESERVED_POOLS = [
376
- "events_inbound",
377
- "events_change",
378
- "events_outbound"
379
- ];
380
-
381
- // runtime/subsystems/bridge/bridge-delivery.memory-backend.ts
382
- import { randomUUID } from "crypto";
383
- var BRIDGE_DELIVERY_UNIQUE_CONSTRAINT = "uq_bridge_delivery_event_trigger";
384
- function key(eventId, triggerId) {
385
- return `${eventId}::${triggerId}`;
386
- }
387
- var MemoryBridgeDeliveryRepo = class {
388
- deliveries = /* @__PURE__ */ new Map();
389
- async insertDelivery(row) {
390
- const k = key(row.eventId, row.triggerId);
391
- if (this.deliveries.has(k)) {
392
- throw new UniqueConstraintError(
393
- BRIDGE_DELIVERY_UNIQUE_CONSTRAINT,
394
- row.eventId,
395
- row.triggerId
396
- );
397
- }
398
- const record = {
399
- id: row.id ?? randomUUID(),
400
- eventId: row.eventId,
401
- triggerId: row.triggerId,
402
- wrapperRunId: row.wrapperRunId ?? null,
403
- userRunId: row.userRunId ?? null,
404
- status: row.status ?? "pending",
405
- skipReason: row.skipReason ?? null,
406
- error: row.error ?? null,
407
- tenantId: row.tenantId ?? null,
408
- attemptedAt: row.attemptedAt instanceof Date ? row.attemptedAt : /* @__PURE__ */ new Date(),
409
- deliveredAt: row.deliveredAt instanceof Date ? row.deliveredAt : null
410
- };
411
- this.deliveries.set(k, record);
412
- }
413
- async findDelivery(eventId, triggerId) {
414
- return this.deliveries.get(key(eventId, triggerId)) ?? null;
415
- }
416
- async findDeliveryById(id) {
417
- for (const record of this.deliveries.values()) {
418
- if (record.id === id) return record;
419
- }
420
- return null;
421
- }
422
- async markDelivered(id, userRunId) {
423
- const record = this.findById(id);
424
- record.status = "delivered";
425
- record.userRunId = userRunId;
426
- record.deliveredAt = /* @__PURE__ */ new Date();
427
- }
428
- async markSkipped(id, reason) {
429
- const record = this.findById(id);
430
- record.status = "skipped";
431
- record.skipReason = reason;
432
- }
433
- async markFailed(id, error) {
434
- const record = this.findById(id);
435
- record.status = "failed";
436
- record.error = error;
437
- }
438
- /**
439
- * Observability read — see `IJobBridge.getStatusHistogram` JSDoc for the
440
- * tenant-filter and windowHours contract.
441
- *
442
- * Unlike `insertDelivery`, this read does NOT call `assertTenantId`:
443
- * `tenantId === undefined` is the supported cross-tenant admin view.
444
- */
445
- async getStatusHistogram(windowHours, tenantId, nowMs = Date.now()) {
446
- if (!Number.isFinite(windowHours) || windowHours <= 0) {
447
- throw new RangeError("windowHours must be positive");
448
- }
449
- const cutoffMs = nowMs - windowHours * 36e5;
450
- const histogram = {
451
- pending: 0,
452
- delivered: 0,
453
- skipped: 0,
454
- failed: 0
455
- };
456
- for (const record of this.deliveries.values()) {
457
- if (record.attemptedAt.getTime() < cutoffMs) continue;
458
- if (tenantId === null && record.tenantId !== null) continue;
459
- if (typeof tenantId === "string" && record.tenantId !== tenantId) {
460
- continue;
461
- }
462
- histogram[record.status] += 1;
463
- }
464
- return histogram;
465
- }
466
- // ─── Test helpers ────────────────────────────────────────────────────────
467
- /** All deliveries for a given event (any status, declaration order). */
468
- getDeliveriesForEvent(eventId) {
469
- return [...this.deliveries.values()].filter((r) => r.eventId === eventId);
470
- }
471
- /** All deliveries currently in the given status. */
472
- getByStatus(status) {
473
- return [...this.deliveries.values()].filter((r) => r.status === status);
474
- }
475
- /** Reset the store. Tests use this in `beforeEach`. */
476
- clear() {
477
- this.deliveries.clear();
478
- }
479
- // ─── Internals ───────────────────────────────────────────────────────────
480
- findById(id) {
481
- for (const record of this.deliveries.values()) {
482
- if (record.id === id) return record;
483
- }
484
- throw new Error(
485
- `MemoryBridgeDeliveryRepo: no delivery with id '${id}' (transition methods may not be called for unknown rows; the framework handler should always findDelivery first or operate on a row it just inserted).`
486
- );
487
- }
488
- };
489
-
490
- // runtime/subsystems/bridge/bridge-delivery-handler.ts
491
- import { Inject, Injectable, Logger, Optional } from "@nestjs/common";
492
-
493
- // runtime/subsystems/token-key.ts
494
- var PKG = "@pattern-stack/codegen";
495
- var tokenKey = (area, name) => `${PKG}.${area}.${name}`;
496
-
497
- // runtime/subsystems/jobs/jobs-domain.tokens.ts
498
- var JOB_ORCHESTRATOR = Symbol.for(tokenKey("jobs", "orchestrator"));
499
- var JOB_RUN_SERVICE = Symbol.for(tokenKey("jobs", "run-service"));
500
- var JOB_STEP_SERVICE = Symbol.for(tokenKey("jobs", "step-service"));
501
- var JOBS_MULTI_TENANT = Symbol.for(tokenKey("jobs", "multi-tenant"));
502
-
503
- // runtime/subsystems/jobs/job-handler.base.ts
504
- var JobHandlerBase = class {
505
- };
506
- var JOB_HANDLER_REGISTRY = /* @__PURE__ */ new Map();
507
- var JOB_HANDLER_METADATA_KEY = Symbol.for(tokenKey("jobs", "handler-metadata"));
508
- function JobHandler(type, meta) {
509
- return (target) => {
510
- if (JOB_HANDLER_REGISTRY.has(type)) {
511
- const env = process.env.NODE_ENV;
512
- if (env === "production") {
513
- throw new Error(
514
- `[JobHandler] Duplicate registration for job type '${type}'. Each @JobHandler must declare a unique type.`
515
- );
516
- }
517
- if (env !== "test") {
518
- console.warn(
519
- `[JobHandler] Duplicate registration for job type '${type}'. Overwriting previous handler \u2014 this is almost certainly a bug.`
520
- );
521
- }
522
- }
523
- Reflect.defineMetadata(JOB_HANDLER_METADATA_KEY, { type, meta }, target);
524
- JOB_HANDLER_REGISTRY.set(type, {
525
- type,
526
- meta,
527
- handlerClass: target
528
- });
529
- };
530
- }
531
- var HandlerRegistry;
532
- ((HandlerRegistry2) => {
533
- function getAll() {
534
- return Array.from(JOB_HANDLER_REGISTRY.values());
535
- }
536
- HandlerRegistry2.getAll = getAll;
537
- function get(type) {
538
- return JOB_HANDLER_REGISTRY.get(type);
539
- }
540
- HandlerRegistry2.get = get;
541
- })(HandlerRegistry || (HandlerRegistry = {}));
542
-
543
- // runtime/subsystems/events/events.tokens.ts
544
- var EVENT_BUS = "EVENT_BUS";
545
- var REDIS_URL = Symbol.for(tokenKey("events", "redis-url"));
546
-
547
- // runtime/subsystems/bridge/bridge-delivery-handler.ts
548
- var BRIDGE_DELIVERY_JOB_TYPE = "@framework/bridge_delivery";
549
- var SPAWN_USER_RUN_STEP = "spawn_user_run";
550
- var BridgeDeliveryHandler = class extends JobHandlerBase {
551
- constructor(repo, orchestrator, events, registry, multiTenant = false) {
552
- super();
553
- this.repo = repo;
554
- this.orchestrator = orchestrator;
555
- this.events = events;
556
- this.registry = registry;
557
- this.multiTenant = multiTenant;
558
- }
559
- repo;
560
- orchestrator;
561
- events;
562
- registry;
563
- multiTenant;
564
- classLogger = new Logger(BridgeDeliveryHandler.name);
565
- async run(ctx) {
566
- const { deliveryId } = ctx.input;
567
- const delivery = await this.repo.findDeliveryById(deliveryId);
568
- if (!delivery) {
569
- this.classLogger.warn(
570
- `bridge_delivery row '${deliveryId}' not found; wrapper completes without spawning a user job.`
571
- );
572
- return { skipped: true, reason: "delivery_row_missing" };
573
- }
574
- assertTenantId(
575
- "BridgeDeliveryHandler.run",
576
- this.multiTenant,
577
- delivery.tenantId
578
- );
579
- const event = await this.events.findById(delivery.eventId);
580
- if (!event) {
581
- this.classLogger.warn(
582
- `domain_events row '${delivery.eventId}' missing for delivery '${deliveryId}'; marking skipped.`
583
- );
584
- await this.repo.markSkipped(delivery.id, "event_row_missing");
585
- return { skipped: true, reason: "event_row_missing" };
586
- }
587
- const entry = this.findRegistryEntry(event.type, delivery.triggerId);
588
- if (!entry) {
589
- await this.repo.markSkipped(delivery.id, "trigger_unregistered");
590
- return { skipped: true, reason: "trigger_unregistered" };
591
- }
592
- if (entry.when && !entry.when(event)) {
593
- await this.repo.markSkipped(delivery.id, "predicate_false");
594
- return { skipped: true, reason: "predicate_false" };
595
- }
596
- const input = entry.map(event);
597
- const { runId } = await ctx.step(
598
- SPAWN_USER_RUN_STEP,
599
- async () => {
600
- const run = await this.orchestrator.start(entry.jobType, input, {
601
- parentRunId: ctx.run.id,
602
- triggerSource: "event",
603
- triggerRef: delivery.eventId,
604
- tenantId: delivery.tenantId
605
- });
606
- return { runId: run.id };
607
- }
608
- );
609
- await this.repo.markDelivered(delivery.id, runId);
610
- return { runId };
611
- }
612
- /**
613
- * Locate the registry entry for `(eventType, triggerId)`. Linear scan
614
- * over the per-event-type array — N is the number of triggers declared
615
- * for one event, typically 1–5; the table is not big enough to warrant
616
- * a secondary index.
617
- */
618
- findRegistryEntry(eventType, triggerId) {
619
- const candidates = this.registry[eventType] ?? void 0;
620
- if (!candidates) return void 0;
621
- return candidates.find((c) => c.triggerId === triggerId);
622
- }
623
- };
624
- BridgeDeliveryHandler = __decorateClass([
625
- Injectable(),
626
- JobHandler(BRIDGE_DELIVERY_JOB_TYPE, {
627
- pool: "events_change",
628
- retry: { attempts: 3, backoff: "exponential", baseMs: 250 },
629
- replayFrom: "last_step"
630
- }),
631
- __decorateParam(0, Inject(BRIDGE_DELIVERY_REPO)),
632
- __decorateParam(1, Inject(JOB_ORCHESTRATOR)),
633
- __decorateParam(2, Inject(EVENT_BUS)),
634
- __decorateParam(3, Inject(BRIDGE_REGISTRY)),
635
- __decorateParam(4, Optional()),
636
- __decorateParam(4, Inject(BRIDGE_MULTI_TENANT))
637
- ], BridgeDeliveryHandler);
638
-
639
- // runtime/subsystems/bridge/bridge-delivery.drizzle-backend.ts
640
- import { Inject as Inject2, Injectable as Injectable2, Optional as Optional2 } from "@nestjs/common";
641
- import { eq, and, gte, isNull, sql as sql4 } from "drizzle-orm";
642
-
643
- // runtime/constants/tokens.ts
644
- var DRIZZLE = "DRIZZLE";
645
-
646
- // runtime/subsystems/bridge/bridge-delivery.drizzle-backend.ts
647
- var DrizzleBridgeDeliveryRepo = class {
648
- constructor(db, multiTenant = false) {
649
- this.db = db;
650
- this.multiTenant = multiTenant;
651
- }
652
- db;
653
- multiTenant;
654
- async insertDelivery(row, tx) {
655
- assertTenantId(
656
- "DrizzleBridgeDeliveryRepo.insertDelivery",
657
- this.multiTenant,
658
- row.tenantId
659
- );
660
- const client = tx ?? this.db;
661
- await client.insert(bridgeDelivery).values(row).onConflictDoNothing({
662
- target: [bridgeDelivery.eventId, bridgeDelivery.triggerId]
663
- });
664
- }
665
- async findDelivery(eventId, triggerId) {
666
- const rows = await this.db.select().from(bridgeDelivery).where(
667
- and(
668
- eq(bridgeDelivery.eventId, eventId),
669
- eq(bridgeDelivery.triggerId, triggerId)
670
- )
671
- ).limit(1);
672
- return rows[0] ?? null;
673
- }
674
- async findDeliveryById(id) {
675
- const rows = await this.db.select().from(bridgeDelivery).where(eq(bridgeDelivery.id, id)).limit(1);
676
- return rows[0] ?? null;
677
- }
678
- async markDelivered(id, userRunId, tx) {
679
- const client = tx ?? this.db;
680
- await client.update(bridgeDelivery).set({
681
- status: "delivered",
682
- userRunId,
683
- deliveredAt: /* @__PURE__ */ new Date()
684
- }).where(eq(bridgeDelivery.id, id));
685
- }
686
- async markSkipped(id, reason, tx) {
687
- const client = tx ?? this.db;
688
- await client.update(bridgeDelivery).set({ status: "skipped", skipReason: reason }).where(eq(bridgeDelivery.id, id));
689
- }
690
- async markFailed(id, error, tx) {
691
- const client = tx ?? this.db;
692
- await client.update(bridgeDelivery).set({ status: "failed", error }).where(eq(bridgeDelivery.id, id));
693
- }
694
- /**
695
- * Observability read — see `IJobBridge.getStatusHistogram` JSDoc for the
696
- * tenant-filter and windowHours contract.
697
- *
698
- * Tenant-filter note: this method intentionally does NOT call
699
- * `assertTenantId`. The write methods on this repo (`insertDelivery`)
700
- * treat `tenantId === undefined` as a misconfiguration and fail fast.
701
- * Reads are different — `undefined` is the supported "cross-tenant
702
- * admin view" mode that OBS-5 uses to render a framework-wide health
703
- * panel. Callers that need strict tenant scoping pass an explicit
704
- * string or `null`.
705
- *
706
- * Cast note: `count(*)::int` is applied in SQL so the node-pg driver
707
- * returns a `number` instead of the default `bigint → string` for
708
- * `count(*)`. Don't relax this cast — consumers (and the protocol)
709
- * type the result as `number`.
710
- */
711
- async getStatusHistogram(windowHours, tenantId) {
712
- if (!Number.isFinite(windowHours) || windowHours <= 0) {
713
- throw new RangeError("windowHours must be positive");
714
- }
715
- const cutoff = sql4`now() - make_interval(hours => ${windowHours})`;
716
- const conditions = [gte(bridgeDelivery.attemptedAt, cutoff)];
717
- if (tenantId === null) {
718
- conditions.push(isNull(bridgeDelivery.tenantId));
719
- } else if (typeof tenantId === "string") {
720
- conditions.push(eq(bridgeDelivery.tenantId, tenantId));
721
- }
722
- const rows = await this.db.select({
723
- status: bridgeDelivery.status,
724
- count: sql4`count(*)::int`
725
- }).from(bridgeDelivery).where(and(...conditions)).groupBy(bridgeDelivery.status);
726
- const histogram = {
727
- pending: 0,
728
- delivered: 0,
729
- skipped: 0,
730
- failed: 0
731
- };
732
- for (const row of rows) {
733
- histogram[row.status] = Number(row.count);
734
- }
735
- return histogram;
736
- }
737
- };
738
- DrizzleBridgeDeliveryRepo = __decorateClass([
739
- Injectable2(),
740
- __decorateParam(0, Inject2(DRIZZLE)),
741
- __decorateParam(1, Optional2()),
742
- __decorateParam(1, Inject2(BRIDGE_MULTI_TENANT))
743
- ], DrizzleBridgeDeliveryRepo);
744
-
745
- // runtime/subsystems/bridge/bridge-outbox-drain-hook.ts
746
- import { Inject as Inject3, Injectable as Injectable3, Logger as Logger2, Optional as Optional3 } from "@nestjs/common";
747
- import { randomUUID as randomUUID2 } from "crypto";
748
- var POOL_BY_DIRECTION = {
749
- inbound: "events_inbound",
750
- change: "events_change",
751
- outbound: "events_outbound"
752
- };
753
- var BridgeOutboxDrainHook = class {
754
- constructor(registry = {}) {
755
- this.registry = registry;
756
- }
757
- registry;
758
- logger = new Logger2(BridgeOutboxDrainHook.name);
759
- warnedNullDirection = false;
760
- warnedAuditTypes = /* @__PURE__ */ new Set();
761
- async processEvent(event, tx) {
762
- if (event.metadata?.["tier"] === "audit") {
763
- this.warnAuditBlockedOnce(event);
764
- return {
765
- delivered: 0,
766
- dedupSkips: 0,
767
- triggerCount: 0,
768
- auditBlocked: 1
769
- };
770
- }
771
- const triggers = this.lookupTriggers(event.type);
772
- if (triggers.length === 0) {
773
- return {
774
- delivered: 0,
775
- dedupSkips: 0,
776
- triggerCount: 0,
777
- auditBlocked: 0
778
- };
779
- }
780
- const direction = event.metadata?.["direction"] ?? null;
781
- const tenantId = event.metadata?.["tenantId"] ?? null;
782
- const wrapperPool = direction ? POOL_BY_DIRECTION[direction] : void 0;
783
- if (!wrapperPool) {
784
- if (!this.warnedNullDirection) {
785
- this.warnedNullDirection = true;
786
- this.logger.warn(
787
- `Skipping bridge fanout for events with null/unknown direction. event.id=${event.id} event.type=${event.type} direction=${String(direction)}. The wrapper pool is derived from direction (events_<direction>); publishers must use TypedEventBus.publish() so direction is stamped on the outbox row.`
788
- );
789
- }
790
- return {
791
- delivered: 0,
792
- dedupSkips: 0,
793
- triggerCount: triggers.length,
794
- auditBlocked: 0
795
- };
796
- }
797
- let delivered = 0;
798
- let dedupSkips = 0;
799
- const client = tx;
800
- for (const trigger of triggers) {
801
- const deliveryId = randomUUID2();
802
- const wrapperRunId = randomUUID2();
803
- const inserted = await tx.insert(bridgeDelivery).values({
804
- id: deliveryId,
805
- eventId: event.id,
806
- triggerId: trigger.triggerId,
807
- wrapperRunId,
808
- status: "pending",
809
- tenantId
810
- }).onConflictDoNothing({
811
- target: [bridgeDelivery.eventId, bridgeDelivery.triggerId]
812
- }).returning({ id: bridgeDelivery.id });
813
- if (inserted.length === 0) {
814
- dedupSkips++;
815
- continue;
816
- }
817
- await tx.insert(jobRuns).values({
818
- id: wrapperRunId,
819
- jobType: BRIDGE_DELIVERY_JOB_TYPE,
820
- jobVersion: 1,
821
- rootRunId: wrapperRunId,
822
- pool: wrapperPool,
823
- status: "pending",
824
- input: { deliveryId },
825
- triggerSource: "event",
826
- triggerRef: event.id,
827
- tenantId
828
- });
829
- delivered++;
830
- }
831
- return {
832
- delivered,
833
- dedupSkips,
834
- triggerCount: triggers.length,
835
- auditBlocked: 0
836
- };
837
- }
838
- warnAuditBlockedOnce(event) {
839
- if (this.warnedAuditTypes.has(event.type)) return;
840
- this.warnedAuditTypes.add(event.type);
841
- this.logger.warn(
842
- `Bridge guard blocked audit-tier event '${event.type}' (event.id=${event.id}). Registry says this event is not bridge-eligible; a bridge_trigger row exists out-of-band. Investigate registry/runtime drift.`
843
- );
844
- }
845
- lookupTriggers(eventType) {
846
- const candidates = this.registry[eventType];
847
- return candidates ?? [];
848
- }
849
- };
850
- BridgeOutboxDrainHook = __decorateClass([
851
- Injectable3(),
852
- __decorateParam(0, Optional3()),
853
- __decorateParam(0, Inject3(BRIDGE_REGISTRY))
854
- ], BridgeOutboxDrainHook);
855
-
856
- // runtime/subsystems/bridge/event-flow.service.ts
857
- import { Inject as Inject4, Injectable as Injectable4, Optional as Optional4 } from "@nestjs/common";
858
- var EventFlowService = class {
859
- constructor(db, eventBus, orchestrator, bridgeRepo, registry = {}, multiTenant = false) {
860
- this.db = db;
861
- this.eventBus = eventBus;
862
- this.orchestrator = orchestrator;
863
- this.bridgeRepo = bridgeRepo;
864
- this.registry = registry;
865
- this.multiTenant = multiTenant;
866
- }
867
- db;
868
- eventBus;
869
- orchestrator;
870
- bridgeRepo;
871
- registry;
872
- multiTenant;
873
- async publish(event, tx) {
874
- await this.eventBus.publish(event, tx);
875
- }
876
- async publishAndStart(event, jobType, input, opts = {}) {
877
- assertTenantId(
878
- "EventFlowService.publishAndStart",
879
- this.multiTenant,
880
- opts.tenantId
881
- );
882
- const tenantId = opts.tenantId ?? null;
883
- const matchingTriggers = this.matchingTriggers(event.type, jobType);
884
- return this.db.transaction(async (tx) => {
885
- await this.eventBus.publish(event, tx);
886
- const run = await this.orchestrator.start(
887
- jobType,
888
- input,
889
- {
890
- parentRunId: opts.parentRunId,
891
- tenantId,
892
- triggerSource: "event",
893
- triggerRef: event.id
894
- },
895
- tx
896
- );
897
- const now = /* @__PURE__ */ new Date();
898
- for (const trigger of matchingTriggers) {
899
- await this.bridgeRepo.insertDelivery(
900
- {
901
- eventId: event.id,
902
- triggerId: trigger.triggerId,
903
- wrapperRunId: null,
904
- // facade never writes a wrapper
905
- userRunId: run.id,
906
- status: "delivered",
907
- tenantId,
908
- attemptedAt: now,
909
- deliveredAt: now
910
- },
911
- tx
912
- );
913
- }
914
- return { runId: run.id };
915
- });
916
- }
917
- /**
918
- * Linear scan of the per-event-type trigger list for entries whose
919
- * `jobType` matches. Typical N is 1–5; the table is not big enough to
920
- * warrant a secondary index. Returns an empty array for Case A.
921
- */
922
- matchingTriggers(eventType, jobType) {
923
- const triggers = this.registry[eventType] ?? [];
924
- return triggers.filter((t) => t.jobType === jobType);
925
- }
926
- };
927
- EventFlowService = __decorateClass([
928
- Injectable4(),
929
- __decorateParam(0, Inject4(DRIZZLE)),
930
- __decorateParam(1, Inject4(EVENT_BUS)),
931
- __decorateParam(2, Inject4(JOB_ORCHESTRATOR)),
932
- __decorateParam(3, Inject4(BRIDGE_DELIVERY_REPO)),
933
- __decorateParam(4, Optional4()),
934
- __decorateParam(4, Inject4(BRIDGE_REGISTRY)),
935
- __decorateParam(5, Optional4()),
936
- __decorateParam(5, Inject4(BRIDGE_MULTI_TENANT))
937
- ], EventFlowService);
938
-
939
- // runtime/subsystems/bridge/bridge.module.ts
9
+ EventFlowService
10
+ } from "../../../chunk-32BMMV4H.js";
940
11
  import {
941
- Inject as Inject13,
942
- Module as Module3,
943
- Optional as Optional7
944
- } from "@nestjs/common";
945
-
946
- // runtime/subsystems/jobs/job-worker.module.ts
12
+ BRIDGE_RESERVED_POOLS
13
+ } from "../../../chunk-EDKJU5BO.js";
947
14
  import {
948
- Inject as Inject12,
949
- Injectable as Injectable12,
950
- Logger as Logger6,
951
- Module as Module2,
952
- Optional as Optional6
953
- } from "@nestjs/common";
954
- import { ModuleRef as ModuleRef2 } from "@nestjs/core";
955
-
956
- // runtime/subsystems/jobs/jobs-domain.module.ts
957
- import { Module } from "@nestjs/common";
958
-
959
- // runtime/subsystems/jobs/job-orchestrator.drizzle-backend.ts
960
- import { randomUUID as randomUUID3 } from "crypto";
961
- import { Inject as Inject5, Injectable as Injectable5, Logger as Logger3 } from "@nestjs/common";
962
- import { and as and2, desc, eq as eq2, gt, inArray, isNotNull, ne, notInArray, sql as sql5 } from "drizzle-orm";
963
-
964
- // runtime/subsystems/jobs/jobs-errors.ts
965
- var JobTypeNotFoundError = class extends Error {
966
- constructor(jobType) {
967
- super(`No job definition registered for type '${jobType}'.`);
968
- this.jobType = jobType;
969
- }
970
- jobType;
971
- name = "JobTypeNotFoundError";
972
- };
973
- var JobCollisionError = class extends Error {
974
- constructor(jobType, concurrencyKey, incumbent) {
975
- super(
976
- `Job type '${jobType}' has an in-flight run with concurrency_key '${concurrencyKey}' (incumbent ${incumbent.id}); collision_mode=reject.`
977
- );
978
- this.jobType = jobType;
979
- this.concurrencyKey = concurrencyKey;
980
- this.incumbent = incumbent;
981
- }
982
- jobType;
983
- concurrencyKey;
984
- incumbent;
985
- name = "JobCollisionError";
986
- };
987
- var JobNotReplayableError = class extends Error {
988
- constructor(runId, currentStatus) {
989
- super(
990
- `Run ${runId} is not replayable from status '${currentStatus}'. Only 'completed', 'failed', 'timed_out', and 'canceled' are eligible.`
991
- );
992
- this.runId = runId;
993
- this.currentStatus = currentStatus;
994
- }
995
- runId;
996
- currentStatus;
997
- name = "JobNotReplayableError";
998
- };
999
- var JobTemplateFieldMissingError = class extends Error {
1000
- constructor(template, field) {
1001
- super(
1002
- `Template '${template}' references input field '${field}' which is missing or undefined on the payload.`
1003
- );
1004
- this.template = template;
1005
- this.field = field;
1006
- }
1007
- template;
1008
- field;
1009
- name = "JobTemplateFieldMissingError";
1010
- };
1011
- var MissingTenantIdError2 = class extends Error {
1012
- constructor(method) {
1013
- super(
1014
- `MissingTenantIdError: JobsDomainModule was configured with multiTenant=true but ${method} was called without tenantId (undefined). Pass an explicit tenantId, or pass null for cross-tenant work.`
1015
- );
1016
- this.method = method;
1017
- }
1018
- method;
1019
- name = "MissingTenantIdError";
1020
- };
1021
- var BootValidationError = class extends Error {
1022
- constructor(missingHandlers) {
1023
- super(
1024
- `BootValidationError: ${missingHandlers.length} orphaned job type(s) in 'job' table with no matching @JobHandler in the running process: [${missingHandlers.join(", ")}]. Either register the handler(s) or remove the rows.`
1025
- );
1026
- this.missingHandlers = missingHandlers;
1027
- }
1028
- missingHandlers;
1029
- name = "BootValidationError";
1030
- };
1031
- var ReservedPoolViolationError = class extends Error {
1032
- constructor(offenders) {
1033
- super(
1034
- `ReservedPoolViolationError: ${offenders.length} @JobHandler(s) target reserved pools \u2014 reserved pools are framework-only:
1035
- ` + offenders.map((o) => ` - ${o.handlerClass} \u2192 pool='${o.pool}'`).join("\n")
1036
- );
1037
- this.offenders = offenders;
1038
- }
1039
- offenders;
1040
- name = "ReservedPoolViolationError";
1041
- };
1042
-
1043
- // runtime/subsystems/jobs/job-orchestrator.drizzle-backend.ts
1044
- var TERMINAL_STATUSES = [
1045
- "completed",
1046
- "failed",
1047
- "timed_out",
1048
- "canceled"
1049
- ];
1050
- var DEDUPE_EXCLUDED_STATUSES = ["canceled", "failed"];
1051
- var IN_FLIGHT_STATUSES = ["pending", "running"];
1052
- function evaluateKeyTemplate(template, input) {
1053
- return template.replace(/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g, (_match, field) => {
1054
- const value = input[field];
1055
- if (value === void 0 || value === null) {
1056
- throw new JobTemplateFieldMissingError(template, field);
1057
- }
1058
- return String(value);
1059
- });
1060
- }
1061
- var DrizzleJobOrchestrator = class {
1062
- constructor(db, multiTenant) {
1063
- this.db = db;
1064
- this.multiTenant = multiTenant;
1065
- }
1066
- db;
1067
- multiTenant;
1068
- // TODO(logging-subsystem): swap to ILogger once ADR-028 lands
1069
- logger = new Logger3(DrizzleJobOrchestrator.name);
1070
- /**
1071
- * JOB-8 — resolve `tenantId` for a mutating / targeted-read call.
1072
- * Returns the tenant value that should be written to the row (or compared
1073
- * against in a WHERE clause). When `multiTenant` is off, the column is
1074
- * forced to `null` regardless of what callers pass. When on, `undefined`
1075
- * throws; `null` and strings pass through untouched.
1076
- */
1077
- resolveTenantId(method, tenantId) {
1078
- if (!this.multiTenant) return null;
1079
- if (tenantId === void 0) throw new MissingTenantIdError2(method);
1080
- return tenantId;
1081
- }
1082
- // ==========================================================================
1083
- // start
1084
- // ==========================================================================
1085
- async start(type, input, opts = {}, tx) {
1086
- const payload = input ?? {};
1087
- const tenantId = this.resolveTenantId("start", opts.tenantId);
1088
- const client = tx ?? this.db;
1089
- const [def] = await client.select().from(jobs).where(eq2(jobs.type, type)).limit(1);
1090
- if (!def) throw new JobTypeNotFoundError(type);
1091
- const definition = def;
1092
- if (definition.dedupeKeyTemplate && definition.dedupeWindowMs) {
1093
- const dedupeKey2 = evaluateKeyTemplate(definition.dedupeKeyTemplate, payload);
1094
- const windowStart = new Date(Date.now() - definition.dedupeWindowMs);
1095
- const existing = await client.select().from(jobRuns).where(
1096
- and2(
1097
- eq2(jobRuns.jobType, type),
1098
- eq2(jobRuns.dedupeKey, dedupeKey2),
1099
- gt(jobRuns.createdAt, windowStart),
1100
- // status NOT IN ('canceled', 'failed')
1101
- notInStatus(DEDUPE_EXCLUDED_STATUSES)
1102
- )
1103
- ).orderBy(desc(jobRuns.createdAt)).limit(1);
1104
- if (existing.length > 0) {
1105
- return existing[0];
1106
- }
1107
- }
1108
- let concurrencyKey = null;
1109
- if (definition.concurrencyKeyTemplate) {
1110
- concurrencyKey = evaluateKeyTemplate(
1111
- definition.concurrencyKeyTemplate,
1112
- payload
1113
- );
1114
- const inFlight = await client.select().from(jobRuns).where(
1115
- and2(
1116
- eq2(jobRuns.concurrencyKey, concurrencyKey),
1117
- inArray(jobRuns.status, IN_FLIGHT_STATUSES)
1118
- )
1119
- ).limit(1);
1120
- if (inFlight.length > 0) {
1121
- const incumbent = inFlight[0];
1122
- switch (definition.collisionMode) {
1123
- case "reject":
1124
- throw new JobCollisionError(type, concurrencyKey, incumbent);
1125
- case "replace":
1126
- await this.cancel(incumbent.id, {
1127
- cascade: true,
1128
- reason: "replaced",
1129
- tenantId: incumbent.tenantId
1130
- });
1131
- break;
1132
- case "queue":
1133
- break;
1134
- }
1135
- }
1136
- }
1137
- const newId = randomUUID3();
1138
- let rootRunId = newId;
1139
- if (opts.parentRunId) {
1140
- const [parent] = await client.select({ rootRunId: jobRuns.rootRunId }).from(jobRuns).where(eq2(jobRuns.id, opts.parentRunId)).limit(1);
1141
- if (!parent) {
1142
- throw new Error(
1143
- `parentRunId ${opts.parentRunId} does not reference an existing job_run`
1144
- );
1145
- }
1146
- rootRunId = parent.rootRunId;
1147
- }
1148
- const dedupeKey = definition.dedupeKeyTemplate ? evaluateKeyTemplate(definition.dedupeKeyTemplate, payload) : null;
1149
- const [inserted] = await client.insert(jobRuns).values({
1150
- id: newId,
1151
- jobType: type,
1152
- jobVersion: definition.version,
1153
- parentRunId: opts.parentRunId ?? null,
1154
- rootRunId,
1155
- parentClosePolicy: opts.parentClosePolicy ?? "terminate",
1156
- scopeEntityType: opts.scope?.entityType ?? null,
1157
- scopeEntityId: opts.scope?.entityId ?? null,
1158
- tenantId,
1159
- tags: opts.tags ?? {},
1160
- pool: opts.pool ?? definition.pool,
1161
- priority: opts.priority ?? definition.priorityDefault,
1162
- concurrencyKey,
1163
- dedupeKey,
1164
- status: "pending",
1165
- input: payload,
1166
- output: null,
1167
- error: null,
1168
- triggerSource: opts.triggerSource ?? "manual",
1169
- triggerRef: opts.triggerRef ?? null,
1170
- runAt: opts.runAt ?? /* @__PURE__ */ new Date(),
1171
- startedAt: null,
1172
- finishedAt: null,
1173
- claimedAt: null,
1174
- attempts: 0
1175
- }).returning();
1176
- return inserted;
1177
- }
1178
- // ==========================================================================
1179
- // cancel
1180
- // ==========================================================================
1181
- async cancel(runId, opts = {}) {
1182
- const tenantId = this.resolveTenantId("cancel", opts.tenantId);
1183
- const [target] = await this.db.select().from(jobRuns).where(eq2(jobRuns.id, runId)).limit(1);
1184
- if (!target) return;
1185
- if (this.multiTenant && target.tenantId !== tenantId) return;
1186
- if (TERMINAL_STATUSES.includes(target.status)) {
1187
- return;
1188
- }
1189
- const [cancelled] = await this.db.update(jobRuns).set({
1190
- status: "canceled",
1191
- finishedAt: /* @__PURE__ */ new Date(),
1192
- updatedAt: /* @__PURE__ */ new Date()
1193
- }).where(
1194
- and2(eq2(jobRuns.id, runId), notInStatus([...TERMINAL_STATUSES]))
1195
- ).returning();
1196
- if (!cancelled) return;
1197
- if (opts.cascade === false) return;
1198
- const descendants = await this.db.select().from(jobRuns).where(
1199
- and2(
1200
- eq2(jobRuns.rootRunId, target.rootRunId),
1201
- ne(jobRuns.id, runId),
1202
- notInStatus([...TERMINAL_STATUSES])
1203
- )
1204
- );
1205
- for (const child of descendants) {
1206
- const policy = child.parentClosePolicy;
1207
- if (policy === "abandon") continue;
1208
- await this.db.update(jobRuns).set({
1209
- status: "canceled",
1210
- finishedAt: /* @__PURE__ */ new Date(),
1211
- updatedAt: /* @__PURE__ */ new Date()
1212
- }).where(
1213
- and2(
1214
- eq2(jobRuns.id, child.id),
1215
- notInStatus([...TERMINAL_STATUSES])
1216
- )
1217
- );
1218
- }
1219
- void opts.reason;
1220
- }
1221
- // ==========================================================================
1222
- // replay
1223
- // ==========================================================================
1224
- async replay(runId) {
1225
- const [target] = await this.db.select().from(jobRuns).where(eq2(jobRuns.id, runId)).limit(1);
1226
- if (!target) {
1227
- throw new Error(`replay: run ${runId} not found`);
1228
- }
1229
- const run = target;
1230
- if (!TERMINAL_STATUSES.includes(run.status)) {
1231
- throw new JobNotReplayableError(runId, run.status);
1232
- }
1233
- const [def] = await this.db.select().from(jobs).where(eq2(jobs.type, run.jobType)).limit(1);
1234
- if (!def) throw new JobTypeNotFoundError(run.jobType);
1235
- const mode = def.replayFrom;
1236
- const result = await this.db.transaction(async (tx) => {
1237
- if (mode === "scratch") {
1238
- await tx.delete(jobSteps).where(eq2(jobSteps.jobRunId, runId));
1239
- } else if (mode === "last_step") {
1240
- await tx.delete(jobSteps).where(
1241
- and2(eq2(jobSteps.jobRunId, runId), ne(jobSteps.status, "completed"))
1242
- );
1243
- } else {
1244
- await tx.delete(jobSteps).where(
1245
- and2(eq2(jobSteps.jobRunId, runId), ne(jobSteps.status, "completed"))
1246
- );
1247
- }
1248
- const [updated] = await tx.update(jobRuns).set({
1249
- status: "pending",
1250
- attempts: 0,
1251
- runAt: /* @__PURE__ */ new Date(),
1252
- startedAt: null,
1253
- finishedAt: null,
1254
- claimedAt: null,
1255
- error: null,
1256
- output: null,
1257
- updatedAt: /* @__PURE__ */ new Date()
1258
- }).where(eq2(jobRuns.id, runId)).returning();
1259
- return updated;
1260
- });
1261
- return result;
1262
- }
1263
- // ==========================================================================
1264
- // upsertJobRows — boot-time materialisation of `job` definitions
1265
- // ==========================================================================
1266
- /**
1267
- * Hash-gated `INSERT … ON CONFLICT (type) DO UPDATE … WHERE` per Q3
1268
- * resolution (2026-04-19): the `UPDATE` branch executes only when one
1269
- * of the persisted metadata fields differs from the incoming payload;
1270
- * `version` bumps only on real change; concurrent boots with identical
1271
- * content are idempotent no-ops.
1272
- *
1273
- * Why this shape (not `DO NOTHING`, not advisory locks):
1274
- * - `DO NOTHING` would let an old-version instance leave a stale row
1275
- * that a new-version instance can't overwrite during a rolling deploy.
1276
- * - Advisory locks add latency and leak risk under crashes.
1277
- * - The `WHERE … IS DISTINCT FROM …` clause makes the conditional
1278
- * atomic — no read-modify-write race on `version` between concurrent
1279
- * boots.
1280
- *
1281
- * Orphan detection: a single `SELECT type FROM job WHERE type NOT IN (...)`
1282
- * returns the types present in DB but absent from `entries`. Caller (boot
1283
- * validator) decides whether to throw `BootValidationError`.
1284
- */
1285
- async upsertJobRows(entries, poolConfig) {
1286
- void poolConfig;
1287
- for (const entry of entries) {
1288
- const meta = entry.meta;
1289
- const pool = meta.pool ?? "batch";
1290
- const retryPolicy = meta.retry ?? {
1291
- attempts: 1,
1292
- backoff: "fixed",
1293
- baseMs: 0
1294
- };
1295
- const concurrencyKeyTemplate = meta.concurrency?.key;
1296
- const concurrencyKeyTemplateStr = typeof concurrencyKeyTemplate === "string" ? concurrencyKeyTemplate : null;
1297
- const collisionMode = meta.concurrency?.collisionMode ?? "queue";
1298
- const dedupeKeyTemplate = meta.dedupe?.key;
1299
- const dedupeKeyTemplateStr = typeof dedupeKeyTemplate === "string" ? dedupeKeyTemplate : null;
1300
- const dedupeWindowMs = meta.dedupe?.windowMs ?? null;
1301
- const timeoutMs = meta.timeoutMs ?? null;
1302
- const replayFrom = meta.replayFrom ?? "last_checkpoint";
1303
- const scopeEntityType = meta.scope?.entity ?? null;
1304
- const priorityDefault = 0;
1305
- await this.db.insert(jobs).values({
1306
- type: entry.type,
1307
- version: 1,
1308
- pool,
1309
- scopeEntityType,
1310
- retryPolicy,
1311
- timeoutMs,
1312
- concurrencyKeyTemplate: concurrencyKeyTemplateStr,
1313
- collisionMode,
1314
- dedupeKeyTemplate: dedupeKeyTemplateStr,
1315
- dedupeWindowMs,
1316
- priorityDefault,
1317
- replayFrom
1318
- }).onConflictDoUpdate({
1319
- target: jobs.type,
1320
- set: {
1321
- pool: sql5`EXCLUDED.pool`,
1322
- scopeEntityType: sql5`EXCLUDED.scope_entity_type`,
1323
- retryPolicy: sql5`EXCLUDED.retry_policy`,
1324
- timeoutMs: sql5`EXCLUDED.timeout_ms`,
1325
- concurrencyKeyTemplate: sql5`EXCLUDED.concurrency_key_template`,
1326
- collisionMode: sql5`EXCLUDED.collision_mode`,
1327
- dedupeKeyTemplate: sql5`EXCLUDED.dedupe_key_template`,
1328
- dedupeWindowMs: sql5`EXCLUDED.dedupe_window_ms`,
1329
- priorityDefault: sql5`EXCLUDED.priority_default`,
1330
- replayFrom: sql5`EXCLUDED.replay_from`,
1331
- version: sql5`${jobs.version} + 1`,
1332
- updatedAt: sql5`now()`
1333
- },
1334
- // The hash gate: every field listed in the Q3 resolution appears
1335
- // here. `IS DISTINCT FROM` is the null-safe inequality operator;
1336
- // jsonb cast to text gives stable comparison without invoking a
1337
- // dedicated hash column (avoids a JOB-1 schema migration).
1338
- setWhere: sql5`
1339
- ${jobs.pool} IS DISTINCT FROM EXCLUDED.pool OR
1340
- ${jobs.retryPolicy}::text IS DISTINCT FROM EXCLUDED.retry_policy::text OR
1341
- ${jobs.timeoutMs} IS DISTINCT FROM EXCLUDED.timeout_ms OR
1342
- ${jobs.concurrencyKeyTemplate} IS DISTINCT FROM EXCLUDED.concurrency_key_template OR
1343
- ${jobs.collisionMode} IS DISTINCT FROM EXCLUDED.collision_mode OR
1344
- ${jobs.dedupeKeyTemplate} IS DISTINCT FROM EXCLUDED.dedupe_key_template OR
1345
- ${jobs.dedupeWindowMs} IS DISTINCT FROM EXCLUDED.dedupe_window_ms OR
1346
- ${jobs.priorityDefault} IS DISTINCT FROM EXCLUDED.priority_default OR
1347
- ${jobs.replayFrom} IS DISTINCT FROM EXCLUDED.replay_from OR
1348
- ${jobs.scopeEntityType} IS DISTINCT FROM EXCLUDED.scope_entity_type
1349
- `
1350
- });
1351
- }
1352
- const types = entries.map((e) => e.type);
1353
- const orphans = types.length === 0 ? await this.db.select({ type: jobs.type }).from(jobs) : await this.db.select({ type: jobs.type }).from(jobs).where(notInArray(jobs.type, types));
1354
- return { orphaned: orphans.map((o) => o.type) };
1355
- }
1356
- };
1357
- DrizzleJobOrchestrator = __decorateClass([
1358
- Injectable5(),
1359
- __decorateParam(0, Inject5(DRIZZLE)),
1360
- __decorateParam(1, Inject5(JOBS_MULTI_TENANT))
1361
- ], DrizzleJobOrchestrator);
1362
- function notInStatus(statuses) {
1363
- const negated = statuses.map((s) => ne(jobRuns.status, s));
1364
- return and2(...negated);
1365
- }
1366
-
1367
- // runtime/subsystems/jobs/job-run-service.drizzle-backend.ts
1368
- import { Inject as Inject6, Injectable as Injectable6 } from "@nestjs/common";
1369
- import { and as and3, asc, desc as desc2, eq as eq3, gte as gte2, inArray as inArray2, isNull as isNull2, lt, or, sql as sql6 } from "drizzle-orm";
1370
-
1371
- // runtime/subsystems/jobs/job-run-keyset-cursor.ts
1372
- var DEFAULT_LIST_LIMIT = 50;
1373
- var MAX_LIST_LIMIT = 200;
1374
- function clampLimit(limit) {
1375
- if (typeof limit !== "number" || !Number.isFinite(limit)) {
1376
- return DEFAULT_LIST_LIMIT;
1377
- }
1378
- const floored = Math.floor(limit);
1379
- if (floored < 1) return 1;
1380
- if (floored > MAX_LIST_LIMIT) return MAX_LIST_LIMIT;
1381
- return floored;
1382
- }
1383
- function encodeKeysetCursor(keyset) {
1384
- const tuple = [keyset.createdAt.toISOString(), keyset.id];
1385
- return Buffer.from(JSON.stringify(tuple), "utf8").toString("base64url");
1386
- }
1387
- function decodeKeysetCursor(cursor) {
1388
- try {
1389
- const json = Buffer.from(cursor, "base64url").toString("utf8");
1390
- const parsed = JSON.parse(json);
1391
- if (!Array.isArray(parsed) || parsed.length !== 2) return null;
1392
- const [iso, id] = parsed;
1393
- if (typeof iso !== "string" || typeof id !== "string") return null;
1394
- const createdAt = new Date(iso);
1395
- if (Number.isNaN(createdAt.getTime())) return null;
1396
- return { createdAt, id };
1397
- } catch {
1398
- return null;
1399
- }
1400
- }
1401
- function toJobRunSummary(r) {
1402
- return {
1403
- runId: r.id,
1404
- rootRunId: r.rootRunId,
1405
- parentRunId: r.parentRunId,
1406
- triggerSource: r.triggerSource,
1407
- triggerRef: r.triggerRef,
1408
- jobType: r.jobType,
1409
- pool: r.pool,
1410
- status: r.status,
1411
- scopeEntityType: r.scopeEntityType,
1412
- scopeEntityId: r.scopeEntityId,
1413
- tenantId: r.tenantId,
1414
- attempts: r.attempts,
1415
- errorMessage: r.error?.message ?? null,
1416
- runAt: r.runAt,
1417
- startedAt: r.startedAt,
1418
- finishedAt: r.finishedAt,
1419
- createdAt: r.createdAt
1420
- };
1421
- }
1422
-
1423
- // runtime/subsystems/jobs/job-run-service.drizzle-backend.ts
1424
- var NON_TERMINAL_STATUSES = [
1425
- "pending",
1426
- "running",
1427
- "waiting"
1428
- ];
1429
- var DrizzleJobRunService = class {
1430
- constructor(db, orchestrator, multiTenant) {
1431
- this.db = db;
1432
- this.orchestrator = orchestrator;
1433
- this.multiTenant = multiTenant;
1434
- }
1435
- db;
1436
- orchestrator;
1437
- multiTenant;
1438
- /**
1439
- * JOB-8 — produce the tenant WHERE fragment (or `null` to opt out).
1440
- * Returns `null` when multi-tenancy is off (caller skips the predicate).
1441
- * Throws `MissingTenantIdError` when on + `undefined`.
1442
- * When on + explicit `null`, filters `tenant_id IS NULL`.
1443
- */
1444
- tenantCondition(method, tenantId) {
1445
- if (!this.multiTenant) return null;
1446
- if (tenantId === void 0) throw new MissingTenantIdError2(method);
1447
- return tenantId === null ? isNull2(jobRuns.tenantId) : eq3(jobRuns.tenantId, tenantId);
1448
- }
1449
- async listForScope(entityType, entityId, opts = {}) {
1450
- const conditions = [
1451
- eq3(jobRuns.scopeEntityType, entityType),
1452
- eq3(jobRuns.scopeEntityId, entityId)
1453
- ];
1454
- const tenantCond = this.tenantCondition("listForScope", opts.tenantId);
1455
- if (tenantCond) conditions.push(tenantCond);
1456
- if (opts.status) {
1457
- if (Array.isArray(opts.status)) {
1458
- conditions.push(inArray2(jobRuns.status, opts.status));
1459
- } else {
1460
- conditions.push(eq3(jobRuns.status, opts.status));
1461
- }
1462
- }
1463
- if (opts.jobType) {
1464
- conditions.push(eq3(jobRuns.jobType, opts.jobType));
1465
- }
1466
- const orderCol = (() => {
1467
- switch (opts.orderBy) {
1468
- case "created_at asc":
1469
- return asc(jobRuns.createdAt);
1470
- case "run_at desc":
1471
- return desc2(jobRuns.runAt);
1472
- case "run_at asc":
1473
- return asc(jobRuns.runAt);
1474
- case "created_at desc":
1475
- default:
1476
- return desc2(jobRuns.createdAt);
1477
- }
1478
- })();
1479
- let q = this.db.select().from(jobRuns).where(and3(...conditions)).orderBy(orderCol).$dynamic();
1480
- if (typeof opts.limit === "number") {
1481
- q = q.limit(opts.limit);
1482
- }
1483
- if (typeof opts.offset === "number") {
1484
- q = q.offset(opts.offset);
1485
- }
1486
- const rows = await q;
1487
- return rows;
1488
- }
1489
- async cancelForScope(entityType, entityId, opts = {}) {
1490
- const tenantCond = this.tenantCondition("cancelForScope", opts.tenantId);
1491
- const conditions = [
1492
- eq3(jobRuns.scopeEntityType, entityType),
1493
- eq3(jobRuns.scopeEntityId, entityId),
1494
- inArray2(jobRuns.status, NON_TERMINAL_STATUSES)
1495
- ];
1496
- if (tenantCond) conditions.push(tenantCond);
1497
- const rows = await this.db.select({ id: jobRuns.id }).from(jobRuns).where(and3(...conditions));
1498
- for (const { id } of rows) {
1499
- await this.orchestrator.cancel(id, {
1500
- cascade: true,
1501
- tenantId: opts.tenantId
1502
- });
1503
- }
1504
- }
1505
- async rescheduleForScope(entityType, entityId, newRunAt, opts = {}) {
1506
- const tenantCond = this.tenantCondition("rescheduleForScope", opts.tenantId);
1507
- const conditions = [
1508
- eq3(jobRuns.scopeEntityType, entityType),
1509
- eq3(jobRuns.scopeEntityId, entityId),
1510
- eq3(jobRuns.status, "pending")
1511
- ];
1512
- if (tenantCond) conditions.push(tenantCond);
1513
- await this.db.update(jobRuns).set({ runAt: newRunAt, updatedAt: /* @__PURE__ */ new Date() }).where(and3(...conditions));
1514
- }
1515
- async countByPoolAndStatus(tenantId) {
1516
- const tenantCond = this.tenantCondition("countByPoolAndStatus", tenantId);
1517
- const rows = await this.db.select({
1518
- pool: jobRuns.pool,
1519
- status: jobRuns.status,
1520
- count: sql6`count(*)::int`.as("count")
1521
- }).from(jobRuns).where(tenantCond ?? void 0).groupBy(jobRuns.pool, jobRuns.status);
1522
- return rows.map((r) => ({
1523
- pool: r.pool,
1524
- status: r.status,
1525
- count: Number(r.count)
1526
- }));
1527
- }
1528
- async listRecentFailed(limit, tenantId) {
1529
- const conditions = [eq3(jobRuns.status, "failed")];
1530
- const tenantCond = this.tenantCondition("listRecentFailed", tenantId);
1531
- if (tenantCond) conditions.push(tenantCond);
1532
- const rows = await this.db.select().from(jobRuns).where(and3(...conditions)).orderBy(desc2(jobRuns.finishedAt), desc2(jobRuns.updatedAt)).limit(limit);
1533
- return rows.map((r) => ({
1534
- runId: r.id,
1535
- jobType: r.jobType,
1536
- pool: r.pool,
1537
- scopeEntityType: r.scopeEntityType,
1538
- scopeEntityId: r.scopeEntityId,
1539
- tenantId: r.tenantId,
1540
- attempts: r.attempts,
1541
- errorMessage: r.error?.message ?? null,
1542
- failedAt: r.finishedAt ?? r.updatedAt,
1543
- createdAt: r.createdAt
1544
- }));
1545
- }
1546
- async listJobRuns(query = {}) {
1547
- const limit = clampLimit(query.limit);
1548
- const conditions = [];
1549
- const tenantCond = this.tenantCondition("listJobRuns", query.tenantId);
1550
- if (tenantCond) conditions.push(tenantCond);
1551
- if (query.poolId) conditions.push(eq3(jobRuns.pool, query.poolId));
1552
- if (query.rootRunId) conditions.push(eq3(jobRuns.rootRunId, query.rootRunId));
1553
- if (query.status) conditions.push(eq3(jobRuns.status, query.status));
1554
- if (query.since) conditions.push(gte2(jobRuns.createdAt, query.since));
1555
- if (query.cursor) {
1556
- const keyset = decodeKeysetCursor(query.cursor);
1557
- if (keyset) {
1558
- conditions.push(
1559
- or(
1560
- lt(jobRuns.createdAt, keyset.createdAt),
1561
- and3(
1562
- eq3(jobRuns.createdAt, keyset.createdAt),
1563
- lt(jobRuns.id, keyset.id)
1564
- )
1565
- )
1566
- );
1567
- }
1568
- }
1569
- const rows = await this.db.select().from(jobRuns).where(conditions.length > 0 ? and3(...conditions) : void 0).orderBy(desc2(jobRuns.createdAt), desc2(jobRuns.id)).limit(limit + 1);
1570
- const hasMore = rows.length > limit;
1571
- const page = hasMore ? rows.slice(0, limit) : rows;
1572
- const items = page.map(toJobRunSummary);
1573
- const last = page[page.length - 1];
1574
- const nextCursor = hasMore && last ? encodeKeysetCursor({ createdAt: last.createdAt, id: last.id }) : null;
1575
- return { items, nextCursor };
1576
- }
1577
- /**
1578
- * Internal helper used by cascade paths (not on the public protocol).
1579
- * Exposed as a public method on the concrete class so infrastructure
1580
- * code (cascade tests, debug tools) can call it without a cast.
1581
- */
1582
- async findByRootRunId(rootRunId) {
1583
- const rows = await this.db.select().from(jobRuns).where(eq3(jobRuns.rootRunId, rootRunId));
1584
- return rows;
1585
- }
1586
- };
1587
- DrizzleJobRunService = __decorateClass([
1588
- Injectable6(),
1589
- __decorateParam(0, Inject6(DRIZZLE)),
1590
- __decorateParam(1, Inject6(JOB_ORCHESTRATOR)),
1591
- __decorateParam(2, Inject6(JOBS_MULTI_TENANT))
1592
- ], DrizzleJobRunService);
1593
-
1594
- // runtime/subsystems/jobs/job-step-service.drizzle-backend.ts
1595
- import { Inject as Inject7, Injectable as Injectable7 } from "@nestjs/common";
1596
- import { and as and4, eq as eq4 } from "drizzle-orm";
1597
- var DrizzleJobStepService = class {
1598
- constructor(db) {
1599
- this.db = db;
1600
- }
1601
- db;
1602
- async recordStep(input) {
1603
- const values = {
1604
- jobRunId: input.jobRunId,
1605
- stepId: input.stepId,
1606
- kind: input.kind,
1607
- seq: input.seq,
1608
- status: input.status,
1609
- input: input.input ?? null,
1610
- output: input.output ?? null,
1611
- error: input.error ?? null,
1612
- attempts: input.attempts ?? 0,
1613
- startedAt: input.startedAt ?? null,
1614
- finishedAt: input.finishedAt ?? null
1615
- };
1616
- const [row] = await this.db.insert(jobSteps).values(values).onConflictDoUpdate({
1617
- target: [jobSteps.jobRunId, jobSteps.stepId],
1618
- set: {
1619
- status: values.status,
1620
- output: values.output,
1621
- error: values.error,
1622
- finishedAt: values.finishedAt,
1623
- attempts: values.attempts
1624
- }
1625
- }).returning();
1626
- return row;
1627
- }
1628
- async findStep(runId, stepId) {
1629
- const [row] = await this.db.select().from(jobSteps).where(and4(eq4(jobSteps.jobRunId, runId), eq4(jobSteps.stepId, stepId))).limit(1);
1630
- return row ?? null;
1631
- }
1632
- };
1633
- DrizzleJobStepService = __decorateClass([
1634
- Injectable7(),
1635
- __decorateParam(0, Inject7(DRIZZLE))
1636
- ], DrizzleJobStepService);
1637
-
1638
- // runtime/subsystems/jobs/job-orchestrator.memory-backend.ts
1639
- import { randomUUID as randomUUID5 } from "crypto";
1640
- import { Inject as Inject9, Injectable as Injectable9, Logger as Logger4, Optional as Optional5 } from "@nestjs/common";
1641
- import { ModuleRef } from "@nestjs/core";
1642
-
1643
- // runtime/subsystems/jobs/memory-job-store.ts
1644
- var MemoryJobStore = class {
1645
- /** Runs keyed by `id` (single source of truth for status/scope/lineage). */
1646
- runs = /* @__PURE__ */ new Map();
1647
- /** Steps keyed by `job_run_id`; array order matches insertion order. */
1648
- steps = /* @__PURE__ */ new Map();
1649
- /** Job definitions keyed by `type` — memory mirror of the `job` table. */
1650
- jobs = /* @__PURE__ */ new Map();
1651
- /** Reset everything. Tests call this in `beforeEach`. */
1652
- clear() {
1653
- this.runs.clear();
1654
- this.steps.clear();
1655
- this.jobs.clear();
1656
- }
1657
- };
1658
-
1659
- // runtime/subsystems/jobs/job-step-service.memory-backend.ts
1660
- import { randomUUID as randomUUID4 } from "crypto";
1661
- import { Inject as Inject8, Injectable as Injectable8 } from "@nestjs/common";
1662
- var MemoryJobStepService = class {
1663
- // ADR-037 (package-mode DI): explicit `@Inject(MemoryJobStore)` — the
1664
- // published bundle carries no `design:paramtypes`, so a by-type inject
1665
- // would resolve to `undefined` in package mode.
1666
- constructor(store) {
1667
- this.store = store;
1668
- }
1669
- store;
1670
- async findStep(runId, stepId) {
1671
- const rows = this.store.steps.get(runId);
1672
- if (!rows) return null;
1673
- const match = rows.find(
1674
- (r) => r.stepId === stepId && r.status === "completed"
1675
- );
1676
- return match ?? null;
1677
- }
1678
- async recordStep(input) {
1679
- const rows = this.getOrCreateRows(input.jobRunId);
1680
- const existingIdx = rows.findIndex((r) => r.stepId === input.stepId);
1681
- const normalisedInput = input.input ?? null;
1682
- const normalisedOutput = input.output ?? null;
1683
- if (existingIdx >= 0) {
1684
- const prev = rows[existingIdx];
1685
- const next = {
1686
- ...prev,
1687
- status: input.status,
1688
- input: normalisedInput ?? prev.input,
1689
- output: normalisedOutput ?? prev.output,
1690
- error: input.error ?? prev.error,
1691
- attempts: input.attempts ?? prev.attempts,
1692
- startedAt: input.startedAt ?? prev.startedAt,
1693
- finishedAt: input.finishedAt ?? prev.finishedAt
1694
- };
1695
- rows[existingIdx] = next;
1696
- return next;
1697
- }
1698
- const seq = input.seq ?? this.nextSeq(rows);
1699
- const row = {
1700
- id: randomUUID4(),
1701
- jobRunId: input.jobRunId,
1702
- stepId: input.stepId,
1703
- kind: input.kind,
1704
- seq,
1705
- status: input.status,
1706
- input: normalisedInput,
1707
- output: normalisedOutput,
1708
- error: input.error ?? null,
1709
- attempts: input.attempts ?? 0,
1710
- startedAt: input.startedAt ?? null,
1711
- finishedAt: input.finishedAt ?? null
1712
- };
1713
- rows.push(row);
1714
- return row;
1715
- }
1716
- /**
1717
- * Replay helper — wipe every step row for a run. Mirrors the `scratch`
1718
- * replay mode of the Drizzle backend (`DELETE FROM job_step WHERE job_run_id = …`).
1719
- */
1720
- clearStepsForRun(runId) {
1721
- this.store.steps.delete(runId);
1722
- }
1723
- /**
1724
- * Remove every non-`completed` row for the run. Memoized (`completed`)
1725
- * rows are preserved — this is the `last_checkpoint` / `last_step`
1726
- * semantics the Drizzle backend implements via
1727
- * `DELETE … WHERE status != 'completed'`. Both replay modes route here
1728
- * (Phase 1 collapses `last_step` onto this behaviour; see JOB-3 notes).
1729
- */
1730
- clearIncompleteSteps(runId) {
1731
- const rows = this.store.steps.get(runId);
1732
- if (!rows) return;
1733
- const kept = rows.filter((r) => r.status === "completed");
1734
- if (kept.length === 0) {
1735
- this.store.steps.delete(runId);
1736
- } else {
1737
- this.store.steps.set(runId, kept);
1738
- }
1739
- }
1740
- getOrCreateRows(runId) {
1741
- let rows = this.store.steps.get(runId);
1742
- if (!rows) {
1743
- rows = [];
1744
- this.store.steps.set(runId, rows);
1745
- }
1746
- return rows;
1747
- }
1748
- nextSeq(rows) {
1749
- let max = 0;
1750
- for (const r of rows) {
1751
- if (r.seq > max) max = r.seq;
1752
- }
1753
- return max + 1;
1754
- }
1755
- };
1756
- MemoryJobStepService = __decorateClass([
1757
- Injectable8(),
1758
- __decorateParam(0, Inject8(MemoryJobStore))
1759
- ], MemoryJobStepService);
1760
-
1761
- // runtime/subsystems/jobs/job-orchestrator.memory-backend.ts
1762
- var QUEUED_RUN_AT = /* @__PURE__ */ new Date(864e13);
1763
- var TERMINAL_STATUSES2 = [
1764
- "completed",
1765
- "failed",
1766
- "timed_out",
1767
- "canceled"
1768
- ];
1769
- var DEDUPE_EXCLUDED_STATUSES2 = ["canceled", "failed"];
1770
- var IN_FLIGHT_STATUSES2 = ["pending", "running"];
1771
- function isTerminal(status) {
1772
- return TERMINAL_STATUSES2.includes(status);
1773
- }
1774
- function evaluateKeyTemplate2(template, input) {
1775
- return template.replace(
1776
- /\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g,
1777
- (_m, field) => {
1778
- const value = input[field];
1779
- if (value === void 0 || value === null) {
1780
- throw new JobTemplateFieldMissingError(template, field);
1781
- }
1782
- return String(value);
1783
- }
1784
- );
1785
- }
1786
- var PromiseMutex = class {
1787
- queue = Promise.resolve();
1788
- async run(fn) {
1789
- const next = this.queue.then(() => fn());
1790
- this.queue = next.then(
1791
- () => void 0,
1792
- () => void 0
1793
- );
1794
- return next;
1795
- }
1796
- };
1797
- var MemoryJobOrchestrator = class {
1798
- constructor(store, stepService, multiTenant, moduleRef) {
1799
- this.store = store;
1800
- this.stepService = stepService;
1801
- this.multiTenant = multiTenant;
1802
- this.moduleRef = moduleRef;
1803
- }
1804
- store;
1805
- stepService;
1806
- multiTenant;
1807
- moduleRef;
1808
- logger = new Logger4(MemoryJobOrchestrator.name);
1809
- mutex = new PromiseMutex();
1810
- handlerRegistry = /* @__PURE__ */ new Map();
1811
- /**
1812
- * `runId → dependent runId[]` — when a run with `concurrencyKey = K`
1813
- * blocks on an incumbent, its id is added here under the incumbent's id.
1814
- * On incumbent terminal transition we advance every dependent's `runAt`
1815
- * back to `now()` so it becomes claimable.
1816
- */
1817
- queueBlockers = /* @__PURE__ */ new Map();
1818
- /**
1819
- * JOB-8 — mirror of the Drizzle backend's `resolveTenantId`. Returns the
1820
- * value to stamp on `tenant_id` / compare against in memory predicates.
1821
- * Off → always `null`. On + `undefined` → throw. On + `null`/string → pass.
1822
- */
1823
- resolveTenantId(method, tenantId) {
1824
- if (!this.multiTenant) return null;
1825
- if (tenantId === void 0) throw new MissingTenantIdError2(method);
1826
- return tenantId;
1827
- }
1828
- // ==========================================================================
1829
- // registerHandler — replaces Drizzle's `job` table upsert
1830
- // ==========================================================================
1831
- /**
1832
- * Populate the in-memory job definition row plus handler class lookup.
1833
- * Called by `JobWorkerModule.onModuleInit` in memory mode, or directly by
1834
- * unit tests that want to seed the registry without NestJS.
1835
- */
1836
- registerHandler(type, meta, handlerClass) {
1837
- const concurrencyKeyTemplate = meta.concurrency?.key ?? null;
1838
- const dedupeKeyTemplate = meta.dedupe?.key ?? null;
1839
- const dedupeWindowMs = meta.dedupe?.windowMs ?? null;
1840
- const now = /* @__PURE__ */ new Date();
1841
- const def = {
1842
- type,
1843
- version: 1,
1844
- pool: meta.pool ?? "batch",
1845
- scopeEntityType: meta.scope?.entity ?? null,
1846
- retryPolicy: meta.retry ?? {
1847
- attempts: 1,
1848
- backoff: "fixed",
1849
- baseMs: 0
1850
- },
1851
- timeoutMs: meta.timeoutMs ?? null,
1852
- concurrencyKeyTemplate: typeof concurrencyKeyTemplate === "string" ? concurrencyKeyTemplate : null,
1853
- collisionMode: meta.concurrency?.collisionMode ?? "queue",
1854
- dedupeKeyTemplate: typeof dedupeKeyTemplate === "string" ? dedupeKeyTemplate : null,
1855
- dedupeWindowMs,
1856
- priorityDefault: 0,
1857
- replayFrom: meta.replayFrom ?? "last_checkpoint",
1858
- createdAt: now,
1859
- updatedAt: now
1860
- };
1861
- this.store.jobs.set(type, def);
1862
- this.handlerRegistry.set(type, {
1863
- type,
1864
- meta,
1865
- handlerClass
1866
- });
1867
- }
1868
- /** Test helper — look up a registered handler without exposing the map. */
1869
- getHandlerRegistration(type) {
1870
- return this.handlerRegistry.get(type);
1871
- }
1872
- /**
1873
- * Boot-time upsert per `IJobOrchestrator.upsertJobRows`. Memory backend
1874
- * just funnels each entry through `registerHandler`. The validator is
1875
- * skipped entirely in memory mode (Q4 resolution 2026-04-19), so the
1876
- * orphaned list is always empty — there are no DB rows to compare against.
1877
- */
1878
- async upsertJobRows(entries, poolConfig) {
1879
- void poolConfig;
1880
- for (const entry of entries) {
1881
- this.registerHandler(
1882
- entry.type,
1883
- entry.meta,
1884
- entry.handlerClass
1885
- );
1886
- }
1887
- return { orphaned: [] };
1888
- }
1889
- // ==========================================================================
1890
- // start
1891
- // ==========================================================================
1892
- async start(type, input, opts = {}, _tx) {
1893
- const tenantId = this.resolveTenantId("start", opts.tenantId);
1894
- return this.mutex.run(async () => {
1895
- const payload = input ?? {};
1896
- const definition = this.store.jobs.get(type);
1897
- if (!definition) throw new JobTypeNotFoundError(type);
1898
- if (definition.dedupeKeyTemplate && definition.dedupeWindowMs) {
1899
- const dedupeKey2 = evaluateKeyTemplate2(
1900
- definition.dedupeKeyTemplate,
1901
- payload
1902
- );
1903
- const windowStart = Date.now() - definition.dedupeWindowMs;
1904
- const existing = this.findDedupeCandidate(type, dedupeKey2, windowStart);
1905
- if (existing) return existing;
1906
- }
1907
- let concurrencyKey = null;
1908
- let queueBlockedBy = null;
1909
- if (definition.concurrencyKeyTemplate) {
1910
- concurrencyKey = evaluateKeyTemplate2(
1911
- definition.concurrencyKeyTemplate,
1912
- payload
1913
- );
1914
- const incumbent = this.findInFlightByConcurrencyKey(concurrencyKey);
1915
- if (incumbent) {
1916
- switch (definition.collisionMode) {
1917
- case "reject":
1918
- throw new JobCollisionError(type, concurrencyKey, incumbent);
1919
- case "replace":
1920
- this.cancelLocked(
1921
- incumbent.id,
1922
- { cascade: true, reason: "replaced" },
1923
- incumbent.tenantId
1924
- );
1925
- break;
1926
- case "queue":
1927
- queueBlockedBy = incumbent.id;
1928
- break;
1929
- }
1930
- }
1931
- }
1932
- const newId = randomUUID5();
1933
- let rootRunId = newId;
1934
- if (opts.parentRunId) {
1935
- const parent = this.store.runs.get(opts.parentRunId);
1936
- if (!parent) {
1937
- throw new Error(
1938
- `parentRunId ${opts.parentRunId} does not reference an existing job_run`
1939
- );
1940
- }
1941
- rootRunId = parent.rootRunId;
1942
- }
1943
- const dedupeKey = definition.dedupeKeyTemplate ? evaluateKeyTemplate2(definition.dedupeKeyTemplate, payload) : null;
1944
- const now = /* @__PURE__ */ new Date();
1945
- const runAt = queueBlockedBy ? QUEUED_RUN_AT : opts.runAt ?? now;
1946
- const row = {
1947
- id: newId,
1948
- jobType: type,
1949
- jobVersion: definition.version,
1950
- parentRunId: opts.parentRunId ?? null,
1951
- rootRunId,
1952
- parentClosePolicy: opts.parentClosePolicy ?? "terminate",
1953
- scopeEntityType: opts.scope?.entityType ?? null,
1954
- scopeEntityId: opts.scope?.entityId ?? null,
1955
- tenantId,
1956
- tags: opts.tags ?? {},
1957
- pool: opts.pool ?? definition.pool,
1958
- priority: opts.priority ?? definition.priorityDefault,
1959
- concurrencyKey,
1960
- dedupeKey,
1961
- status: "pending",
1962
- input: payload,
1963
- output: null,
1964
- error: null,
1965
- triggerSource: opts.triggerSource ?? "manual",
1966
- triggerRef: opts.triggerRef ?? null,
1967
- runAt,
1968
- startedAt: null,
1969
- finishedAt: null,
1970
- claimedAt: null,
1971
- attempts: 0,
1972
- waitKind: null,
1973
- resumeToken: null,
1974
- waitDeadline: null,
1975
- createdAt: now,
1976
- updatedAt: now
1977
- };
1978
- this.store.runs.set(newId, row);
1979
- if (queueBlockedBy) {
1980
- const list = this.queueBlockers.get(queueBlockedBy) ?? [];
1981
- list.push(newId);
1982
- this.queueBlockers.set(queueBlockedBy, list);
1983
- }
1984
- return row;
1985
- });
1986
- }
1987
- // ==========================================================================
1988
- // cancel
1989
- // ==========================================================================
1990
- async cancel(runId, opts = {}) {
1991
- const tenantId = this.resolveTenantId("cancel", opts.tenantId);
1992
- await this.mutex.run(async () => {
1993
- this.cancelLocked(runId, opts, tenantId);
1994
- });
1995
- }
1996
- /**
1997
- * Internal cancel that assumes the caller already holds the mutex.
1998
- * Synchronous because all store ops are in-memory. Idempotent.
1999
- *
2000
- * `tenantForGate` is the already-validated tenant id (or `null`). When
2001
- * non-null it gates the initial cancellation to that tenant's run; the
2002
- * cascade step then sweeps descendants on the same `rootRunId` without
2003
- * re-checking — children of a tenant-gated parent always share the
2004
- * tenant (enforced at `start` time).
2005
- */
2006
- cancelLocked(runId, opts, tenantForGate) {
2007
- const run = this.store.runs.get(runId);
2008
- if (!run) return;
2009
- if (this.multiTenant && run.tenantId !== tenantForGate) return;
2010
- if (isTerminal(run.status)) return;
2011
- const now = /* @__PURE__ */ new Date();
2012
- const descendants = opts.cascade === false ? [] : Array.from(this.store.runs.values()).filter(
2013
- (r) => r.rootRunId === run.rootRunId && r.id !== runId && !isTerminal(r.status)
2014
- );
2015
- const terminateChildren = descendants.filter(
2016
- (d) => d.parentClosePolicy === "terminate" /* Terminate */
2017
- );
2018
- const cancelChildren = descendants.filter(
2019
- (d) => d.parentClosePolicy === "cancel" /* Cancel */
2020
- );
2021
- for (const child of terminateChildren) {
2022
- this.transitionToCanceled(child.id, now);
2023
- }
2024
- for (const child of cancelChildren) {
2025
- this.transitionToCanceled(child.id, now);
2026
- }
2027
- this.transitionToCanceled(runId, now);
2028
- void opts.reason;
2029
- }
2030
- transitionToCanceled(runId, at) {
2031
- const run = this.store.runs.get(runId);
2032
- if (!run) return;
2033
- if (isTerminal(run.status)) return;
2034
- const next = {
2035
- ...run,
2036
- status: "canceled",
2037
- finishedAt: at,
2038
- updatedAt: at
2039
- };
2040
- this.store.runs.set(runId, next);
2041
- this.unblockQueuedDependents(runId);
2042
- }
2043
- /**
2044
- * When `runId` transitions to a terminal state, advance every dependent
2045
- * `queue`-blocked run's `run_at` back to `now()` so `claimNext` picks
2046
- * them up.
2047
- */
2048
- unblockQueuedDependents(runId) {
2049
- const dependents = this.queueBlockers.get(runId);
2050
- if (!dependents || dependents.length === 0) return;
2051
- const now = /* @__PURE__ */ new Date();
2052
- for (const dep of dependents) {
2053
- const depRun = this.store.runs.get(dep);
2054
- if (!depRun) continue;
2055
- if (depRun.status !== "pending") continue;
2056
- this.store.runs.set(dep, { ...depRun, runAt: now, updatedAt: now });
2057
- }
2058
- this.queueBlockers.delete(runId);
2059
- }
2060
- // ==========================================================================
2061
- // claimNext — consumed by JobWorker in memory mode (tests exercise directly)
2062
- // ==========================================================================
2063
- async claimNext(pool) {
2064
- return this.mutex.run(async () => {
2065
- const now = Date.now();
2066
- const candidates = Array.from(this.store.runs.values()).filter(
2067
- (r) => r.status === "pending" && r.pool === pool && r.runAt.getTime() <= now
2068
- );
2069
- if (candidates.length === 0) return null;
2070
- candidates.sort((a, b) => {
2071
- if (a.priority !== b.priority) return b.priority - a.priority;
2072
- return a.runAt.getTime() - b.runAt.getTime();
2073
- });
2074
- const winner = candidates[0];
2075
- const claimedAt = /* @__PURE__ */ new Date();
2076
- const next = {
2077
- ...winner,
2078
- status: "running",
2079
- claimedAt,
2080
- startedAt: claimedAt,
2081
- updatedAt: claimedAt
2082
- };
2083
- this.store.runs.set(winner.id, next);
2084
- return next;
2085
- });
2086
- }
2087
- // ==========================================================================
2088
- // replay
2089
- // ==========================================================================
2090
- async replay(runId) {
2091
- return this.mutex.run(async () => {
2092
- const run = this.store.runs.get(runId);
2093
- if (!run) throw new Error(`replay: run ${runId} not found`);
2094
- if (!isTerminal(run.status)) {
2095
- throw new JobNotReplayableError(runId, run.status);
2096
- }
2097
- const def = this.store.jobs.get(run.jobType);
2098
- if (!def) throw new JobTypeNotFoundError(run.jobType);
2099
- const mode = def.replayFrom;
2100
- if (mode === "scratch") {
2101
- this.stepService.clearStepsForRun(runId);
2102
- } else {
2103
- this.stepService.clearIncompleteSteps(runId);
2104
- }
2105
- const now = /* @__PURE__ */ new Date();
2106
- const next = {
2107
- ...run,
2108
- status: "pending",
2109
- attempts: 0,
2110
- runAt: now,
2111
- startedAt: null,
2112
- finishedAt: null,
2113
- claimedAt: null,
2114
- error: null,
2115
- output: null,
2116
- updatedAt: now
2117
- };
2118
- this.store.runs.set(runId, next);
2119
- return next;
2120
- });
2121
- }
2122
- // ==========================================================================
2123
- // tick — used by unit tests + memory-mode JobWorker
2124
- // ==========================================================================
2125
- /**
2126
- * Execute a single claimed run to completion, retry, or failure. Not on
2127
- * `IJobOrchestrator` — it's the memory equivalent of the Drizzle
2128
- * `JobWorker.processRun` code path. The unit tests drive it directly so
2129
- * they can assert memoization across ticks without spinning up a worker.
2130
- */
2131
- async tick(runId) {
2132
- const run = this.store.runs.get(runId);
2133
- if (!run) throw new Error(`tick: run ${runId} not found`);
2134
- if (run.status !== "running") {
2135
- throw new Error(
2136
- `tick: run ${runId} must be 'running' (got '${run.status}')`
2137
- );
2138
- }
2139
- const registration = this.handlerRegistry.get(run.jobType);
2140
- if (!registration) {
2141
- await this.markFailed(run, new Error(
2142
- `No handler registered for jobType='${run.jobType}'`
2143
- ), (run.attempts ?? 0) + 1);
2144
- return;
2145
- }
2146
- const meta = registration.meta;
2147
- const HandlerClass = registration.handlerClass;
2148
- const handler = this.moduleRef ? this.moduleRef.get(
2149
- HandlerClass,
2150
- { strict: false }
2151
- ) : new HandlerClass();
2152
- const ctx = {
2153
- input: run.input,
2154
- run,
2155
- step: this.makeStepFn(run),
2156
- spawnChild: this.makeSpawnFn(run),
2157
- logger: new Logger4(`JobRun:${run.id}`)
2158
- };
2159
- const attemptsBefore = run.attempts ?? 0;
2160
- try {
2161
- const output = await handler.run(ctx);
2162
- await this.markCompleted(run, output ?? {}, attemptsBefore + 1);
2163
- } catch (err) {
2164
- const policy = meta.retry;
2165
- const decision = classifyError(err, policy, attemptsBefore);
2166
- const nextAttempts = attemptsBefore + 1;
2167
- if (decision === "retry" && policy) {
2168
- const delay = computeBackoff(policy, nextAttempts);
2169
- await this.rescheduleForRetry(run, err, nextAttempts, delay);
2170
- } else {
2171
- await this.markFailed(run, err, nextAttempts);
2172
- }
2173
- }
2174
- }
2175
- makeStepFn(run) {
2176
- return async (stepId, fn, _opts) => {
2177
- void _opts;
2178
- const existing = await this.stepService.findStep(run.id, stepId);
2179
- if (existing?.status === "completed") {
2180
- return existing.output;
2181
- }
2182
- const seq = this.nextStepSeq(run.id);
2183
- const startedAt = /* @__PURE__ */ new Date();
2184
- const nextAttempts = (existing?.attempts ?? 0) + 1;
2185
- await this.stepService.recordStep({
2186
- jobRunId: run.id,
2187
- stepId,
2188
- kind: "task",
2189
- seq,
2190
- status: "running",
2191
- startedAt,
2192
- attempts: nextAttempts
2193
- });
2194
- try {
2195
- const output = await fn();
2196
- await this.stepService.recordStep({
2197
- jobRunId: run.id,
2198
- stepId,
2199
- kind: "task",
2200
- seq,
2201
- status: "completed",
2202
- output,
2203
- finishedAt: /* @__PURE__ */ new Date(),
2204
- attempts: nextAttempts
2205
- });
2206
- return output;
2207
- } catch (err) {
2208
- await this.stepService.recordStep({
2209
- jobRunId: run.id,
2210
- stepId,
2211
- kind: "task",
2212
- seq,
2213
- status: "failed",
2214
- error: serialiseError(err, nextAttempts, false),
2215
- finishedAt: /* @__PURE__ */ new Date(),
2216
- attempts: nextAttempts
2217
- });
2218
- throw err;
2219
- }
2220
- };
2221
- }
2222
- makeSpawnFn(run) {
2223
- return async (type, input, opts) => {
2224
- return this.start(type, input, {
2225
- parentRunId: run.id,
2226
- parentClosePolicy: opts?.closePolicy,
2227
- runAt: opts?.runAt,
2228
- priority: opts?.priority,
2229
- tags: opts?.tags,
2230
- triggerSource: "parent",
2231
- triggerRef: run.id
2232
- });
2233
- };
2234
- }
2235
- nextStepSeq(runId) {
2236
- const rows = this.store.steps.get(runId);
2237
- if (!rows || rows.length === 0) return 1;
2238
- let max = 0;
2239
- for (const r of rows) if (r.seq > max) max = r.seq;
2240
- return max + 1;
2241
- }
2242
- async markCompleted(run, output, attempts) {
2243
- await this.mutex.run(async () => {
2244
- const current = this.store.runs.get(run.id);
2245
- if (!current || isTerminal(current.status)) return;
2246
- const now = /* @__PURE__ */ new Date();
2247
- this.store.runs.set(run.id, {
2248
- ...current,
2249
- status: "completed",
2250
- output,
2251
- finishedAt: now,
2252
- updatedAt: now,
2253
- attempts
2254
- });
2255
- this.unblockQueuedDependents(run.id);
2256
- });
2257
- }
2258
- async markFailed(run, err, attempts) {
2259
- await this.mutex.run(async () => {
2260
- const current = this.store.runs.get(run.id);
2261
- if (!current || isTerminal(current.status)) return;
2262
- const now = /* @__PURE__ */ new Date();
2263
- this.store.runs.set(run.id, {
2264
- ...current,
2265
- status: "failed",
2266
- finishedAt: now,
2267
- updatedAt: now,
2268
- attempts,
2269
- error: serialiseError(err, attempts, false)
2270
- });
2271
- this.unblockQueuedDependents(run.id);
2272
- });
2273
- if (run.parentClosePolicy === "terminate") {
2274
- try {
2275
- await this.cancel(run.id, {
2276
- cascade: true,
2277
- reason: "parent-failed",
2278
- tenantId: run.tenantId
2279
- });
2280
- } catch (cascadeErr) {
2281
- this.logger.warn(
2282
- `cascade on failed run ${run.id}: ${cascadeErr.message}`
2283
- );
2284
- }
2285
- }
2286
- }
2287
- async rescheduleForRetry(run, err, attempts, delayMs) {
2288
- await this.mutex.run(async () => {
2289
- const current = this.store.runs.get(run.id);
2290
- if (!current || isTerminal(current.status)) return;
2291
- const now = /* @__PURE__ */ new Date();
2292
- this.store.runs.set(run.id, {
2293
- ...current,
2294
- status: "pending",
2295
- attempts,
2296
- runAt: new Date(Date.now() + delayMs),
2297
- startedAt: null,
2298
- claimedAt: null,
2299
- updatedAt: now,
2300
- error: serialiseError(err, attempts, true)
2301
- });
2302
- });
2303
- }
2304
- // ==========================================================================
2305
- // Internal queries — used by start / cancel
2306
- // ==========================================================================
2307
- findDedupeCandidate(jobType, dedupeKey, windowStartMs) {
2308
- let best = null;
2309
- for (const r of this.store.runs.values()) {
2310
- if (r.jobType !== jobType) continue;
2311
- if (r.dedupeKey !== dedupeKey) continue;
2312
- if (DEDUPE_EXCLUDED_STATUSES2.includes(r.status)) continue;
2313
- if (r.createdAt.getTime() <= windowStartMs) continue;
2314
- if (!best || r.createdAt.getTime() > best.createdAt.getTime()) {
2315
- best = r;
2316
- }
2317
- }
2318
- return best;
2319
- }
2320
- findInFlightByConcurrencyKey(key2) {
2321
- for (const r of this.store.runs.values()) {
2322
- if (r.concurrencyKey !== key2) continue;
2323
- if (!IN_FLIGHT_STATUSES2.includes(r.status)) continue;
2324
- return r;
2325
- }
2326
- return null;
2327
- }
2328
- };
2329
- MemoryJobOrchestrator = __decorateClass([
2330
- Injectable9(),
2331
- __decorateParam(0, Inject9(MemoryJobStore)),
2332
- __decorateParam(1, Inject9(MemoryJobStepService)),
2333
- __decorateParam(2, Inject9(JOBS_MULTI_TENANT)),
2334
- __decorateParam(3, Optional5()),
2335
- __decorateParam(3, Inject9(ModuleRef))
2336
- ], MemoryJobOrchestrator);
2337
- function classifyError(err, policy, currentAttempts) {
2338
- if (!policy) return "fail";
2339
- const errObj = err;
2340
- const name = errObj?.name;
2341
- const code = errObj?.code;
2342
- const nonRetryable = policy.nonRetryableErrors ?? [];
2343
- if (nonRetryable.some((n) => n === name || n === code)) return "fail";
2344
- if (currentAttempts + 1 >= policy.attempts) return "fail";
2345
- return "retry";
2346
- }
2347
- function computeBackoff(policy, attempts) {
2348
- const base = Math.max(policy.baseMs, 0);
2349
- if (policy.backoff === "fixed") return base;
2350
- const exponent = Math.max(attempts - 1, 0);
2351
- if (exponent >= 53) return Number.MAX_SAFE_INTEGER;
2352
- const raw = base * Math.pow(2, exponent);
2353
- if (!Number.isFinite(raw) || raw >= Number.MAX_SAFE_INTEGER) {
2354
- return Number.MAX_SAFE_INTEGER;
2355
- }
2356
- return raw;
2357
- }
2358
- function serialiseError(err, attempt, retryable) {
2359
- const e = err;
2360
- return {
2361
- message: e?.message ?? String(err),
2362
- stack: e?.stack,
2363
- retryable,
2364
- attempt
2365
- };
2366
- }
2367
-
2368
- // runtime/subsystems/jobs/job-run-service.memory-backend.ts
2369
- import { Inject as Inject10, Injectable as Injectable10 } from "@nestjs/common";
2370
- var NON_TERMINAL_STATUSES2 = [
2371
- "pending",
2372
- "running",
2373
- "waiting"
2374
- ];
2375
- var MemoryJobRunService = class {
2376
- constructor(store, orchestrator, multiTenant) {
2377
- this.store = store;
2378
- this.orchestrator = orchestrator;
2379
- this.multiTenant = multiTenant;
2380
- }
2381
- store;
2382
- orchestrator;
2383
- multiTenant;
2384
- /**
2385
- * JOB-8 — produce a per-row predicate for the tenant gate.
2386
- * Returns `null` when multi-tenancy is off (caller doesn't check).
2387
- * Throws when on + `undefined`; matches `tenant_id IS NULL` on explicit
2388
- * `null` to support cross-tenant background work.
2389
- */
2390
- tenantPredicate(method, tenantId) {
2391
- if (!this.multiTenant) return null;
2392
- if (tenantId === void 0) throw new MissingTenantIdError2(method);
2393
- return (r) => r.tenantId === tenantId;
2394
- }
2395
- async listForScope(entityType, entityId, opts = {}) {
2396
- const statusFilter = opts.status ? Array.isArray(opts.status) ? new Set(opts.status) : /* @__PURE__ */ new Set([opts.status]) : null;
2397
- const tenantCheck = this.tenantPredicate("listForScope", opts.tenantId);
2398
- const rows = [];
2399
- for (const r of this.store.runs.values()) {
2400
- if (r.scopeEntityType !== entityType) continue;
2401
- if (r.scopeEntityId !== entityId) continue;
2402
- if (statusFilter && !statusFilter.has(r.status)) continue;
2403
- if (opts.jobType && r.jobType !== opts.jobType) continue;
2404
- if (tenantCheck && !tenantCheck(r)) continue;
2405
- rows.push(r);
2406
- }
2407
- const orderBy = opts.orderBy ?? "created_at desc";
2408
- rows.sort((a, b) => compareBy(a, b, orderBy));
2409
- const offset = opts.offset ?? 0;
2410
- const limit = opts.limit;
2411
- const sliced = typeof limit === "number" ? rows.slice(offset, offset + limit) : rows.slice(offset);
2412
- return sliced;
2413
- }
2414
- async cancelForScope(entityType, entityId, opts = {}) {
2415
- const tenantCheck = this.tenantPredicate("cancelForScope", opts.tenantId);
2416
- const ids = [];
2417
- for (const r of this.store.runs.values()) {
2418
- if (r.scopeEntityType !== entityType) continue;
2419
- if (r.scopeEntityId !== entityId) continue;
2420
- if (!NON_TERMINAL_STATUSES2.includes(r.status)) continue;
2421
- if (tenantCheck && !tenantCheck(r)) continue;
2422
- ids.push(r.id);
2423
- }
2424
- for (const id of ids) {
2425
- await this.orchestrator.cancel(id, {
2426
- cascade: true,
2427
- tenantId: opts.tenantId
2428
- });
2429
- }
2430
- }
2431
- async rescheduleForScope(entityType, entityId, newRunAt, opts = {}) {
2432
- const tenantCheck = this.tenantPredicate("rescheduleForScope", opts.tenantId);
2433
- for (const r of this.store.runs.values()) {
2434
- if (r.scopeEntityType !== entityType) continue;
2435
- if (r.scopeEntityId !== entityId) continue;
2436
- if (r.status !== "pending") continue;
2437
- if (tenantCheck && !tenantCheck(r)) continue;
2438
- this.store.runs.set(r.id, {
2439
- ...r,
2440
- runAt: newRunAt,
2441
- updatedAt: /* @__PURE__ */ new Date()
2442
- });
2443
- }
2444
- }
2445
- async countByPoolAndStatus(tenantId) {
2446
- const tenantCheck = this.tenantPredicate("countByPoolAndStatus", tenantId);
2447
- const map = /* @__PURE__ */ new Map();
2448
- for (const r of this.store.runs.values()) {
2449
- if (tenantCheck && !tenantCheck(r)) continue;
2450
- const key2 = `${r.pool}\0${r.status}`;
2451
- const cur = map.get(key2);
2452
- if (cur) {
2453
- cur.count += 1;
2454
- } else {
2455
- map.set(key2, { pool: r.pool, status: r.status, count: 1 });
2456
- }
2457
- }
2458
- return Array.from(map.values());
2459
- }
2460
- async listRecentFailed(limit, tenantId) {
2461
- const tenantCheck = this.tenantPredicate("listRecentFailed", tenantId);
2462
- const failed = [];
2463
- for (const r of this.store.runs.values()) {
2464
- if (r.status !== "failed") continue;
2465
- if (tenantCheck && !tenantCheck(r)) continue;
2466
- failed.push(r);
2467
- }
2468
- failed.sort((a, b) => {
2469
- const at = (a.finishedAt ?? a.updatedAt).getTime();
2470
- const bt = (b.finishedAt ?? b.updatedAt).getTime();
2471
- return bt - at;
2472
- });
2473
- return failed.slice(0, limit).map((r) => ({
2474
- runId: r.id,
2475
- jobType: r.jobType,
2476
- pool: r.pool,
2477
- scopeEntityType: r.scopeEntityType,
2478
- scopeEntityId: r.scopeEntityId,
2479
- tenantId: r.tenantId,
2480
- attempts: r.attempts,
2481
- errorMessage: r.error?.message ?? null,
2482
- failedAt: r.finishedAt ?? r.updatedAt,
2483
- createdAt: r.createdAt
2484
- }));
2485
- }
2486
- async listJobRuns(query = {}) {
2487
- const limit = clampLimit(query.limit);
2488
- const tenantCheck = this.tenantPredicate("listJobRuns", query.tenantId);
2489
- const keyset = query.cursor ? decodeKeysetCursor(query.cursor) : null;
2490
- const matched = [];
2491
- for (const r of this.store.runs.values()) {
2492
- if (tenantCheck && !tenantCheck(r)) continue;
2493
- if (query.poolId && r.pool !== query.poolId) continue;
2494
- if (query.rootRunId && r.rootRunId !== query.rootRunId) continue;
2495
- if (query.status && r.status !== query.status) continue;
2496
- if (query.since && r.createdAt.getTime() < query.since.getTime()) continue;
2497
- matched.push(r);
2498
- }
2499
- matched.sort((a, b) => {
2500
- const dt = b.createdAt.getTime() - a.createdAt.getTime();
2501
- if (dt !== 0) return dt;
2502
- return a.id < b.id ? 1 : a.id > b.id ? -1 : 0;
2503
- });
2504
- const seeked = keyset ? matched.filter((r) => {
2505
- const ct = r.createdAt.getTime();
2506
- const kt = keyset.createdAt.getTime();
2507
- if (ct < kt) return true;
2508
- if (ct > kt) return false;
2509
- return r.id < keyset.id;
2510
- }) : matched;
2511
- const hasMore = seeked.length > limit;
2512
- const page = hasMore ? seeked.slice(0, limit) : seeked;
2513
- const items = page.map(toJobRunSummary);
2514
- const last = page[page.length - 1];
2515
- const nextCursor = hasMore && last ? encodeKeysetCursor({ createdAt: last.createdAt, id: last.id }) : null;
2516
- return { items, nextCursor };
2517
- }
2518
- /**
2519
- * Direct lookup. Not on the protocol — concrete-class convenience for
2520
- * tests. Matches `DrizzleJobRunService.findByRootRunId` in spirit; both
2521
- * are debug / test helpers that sidestep the orchestrator.
2522
- */
2523
- findById(runId) {
2524
- return this.store.runs.get(runId) ?? null;
2525
- }
2526
- /** Public counterpart to the Drizzle backend's `findByRootRunId` helper. */
2527
- findByRootRunId(rootRunId) {
2528
- const out = [];
2529
- for (const r of this.store.runs.values()) {
2530
- if (r.rootRunId === rootRunId) out.push(r);
2531
- }
2532
- return out;
2533
- }
2534
- };
2535
- MemoryJobRunService = __decorateClass([
2536
- Injectable10(),
2537
- __decorateParam(0, Inject10(MemoryJobStore)),
2538
- __decorateParam(1, Inject10(JOB_ORCHESTRATOR)),
2539
- __decorateParam(2, Inject10(JOBS_MULTI_TENANT))
2540
- ], MemoryJobRunService);
2541
- function compareBy(a, b, order) {
2542
- switch (order) {
2543
- case "created_at asc":
2544
- return a.createdAt.getTime() - b.createdAt.getTime();
2545
- case "run_at desc":
2546
- return b.runAt.getTime() - a.runAt.getTime();
2547
- case "run_at asc":
2548
- return a.runAt.getTime() - b.runAt.getTime();
2549
- case "created_at desc":
2550
- default:
2551
- return b.createdAt.getTime() - a.createdAt.getTime();
2552
- }
2553
- }
2554
-
2555
- // runtime/subsystems/jobs/pool-config.loader.ts
2556
- import { existsSync, readFileSync } from "fs";
2557
- import { resolve } from "path";
2558
- import { parse as parseYaml } from "yaml";
2559
- var FRAMEWORK_POOLS = Object.freeze({
2560
- events_inbound: Object.freeze({
2561
- queue: "jobs-events-inbound",
2562
- concurrency: 20,
2563
- reserved: true,
2564
- description: "Inbound events drain (events subsystem outbox)."
2565
- }),
2566
- events_change: Object.freeze({
2567
- queue: "jobs-events-change",
2568
- concurrency: 30,
2569
- reserved: true,
2570
- description: "Change events drain (events subsystem outbox)."
2571
- }),
2572
- events_outbound: Object.freeze({
2573
- queue: "jobs-events-outbound",
2574
- concurrency: 10,
2575
- reserved: true,
2576
- description: "Outbound events drain (events subsystem outbox)."
2577
- }),
2578
- interactive: Object.freeze({
2579
- queue: "jobs-interactive",
2580
- concurrency: 20,
2581
- reserved: false,
2582
- description: "User-facing latency-sensitive jobs."
2583
- }),
2584
- batch: Object.freeze({
2585
- queue: "jobs-batch",
2586
- concurrency: 5,
2587
- reserved: false,
2588
- description: "Default pool for background jobs."
2589
- })
2590
- });
2591
- var RESERVED_POOL_NAMES = new Set(
2592
- Object.entries(FRAMEWORK_POOLS).filter(([, def]) => def.reserved).map(([name]) => name)
2593
- );
2594
- var cache = /* @__PURE__ */ new Map();
2595
- function loadPoolConfig(configPath) {
2596
- const resolved = resolve(configPath ?? `${process.cwd()}/codegen.config.yaml`);
2597
- const cached = cache.get(resolved);
2598
- if (cached) return cached;
2599
- const merged = /* @__PURE__ */ new Map();
2600
- for (const [name, def] of Object.entries(FRAMEWORK_POOLS)) {
2601
- merged.set(name, { ...def });
2602
- }
2603
- if (!existsSync(resolved)) {
2604
- cache.set(resolved, merged);
2605
- return merged;
2606
- }
2607
- let raw;
2608
- try {
2609
- raw = parseYaml(readFileSync(resolved, "utf8"));
2610
- } catch (err) {
2611
- throw new Error(
2612
- `pool-config.loader: failed to parse YAML at ${resolved}: ${err.message}`
2613
- );
2614
- }
2615
- const userPools = extractUserPools(raw);
2616
- for (const [name, userDef] of Object.entries(userPools)) {
2617
- const existing = merged.get(name);
2618
- if (existing) {
2619
- const next = {
2620
- queue: existing.queue,
2621
- concurrency: typeof userDef.concurrency === "number" ? userDef.concurrency : existing.concurrency,
2622
- reserved: existing.reserved,
2623
- description: userDef.description ?? existing.description
2624
- };
2625
- merged.set(name, next);
2626
- continue;
2627
- }
2628
- if (typeof userDef.queue !== "string" || userDef.queue.length === 0) {
2629
- throw new Error(
2630
- `pool-config.loader: pool '${name}' must declare a non-empty 'queue'.`
2631
- );
2632
- }
2633
- if (typeof userDef.concurrency !== "number" || userDef.concurrency <= 0) {
2634
- throw new Error(
2635
- `pool-config.loader: pool '${name}' must declare a positive 'concurrency'.`
2636
- );
2637
- }
2638
- if (userDef.reserved === true) {
2639
- throw new Error(
2640
- `pool-config.loader: user-defined pool '${name}' cannot set 'reserved: true' \u2014 reserved is framework-only.`
2641
- );
2642
- }
2643
- merged.set(name, {
2644
- queue: userDef.queue,
2645
- concurrency: userDef.concurrency,
2646
- reserved: false,
2647
- description: userDef.description
2648
- });
2649
- }
2650
- cache.set(resolved, merged);
2651
- return merged;
2652
- }
2653
- function allNonReservedPoolNames(config) {
2654
- const out = [];
2655
- for (const [name, def] of config) {
2656
- if (!def.reserved) out.push(name);
2657
- }
2658
- return out;
2659
- }
2660
- function allPoolNames(config) {
2661
- return [...config.keys()];
2662
- }
2663
- function extractUserPools(raw) {
2664
- if (!raw || typeof raw !== "object") return {};
2665
- const jobs2 = raw.jobs;
2666
- if (!jobs2 || typeof jobs2 !== "object") return {};
2667
- const pools = jobs2.pools;
2668
- if (!pools || typeof pools !== "object") return {};
2669
- const out = {};
2670
- for (const [name, def] of Object.entries(pools)) {
2671
- if (!def || typeof def !== "object") continue;
2672
- out[name] = def;
2673
- }
2674
- return out;
2675
- }
2676
-
2677
- // runtime/subsystems/jobs/bullmq.config.ts
2678
- var BULLMQ_CONNECTION = Symbol.for(tokenKey("jobs", "bullmq-connection"));
2679
- var BULLMQ_RESOLVED_CONFIG = Symbol.for(tokenKey("jobs", "bullmq-resolved-config"));
2680
- var DEFAULT_REDIS_URL = "redis://localhost:6379";
2681
- var DEFAULT_BULL_BOARD_MOUNT = "/admin/queues";
2682
- function resolveBullMqConfig(ext) {
2683
- const url = ext?.redis_url ?? process.env.REDIS_URL ?? DEFAULT_REDIS_URL;
2684
- const resolved = {
2685
- connection: { url },
2686
- queuePrefix: ext?.queue_prefix
2687
- };
2688
- if (ext?.bull_board?.enabled) {
2689
- resolved.bullBoard = {
2690
- enabled: true,
2691
- mountPath: ext.bull_board.mount_path ?? DEFAULT_BULL_BOARD_MOUNT
2692
- };
2693
- }
2694
- return resolved;
2695
- }
2696
- function resolvePoolQueueName(pool, config, poolConfig = loadPoolConfig()) {
2697
- const alias = poolConfig.get(pool)?.queue ?? pool;
2698
- const prefix = config?.queuePrefix;
2699
- return prefix ? `${prefix}:${alias}` : alias;
2700
- }
2701
-
2702
- // runtime/subsystems/jobs/jobs-domain.module.ts
2703
- var JobsDomainModule = class {
2704
- static forRoot(opts) {
2705
- const multiTenant = opts.multiTenant ?? false;
2706
- const providers = [
2707
- // JOB-8 — boolean provider consumed by the four service-layer backends.
2708
- // Always provided (even when `multiTenant === false`) so `@Inject`
2709
- // always resolves; backends short-circuit the enforcement path when
2710
- // the value is `false`. See `jobs-domain.tokens.ts` for the claim-loop
2711
- // cross-tenant-by-design decision.
2712
- { provide: JOBS_MULTI_TENANT, useValue: multiTenant }
2713
- ];
2714
- if (opts.backend === "memory") {
2715
- const store = new MemoryJobStore();
2716
- providers.push({ provide: MemoryJobStore, useValue: store });
2717
- providers.push(MemoryJobStepService);
2718
- providers.push({ provide: JOB_STEP_SERVICE, useExisting: MemoryJobStepService });
2719
- providers.push(MemoryJobOrchestrator);
2720
- providers.push({ provide: JOB_ORCHESTRATOR, useExisting: MemoryJobOrchestrator });
2721
- providers.push(MemoryJobRunService);
2722
- providers.push({ provide: JOB_RUN_SERVICE, useExisting: MemoryJobRunService });
2723
- } else if (opts.backend === "bullmq") {
2724
- const resolved = resolveBullMqConfig(opts.extensions?.bullmq);
2725
- providers.push({ provide: BULLMQ_CONNECTION, useValue: resolved.connection });
2726
- providers.push({ provide: BULLMQ_RESOLVED_CONFIG, useValue: resolved });
2727
- providers.push({
2728
- provide: JOB_ORCHESTRATOR,
2729
- useFactory: async (...args) => {
2730
- const specifier = "./job-orchestrator.bullmq-backend";
2731
- const mod = await import(specifier);
2732
- return new mod.BullMQJobOrchestrator(...args);
2733
- },
2734
- // The bullmq orchestrator constructor mirrors DrizzleJobOrchestrator's
2735
- // injection list: DRIZZLE + JOBS_MULTI_TENANT + the resolved BullMQ
2736
- // tokens. Importing token references would force a static dep on the
2737
- // tokens file in this module's import graph; using the existing
2738
- // symbols already in scope is sufficient.
2739
- inject: [DRIZZLE, JOBS_MULTI_TENANT, BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG]
2740
- });
2741
- providers.push({ provide: JOB_RUN_SERVICE, useClass: DrizzleJobRunService });
2742
- providers.push({ provide: JOB_STEP_SERVICE, useClass: DrizzleJobStepService });
2743
- } else {
2744
- providers.push({ provide: JOB_ORCHESTRATOR, useClass: DrizzleJobOrchestrator });
2745
- providers.push({ provide: JOB_RUN_SERVICE, useClass: DrizzleJobRunService });
2746
- providers.push({ provide: JOB_STEP_SERVICE, useClass: DrizzleJobStepService });
2747
- }
2748
- const exports = [
2749
- JOB_ORCHESTRATOR,
2750
- JOB_RUN_SERVICE,
2751
- JOB_STEP_SERVICE,
2752
- JOBS_MULTI_TENANT
2753
- ];
2754
- if (opts.backend === "bullmq") {
2755
- exports.push(BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG);
2756
- }
2757
- return {
2758
- module: JobsDomainModule,
2759
- global: true,
2760
- providers,
2761
- exports
2762
- };
2763
- }
2764
- };
2765
- JobsDomainModule = __decorateClass([
2766
- Module({})
2767
- ], JobsDomainModule);
2768
-
2769
- // runtime/subsystems/jobs/job-worker.ts
2770
- import { Inject as Inject11, Injectable as Injectable11, Logger as Logger5 } from "@nestjs/common";
2771
- import { and as and5, asc as asc2, desc as desc3, eq as eq5, inArray as inArray3, lt as lt2, lte, sql as sql7 } from "drizzle-orm";
2772
- var JOB_WORKER_OPTIONS = Symbol.for(tokenKey("jobs", "worker-options"));
2773
- var DEFAULT_POLL_INTERVAL_MS = 1e3;
2774
- var DEFAULT_STALE_SWEEPER_INTERVAL_MS = 6e4;
2775
- var DEFAULT_STALE_THRESHOLD_MS = 5 * 6e4;
2776
- var DEFAULT_SHUTDOWN_TIMEOUT_MS = 3e4;
2777
- function computeBackoff2(policy, attempts) {
2778
- const base = Math.max(policy.baseMs, 0);
2779
- if (policy.backoff === "fixed") {
2780
- return base;
2781
- }
2782
- const exponent = Math.max(attempts - 1, 0);
2783
- if (exponent >= 53) return Number.MAX_SAFE_INTEGER;
2784
- const raw = base * Math.pow(2, exponent);
2785
- if (!Number.isFinite(raw) || raw >= Number.MAX_SAFE_INTEGER) {
2786
- return Number.MAX_SAFE_INTEGER;
2787
- }
2788
- return raw;
2789
- }
2790
- function classifyError2(err, policy, currentAttempts) {
2791
- if (!policy) return "fail";
2792
- const errObj = err;
2793
- const name = errObj?.name;
2794
- const code = errObj?.code;
2795
- const nonRetryable = policy.nonRetryableErrors ?? [];
2796
- if (nonRetryable.some((n) => n === name || n === code)) return "fail";
2797
- if (currentAttempts + 1 >= policy.attempts) return "fail";
2798
- return "retry";
2799
- }
2800
- function serialiseError2(err, attempt, retryable) {
2801
- const e = err;
2802
- return {
2803
- message: e?.message ?? String(err),
2804
- stack: e?.stack,
2805
- retryable,
2806
- attempt
2807
- };
2808
- }
2809
- var JobWorker = class {
2810
- constructor(db, orchestrator, runService, stepService, options, moduleRef) {
2811
- this.db = db;
2812
- this.orchestrator = orchestrator;
2813
- this.runService = runService;
2814
- this.stepService = stepService;
2815
- this.options = options;
2816
- this.moduleRef = moduleRef;
2817
- this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
2818
- this.staleSweeperIntervalMs = options.staleSweeperIntervalMs ?? DEFAULT_STALE_SWEEPER_INTERVAL_MS;
2819
- this.staleThresholdMs = options.staleThresholdMs ?? DEFAULT_STALE_THRESHOLD_MS;
2820
- this.shutdownTimeoutMs = options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
2821
- this.sigtermHandler = () => {
2822
- if (this.sigtermHandled) return;
2823
- this.sigtermHandled = true;
2824
- void this.onModuleDestroy();
2825
- };
2826
- void this.runService;
2827
- }
2828
- db;
2829
- orchestrator;
2830
- runService;
2831
- stepService;
2832
- options;
2833
- moduleRef;
2834
- logger = new Logger5(JobWorker.name);
2835
- shuttingDown = false;
2836
- inFlight = /* @__PURE__ */ new Set();
2837
- pollTimer = null;
2838
- sweeperTimer = null;
2839
- sigtermHandled = false;
2840
- sigtermHandler;
2841
- pollIntervalMs;
2842
- staleSweeperIntervalMs;
2843
- staleThresholdMs;
2844
- shutdownTimeoutMs;
2845
- // ============================================================================
2846
- // Lifecycle
2847
- // ============================================================================
2848
- onModuleInit() {
2849
- this.pollTimer = setInterval(() => {
2850
- void this.pollAndProcess();
2851
- }, this.pollIntervalMs);
2852
- this.sweeperTimer = setInterval(() => {
2853
- void this.sweepStaleClaims();
2854
- }, this.staleSweeperIntervalMs);
2855
- process.on("SIGTERM", this.sigtermHandler);
2856
- }
2857
- async onModuleDestroy() {
2858
- if (this.shuttingDown) {
2859
- await this.drainInFlight();
2860
- return;
2861
- }
2862
- this.shuttingDown = true;
2863
- if (this.pollTimer) {
2864
- clearInterval(this.pollTimer);
2865
- this.pollTimer = null;
2866
- }
2867
- if (this.sweeperTimer) {
2868
- clearInterval(this.sweeperTimer);
2869
- this.sweeperTimer = null;
2870
- }
2871
- process.removeListener("SIGTERM", this.sigtermHandler);
2872
- await this.drainInFlight();
2873
- try {
2874
- await this.db.update(jobRuns).set({ status: "pending", claimedAt: null, startedAt: null }).where(
2875
- and5(eq5(jobRuns.status, "running"), eq5(jobRuns.pool, this.options.pool))
2876
- );
2877
- } catch (err) {
2878
- this.logger.error(`shutdown reset failed: ${err.message}`);
2879
- }
2880
- }
2881
- async drainInFlight() {
2882
- if (this.inFlight.size === 0) return;
2883
- const timeout = new Promise(
2884
- (resolve2) => setTimeout(resolve2, this.shutdownTimeoutMs)
2885
- );
2886
- await Promise.race([
2887
- Promise.allSettled([...this.inFlight]).then(() => void 0),
2888
- timeout
2889
- ]);
2890
- }
2891
- // ============================================================================
2892
- // Poll loop
2893
- // ============================================================================
2894
- async pollAndProcess() {
2895
- if (this.shuttingDown) return;
2896
- if (this.inFlight.size >= this.options.concurrency) return;
2897
- let claimed;
2898
- try {
2899
- claimed = await this.claimNext(this.options.pool);
2900
- } catch (err) {
2901
- this.logger.error(`claimNext failed: ${err.message}`);
2902
- return;
2903
- }
2904
- if (!claimed) return;
2905
- const run = claimed;
2906
- const promise = this.processRun(run).catch((err) => {
2907
- this.logger.error(
2908
- `processRun(${run.id}) unhandled: ${err.message}`
2909
- );
2910
- });
2911
- this.inFlight.add(promise);
2912
- promise.finally(() => {
2913
- this.inFlight.delete(promise);
2914
- });
2915
- }
2916
- /**
2917
- * Claim the next runnable row from the pool. Transaction ensures the
2918
- * select-candidate + update-to-running pair is atomic; FOR UPDATE SKIP
2919
- * LOCKED lets multiple workers share the table without serialising.
2920
- */
2921
- async claimNext(pool) {
2922
- return this.db.transaction(async (tx) => {
2923
- const candidates = await tx.select({ id: jobRuns.id }).from(jobRuns).where(
2924
- and5(
2925
- eq5(jobRuns.status, "pending"),
2926
- eq5(jobRuns.pool, pool),
2927
- lte(jobRuns.runAt, /* @__PURE__ */ new Date())
2928
- )
2929
- ).orderBy(desc3(jobRuns.priority), asc2(jobRuns.runAt)).limit(1).for("update", { skipLocked: true });
2930
- const candidate = candidates[0];
2931
- if (!candidate) return null;
2932
- const [claimed] = await tx.update(jobRuns).set({
2933
- status: "running",
2934
- claimedAt: /* @__PURE__ */ new Date(),
2935
- startedAt: /* @__PURE__ */ new Date(),
2936
- updatedAt: /* @__PURE__ */ new Date()
2937
- }).where(eq5(jobRuns.id, candidate.id)).returning();
2938
- return claimed ?? null;
2939
- });
2940
- }
2941
- // ============================================================================
2942
- // Stale claim sweeper
2943
- // ============================================================================
2944
- /**
2945
- * Release rows whose `claimed_at` is older than the threshold. Safe to
2946
- * run concurrently across workers — the two-phase tx (select-for-update
2947
- * then update) guarantees each stranded row is only reset once.
2948
- */
2949
- async sweepStaleClaims() {
2950
- if (this.shuttingDown) return;
2951
- try {
2952
- await this.db.transaction(async (tx) => {
2953
- const threshold = new Date(Date.now() - this.staleThresholdMs);
2954
- const stale = await tx.select({ id: jobRuns.id }).from(jobRuns).where(
2955
- and5(eq5(jobRuns.status, "running"), lt2(jobRuns.claimedAt, threshold))
2956
- ).for("update", { skipLocked: true });
2957
- if (stale.length === 0) return;
2958
- const ids = stale.map((r) => r.id);
2959
- await tx.update(jobRuns).set({ status: "pending", claimedAt: null, startedAt: null }).where(inArray3(jobRuns.id, ids));
2960
- for (const id of ids) {
2961
- this.logger.warn(`Recovered stale claim on run ${id}`);
2962
- }
2963
- });
2964
- } catch (err) {
2965
- this.logger.error(`sweepStaleClaims failed: ${err.message}`);
2966
- }
2967
- }
2968
- // ============================================================================
2969
- // processRun
2970
- // ============================================================================
2971
- async processRun(claimed) {
2972
- const registryEntry = JOB_HANDLER_REGISTRY.get(claimed.jobType);
2973
- if (!registryEntry) {
2974
- this.logger.error(
2975
- `No handler registered for jobType='${claimed.jobType}' (run ${claimed.id})`
2976
- );
2977
- await this.markFailed(
2978
- claimed,
2979
- new Error(`No handler registered for jobType='${claimed.jobType}'`),
2980
- /*finalAttempts*/
2981
- (claimed.attempts ?? 0) + 1
2982
- );
2983
- return;
2984
- }
2985
- if (claimed.concurrencyKey) {
2986
- const inflight = await this.db.select({ id: jobRuns.id }).from(jobRuns).where(
2987
- and5(
2988
- eq5(jobRuns.concurrencyKey, claimed.concurrencyKey),
2989
- eq5(jobRuns.status, "running")
2990
- )
2991
- );
2992
- const other = inflight.find((r) => r.id !== claimed.id);
2993
- if (other) {
2994
- await this.db.update(jobRuns).set({
2995
- status: "pending",
2996
- claimedAt: null,
2997
- startedAt: null,
2998
- updatedAt: /* @__PURE__ */ new Date()
2999
- }).where(eq5(jobRuns.id, claimed.id));
3000
- return;
3001
- }
3002
- }
3003
- const meta = registryEntry.meta;
3004
- const HandlerClass = registryEntry.handlerClass;
3005
- const handler = this.moduleRef.get(
3006
- HandlerClass,
3007
- { strict: false }
3008
- );
3009
- const ctx = {
3010
- input: claimed.input,
3011
- run: claimed,
3012
- step: this.makeStepFn(claimed),
3013
- spawnChild: this.makeSpawnFn(claimed),
3014
- logger: new Logger5(`JobRun:${claimed.id}`)
3015
- };
3016
- const attemptsBefore = claimed.attempts ?? 0;
3017
- try {
3018
- const output = await handler.run(ctx);
3019
- await this.db.update(jobRuns).set({
3020
- status: "completed",
3021
- output: output ?? {},
3022
- finishedAt: /* @__PURE__ */ new Date(),
3023
- updatedAt: /* @__PURE__ */ new Date(),
3024
- attempts: attemptsBefore + 1
3025
- }).where(eq5(jobRuns.id, claimed.id));
3026
- } catch (err) {
3027
- const policy = meta.retry;
3028
- const decision = classifyError2(err, policy, attemptsBefore);
3029
- const nextAttempts = attemptsBefore + 1;
3030
- if (decision === "retry" && policy) {
3031
- const delay = computeBackoff2(policy, nextAttempts);
3032
- await this.db.update(jobRuns).set({
3033
- status: "pending",
3034
- attempts: nextAttempts,
3035
- runAt: new Date(Date.now() + delay),
3036
- startedAt: null,
3037
- claimedAt: null,
3038
- error: serialiseError2(err, nextAttempts, true),
3039
- updatedAt: /* @__PURE__ */ new Date()
3040
- }).where(eq5(jobRuns.id, claimed.id));
3041
- } else {
3042
- await this.markFailed(claimed, err, nextAttempts);
3043
- }
3044
- }
3045
- }
3046
- async markFailed(claimed, err, finalAttempts) {
3047
- await this.db.update(jobRuns).set({
3048
- status: "failed",
3049
- attempts: finalAttempts,
3050
- finishedAt: /* @__PURE__ */ new Date(),
3051
- error: serialiseError2(err, finalAttempts, false),
3052
- updatedAt: /* @__PURE__ */ new Date()
3053
- }).where(eq5(jobRuns.id, claimed.id));
3054
- if (claimed.parentClosePolicy === "terminate") {
3055
- try {
3056
- await this.orchestrator.cancel(claimed.id, {
3057
- cascade: true,
3058
- reason: "parent-failed",
3059
- tenantId: claimed.tenantId
3060
- });
3061
- } catch (cascadeErr) {
3062
- this.logger.warn(
3063
- `cascade on failed run ${claimed.id}: ${cascadeErr.message}`
3064
- );
3065
- }
3066
- }
3067
- }
3068
- // ============================================================================
3069
- // ctx.step / ctx.spawnChild builders
3070
- // ============================================================================
3071
- makeStepFn(run) {
3072
- return async (stepId, fn, _opts) => {
3073
- void _opts;
3074
- const existing = await this.stepService.findStep(run.id, stepId);
3075
- if (existing?.status === "completed") {
3076
- return existing.output;
3077
- }
3078
- const seq = await this.nextStepSeq(run.id);
3079
- const startedAt = /* @__PURE__ */ new Date();
3080
- const nextAttempts = (existing?.attempts ?? 0) + 1;
3081
- await this.stepService.recordStep({
3082
- jobRunId: run.id,
3083
- stepId,
3084
- kind: "task",
3085
- seq,
3086
- status: "running",
3087
- startedAt,
3088
- attempts: nextAttempts
3089
- });
3090
- try {
3091
- const output = await fn();
3092
- await this.stepService.recordStep({
3093
- jobRunId: run.id,
3094
- stepId,
3095
- kind: "task",
3096
- seq,
3097
- status: "completed",
3098
- output,
3099
- finishedAt: /* @__PURE__ */ new Date(),
3100
- attempts: nextAttempts
3101
- });
3102
- return output;
3103
- } catch (err) {
3104
- await this.stepService.recordStep({
3105
- jobRunId: run.id,
3106
- stepId,
3107
- kind: "task",
3108
- seq,
3109
- status: "failed",
3110
- error: serialiseError2(err, nextAttempts, false),
3111
- finishedAt: /* @__PURE__ */ new Date(),
3112
- attempts: nextAttempts
3113
- });
3114
- throw err;
3115
- }
3116
- };
3117
- }
3118
- makeSpawnFn(run) {
3119
- return async (type, input, opts) => {
3120
- return this.orchestrator.start(type, input, {
3121
- parentRunId: run.id,
3122
- parentClosePolicy: opts?.closePolicy,
3123
- runAt: opts?.runAt,
3124
- priority: opts?.priority,
3125
- tags: opts?.tags,
3126
- triggerSource: "parent",
3127
- triggerRef: run.id
3128
- });
3129
- };
3130
- }
3131
- /**
3132
- * Allocate the next `seq` for a given run. SELECT-max approach — runs
3133
- * typically have <100 steps so the scan is cheap, and correctness across
3134
- * retries is more important than the microseconds saved by an in-memory
3135
- * counter (which would drift if the worker crashes mid-run and another
3136
- * worker resumes via stale-claim sweep).
3137
- */
3138
- async nextStepSeq(runId) {
3139
- const [row] = await this.db.execute(
3140
- sql7`SELECT COALESCE(MAX(seq), 0) + 1 AS next FROM job_step WHERE job_run_id = ${runId}`
3141
- );
3142
- const maybeRows = row?.rows;
3143
- if (Array.isArray(maybeRows) && maybeRows.length > 0) {
3144
- return Number(maybeRows[0].next ?? 1);
3145
- }
3146
- if (row && typeof row.next !== "undefined") {
3147
- return Number(row.next);
3148
- }
3149
- return 1;
3150
- }
3151
- // ============================================================================
3152
- // (suppress unused-import noise)
3153
- // ============================================================================
3154
- };
3155
- JobWorker = __decorateClass([
3156
- Injectable11(),
3157
- __decorateParam(0, Inject11(DRIZZLE)),
3158
- __decorateParam(1, Inject11(JOB_ORCHESTRATOR)),
3159
- __decorateParam(2, Inject11(JOB_RUN_SERVICE)),
3160
- __decorateParam(3, Inject11(JOB_STEP_SERVICE)),
3161
- __decorateParam(4, Inject11(JOB_WORKER_OPTIONS))
3162
- ], JobWorker);
3163
-
3164
- // runtime/subsystems/jobs/job-worker.module.ts
3165
- var DEFAULT_SHUTDOWN_TIMEOUT_MS2 = 3e4;
3166
- var JOB_WORKER_MODULE_OPTIONS = Symbol.for(tokenKey("jobs", "worker-module-options"));
3167
- var JobWorkerOrchestrator = class {
3168
- constructor(orchestrator, runService, stepService, options, db = null, moduleRef, bullConnection = null, bullConfig = null) {
3169
- this.orchestrator = orchestrator;
3170
- this.runService = runService;
3171
- this.stepService = stepService;
3172
- this.options = options;
3173
- this.db = db;
3174
- this.moduleRef = moduleRef;
3175
- this.bullConnection = bullConnection;
3176
- this.bullConfig = bullConfig;
3177
- }
3178
- orchestrator;
3179
- runService;
3180
- stepService;
3181
- options;
3182
- db;
3183
- moduleRef;
3184
- bullConnection;
3185
- bullConfig;
3186
- logger = new Logger6(JobWorkerOrchestrator.name);
3187
- workers = [];
3188
- // ============================================================================
3189
- // Lifecycle
3190
- // ============================================================================
3191
- async onModuleInit() {
3192
- const backend = this.options.backend ?? "drizzle";
3193
- const poolConfig = loadPoolConfig(this.options.configPath);
3194
- const entries = HandlerRegistry.getAll();
3195
- this.assertNoReservedPoolHandlers(entries, poolConfig);
3196
- const { orphaned } = await this.orchestrator.upsertJobRows(
3197
- entries,
3198
- poolConfig
3199
- );
3200
- if (backend !== "memory" && orphaned.length > 0) {
3201
- throw new BootValidationError(orphaned);
3202
- }
3203
- const activePools = this.options.pools ? this.options.pools : this.options.allPools ? allPoolNames(poolConfig) : allNonReservedPoolNames(poolConfig);
3204
- for (const poolName of activePools) {
3205
- const def = poolConfig.get(poolName);
3206
- if (!def) {
3207
- throw new Error(
3208
- `JobWorkerModule: active pool '${poolName}' is not defined in the resolved pool config. Configured pools: [${[...poolConfig.keys()].join(", ")}].`
3209
- );
3210
- }
3211
- const workerOptions = {
3212
- pool: poolName,
3213
- concurrency: def.concurrency,
3214
- shutdownTimeoutMs: this.options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS2
3215
- };
3216
- const worker = this.options.workerFactory ? this.options.workerFactory(workerOptions) : backend === "bullmq" ? await this.spawnBullMQWorker(poolName, def.queue, def.concurrency, poolConfig) : this.spawnWorker(workerOptions);
3217
- await worker.onModuleInit();
3218
- this.workers.push(worker);
3219
- this.logger.log(
3220
- `JobWorker started: pool='${poolName}' (queue='${def.queue}') concurrency=${def.concurrency} backend='${backend}'`
3221
- );
3222
- }
3223
- }
3224
- async onModuleDestroy() {
3225
- for (let i = this.workers.length - 1; i >= 0; i--) {
3226
- const worker = this.workers[i];
3227
- if (!worker) continue;
3228
- try {
3229
- await worker.onModuleDestroy();
3230
- } catch (err) {
3231
- this.logger.error(
3232
- `JobWorker shutdown failed: ${err.message}`
3233
- );
3234
- }
3235
- }
3236
- this.workers.length = 0;
3237
- const orch = this.orchestrator;
3238
- if (typeof orch.closeConnections === "function") {
3239
- try {
3240
- await orch.closeConnections();
3241
- } catch (err) {
3242
- this.logger.error(
3243
- `BullMQ orchestrator connection close failed: ${err.message}`
3244
- );
3245
- }
3246
- }
3247
- }
3248
- // ============================================================================
3249
- // Internals
3250
- // ============================================================================
3251
- /**
3252
- * Walk every registered handler; collect any whose declared `pool`
3253
- * targets a reserved pool from the resolved config. If non-empty,
3254
- * throw `ReservedPoolViolationError` with the offender list so the
3255
- * operator sees every violating class on a single boot.
3256
- */
3257
- assertNoReservedPoolHandlers(entries, poolConfig) {
3258
- const offenders = [];
3259
- for (const entry of entries) {
3260
- if (entry.type.startsWith("@framework/")) continue;
3261
- const declaredPool = entry.meta.pool ?? "batch";
3262
- const def = poolConfig.get(declaredPool);
3263
- if (def?.reserved) {
3264
- offenders.push({
3265
- handlerClass: entry.handlerClass.name,
3266
- pool: declaredPool
3267
- });
3268
- }
3269
- }
3270
- if (offenders.length > 0) {
3271
- throw new ReservedPoolViolationError(offenders);
3272
- }
3273
- }
3274
- /**
3275
- * Production worker spawn. `JobWorker` requires `DRIZZLE` so this only
3276
- * succeeds when the module was booted with `backend: 'drizzle'`. Memory
3277
- * mode tests must supply `workerFactory` — the memory backend has no
3278
- * polling loop equivalent (`MemoryJobOrchestrator` is direct-invocation
3279
- * only).
3280
- *
3281
- * We instantiate outside the Nest container because the module spawns
3282
- * N workers from a single options shape, which doesn't fit Nest's
3283
- * "one provider per token" model. The dependencies are passed
3284
- * positionally; the constructor's `@Inject` decorators are unused on
3285
- * this path (Nest still uses them when `JobWorker` is a provider — e.g.
3286
- * in JOB-6's standalone `worker.ts` entrypoint).
3287
- */
3288
- spawnWorker(workerOptions) {
3289
- if (!this.db) {
3290
- throw new Error(
3291
- `JobWorkerModule: in-process worker spawning requires the Drizzle backend (no DRIZZLE provider available). Memory-mode tests must pass 'workerFactory' to inject a stub.`
3292
- );
3293
- }
3294
- if (!this.moduleRef) {
3295
- throw new Error(
3296
- `JobWorkerModule: ModuleRef not available \u2014 cannot construct JobWorker with handler DI support. Ensure the orchestrator is resolved through the Nest container (not instantiated manually in tests).`
3297
- );
3298
- }
3299
- return new JobWorker(
3300
- this.db,
3301
- this.orchestrator,
3302
- this.runService,
3303
- this.stepService,
3304
- workerOptions,
3305
- this.moduleRef
3306
- );
3307
- }
3308
- /**
3309
- * BULLMQ-1 — spawn a per-pool `BullMQJobWorker`. Requires the Drizzle
3310
- * client (the worker drives `job_run` as the source of truth) AND the
3311
- * resolved BullMQ connection (bound by `JobsDomainModule` when
3312
- * `backend: 'bullmq'`). The queue name is derived identically to the
3313
- * orchestrator's `dispatch` via `resolvePoolQueueName(pool, …)` so producer
3314
- * and consumer agree.
3315
- */
3316
- /**
3317
- * #6 — async + dynamic-import. The `job-worker.bullmq-backend.ts` file is
3318
- * filtered out of the vendor set for drizzle/memory installs (no `bullmq`
3319
- * peer dep needed). The non-literal import specifier makes TS treat the
3320
- * module as `any` so the consumer's tsc never tries to resolve an absent
3321
- * file. This method is only entered when `backend === 'bullmq'` — at which
3322
- * point the file IS vendored.
3323
- */
3324
- async spawnBullMQWorker(pool, _queueAlias, concurrency, poolConfig) {
3325
- if (!this.db) {
3326
- throw new Error(
3327
- `JobWorkerModule: BullMQ worker spawning requires the Drizzle client (no DRIZZLE provider available) \u2014 job_run remains the source of truth.`
3328
- );
3329
- }
3330
- if (!this.bullConnection) {
3331
- throw new Error(
3332
- `JobWorkerModule: BullMQ worker spawning requires a resolved BULLMQ_CONNECTION. Ensure JobsDomainModule was booted with backend: 'bullmq'.`
3333
- );
3334
- }
3335
- if (!this.moduleRef) {
3336
- throw new Error(
3337
- `JobWorkerModule: ModuleRef not available \u2014 cannot construct BullMQJobWorker with handler DI support.`
3338
- );
3339
- }
3340
- const queueName = resolvePoolQueueName(pool, this.bullConfig, poolConfig);
3341
- const specifier = "./job-worker.bullmq-backend";
3342
- const mod = await import(specifier);
3343
- return new mod.BullMQJobWorker(
3344
- this.db,
3345
- this.orchestrator,
3346
- this.stepService,
3347
- {
3348
- pool,
3349
- queueName,
3350
- concurrency,
3351
- connection: this.bullConnection
3352
- },
3353
- this.moduleRef
3354
- );
3355
- }
3356
- };
3357
- JobWorkerOrchestrator = __decorateClass([
3358
- Injectable12(),
3359
- __decorateParam(0, Inject12(JOB_ORCHESTRATOR)),
3360
- __decorateParam(1, Inject12(JOB_RUN_SERVICE)),
3361
- __decorateParam(2, Inject12(JOB_STEP_SERVICE)),
3362
- __decorateParam(3, Inject12(JOB_WORKER_MODULE_OPTIONS)),
3363
- __decorateParam(4, Optional6()),
3364
- __decorateParam(4, Inject12(DRIZZLE)),
3365
- __decorateParam(5, Inject12(ModuleRef2)),
3366
- __decorateParam(6, Optional6()),
3367
- __decorateParam(6, Inject12(BULLMQ_CONNECTION)),
3368
- __decorateParam(7, Optional6()),
3369
- __decorateParam(7, Inject12(BULLMQ_RESOLVED_CONFIG))
3370
- ], JobWorkerOrchestrator);
3371
- var JobWorkerModule = class {
3372
- static forRoot(opts) {
3373
- return {
3374
- module: JobWorkerModule,
3375
- imports: [
3376
- JobsDomainModule.forRoot({
3377
- backend: opts.backend ?? "drizzle",
3378
- extensions: opts.domainModuleExtensions,
3379
- multiTenant: opts.multiTenant
3380
- })
3381
- ],
3382
- providers: [
3383
- { provide: JOB_WORKER_MODULE_OPTIONS, useValue: opts },
3384
- JobWorkerOrchestrator
3385
- ],
3386
- // BULLMQ-1 Phase 1 — export the options token so `BridgeModule`'s
3387
- // reserved-pool guard (`onModuleInit`) can actually inject it.
3388
- // Previously `exports: []` left the `@Optional()` inject resolving to
3389
- // `undefined` and the guard silently no-opped (a dead check). With the
3390
- // token exported the guard fires for real; consumers that omit the
3391
- // reserved pools (and don't set `allPools`) now fail fast with
3392
- // `BridgeReservedPoolsNotPolledError` — which is correct.
3393
- exports: [JOB_WORKER_MODULE_OPTIONS]
3394
- };
3395
- }
3396
- };
3397
- JobWorkerModule = __decorateClass([
3398
- Module2({})
3399
- ], JobWorkerModule);
3400
-
3401
- // runtime/subsystems/bridge/generated/registry.ts
3402
- var bridgeRegistry = {};
3403
-
3404
- // runtime/subsystems/bridge/bridge.module.ts
3405
- var BridgeModule = class {
3406
- /**
3407
- * `JOB_WORKER_MODULE_OPTIONS` is declared `@Optional()` so unit tests
3408
- * that mount `BridgeModule` alone (no `JobWorkerModule`) boot
3409
- * cleanly — the boot-time check skips when the token is undefined.
3410
- */
3411
- constructor(workerOpts) {
3412
- this.workerOpts = workerOpts;
3413
- }
3414
- workerOpts;
3415
- static forRoot(opts) {
3416
- const repoProvider = opts.backend === "memory" ? { provide: BRIDGE_DELIVERY_REPO, useClass: MemoryBridgeDeliveryRepo } : { provide: BRIDGE_DELIVERY_REPO, useClass: DrizzleBridgeDeliveryRepo };
3417
- return {
3418
- module: BridgeModule,
3419
- global: true,
3420
- // BridgeModule consumes EVENT_BUS / JOB_ORCHESTRATOR / DRIZZLE
3421
- // from sibling subsystems via DI; no `imports` needed here. The
3422
- // consumer is responsible for wiring EventsModule + JobsDomainModule
3423
- // (or JobWorkerModule, which transitively imports the latter)
3424
- // BEFORE BridgeModule.
3425
- providers: [
3426
- { provide: BRIDGE_MODULE_OPTIONS, useValue: opts },
3427
- { provide: BRIDGE_MULTI_TENANT, useValue: opts.multiTenant ?? false },
3428
- // Package mode threads the consumer's generated registry through
3429
- // `opts.registry`; vendored mode omits it and we fall back to the
3430
- // bundled `./generated/registry` (which IS the consumer's generated
3431
- // file in a vendored tree). See `BridgeModuleOptions.registry`.
3432
- { provide: BRIDGE_REGISTRY, useValue: opts.registry ?? bridgeRegistry },
3433
- repoProvider,
3434
- // Drain hook — always wired; `DrizzleEventBus` consumes it via
3435
- // `@Optional()`, so non-bridge mounts simply see `undefined`.
3436
- { provide: BRIDGE_OUTBOX_DRAIN_HOOK, useClass: BridgeOutboxDrainHook },
3437
- // Facade — class provider + token alias.
3438
- EventFlowService,
3439
- { provide: EVENT_FLOW, useExisting: EventFlowService },
3440
- // Framework handler — provider so DI can construct it. The
3441
- // `@JobHandler` decorator already auto-registers it in
3442
- // `JOB_HANDLER_REGISTRY` at module-load time, and its `jobs`
3443
- // row is upserted at `JobWorkerModule.onModuleInit`. We just
3444
- // need the class instantiated as a Nest provider so its DI
3445
- // deps (BRIDGE_DELIVERY_REPO, JOB_ORCHESTRATOR, EVENT_BUS,
3446
- // BRIDGE_REGISTRY, BRIDGE_MULTI_TENANT) resolve.
3447
- BridgeDeliveryHandler
3448
- ],
3449
- exports: [
3450
- EVENT_FLOW,
3451
- BRIDGE_DELIVERY_REPO,
3452
- BRIDGE_REGISTRY,
3453
- BRIDGE_MULTI_TENANT,
3454
- BRIDGE_MODULE_OPTIONS,
3455
- BRIDGE_OUTBOX_DRAIN_HOOK
3456
- ]
3457
- };
3458
- }
3459
- async onModuleInit() {
3460
- if (!this.workerOpts) return;
3461
- if (this.workerOpts.allPools) return;
3462
- const activePools = this.workerOpts.pools ?? [];
3463
- const missing = BRIDGE_RESERVED_POOLS.filter(
3464
- (p) => !activePools.includes(p)
3465
- );
3466
- if (missing.length > 0) {
3467
- throw new BridgeReservedPoolsNotPolledError(missing);
3468
- }
3469
- }
3470
- };
3471
- BridgeModule = __decorateClass([
3472
- Module3({}),
3473
- __decorateParam(0, Optional7()),
3474
- __decorateParam(0, Inject13(JOB_WORKER_MODULE_OPTIONS))
3475
- ], BridgeModule);
15
+ BRIDGE_DELIVERY_JOB_TYPE,
16
+ BridgeDeliveryHandler
17
+ } from "../../../chunk-YTN6BKWA.js";
18
+ import {
19
+ DrizzleBridgeDeliveryRepo
20
+ } from "../../../chunk-K2I6XIK5.js";
21
+ import {
22
+ assertTenantId
23
+ } from "../../../chunk-6DWFJNIK.js";
24
+ import {
25
+ MemoryBridgeDeliveryRepo
26
+ } from "../../../chunk-4DOJBQTP.js";
27
+ import {
28
+ bridgeDelivery,
29
+ bridgeDeliveryStatusEnum
30
+ } from "../../../chunk-2TVVBC53.js";
31
+ import {
32
+ BridgeReservedPoolsNotPolledError,
33
+ MissingTenantIdError,
34
+ UniqueConstraintError
35
+ } from "../../../chunk-NXXDZ6ZF.js";
36
+ import "../../../chunk-WPXNN6QS.js";
37
+ import "../../../chunk-RC23QROE.js";
38
+ import "../../../chunk-KMZCQASO.js";
39
+ import "../../../chunk-I6MG4M3F.js";
40
+ import "../../../chunk-JRVNVKN6.js";
41
+ import "../../../chunk-DV4RV2DC.js";
42
+ import "../../../chunk-5Y7W3XR6.js";
43
+ import "../../../chunk-4RFHUZXU.js";
44
+ import "../../../chunk-PNZSGAB2.js";
45
+ import "../../../chunk-SNQ3TOWP.js";
46
+ import "../../../chunk-T4BIIU5E.js";
47
+ import "../../../chunk-L3LZWWSX.js";
48
+ import "../../../chunk-I6MVCB5A.js";
49
+ import "../../../chunk-RHVN6NA7.js";
50
+ import "../../../chunk-CO6LUM72.js";
51
+ import "../../../chunk-BIO6F7YI.js";
52
+ import "../../../chunk-H5NH7KPE.js";
53
+ import {
54
+ BRIDGE_DELIVERY_REPO,
55
+ BRIDGE_MODULE_OPTIONS,
56
+ BRIDGE_MULTI_TENANT,
57
+ BRIDGE_OUTBOX_DRAIN_HOOK,
58
+ BRIDGE_REGISTRY,
59
+ EVENT_FLOW
60
+ } from "../../../chunk-4LH67P4U.js";
61
+ import "../../../chunk-OKXZ63IA.js";
62
+ import "../../../chunk-OFRRBC7M.js";
63
+ import "../../../chunk-GYGNEQSC.js";
64
+ import "../../../chunk-U64T4YZE.js";
65
+ import "../../../chunk-2E224ZSN.js";
3476
66
  export {
3477
67
  BRIDGE_DELIVERY_JOB_TYPE,
3478
68
  BRIDGE_DELIVERY_REPO,