@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.
- package/CHANGELOG.md +53 -0
- package/dist/chunk-24CWKBK5.js +94 -0
- package/dist/chunk-24CWKBK5.js.map +1 -0
- package/dist/chunk-2E224ZSN.js +20 -0
- package/dist/chunk-2E224ZSN.js.map +1 -0
- package/dist/chunk-2FTZLDBP.js +179 -0
- package/dist/chunk-2FTZLDBP.js.map +1 -0
- package/dist/chunk-2N4UG4VD.js +20 -0
- package/dist/chunk-2N4UG4VD.js.map +1 -0
- package/dist/chunk-2TVVBC53.js +92 -0
- package/dist/chunk-2TVVBC53.js.map +1 -0
- package/dist/chunk-2VHZ7EKC.js +37 -0
- package/dist/chunk-2VHZ7EKC.js.map +1 -0
- package/dist/chunk-32BMMV4H.js +109 -0
- package/dist/chunk-32BMMV4H.js.map +1 -0
- package/dist/chunk-32DOFN3T.js +4042 -0
- package/dist/chunk-32DOFN3T.js.map +1 -0
- package/dist/chunk-36U5UGIO.js +107 -0
- package/dist/chunk-36U5UGIO.js.map +1 -0
- package/dist/chunk-3CJFPU6Q.js +14 -0
- package/dist/chunk-3CJFPU6Q.js.map +1 -0
- package/dist/chunk-3NMCDN7L.js +90 -0
- package/dist/chunk-3NMCDN7L.js.map +1 -0
- package/dist/chunk-3SZFUTXE.js +62 -0
- package/dist/chunk-3SZFUTXE.js.map +1 -0
- package/dist/chunk-4DOJBQTP.js +117 -0
- package/dist/chunk-4DOJBQTP.js.map +1 -0
- package/dist/chunk-4JLJYWJC.js +308 -0
- package/dist/chunk-4JLJYWJC.js.map +1 -0
- package/dist/chunk-4KNXX6TI.js +29 -0
- package/dist/chunk-4KNXX6TI.js.map +1 -0
- package/dist/chunk-4LH67P4U.js +17 -0
- package/dist/chunk-4LH67P4U.js.map +1 -0
- package/dist/chunk-4MVGAMUA.js +40 -0
- package/dist/chunk-4MVGAMUA.js.map +1 -0
- package/dist/chunk-4OMHBMZJ.js +75 -0
- package/dist/chunk-4OMHBMZJ.js.map +1 -0
- package/dist/chunk-4RFHUZXU.js +635 -0
- package/dist/chunk-4RFHUZXU.js.map +1 -0
- package/dist/chunk-5A432NZJ.js +7 -0
- package/dist/chunk-5A432NZJ.js.map +1 -0
- package/dist/chunk-5Y7W3XR6.js +356 -0
- package/dist/chunk-5Y7W3XR6.js.map +1 -0
- package/dist/chunk-6DWFJNIK.js +15 -0
- package/dist/chunk-6DWFJNIK.js.map +1 -0
- package/dist/chunk-6I7ULIN6.js +15 -0
- package/dist/chunk-6I7ULIN6.js.map +1 -0
- package/dist/chunk-6XY6ZMMD.js +25 -0
- package/dist/chunk-6XY6ZMMD.js.map +1 -0
- package/dist/chunk-7B3RYX45.js +63 -0
- package/dist/chunk-7B3RYX45.js.map +1 -0
- package/dist/chunk-7C3FOSDI.js +1 -0
- package/dist/chunk-7C3FOSDI.js.map +1 -0
- package/dist/chunk-7KOW6PU6.js +59 -0
- package/dist/chunk-7KOW6PU6.js.map +1 -0
- package/dist/chunk-7LKAMLV4.js +92 -0
- package/dist/chunk-7LKAMLV4.js.map +1 -0
- package/dist/chunk-7RELQJIN.js +22 -0
- package/dist/chunk-7RELQJIN.js.map +1 -0
- package/dist/chunk-AHV4GDYM.js +63 -0
- package/dist/chunk-AHV4GDYM.js.map +1 -0
- package/dist/chunk-AQFQ4BYM.js +81 -0
- package/dist/chunk-AQFQ4BYM.js.map +1 -0
- package/dist/chunk-AS3NAZB6.js +14 -0
- package/dist/chunk-AS3NAZB6.js.map +1 -0
- package/dist/chunk-BGULBWKJ.js +88 -0
- package/dist/chunk-BGULBWKJ.js.map +1 -0
- package/dist/chunk-BIO6F7YI.js +17 -0
- package/dist/chunk-BIO6F7YI.js.map +1 -0
- package/dist/chunk-BOPZWRJK.js +36 -0
- package/dist/chunk-BOPZWRJK.js.map +1 -0
- package/dist/chunk-BPARRK6F.js +14 -0
- package/dist/chunk-BPARRK6F.js.map +1 -0
- package/dist/chunk-CO6LUM72.js +59 -0
- package/dist/chunk-CO6LUM72.js.map +1 -0
- package/dist/chunk-COGHTKXY.js +84 -0
- package/dist/chunk-COGHTKXY.js.map +1 -0
- package/dist/chunk-DCCZB4UC.js +100 -0
- package/dist/chunk-DCCZB4UC.js.map +1 -0
- package/dist/chunk-DKKFTHHI.js +53 -0
- package/dist/chunk-DKKFTHHI.js.map +1 -0
- package/dist/chunk-DV4RV2DC.js +59 -0
- package/dist/chunk-DV4RV2DC.js.map +1 -0
- package/dist/chunk-EDKJU5BO.js +11 -0
- package/dist/chunk-EDKJU5BO.js.map +1 -0
- package/dist/chunk-EO2QPOKH.js +116 -0
- package/dist/chunk-EO2QPOKH.js.map +1 -0
- package/dist/chunk-EOLLMEAH.js +155 -0
- package/dist/chunk-EOLLMEAH.js.map +1 -0
- package/dist/chunk-EWYCWP4H.js +14 -0
- package/dist/chunk-EWYCWP4H.js.map +1 -0
- package/dist/chunk-EXVDJMIY.js +33 -0
- package/dist/chunk-EXVDJMIY.js.map +1 -0
- package/dist/chunk-FASRXRX5.js +19 -0
- package/dist/chunk-FASRXRX5.js.map +1 -0
- package/dist/chunk-FI34KYZ5.js +1 -0
- package/dist/chunk-FI34KYZ5.js.map +1 -0
- package/dist/chunk-FN2PYDPP.js +1 -0
- package/dist/chunk-FN2PYDPP.js.map +1 -0
- package/dist/chunk-GM3RMJIJ.js +92 -0
- package/dist/chunk-GM3RMJIJ.js.map +1 -0
- package/dist/chunk-GYGNEQSC.js +9 -0
- package/dist/chunk-GYGNEQSC.js.map +1 -0
- package/dist/chunk-H5NH7KPE.js +21 -0
- package/dist/chunk-H5NH7KPE.js.map +1 -0
- package/dist/chunk-HNWZFNKP.js +168 -0
- package/dist/chunk-HNWZFNKP.js.map +1 -0
- package/dist/chunk-HUH73XGI.js +1 -0
- package/dist/chunk-HUH73XGI.js.map +1 -0
- package/dist/chunk-I6MG4M3F.js +201 -0
- package/dist/chunk-I6MG4M3F.js.map +1 -0
- package/dist/chunk-I6MVCB5A.js +39 -0
- package/dist/chunk-I6MVCB5A.js.map +1 -0
- package/dist/chunk-IBGER4YK.js +12 -0
- package/dist/chunk-IBGER4YK.js.map +1 -0
- package/dist/chunk-IF5I3DAA.js +92 -0
- package/dist/chunk-IF5I3DAA.js.map +1 -0
- package/dist/chunk-IP4OO26U.js +54 -0
- package/dist/chunk-IP4OO26U.js.map +1 -0
- package/dist/chunk-IWAOY6KC.js +1 -0
- package/dist/chunk-IWAOY6KC.js.map +1 -0
- package/dist/chunk-J37YWU7Y.js +19 -0
- package/dist/chunk-J37YWU7Y.js.map +1 -0
- package/dist/chunk-J6KZS54B.js +269 -0
- package/dist/chunk-J6KZS54B.js.map +1 -0
- package/dist/chunk-J6MN42LG.js +19 -0
- package/dist/chunk-J6MN42LG.js.map +1 -0
- package/dist/chunk-JRQO2IOF.js +65 -0
- package/dist/chunk-JRQO2IOF.js.map +1 -0
- package/dist/chunk-JRVNVKN6.js +212 -0
- package/dist/chunk-JRVNVKN6.js.map +1 -0
- package/dist/chunk-JWNHNUYL.js +96 -0
- package/dist/chunk-JWNHNUYL.js.map +1 -0
- package/dist/chunk-K2I6XIK5.js +122 -0
- package/dist/chunk-K2I6XIK5.js.map +1 -0
- package/dist/chunk-KMZCQASO.js +111 -0
- package/dist/chunk-KMZCQASO.js.map +1 -0
- package/dist/chunk-KVOWSC5S.js +1 -0
- package/dist/chunk-KVOWSC5S.js.map +1 -0
- package/dist/chunk-KYR3B3OW.js +79 -0
- package/dist/chunk-KYR3B3OW.js.map +1 -0
- package/dist/chunk-L3LZWWSX.js +61 -0
- package/dist/chunk-L3LZWWSX.js.map +1 -0
- package/dist/chunk-L4SDDEEU.js +1 -0
- package/dist/chunk-L4SDDEEU.js.map +1 -0
- package/dist/chunk-L6FTY45T.js +13 -0
- package/dist/chunk-L6FTY45T.js.map +1 -0
- package/dist/chunk-L7BNNRGI.js +134 -0
- package/dist/chunk-L7BNNRGI.js.map +1 -0
- package/dist/chunk-LG57S2SC.js +150 -0
- package/dist/chunk-LG57S2SC.js.map +1 -0
- package/dist/chunk-M6QLSLPO.js +97 -0
- package/dist/chunk-M6QLSLPO.js.map +1 -0
- package/dist/chunk-MZ6GV4YF.js +21 -0
- package/dist/chunk-MZ6GV4YF.js.map +1 -0
- package/dist/chunk-N5OTOWTP.js +55 -0
- package/dist/chunk-N5OTOWTP.js.map +1 -0
- package/dist/chunk-NN7XZEGF.js +14 -0
- package/dist/chunk-NN7XZEGF.js.map +1 -0
- package/dist/chunk-NPFPZ2HO.js +13 -0
- package/dist/chunk-NPFPZ2HO.js.map +1 -0
- package/dist/chunk-NXXDZ6ZF.js +42 -0
- package/dist/chunk-NXXDZ6ZF.js.map +1 -0
- package/dist/chunk-NYBCQZC7.js +11 -0
- package/dist/chunk-NYBCQZC7.js.map +1 -0
- package/dist/chunk-OFRRBC7M.js +78 -0
- package/dist/chunk-OFRRBC7M.js.map +1 -0
- package/dist/chunk-OGIZXGPY.js +222 -0
- package/dist/chunk-OGIZXGPY.js.map +1 -0
- package/dist/chunk-OKXZ63IA.js +168 -0
- package/dist/chunk-OKXZ63IA.js.map +1 -0
- package/dist/chunk-OSQRXVG2.js +58 -0
- package/dist/chunk-OSQRXVG2.js.map +1 -0
- package/dist/chunk-OTDN3OUQ.js +215 -0
- package/dist/chunk-OTDN3OUQ.js.map +1 -0
- package/dist/chunk-OZZJDRGW.js +122 -0
- package/dist/chunk-OZZJDRGW.js.map +1 -0
- package/dist/chunk-PNZSGAB2.js +114 -0
- package/dist/chunk-PNZSGAB2.js.map +1 -0
- package/dist/chunk-PRWIX6UW.js +21 -0
- package/dist/chunk-PRWIX6UW.js.map +1 -0
- package/dist/chunk-PSXUNOVU.js +7 -0
- package/dist/chunk-PSXUNOVU.js.map +1 -0
- package/dist/chunk-QLTJSCE6.js +44 -0
- package/dist/chunk-QLTJSCE6.js.map +1 -0
- package/dist/chunk-RC23QROE.js +447 -0
- package/dist/chunk-RC23QROE.js.map +1 -0
- package/dist/chunk-RFH7N6EP.js +36 -0
- package/dist/chunk-RFH7N6EP.js.map +1 -0
- package/dist/chunk-RHVN6NA7.js +134 -0
- package/dist/chunk-RHVN6NA7.js.map +1 -0
- package/dist/chunk-S7C6TIIF.js +21 -0
- package/dist/chunk-S7C6TIIF.js.map +1 -0
- package/dist/chunk-SNQ3TOWP.js +20 -0
- package/dist/chunk-SNQ3TOWP.js.map +1 -0
- package/dist/chunk-SOVM2VEK.js +14 -0
- package/dist/chunk-SOVM2VEK.js.map +1 -0
- package/dist/chunk-SQDOBLBP.js +13 -0
- package/dist/chunk-SQDOBLBP.js.map +1 -0
- package/dist/chunk-SR7F3TJY.js +130 -0
- package/dist/chunk-SR7F3TJY.js.map +1 -0
- package/dist/chunk-SZVPIHWE.js +129 -0
- package/dist/chunk-SZVPIHWE.js.map +1 -0
- package/dist/chunk-T4BIIU5E.js +89 -0
- package/dist/chunk-T4BIIU5E.js.map +1 -0
- package/dist/chunk-T6C4LFLC.js +112 -0
- package/dist/chunk-T6C4LFLC.js.map +1 -0
- package/dist/chunk-TNXH7BJS.js +48 -0
- package/dist/chunk-TNXH7BJS.js.map +1 -0
- package/dist/chunk-U64T4YZE.js +9 -0
- package/dist/chunk-U64T4YZE.js.map +1 -0
- package/dist/chunk-UQ5EHOH2.js +39 -0
- package/dist/chunk-UQ5EHOH2.js.map +1 -0
- package/dist/chunk-UTN4GBPQ.js +1 -0
- package/dist/chunk-UTN4GBPQ.js.map +1 -0
- package/dist/chunk-V4AF6DI4.js +16 -0
- package/dist/chunk-V4AF6DI4.js.map +1 -0
- package/dist/chunk-W72PRNJY.js +126 -0
- package/dist/chunk-W72PRNJY.js.map +1 -0
- package/dist/chunk-WEVWJKOW.js +81 -0
- package/dist/chunk-WEVWJKOW.js.map +1 -0
- package/dist/chunk-WL67FZGF.js +21 -0
- package/dist/chunk-WL67FZGF.js.map +1 -0
- package/dist/chunk-WPXNN6QS.js +290 -0
- package/dist/chunk-WPXNN6QS.js.map +1 -0
- package/dist/chunk-WRUUSZDJ.js +29 -0
- package/dist/chunk-WRUUSZDJ.js.map +1 -0
- package/dist/chunk-X2GMTYPA.js +50 -0
- package/dist/chunk-X2GMTYPA.js.map +1 -0
- package/dist/chunk-XCEI7NUH.js +41 -0
- package/dist/chunk-XCEI7NUH.js.map +1 -0
- package/dist/chunk-Y7GDG744.js +88 -0
- package/dist/chunk-Y7GDG744.js.map +1 -0
- package/dist/chunk-Y7RRSEOC.js +9 -0
- package/dist/chunk-Y7RRSEOC.js.map +1 -0
- package/dist/chunk-YPWODKD5.js +184 -0
- package/dist/chunk-YPWODKD5.js.map +1 -0
- package/dist/chunk-YSLTTQLC.js +25 -0
- package/dist/chunk-YSLTTQLC.js.map +1 -0
- package/dist/chunk-YTN6BKWA.js +121 -0
- package/dist/chunk-YTN6BKWA.js.map +1 -0
- package/dist/chunk-Z7PQCAVK.js +200 -0
- package/dist/chunk-Z7PQCAVK.js.map +1 -0
- package/dist/chunk-ZUKFQL6E.js +47 -0
- package/dist/chunk-ZUKFQL6E.js.map +1 -0
- package/dist/chunk-ZUMULSEQ.js +1 -0
- package/dist/chunk-ZUMULSEQ.js.map +1 -0
- package/dist/runtime/analytics/index.js +8 -41
- package/dist/runtime/analytics/index.js.map +1 -1
- package/dist/runtime/analytics/types.js +8 -41
- package/dist/runtime/analytics/types.js.map +1 -1
- package/dist/runtime/base-classes/activity-entity-repository.js +6 -312
- package/dist/runtime/base-classes/activity-entity-repository.js.map +1 -1
- package/dist/runtime/base-classes/activity-entity-service.js +6 -212
- package/dist/runtime/base-classes/activity-entity-service.js.map +1 -1
- package/dist/runtime/base-classes/base-read-use-cases.js +5 -27
- package/dist/runtime/base-classes/base-read-use-cases.js.map +1 -1
- package/dist/runtime/base-classes/base-repository.js +5 -277
- package/dist/runtime/base-classes/base-repository.js.map +1 -1
- package/dist/runtime/base-classes/base-service.js +5 -184
- package/dist/runtime/base-classes/base-service.js.map +1 -1
- package/dist/runtime/base-classes/index.js +59 -1076
- package/dist/runtime/base-classes/index.js.map +1 -1
- package/dist/runtime/base-classes/integrated-entity-repository.js +6 -486
- package/dist/runtime/base-classes/integrated-entity-repository.js.map +1 -1
- package/dist/runtime/base-classes/integrated-entity-service.js +6 -213
- package/dist/runtime/base-classes/integrated-entity-service.js.map +1 -1
- package/dist/runtime/base-classes/junction-integration-repository.js +8 -448
- package/dist/runtime/base-classes/junction-integration-repository.js.map +1 -1
- package/dist/runtime/base-classes/knowledge-entity-repository.js +6 -283
- package/dist/runtime/base-classes/knowledge-entity-repository.js.map +1 -1
- package/dist/runtime/base-classes/knowledge-entity-service.js +6 -190
- package/dist/runtime/base-classes/knowledge-entity-service.js.map +1 -1
- package/dist/runtime/base-classes/lifecycle-events.js +8 -70
- package/dist/runtime/base-classes/lifecycle-events.js.map +1 -1
- package/dist/runtime/base-classes/metadata-entity-repository.js +6 -330
- package/dist/runtime/base-classes/metadata-entity-repository.js.map +1 -1
- package/dist/runtime/base-classes/metadata-entity-service.js +6 -212
- package/dist/runtime/base-classes/metadata-entity-service.js.map +1 -1
- package/dist/runtime/base-classes/tenant-context.js +10 -36
- package/dist/runtime/base-classes/tenant-context.js.map +1 -1
- package/dist/runtime/base-classes/with-analytics.js +4 -7
- package/dist/runtime/base-classes/with-analytics.js.map +1 -1
- package/dist/runtime/constants/tokens.js +5 -3
- package/dist/runtime/constants/tokens.js.map +1 -1
- package/dist/runtime/eav-helpers.js +2 -0
- package/dist/runtime/eav-helpers.js.map +1 -1
- package/dist/runtime/pipes/zod-validation.pipe.js +3 -10
- package/dist/runtime/pipes/zod-validation.pipe.js.map +1 -1
- package/dist/runtime/shared/openapi/error-response.dto.js +5 -8
- package/dist/runtime/shared/openapi/error-response.dto.js.map +1 -1
- package/dist/runtime/shared/openapi/errors.js +5 -19
- package/dist/runtime/shared/openapi/errors.js.map +1 -1
- package/dist/runtime/shared/openapi/index.js +15 -106
- package/dist/runtime/shared/openapi/index.js.map +1 -1
- package/dist/runtime/shared/openapi/registry.js +6 -103
- package/dist/runtime/shared/openapi/registry.js.map +1 -1
- package/dist/runtime/shared/openapi/registry.tokens.js +4 -2
- package/dist/runtime/shared/openapi/registry.tokens.js.map +1 -1
- package/dist/runtime/subsystems/analytics/analytics.module.js +8 -117
- package/dist/runtime/subsystems/analytics/analytics.module.js.map +1 -1
- package/dist/runtime/subsystems/analytics/analytics.tokens.js +7 -8
- package/dist/runtime/subsystems/analytics/analytics.tokens.js.map +1 -1
- package/dist/runtime/subsystems/analytics/cube-backend.js +6 -71
- package/dist/runtime/subsystems/analytics/cube-backend.js.map +1 -1
- package/dist/runtime/subsystems/analytics/index.js +16 -117
- package/dist/runtime/subsystems/analytics/index.js.map +1 -1
- package/dist/runtime/subsystems/analytics/noop-backend.js +4 -21
- package/dist/runtime/subsystems/analytics/noop-backend.js.map +1 -1
- package/dist/runtime/subsystems/auth/auth-oauth-state.schema.js +4 -8
- package/dist/runtime/subsystems/auth/auth-oauth-state.schema.js.map +1 -1
- package/dist/runtime/subsystems/auth/auth.module.js +12 -359
- package/dist/runtime/subsystems/auth/auth.module.js.map +1 -1
- package/dist/runtime/subsystems/auth/auth.tokens.js +12 -13
- package/dist/runtime/subsystems/auth/auth.tokens.js.map +1 -1
- package/dist/runtime/subsystems/auth/backends/encryption-key/env.js +4 -49
- package/dist/runtime/subsystems/auth/backends/encryption-key/env.js.map +1 -1
- package/dist/runtime/subsystems/auth/backends/state-store.drizzle-backend.js +6 -64
- package/dist/runtime/subsystems/auth/backends/state-store.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/auth/backends/state-store.memory-backend.js +5 -47
- package/dist/runtime/subsystems/auth/backends/state-store.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/auth/controllers/auth.controller.js +5 -139
- package/dist/runtime/subsystems/auth/controllers/auth.controller.js.map +1 -1
- package/dist/runtime/subsystems/auth/index.js +53 -542
- package/dist/runtime/subsystems/auth/index.js.map +1 -1
- package/dist/runtime/subsystems/auth/middleware/requester-context.js +9 -65
- package/dist/runtime/subsystems/auth/middleware/requester-context.js.map +1 -1
- package/dist/runtime/subsystems/auth/protocols/oauth-state-store.js +4 -9
- package/dist/runtime/subsystems/auth/protocols/oauth-state-store.js.map +1 -1
- package/dist/runtime/subsystems/auth/runtime/connection-broken.error.js +4 -15
- package/dist/runtime/subsystems/auth/runtime/connection-broken.error.js.map +1 -1
- package/dist/runtime/subsystems/auth/runtime/oauth2-refresh.strategy.js +5 -104
- package/dist/runtime/subsystems/auth/runtime/oauth2-refresh.strategy.js.map +1 -1
- package/dist/runtime/subsystems/auth/runtime/session-expired.error.js +5 -16
- package/dist/runtime/subsystems/auth/runtime/session-expired.error.js.map +1 -1
- package/dist/runtime/subsystems/auth/runtime/with-auth-retry.js +5 -29
- package/dist/runtime/subsystems/auth/runtime/with-auth-retry.js.map +1 -1
- package/dist/runtime/subsystems/bridge/assert-tenant-id.js +5 -18
- package/dist/runtime/subsystems/bridge/assert-tenant-id.js.map +1 -1
- package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js +12 -184
- package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js.map +1 -1
- package/dist/runtime/subsystems/bridge/bridge-delivery.drizzle-backend.js +10 -448
- package/dist/runtime/subsystems/bridge/bridge-delivery.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/bridge/bridge-delivery.memory-backend.js +5 -126
- package/dist/runtime/subsystems/bridge/bridge-delivery.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/bridge/bridge-delivery.schema.js +6 -308
- package/dist/runtime/subsystems/bridge/bridge-delivery.schema.js.map +1 -1
- package/dist/runtime/subsystems/bridge/bridge-errors.js +6 -35
- package/dist/runtime/subsystems/bridge/bridge-errors.js.map +1 -1
- package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js +14 -606
- package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js.map +1 -1
- package/dist/runtime/subsystems/bridge/bridge.module.js +35 -3476
- package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
- package/dist/runtime/subsystems/bridge/bridge.tokens.js +9 -7
- package/dist/runtime/subsystems/bridge/bridge.tokens.js.map +1 -1
- package/dist/runtime/subsystems/bridge/event-flow.service.js +11 -137
- package/dist/runtime/subsystems/bridge/event-flow.service.js.map +1 -1
- package/dist/runtime/subsystems/bridge/generated/registry.js +4 -2
- package/dist/runtime/subsystems/bridge/generated/registry.js.map +1 -1
- package/dist/runtime/subsystems/bridge/index.js +60 -3470
- package/dist/runtime/subsystems/bridge/index.js.map +1 -1
- package/dist/runtime/subsystems/bridge/reserved-pools.js +4 -6
- package/dist/runtime/subsystems/bridge/reserved-pools.js.map +1 -1
- package/dist/runtime/subsystems/cache/cache.drizzle-backend.js +10 -133
- package/dist/runtime/subsystems/cache/cache.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/cache/cache.memory-backend.js +6 -101
- package/dist/runtime/subsystems/cache/cache.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/cache/cache.module.js +10 -278
- package/dist/runtime/subsystems/cache/cache.module.js.map +1 -1
- package/dist/runtime/subsystems/cache/cache.schema.js +4 -14
- package/dist/runtime/subsystems/cache/cache.schema.js.map +1 -1
- package/dist/runtime/subsystems/cache/cache.tokens.js +6 -7
- package/dist/runtime/subsystems/cache/cache.tokens.js.map +1 -1
- package/dist/runtime/subsystems/cache/index.js +20 -278
- package/dist/runtime/subsystems/cache/index.js.map +1 -1
- package/dist/runtime/subsystems/events/domain-events.schema.js +3 -72
- package/dist/runtime/subsystems/events/domain-events.schema.js.map +1 -1
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +9 -413
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/events/event-bus.memory-backend.js +7 -235
- package/dist/runtime/subsystems/events/event-bus.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/events/event-bus.redis-backend.js +8 -20
- package/dist/runtime/subsystems/events/event-bus.redis-backend.js.map +1 -1
- package/dist/runtime/subsystems/events/event-keyset-cursor.js +8 -30
- package/dist/runtime/subsystems/events/event-keyset-cursor.js.map +1 -1
- package/dist/runtime/subsystems/events/event-read.protocol.js +2 -0
- package/dist/runtime/subsystems/events/event-read.protocol.js.map +1 -1
- package/dist/runtime/subsystems/events/events-errors.js +4 -11
- package/dist/runtime/subsystems/events/events-errors.js.map +1 -1
- package/dist/runtime/subsystems/events/events.module.js +15 -949
- package/dist/runtime/subsystems/events/events.module.js.map +1 -1
- package/dist/runtime/subsystems/events/events.tokens.js +10 -11
- package/dist/runtime/subsystems/events/events.tokens.js.map +1 -1
- package/dist/runtime/subsystems/events/generated/bus.js +9 -240
- package/dist/runtime/subsystems/events/generated/bus.js.map +1 -1
- package/dist/runtime/subsystems/events/generated/index.js +23 -240
- package/dist/runtime/subsystems/events/generated/index.js.map +1 -1
- package/dist/runtime/subsystems/events/generated/registry.js +5 -82
- package/dist/runtime/subsystems/events/generated/registry.js.map +1 -1
- package/dist/runtime/subsystems/events/generated/schemas.js +12 -52
- package/dist/runtime/subsystems/events/generated/schemas.js.map +1 -1
- package/dist/runtime/subsystems/events/generated/types.js +1 -0
- package/dist/runtime/subsystems/events/index.js +32 -949
- package/dist/runtime/subsystems/events/index.js.map +1 -1
- package/dist/runtime/subsystems/index.js +171 -5912
- package/dist/runtime/subsystems/index.js.map +1 -1
- package/dist/runtime/subsystems/integration/build-change-source.js +6 -178
- package/dist/runtime/subsystems/integration/build-change-source.js.map +1 -1
- package/dist/runtime/subsystems/integration/deep-equal.differ.js +4 -109
- package/dist/runtime/subsystems/integration/deep-equal.differ.js.map +1 -1
- package/dist/runtime/subsystems/integration/detection-config.schema.js +11 -78
- package/dist/runtime/subsystems/integration/detection-config.schema.js.map +1 -1
- package/dist/runtime/subsystems/integration/entity-change-source-registry.memory.js +5 -30
- package/dist/runtime/subsystems/integration/entity-change-source-registry.memory.js.map +1 -1
- package/dist/runtime/subsystems/integration/entity-change-source-registry.protocol.js +4 -9
- package/dist/runtime/subsystems/integration/entity-change-source-registry.protocol.js.map +1 -1
- package/dist/runtime/subsystems/integration/execute-integration.use-case.js +6 -239
- package/dist/runtime/subsystems/integration/execute-integration.use-case.js.map +1 -1
- package/dist/runtime/subsystems/integration/incremental-read.js +5 -144
- package/dist/runtime/subsystems/integration/incremental-read.js.map +1 -1
- package/dist/runtime/subsystems/integration/index.js +83 -1352
- package/dist/runtime/subsystems/integration/index.js.map +1 -1
- package/dist/runtime/subsystems/integration/integration-audit.schema.js +10 -155
- package/dist/runtime/subsystems/integration/integration-audit.schema.js.map +1 -1
- package/dist/runtime/subsystems/integration/integration-cursor-store.drizzle-backend.js +7 -270
- package/dist/runtime/subsystems/integration/integration-cursor-store.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/integration/integration-cursor-store.memory-backend.js +4 -65
- package/dist/runtime/subsystems/integration/integration-cursor-store.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/integration/integration-errors.js +5 -15
- package/dist/runtime/subsystems/integration/integration-errors.js.map +1 -1
- package/dist/runtime/subsystems/integration/integration-field-diff.protocol.js +5 -7
- package/dist/runtime/subsystems/integration/integration-field-diff.protocol.js.map +1 -1
- package/dist/runtime/subsystems/integration/integration-run-recorder.drizzle-backend.js +8 -303
- package/dist/runtime/subsystems/integration/integration-run-recorder.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/integration/integration-run-recorder.memory-backend.js +5 -125
- package/dist/runtime/subsystems/integration/integration-run-recorder.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/integration/integration.module.js +13 -700
- package/dist/runtime/subsystems/integration/integration.module.js.map +1 -1
- package/dist/runtime/subsystems/integration/integration.tokens.js +11 -9
- package/dist/runtime/subsystems/integration/integration.tokens.js.map +1 -1
- package/dist/runtime/subsystems/integration/loopback.middleware.js +4 -16
- package/dist/runtime/subsystems/integration/loopback.middleware.js.map +1 -1
- package/dist/runtime/subsystems/integration/poll-change-source.js +4 -89
- package/dist/runtime/subsystems/integration/poll-change-source.js.map +1 -1
- package/dist/runtime/subsystems/integration/webhook-change-source.js +4 -70
- package/dist/runtime/subsystems/integration/webhook-change-source.js.map +1 -1
- package/dist/runtime/subsystems/jobs/bullmq.config.js +9 -140
- package/dist/runtime/subsystems/jobs/bullmq.config.js.map +1 -1
- package/dist/runtime/subsystems/jobs/index.js +88 -2691
- package/dist/runtime/subsystems/jobs/index.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-handler.base.js +10 -49
- package/dist/runtime/subsystems/jobs/job-handler.base.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-orchestration.schema.js +13 -152
- package/dist/runtime/subsystems/jobs/job-orchestration.schema.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +36 -699
- package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js +10 -564
- package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js +10 -824
- package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.js +9 -51
- package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js +9 -416
- package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +9 -290
- package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-step-service.drizzle-backend.js +5 -213
- package/dist/runtime/subsystems/jobs/job-step-service.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-step-service.memory-backend.js +5 -131
- package/dist/runtime/subsystems/jobs/job-step-service.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +9 -175
- package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-worker.js +14 -613
- package/dist/runtime/subsystems/jobs/job-worker.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-worker.module.js +23 -2647
- package/dist/runtime/subsystems/jobs/job-worker.module.js.map +1 -1
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js +19 -1897
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js.map +1 -1
- package/dist/runtime/subsystems/jobs/jobs-domain.tokens.js +8 -9
- package/dist/runtime/subsystems/jobs/jobs-domain.tokens.js.map +1 -1
- package/dist/runtime/subsystems/jobs/jobs-errors.js +10 -78
- package/dist/runtime/subsystems/jobs/jobs-errors.js.map +1 -1
- package/dist/runtime/subsystems/jobs/memory-job-store.js +4 -15
- package/dist/runtime/subsystems/jobs/memory-job-store.js.map +1 -1
- package/dist/runtime/subsystems/jobs/pool-config.loader.js +9 -124
- package/dist/runtime/subsystems/jobs/pool-config.loader.js.map +1 -1
- package/dist/runtime/subsystems/observability/index.js +21 -310
- package/dist/runtime/subsystems/observability/index.js.map +1 -1
- package/dist/runtime/subsystems/observability/observability-errors.js +4 -9
- package/dist/runtime/subsystems/observability/observability-errors.js.map +1 -1
- package/dist/runtime/subsystems/observability/observability.module.js +11 -300
- package/dist/runtime/subsystems/observability/observability.module.js.map +1 -1
- package/dist/runtime/subsystems/observability/observability.service.js +9 -197
- package/dist/runtime/subsystems/observability/observability.service.js.map +1 -1
- package/dist/runtime/subsystems/observability/observability.tokens.js +5 -3
- package/dist/runtime/subsystems/observability/observability.tokens.js.map +1 -1
- package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.js +4 -84
- package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.js.map +1 -1
- package/dist/runtime/subsystems/observability/reporters/index.js +5 -84
- package/dist/runtime/subsystems/observability/reporters/index.js.map +1 -1
- package/dist/runtime/subsystems/storage/index.js +15 -200
- package/dist/runtime/subsystems/storage/index.js.map +1 -1
- package/dist/runtime/subsystems/storage/storage.local-backend.js +4 -103
- package/dist/runtime/subsystems/storage/storage.local-backend.js.map +1 -1
- package/dist/runtime/subsystems/storage/storage.memory-backend.js +5 -68
- package/dist/runtime/subsystems/storage/storage.memory-backend.js.map +1 -1
- package/dist/runtime/subsystems/storage/storage.module.js +8 -200
- package/dist/runtime/subsystems/storage/storage.module.js.map +1 -1
- package/dist/runtime/subsystems/storage/storage.tokens.js +5 -6
- package/dist/runtime/subsystems/storage/storage.tokens.js.map +1 -1
- package/dist/runtime/subsystems/storage/storage.utils.js +4 -14
- package/dist/runtime/subsystems/storage/storage.utils.js.map +1 -1
- package/dist/runtime/subsystems/token-key.js +5 -3
- package/dist/runtime/subsystems/token-key.js.map +1 -1
- package/dist/src/cli/index.js +637 -5454
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/index.js +68 -4170
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -1
- package/runtime/subsystems/bridge/bridge-outbox-drain-hook.ts +44 -21
- package/runtime/subsystems/jobs/job-worker.ts +17 -11
|
@@ -1,2696 +1,93 @@
|
|
|
1
|
-
|
|
2
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
-
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
-
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
-
if (decorator = decorators[i])
|
|
7
|
-
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
-
if (kind && result) __defProp(target, key, result);
|
|
9
|
-
return result;
|
|
10
|
-
};
|
|
11
|
-
var __decorateParam = (index2, decorator) => (target, key) => decorator(target, key, index2);
|
|
12
|
-
|
|
13
|
-
// runtime/subsystems/jobs/job-orchestration.schema.ts
|
|
1
|
+
import "../../../chunk-UTN4GBPQ.js";
|
|
14
2
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
text,
|
|
19
|
-
jsonb,
|
|
20
|
-
integer,
|
|
21
|
-
timestamp,
|
|
22
|
-
index,
|
|
23
|
-
uniqueIndex
|
|
24
|
-
} from "drizzle-orm/pg-core";
|
|
25
|
-
import { sql } from "drizzle-orm";
|
|
26
|
-
var jobRunStatusEnum = pgEnum("job_run_status", [
|
|
27
|
-
"pending",
|
|
28
|
-
"running",
|
|
29
|
-
"waiting",
|
|
30
|
-
"completed",
|
|
31
|
-
"failed",
|
|
32
|
-
"timed_out",
|
|
33
|
-
"canceled"
|
|
34
|
-
]);
|
|
35
|
-
var jobStepKindEnum = pgEnum("job_step_kind", ["task"]);
|
|
36
|
-
var jobStepStatusEnum = pgEnum("job_step_status", [
|
|
37
|
-
"pending",
|
|
38
|
-
"running",
|
|
39
|
-
"completed",
|
|
40
|
-
"failed",
|
|
41
|
-
"skipped"
|
|
42
|
-
]);
|
|
43
|
-
var collisionModeEnum = pgEnum("job_collision_mode", [
|
|
44
|
-
"queue",
|
|
45
|
-
"reject",
|
|
46
|
-
"replace"
|
|
47
|
-
]);
|
|
48
|
-
var replayFromEnum = pgEnum("job_replay_from", [
|
|
49
|
-
"scratch",
|
|
50
|
-
"last_step",
|
|
51
|
-
"last_checkpoint"
|
|
52
|
-
]);
|
|
53
|
-
var parentClosePolicyEnum = pgEnum("job_parent_close_policy", [
|
|
54
|
-
"terminate",
|
|
55
|
-
"cancel",
|
|
56
|
-
"abandon"
|
|
57
|
-
]);
|
|
58
|
-
var waitKindEnum = pgEnum("job_wait_kind", ["signal"]);
|
|
59
|
-
var triggerSourceEnum = pgEnum("job_trigger_source", [
|
|
60
|
-
"manual",
|
|
61
|
-
"schedule",
|
|
62
|
-
"event",
|
|
63
|
-
"parent"
|
|
64
|
-
]);
|
|
65
|
-
var jobs = pgTable("job", {
|
|
66
|
-
type: text("type").primaryKey(),
|
|
67
|
-
version: integer("version").notNull().default(1),
|
|
68
|
-
pool: text("pool").notNull(),
|
|
69
|
-
scopeEntityType: text("scope_entity_type"),
|
|
70
|
-
retryPolicy: jsonb("retry_policy").notNull().$type(),
|
|
71
|
-
timeoutMs: integer("timeout_ms"),
|
|
72
|
-
concurrencyKeyTemplate: text("concurrency_key_template"),
|
|
73
|
-
collisionMode: collisionModeEnum("collision_mode").notNull().default("queue"),
|
|
74
|
-
dedupeKeyTemplate: text("dedupe_key_template"),
|
|
75
|
-
dedupeWindowMs: integer("dedupe_window_ms"),
|
|
76
|
-
priorityDefault: integer("priority_default").notNull().default(0),
|
|
77
|
-
replayFrom: replayFromEnum("replay_from").notNull().default("last_checkpoint"),
|
|
78
|
-
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
79
|
-
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
80
|
-
});
|
|
81
|
-
var jobRuns = pgTable(
|
|
82
|
-
"job_run",
|
|
83
|
-
{
|
|
84
|
-
id: uuid("id").primaryKey().defaultRandom(),
|
|
85
|
-
jobType: text("job_type").notNull().references(() => jobs.type),
|
|
86
|
-
jobVersion: integer("job_version").notNull(),
|
|
87
|
-
parentRunId: uuid("parent_run_id").references(() => jobRuns.id),
|
|
88
|
-
/**
|
|
89
|
-
* Service generates `id` client-side via randomUUID() and sets
|
|
90
|
-
* root_run_id = id for root runs (single INSERT, no self-FK race).
|
|
91
|
-
*/
|
|
92
|
-
rootRunId: uuid("root_run_id").notNull(),
|
|
93
|
-
parentClosePolicy: parentClosePolicyEnum("parent_close_policy").notNull().default("terminate"),
|
|
94
|
-
scopeEntityType: text("scope_entity_type"),
|
|
95
|
-
scopeEntityId: text("scope_entity_id"),
|
|
96
|
-
tenantId: text("tenant_id"),
|
|
97
|
-
tags: jsonb("tags").notNull().default({}).$type(),
|
|
98
|
-
pool: text("pool").notNull(),
|
|
99
|
-
priority: integer("priority").notNull().default(0),
|
|
100
|
-
concurrencyKey: text("concurrency_key"),
|
|
101
|
-
dedupeKey: text("dedupe_key"),
|
|
102
|
-
status: jobRunStatusEnum("status").notNull().default("pending"),
|
|
103
|
-
input: jsonb("input").notNull().$type(),
|
|
104
|
-
output: jsonb("output").$type(),
|
|
105
|
-
error: jsonb("error").$type(),
|
|
106
|
-
triggerSource: triggerSourceEnum("trigger_source").notNull(),
|
|
107
|
-
triggerRef: text("trigger_ref"),
|
|
108
|
-
runAt: timestamp("run_at", { withTimezone: true }).notNull().defaultNow(),
|
|
109
|
-
startedAt: timestamp("started_at", { withTimezone: true }),
|
|
110
|
-
finishedAt: timestamp("finished_at", { withTimezone: true }),
|
|
111
|
-
claimedAt: timestamp("claimed_at", { withTimezone: true }),
|
|
112
|
-
attempts: integer("attempts").notNull().default(0),
|
|
113
|
-
// Phase 3 placeholder — see ADR-025
|
|
114
|
-
waitKind: waitKindEnum("wait_kind"),
|
|
115
|
-
// Phase 3 placeholder — see ADR-025
|
|
116
|
-
resumeToken: text("resume_token"),
|
|
117
|
-
// Phase 3 placeholder — see ADR-025
|
|
118
|
-
waitDeadline: timestamp("wait_deadline", { withTimezone: true }),
|
|
119
|
-
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
120
|
-
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
121
|
-
},
|
|
122
|
-
(t) => ({
|
|
123
|
-
/** Claim query: ORDER BY priority DESC, run_at ASC. */
|
|
124
|
-
idxJobRunClaim: index("idx_job_run_claim").on(t.status, t.pool, t.runAt),
|
|
125
|
-
/** Tree traversal / cascade cancel. */
|
|
126
|
-
idxJobRunRoot: index("idx_job_run_root").on(t.rootRunId),
|
|
127
|
-
/** listForScope query. */
|
|
128
|
-
idxJobRunScope: index("idx_job_run_scope").on(t.scopeEntityType, t.scopeEntityId),
|
|
129
|
-
/** Idempotency collapse — partial index. */
|
|
130
|
-
idxJobRunDedupe: index("idx_job_run_dedupe").on(t.jobType, t.dedupeKey).where(sql`${t.dedupeKey} IS NOT NULL`),
|
|
131
|
-
/** Collision check — partial index. */
|
|
132
|
-
idxJobRunConcurrency: index("idx_job_run_concurrency").on(t.concurrencyKey).where(
|
|
133
|
-
sql`${t.concurrencyKey} IS NOT NULL AND ${t.status} IN ('pending','running')`
|
|
134
|
-
)
|
|
135
|
-
})
|
|
136
|
-
);
|
|
137
|
-
var jobSteps = pgTable(
|
|
138
|
-
"job_step",
|
|
139
|
-
{
|
|
140
|
-
id: uuid("id").primaryKey().defaultRandom(),
|
|
141
|
-
jobRunId: uuid("job_run_id").notNull().references(() => jobRuns.id),
|
|
142
|
-
stepId: text("step_id").notNull(),
|
|
143
|
-
kind: jobStepKindEnum("kind").notNull().default("task"),
|
|
144
|
-
/**
|
|
145
|
-
* Monotonic within run. integer (max ~2B per run) is sufficient —
|
|
146
|
-
* downgraded from ADR-022's bigint; revisit only if a single run
|
|
147
|
-
* ever exceeds 2 billion steps.
|
|
148
|
-
*/
|
|
149
|
-
seq: integer("seq").notNull(),
|
|
150
|
-
status: jobStepStatusEnum("status").notNull().default("pending"),
|
|
151
|
-
input: jsonb("input").$type(),
|
|
152
|
-
/** Memoised on success for replay. */
|
|
153
|
-
output: jsonb("output").$type(),
|
|
154
|
-
error: jsonb("error").$type(),
|
|
155
|
-
attempts: integer("attempts").notNull().default(0),
|
|
156
|
-
startedAt: timestamp("started_at", { withTimezone: true }),
|
|
157
|
-
finishedAt: timestamp("finished_at", { withTimezone: true })
|
|
158
|
-
},
|
|
159
|
-
(t) => ({
|
|
160
|
-
/** No duplicate step IDs per run. */
|
|
161
|
-
idxJobStepRunStep: uniqueIndex("idx_job_step_run_step").on(t.jobRunId, t.stepId),
|
|
162
|
-
/** Ordered timeline reads. */
|
|
163
|
-
idxJobStepTimeline: index("idx_job_step_timeline").on(t.jobRunId, t.seq)
|
|
164
|
-
})
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
// runtime/subsystems/token-key.ts
|
|
168
|
-
var PKG = "@pattern-stack/codegen";
|
|
169
|
-
var tokenKey = (area, name) => `${PKG}.${area}.${name}`;
|
|
170
|
-
|
|
171
|
-
// runtime/subsystems/jobs/jobs-domain.tokens.ts
|
|
172
|
-
var JOB_ORCHESTRATOR = Symbol.for(tokenKey("jobs", "orchestrator"));
|
|
173
|
-
var JOB_RUN_SERVICE = Symbol.for(tokenKey("jobs", "run-service"));
|
|
174
|
-
var JOB_STEP_SERVICE = Symbol.for(tokenKey("jobs", "step-service"));
|
|
175
|
-
var JOBS_MULTI_TENANT = Symbol.for(tokenKey("jobs", "multi-tenant"));
|
|
176
|
-
|
|
177
|
-
// runtime/subsystems/jobs/job-handler.base.ts
|
|
178
|
-
var ParentClosePolicy = /* @__PURE__ */ ((ParentClosePolicy2) => {
|
|
179
|
-
ParentClosePolicy2["Terminate"] = "terminate";
|
|
180
|
-
ParentClosePolicy2["Cancel"] = "cancel";
|
|
181
|
-
ParentClosePolicy2["Abandon"] = "abandon";
|
|
182
|
-
return ParentClosePolicy2;
|
|
183
|
-
})(ParentClosePolicy || {});
|
|
184
|
-
var JobHandlerBase = class {
|
|
185
|
-
};
|
|
186
|
-
var JOB_HANDLER_REGISTRY = /* @__PURE__ */ new Map();
|
|
187
|
-
var JOB_HANDLER_METADATA_KEY = Symbol.for(tokenKey("jobs", "handler-metadata"));
|
|
188
|
-
function JobHandler(type, meta) {
|
|
189
|
-
return (target) => {
|
|
190
|
-
if (JOB_HANDLER_REGISTRY.has(type)) {
|
|
191
|
-
const env = process.env.NODE_ENV;
|
|
192
|
-
if (env === "production") {
|
|
193
|
-
throw new Error(
|
|
194
|
-
`[JobHandler] Duplicate registration for job type '${type}'. Each @JobHandler must declare a unique type.`
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
if (env !== "test") {
|
|
198
|
-
console.warn(
|
|
199
|
-
`[JobHandler] Duplicate registration for job type '${type}'. Overwriting previous handler \u2014 this is almost certainly a bug.`
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
Reflect.defineMetadata(JOB_HANDLER_METADATA_KEY, { type, meta }, target);
|
|
204
|
-
JOB_HANDLER_REGISTRY.set(type, {
|
|
205
|
-
type,
|
|
206
|
-
meta,
|
|
207
|
-
handlerClass: target
|
|
208
|
-
});
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
var HandlerRegistry;
|
|
212
|
-
((HandlerRegistry2) => {
|
|
213
|
-
function getAll() {
|
|
214
|
-
return Array.from(JOB_HANDLER_REGISTRY.values());
|
|
215
|
-
}
|
|
216
|
-
HandlerRegistry2.getAll = getAll;
|
|
217
|
-
function get(type) {
|
|
218
|
-
return JOB_HANDLER_REGISTRY.get(type);
|
|
219
|
-
}
|
|
220
|
-
HandlerRegistry2.get = get;
|
|
221
|
-
})(HandlerRegistry || (HandlerRegistry = {}));
|
|
222
|
-
|
|
223
|
-
// runtime/subsystems/jobs/job-orchestrator.drizzle-backend.ts
|
|
224
|
-
import { randomUUID } from "crypto";
|
|
225
|
-
import { Inject, Injectable, Logger } from "@nestjs/common";
|
|
226
|
-
import { and, desc, eq, gt, inArray, isNotNull, ne, notInArray, sql as sql2 } from "drizzle-orm";
|
|
227
|
-
|
|
228
|
-
// runtime/constants/tokens.ts
|
|
229
|
-
var DRIZZLE = "DRIZZLE";
|
|
230
|
-
|
|
231
|
-
// runtime/subsystems/jobs/jobs-errors.ts
|
|
232
|
-
var JobTypeNotFoundError = class extends Error {
|
|
233
|
-
constructor(jobType) {
|
|
234
|
-
super(`No job definition registered for type '${jobType}'.`);
|
|
235
|
-
this.jobType = jobType;
|
|
236
|
-
}
|
|
237
|
-
jobType;
|
|
238
|
-
name = "JobTypeNotFoundError";
|
|
239
|
-
};
|
|
240
|
-
var JobCollisionError = class extends Error {
|
|
241
|
-
constructor(jobType, concurrencyKey, incumbent) {
|
|
242
|
-
super(
|
|
243
|
-
`Job type '${jobType}' has an in-flight run with concurrency_key '${concurrencyKey}' (incumbent ${incumbent.id}); collision_mode=reject.`
|
|
244
|
-
);
|
|
245
|
-
this.jobType = jobType;
|
|
246
|
-
this.concurrencyKey = concurrencyKey;
|
|
247
|
-
this.incumbent = incumbent;
|
|
248
|
-
}
|
|
249
|
-
jobType;
|
|
250
|
-
concurrencyKey;
|
|
251
|
-
incumbent;
|
|
252
|
-
name = "JobCollisionError";
|
|
253
|
-
};
|
|
254
|
-
var JobNotReplayableError = class extends Error {
|
|
255
|
-
constructor(runId, currentStatus) {
|
|
256
|
-
super(
|
|
257
|
-
`Run ${runId} is not replayable from status '${currentStatus}'. Only 'completed', 'failed', 'timed_out', and 'canceled' are eligible.`
|
|
258
|
-
);
|
|
259
|
-
this.runId = runId;
|
|
260
|
-
this.currentStatus = currentStatus;
|
|
261
|
-
}
|
|
262
|
-
runId;
|
|
263
|
-
currentStatus;
|
|
264
|
-
name = "JobNotReplayableError";
|
|
265
|
-
};
|
|
266
|
-
var JobTemplateFieldMissingError = class extends Error {
|
|
267
|
-
constructor(template, field) {
|
|
268
|
-
super(
|
|
269
|
-
`Template '${template}' references input field '${field}' which is missing or undefined on the payload.`
|
|
270
|
-
);
|
|
271
|
-
this.template = template;
|
|
272
|
-
this.field = field;
|
|
273
|
-
}
|
|
274
|
-
template;
|
|
275
|
-
field;
|
|
276
|
-
name = "JobTemplateFieldMissingError";
|
|
277
|
-
};
|
|
278
|
-
var MissingTenantIdError = class extends Error {
|
|
279
|
-
constructor(method) {
|
|
280
|
-
super(
|
|
281
|
-
`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.`
|
|
282
|
-
);
|
|
283
|
-
this.method = method;
|
|
284
|
-
}
|
|
285
|
-
method;
|
|
286
|
-
name = "MissingTenantIdError";
|
|
287
|
-
};
|
|
288
|
-
var BootValidationError = class extends Error {
|
|
289
|
-
constructor(missingHandlers) {
|
|
290
|
-
super(
|
|
291
|
-
`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.`
|
|
292
|
-
);
|
|
293
|
-
this.missingHandlers = missingHandlers;
|
|
294
|
-
}
|
|
295
|
-
missingHandlers;
|
|
296
|
-
name = "BootValidationError";
|
|
297
|
-
};
|
|
298
|
-
var ReservedPoolViolationError = class extends Error {
|
|
299
|
-
constructor(offenders) {
|
|
300
|
-
super(
|
|
301
|
-
`ReservedPoolViolationError: ${offenders.length} @JobHandler(s) target reserved pools \u2014 reserved pools are framework-only:
|
|
302
|
-
` + offenders.map((o) => ` - ${o.handlerClass} \u2192 pool='${o.pool}'`).join("\n")
|
|
303
|
-
);
|
|
304
|
-
this.offenders = offenders;
|
|
305
|
-
}
|
|
306
|
-
offenders;
|
|
307
|
-
name = "ReservedPoolViolationError";
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
// runtime/subsystems/jobs/job-orchestrator.drizzle-backend.ts
|
|
311
|
-
var TERMINAL_STATUSES = [
|
|
312
|
-
"completed",
|
|
313
|
-
"failed",
|
|
314
|
-
"timed_out",
|
|
315
|
-
"canceled"
|
|
316
|
-
];
|
|
317
|
-
var DEDUPE_EXCLUDED_STATUSES = ["canceled", "failed"];
|
|
318
|
-
var IN_FLIGHT_STATUSES = ["pending", "running"];
|
|
319
|
-
function evaluateKeyTemplate(template, input) {
|
|
320
|
-
return template.replace(/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g, (_match, field) => {
|
|
321
|
-
const value = input[field];
|
|
322
|
-
if (value === void 0 || value === null) {
|
|
323
|
-
throw new JobTemplateFieldMissingError(template, field);
|
|
324
|
-
}
|
|
325
|
-
return String(value);
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
var DrizzleJobOrchestrator = class {
|
|
329
|
-
constructor(db, multiTenant) {
|
|
330
|
-
this.db = db;
|
|
331
|
-
this.multiTenant = multiTenant;
|
|
332
|
-
}
|
|
333
|
-
db;
|
|
334
|
-
multiTenant;
|
|
335
|
-
// TODO(logging-subsystem): swap to ILogger once ADR-028 lands
|
|
336
|
-
logger = new Logger(DrizzleJobOrchestrator.name);
|
|
337
|
-
/**
|
|
338
|
-
* JOB-8 — resolve `tenantId` for a mutating / targeted-read call.
|
|
339
|
-
* Returns the tenant value that should be written to the row (or compared
|
|
340
|
-
* against in a WHERE clause). When `multiTenant` is off, the column is
|
|
341
|
-
* forced to `null` regardless of what callers pass. When on, `undefined`
|
|
342
|
-
* throws; `null` and strings pass through untouched.
|
|
343
|
-
*/
|
|
344
|
-
resolveTenantId(method, tenantId) {
|
|
345
|
-
if (!this.multiTenant) return null;
|
|
346
|
-
if (tenantId === void 0) throw new MissingTenantIdError(method);
|
|
347
|
-
return tenantId;
|
|
348
|
-
}
|
|
349
|
-
// ==========================================================================
|
|
350
|
-
// start
|
|
351
|
-
// ==========================================================================
|
|
352
|
-
async start(type, input, opts = {}, tx) {
|
|
353
|
-
const payload = input ?? {};
|
|
354
|
-
const tenantId = this.resolveTenantId("start", opts.tenantId);
|
|
355
|
-
const client = tx ?? this.db;
|
|
356
|
-
const [def] = await client.select().from(jobs).where(eq(jobs.type, type)).limit(1);
|
|
357
|
-
if (!def) throw new JobTypeNotFoundError(type);
|
|
358
|
-
const definition = def;
|
|
359
|
-
if (definition.dedupeKeyTemplate && definition.dedupeWindowMs) {
|
|
360
|
-
const dedupeKey2 = evaluateKeyTemplate(definition.dedupeKeyTemplate, payload);
|
|
361
|
-
const windowStart = new Date(Date.now() - definition.dedupeWindowMs);
|
|
362
|
-
const existing = await client.select().from(jobRuns).where(
|
|
363
|
-
and(
|
|
364
|
-
eq(jobRuns.jobType, type),
|
|
365
|
-
eq(jobRuns.dedupeKey, dedupeKey2),
|
|
366
|
-
gt(jobRuns.createdAt, windowStart),
|
|
367
|
-
// status NOT IN ('canceled', 'failed')
|
|
368
|
-
notInStatus(DEDUPE_EXCLUDED_STATUSES)
|
|
369
|
-
)
|
|
370
|
-
).orderBy(desc(jobRuns.createdAt)).limit(1);
|
|
371
|
-
if (existing.length > 0) {
|
|
372
|
-
return existing[0];
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
let concurrencyKey = null;
|
|
376
|
-
if (definition.concurrencyKeyTemplate) {
|
|
377
|
-
concurrencyKey = evaluateKeyTemplate(
|
|
378
|
-
definition.concurrencyKeyTemplate,
|
|
379
|
-
payload
|
|
380
|
-
);
|
|
381
|
-
const inFlight = await client.select().from(jobRuns).where(
|
|
382
|
-
and(
|
|
383
|
-
eq(jobRuns.concurrencyKey, concurrencyKey),
|
|
384
|
-
inArray(jobRuns.status, IN_FLIGHT_STATUSES)
|
|
385
|
-
)
|
|
386
|
-
).limit(1);
|
|
387
|
-
if (inFlight.length > 0) {
|
|
388
|
-
const incumbent = inFlight[0];
|
|
389
|
-
switch (definition.collisionMode) {
|
|
390
|
-
case "reject":
|
|
391
|
-
throw new JobCollisionError(type, concurrencyKey, incumbent);
|
|
392
|
-
case "replace":
|
|
393
|
-
await this.cancel(incumbent.id, {
|
|
394
|
-
cascade: true,
|
|
395
|
-
reason: "replaced",
|
|
396
|
-
tenantId: incumbent.tenantId
|
|
397
|
-
});
|
|
398
|
-
break;
|
|
399
|
-
case "queue":
|
|
400
|
-
break;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
const newId = randomUUID();
|
|
405
|
-
let rootRunId = newId;
|
|
406
|
-
if (opts.parentRunId) {
|
|
407
|
-
const [parent] = await client.select({ rootRunId: jobRuns.rootRunId }).from(jobRuns).where(eq(jobRuns.id, opts.parentRunId)).limit(1);
|
|
408
|
-
if (!parent) {
|
|
409
|
-
throw new Error(
|
|
410
|
-
`parentRunId ${opts.parentRunId} does not reference an existing job_run`
|
|
411
|
-
);
|
|
412
|
-
}
|
|
413
|
-
rootRunId = parent.rootRunId;
|
|
414
|
-
}
|
|
415
|
-
const dedupeKey = definition.dedupeKeyTemplate ? evaluateKeyTemplate(definition.dedupeKeyTemplate, payload) : null;
|
|
416
|
-
const [inserted] = await client.insert(jobRuns).values({
|
|
417
|
-
id: newId,
|
|
418
|
-
jobType: type,
|
|
419
|
-
jobVersion: definition.version,
|
|
420
|
-
parentRunId: opts.parentRunId ?? null,
|
|
421
|
-
rootRunId,
|
|
422
|
-
parentClosePolicy: opts.parentClosePolicy ?? "terminate",
|
|
423
|
-
scopeEntityType: opts.scope?.entityType ?? null,
|
|
424
|
-
scopeEntityId: opts.scope?.entityId ?? null,
|
|
425
|
-
tenantId,
|
|
426
|
-
tags: opts.tags ?? {},
|
|
427
|
-
pool: opts.pool ?? definition.pool,
|
|
428
|
-
priority: opts.priority ?? definition.priorityDefault,
|
|
429
|
-
concurrencyKey,
|
|
430
|
-
dedupeKey,
|
|
431
|
-
status: "pending",
|
|
432
|
-
input: payload,
|
|
433
|
-
output: null,
|
|
434
|
-
error: null,
|
|
435
|
-
triggerSource: opts.triggerSource ?? "manual",
|
|
436
|
-
triggerRef: opts.triggerRef ?? null,
|
|
437
|
-
runAt: opts.runAt ?? /* @__PURE__ */ new Date(),
|
|
438
|
-
startedAt: null,
|
|
439
|
-
finishedAt: null,
|
|
440
|
-
claimedAt: null,
|
|
441
|
-
attempts: 0
|
|
442
|
-
}).returning();
|
|
443
|
-
return inserted;
|
|
444
|
-
}
|
|
445
|
-
// ==========================================================================
|
|
446
|
-
// cancel
|
|
447
|
-
// ==========================================================================
|
|
448
|
-
async cancel(runId, opts = {}) {
|
|
449
|
-
const tenantId = this.resolveTenantId("cancel", opts.tenantId);
|
|
450
|
-
const [target] = await this.db.select().from(jobRuns).where(eq(jobRuns.id, runId)).limit(1);
|
|
451
|
-
if (!target) return;
|
|
452
|
-
if (this.multiTenant && target.tenantId !== tenantId) return;
|
|
453
|
-
if (TERMINAL_STATUSES.includes(target.status)) {
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
const [cancelled] = await this.db.update(jobRuns).set({
|
|
457
|
-
status: "canceled",
|
|
458
|
-
finishedAt: /* @__PURE__ */ new Date(),
|
|
459
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
460
|
-
}).where(
|
|
461
|
-
and(eq(jobRuns.id, runId), notInStatus([...TERMINAL_STATUSES]))
|
|
462
|
-
).returning();
|
|
463
|
-
if (!cancelled) return;
|
|
464
|
-
if (opts.cascade === false) return;
|
|
465
|
-
const descendants = await this.db.select().from(jobRuns).where(
|
|
466
|
-
and(
|
|
467
|
-
eq(jobRuns.rootRunId, target.rootRunId),
|
|
468
|
-
ne(jobRuns.id, runId),
|
|
469
|
-
notInStatus([...TERMINAL_STATUSES])
|
|
470
|
-
)
|
|
471
|
-
);
|
|
472
|
-
for (const child of descendants) {
|
|
473
|
-
const policy = child.parentClosePolicy;
|
|
474
|
-
if (policy === "abandon") continue;
|
|
475
|
-
await this.db.update(jobRuns).set({
|
|
476
|
-
status: "canceled",
|
|
477
|
-
finishedAt: /* @__PURE__ */ new Date(),
|
|
478
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
479
|
-
}).where(
|
|
480
|
-
and(
|
|
481
|
-
eq(jobRuns.id, child.id),
|
|
482
|
-
notInStatus([...TERMINAL_STATUSES])
|
|
483
|
-
)
|
|
484
|
-
);
|
|
485
|
-
}
|
|
486
|
-
void opts.reason;
|
|
487
|
-
}
|
|
488
|
-
// ==========================================================================
|
|
489
|
-
// replay
|
|
490
|
-
// ==========================================================================
|
|
491
|
-
async replay(runId) {
|
|
492
|
-
const [target] = await this.db.select().from(jobRuns).where(eq(jobRuns.id, runId)).limit(1);
|
|
493
|
-
if (!target) {
|
|
494
|
-
throw new Error(`replay: run ${runId} not found`);
|
|
495
|
-
}
|
|
496
|
-
const run = target;
|
|
497
|
-
if (!TERMINAL_STATUSES.includes(run.status)) {
|
|
498
|
-
throw new JobNotReplayableError(runId, run.status);
|
|
499
|
-
}
|
|
500
|
-
const [def] = await this.db.select().from(jobs).where(eq(jobs.type, run.jobType)).limit(1);
|
|
501
|
-
if (!def) throw new JobTypeNotFoundError(run.jobType);
|
|
502
|
-
const mode = def.replayFrom;
|
|
503
|
-
const result = await this.db.transaction(async (tx) => {
|
|
504
|
-
if (mode === "scratch") {
|
|
505
|
-
await tx.delete(jobSteps).where(eq(jobSteps.jobRunId, runId));
|
|
506
|
-
} else if (mode === "last_step") {
|
|
507
|
-
await tx.delete(jobSteps).where(
|
|
508
|
-
and(eq(jobSteps.jobRunId, runId), ne(jobSteps.status, "completed"))
|
|
509
|
-
);
|
|
510
|
-
} else {
|
|
511
|
-
await tx.delete(jobSteps).where(
|
|
512
|
-
and(eq(jobSteps.jobRunId, runId), ne(jobSteps.status, "completed"))
|
|
513
|
-
);
|
|
514
|
-
}
|
|
515
|
-
const [updated] = await tx.update(jobRuns).set({
|
|
516
|
-
status: "pending",
|
|
517
|
-
attempts: 0,
|
|
518
|
-
runAt: /* @__PURE__ */ new Date(),
|
|
519
|
-
startedAt: null,
|
|
520
|
-
finishedAt: null,
|
|
521
|
-
claimedAt: null,
|
|
522
|
-
error: null,
|
|
523
|
-
output: null,
|
|
524
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
525
|
-
}).where(eq(jobRuns.id, runId)).returning();
|
|
526
|
-
return updated;
|
|
527
|
-
});
|
|
528
|
-
return result;
|
|
529
|
-
}
|
|
530
|
-
// ==========================================================================
|
|
531
|
-
// upsertJobRows — boot-time materialisation of `job` definitions
|
|
532
|
-
// ==========================================================================
|
|
533
|
-
/**
|
|
534
|
-
* Hash-gated `INSERT … ON CONFLICT (type) DO UPDATE … WHERE` per Q3
|
|
535
|
-
* resolution (2026-04-19): the `UPDATE` branch executes only when one
|
|
536
|
-
* of the persisted metadata fields differs from the incoming payload;
|
|
537
|
-
* `version` bumps only on real change; concurrent boots with identical
|
|
538
|
-
* content are idempotent no-ops.
|
|
539
|
-
*
|
|
540
|
-
* Why this shape (not `DO NOTHING`, not advisory locks):
|
|
541
|
-
* - `DO NOTHING` would let an old-version instance leave a stale row
|
|
542
|
-
* that a new-version instance can't overwrite during a rolling deploy.
|
|
543
|
-
* - Advisory locks add latency and leak risk under crashes.
|
|
544
|
-
* - The `WHERE … IS DISTINCT FROM …` clause makes the conditional
|
|
545
|
-
* atomic — no read-modify-write race on `version` between concurrent
|
|
546
|
-
* boots.
|
|
547
|
-
*
|
|
548
|
-
* Orphan detection: a single `SELECT type FROM job WHERE type NOT IN (...)`
|
|
549
|
-
* returns the types present in DB but absent from `entries`. Caller (boot
|
|
550
|
-
* validator) decides whether to throw `BootValidationError`.
|
|
551
|
-
*/
|
|
552
|
-
async upsertJobRows(entries, poolConfig) {
|
|
553
|
-
void poolConfig;
|
|
554
|
-
for (const entry of entries) {
|
|
555
|
-
const meta = entry.meta;
|
|
556
|
-
const pool = meta.pool ?? "batch";
|
|
557
|
-
const retryPolicy = meta.retry ?? {
|
|
558
|
-
attempts: 1,
|
|
559
|
-
backoff: "fixed",
|
|
560
|
-
baseMs: 0
|
|
561
|
-
};
|
|
562
|
-
const concurrencyKeyTemplate = meta.concurrency?.key;
|
|
563
|
-
const concurrencyKeyTemplateStr = typeof concurrencyKeyTemplate === "string" ? concurrencyKeyTemplate : null;
|
|
564
|
-
const collisionMode = meta.concurrency?.collisionMode ?? "queue";
|
|
565
|
-
const dedupeKeyTemplate = meta.dedupe?.key;
|
|
566
|
-
const dedupeKeyTemplateStr = typeof dedupeKeyTemplate === "string" ? dedupeKeyTemplate : null;
|
|
567
|
-
const dedupeWindowMs = meta.dedupe?.windowMs ?? null;
|
|
568
|
-
const timeoutMs = meta.timeoutMs ?? null;
|
|
569
|
-
const replayFrom = meta.replayFrom ?? "last_checkpoint";
|
|
570
|
-
const scopeEntityType = meta.scope?.entity ?? null;
|
|
571
|
-
const priorityDefault = 0;
|
|
572
|
-
await this.db.insert(jobs).values({
|
|
573
|
-
type: entry.type,
|
|
574
|
-
version: 1,
|
|
575
|
-
pool,
|
|
576
|
-
scopeEntityType,
|
|
577
|
-
retryPolicy,
|
|
578
|
-
timeoutMs,
|
|
579
|
-
concurrencyKeyTemplate: concurrencyKeyTemplateStr,
|
|
580
|
-
collisionMode,
|
|
581
|
-
dedupeKeyTemplate: dedupeKeyTemplateStr,
|
|
582
|
-
dedupeWindowMs,
|
|
583
|
-
priorityDefault,
|
|
584
|
-
replayFrom
|
|
585
|
-
}).onConflictDoUpdate({
|
|
586
|
-
target: jobs.type,
|
|
587
|
-
set: {
|
|
588
|
-
pool: sql2`EXCLUDED.pool`,
|
|
589
|
-
scopeEntityType: sql2`EXCLUDED.scope_entity_type`,
|
|
590
|
-
retryPolicy: sql2`EXCLUDED.retry_policy`,
|
|
591
|
-
timeoutMs: sql2`EXCLUDED.timeout_ms`,
|
|
592
|
-
concurrencyKeyTemplate: sql2`EXCLUDED.concurrency_key_template`,
|
|
593
|
-
collisionMode: sql2`EXCLUDED.collision_mode`,
|
|
594
|
-
dedupeKeyTemplate: sql2`EXCLUDED.dedupe_key_template`,
|
|
595
|
-
dedupeWindowMs: sql2`EXCLUDED.dedupe_window_ms`,
|
|
596
|
-
priorityDefault: sql2`EXCLUDED.priority_default`,
|
|
597
|
-
replayFrom: sql2`EXCLUDED.replay_from`,
|
|
598
|
-
version: sql2`${jobs.version} + 1`,
|
|
599
|
-
updatedAt: sql2`now()`
|
|
600
|
-
},
|
|
601
|
-
// The hash gate: every field listed in the Q3 resolution appears
|
|
602
|
-
// here. `IS DISTINCT FROM` is the null-safe inequality operator;
|
|
603
|
-
// jsonb cast to text gives stable comparison without invoking a
|
|
604
|
-
// dedicated hash column (avoids a JOB-1 schema migration).
|
|
605
|
-
setWhere: sql2`
|
|
606
|
-
${jobs.pool} IS DISTINCT FROM EXCLUDED.pool OR
|
|
607
|
-
${jobs.retryPolicy}::text IS DISTINCT FROM EXCLUDED.retry_policy::text OR
|
|
608
|
-
${jobs.timeoutMs} IS DISTINCT FROM EXCLUDED.timeout_ms OR
|
|
609
|
-
${jobs.concurrencyKeyTemplate} IS DISTINCT FROM EXCLUDED.concurrency_key_template OR
|
|
610
|
-
${jobs.collisionMode} IS DISTINCT FROM EXCLUDED.collision_mode OR
|
|
611
|
-
${jobs.dedupeKeyTemplate} IS DISTINCT FROM EXCLUDED.dedupe_key_template OR
|
|
612
|
-
${jobs.dedupeWindowMs} IS DISTINCT FROM EXCLUDED.dedupe_window_ms OR
|
|
613
|
-
${jobs.priorityDefault} IS DISTINCT FROM EXCLUDED.priority_default OR
|
|
614
|
-
${jobs.replayFrom} IS DISTINCT FROM EXCLUDED.replay_from OR
|
|
615
|
-
${jobs.scopeEntityType} IS DISTINCT FROM EXCLUDED.scope_entity_type
|
|
616
|
-
`
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
const types = entries.map((e) => e.type);
|
|
620
|
-
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));
|
|
621
|
-
return { orphaned: orphans.map((o) => o.type) };
|
|
622
|
-
}
|
|
623
|
-
};
|
|
624
|
-
DrizzleJobOrchestrator = __decorateClass([
|
|
625
|
-
Injectable(),
|
|
626
|
-
__decorateParam(0, Inject(DRIZZLE)),
|
|
627
|
-
__decorateParam(1, Inject(JOBS_MULTI_TENANT))
|
|
628
|
-
], DrizzleJobOrchestrator);
|
|
629
|
-
function notInStatus(statuses) {
|
|
630
|
-
const negated = statuses.map((s) => ne(jobRuns.status, s));
|
|
631
|
-
return and(...negated);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
// runtime/subsystems/jobs/job-run-service.drizzle-backend.ts
|
|
635
|
-
import { Inject as Inject2, Injectable as Injectable2 } from "@nestjs/common";
|
|
636
|
-
import { and as and2, asc, desc as desc2, eq as eq2, gte, inArray as inArray2, isNull, lt, or, sql as sql3 } from "drizzle-orm";
|
|
637
|
-
|
|
638
|
-
// runtime/subsystems/jobs/job-run-keyset-cursor.ts
|
|
639
|
-
var DEFAULT_LIST_LIMIT = 50;
|
|
640
|
-
var MAX_LIST_LIMIT = 200;
|
|
641
|
-
function clampLimit(limit) {
|
|
642
|
-
if (typeof limit !== "number" || !Number.isFinite(limit)) {
|
|
643
|
-
return DEFAULT_LIST_LIMIT;
|
|
644
|
-
}
|
|
645
|
-
const floored = Math.floor(limit);
|
|
646
|
-
if (floored < 1) return 1;
|
|
647
|
-
if (floored > MAX_LIST_LIMIT) return MAX_LIST_LIMIT;
|
|
648
|
-
return floored;
|
|
649
|
-
}
|
|
650
|
-
function encodeKeysetCursor(keyset) {
|
|
651
|
-
const tuple = [keyset.createdAt.toISOString(), keyset.id];
|
|
652
|
-
return Buffer.from(JSON.stringify(tuple), "utf8").toString("base64url");
|
|
653
|
-
}
|
|
654
|
-
function decodeKeysetCursor(cursor) {
|
|
655
|
-
try {
|
|
656
|
-
const json = Buffer.from(cursor, "base64url").toString("utf8");
|
|
657
|
-
const parsed = JSON.parse(json);
|
|
658
|
-
if (!Array.isArray(parsed) || parsed.length !== 2) return null;
|
|
659
|
-
const [iso, id] = parsed;
|
|
660
|
-
if (typeof iso !== "string" || typeof id !== "string") return null;
|
|
661
|
-
const createdAt = new Date(iso);
|
|
662
|
-
if (Number.isNaN(createdAt.getTime())) return null;
|
|
663
|
-
return { createdAt, id };
|
|
664
|
-
} catch {
|
|
665
|
-
return null;
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
function toJobRunSummary(r) {
|
|
669
|
-
return {
|
|
670
|
-
runId: r.id,
|
|
671
|
-
rootRunId: r.rootRunId,
|
|
672
|
-
parentRunId: r.parentRunId,
|
|
673
|
-
triggerSource: r.triggerSource,
|
|
674
|
-
triggerRef: r.triggerRef,
|
|
675
|
-
jobType: r.jobType,
|
|
676
|
-
pool: r.pool,
|
|
677
|
-
status: r.status,
|
|
678
|
-
scopeEntityType: r.scopeEntityType,
|
|
679
|
-
scopeEntityId: r.scopeEntityId,
|
|
680
|
-
tenantId: r.tenantId,
|
|
681
|
-
attempts: r.attempts,
|
|
682
|
-
errorMessage: r.error?.message ?? null,
|
|
683
|
-
runAt: r.runAt,
|
|
684
|
-
startedAt: r.startedAt,
|
|
685
|
-
finishedAt: r.finishedAt,
|
|
686
|
-
createdAt: r.createdAt
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// runtime/subsystems/jobs/job-run-service.drizzle-backend.ts
|
|
691
|
-
var NON_TERMINAL_STATUSES = [
|
|
692
|
-
"pending",
|
|
693
|
-
"running",
|
|
694
|
-
"waiting"
|
|
695
|
-
];
|
|
696
|
-
var DrizzleJobRunService = class {
|
|
697
|
-
constructor(db, orchestrator, multiTenant) {
|
|
698
|
-
this.db = db;
|
|
699
|
-
this.orchestrator = orchestrator;
|
|
700
|
-
this.multiTenant = multiTenant;
|
|
701
|
-
}
|
|
702
|
-
db;
|
|
703
|
-
orchestrator;
|
|
704
|
-
multiTenant;
|
|
705
|
-
/**
|
|
706
|
-
* JOB-8 — produce the tenant WHERE fragment (or `null` to opt out).
|
|
707
|
-
* Returns `null` when multi-tenancy is off (caller skips the predicate).
|
|
708
|
-
* Throws `MissingTenantIdError` when on + `undefined`.
|
|
709
|
-
* When on + explicit `null`, filters `tenant_id IS NULL`.
|
|
710
|
-
*/
|
|
711
|
-
tenantCondition(method, tenantId) {
|
|
712
|
-
if (!this.multiTenant) return null;
|
|
713
|
-
if (tenantId === void 0) throw new MissingTenantIdError(method);
|
|
714
|
-
return tenantId === null ? isNull(jobRuns.tenantId) : eq2(jobRuns.tenantId, tenantId);
|
|
715
|
-
}
|
|
716
|
-
async listForScope(entityType, entityId, opts = {}) {
|
|
717
|
-
const conditions = [
|
|
718
|
-
eq2(jobRuns.scopeEntityType, entityType),
|
|
719
|
-
eq2(jobRuns.scopeEntityId, entityId)
|
|
720
|
-
];
|
|
721
|
-
const tenantCond = this.tenantCondition("listForScope", opts.tenantId);
|
|
722
|
-
if (tenantCond) conditions.push(tenantCond);
|
|
723
|
-
if (opts.status) {
|
|
724
|
-
if (Array.isArray(opts.status)) {
|
|
725
|
-
conditions.push(inArray2(jobRuns.status, opts.status));
|
|
726
|
-
} else {
|
|
727
|
-
conditions.push(eq2(jobRuns.status, opts.status));
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
if (opts.jobType) {
|
|
731
|
-
conditions.push(eq2(jobRuns.jobType, opts.jobType));
|
|
732
|
-
}
|
|
733
|
-
const orderCol = (() => {
|
|
734
|
-
switch (opts.orderBy) {
|
|
735
|
-
case "created_at asc":
|
|
736
|
-
return asc(jobRuns.createdAt);
|
|
737
|
-
case "run_at desc":
|
|
738
|
-
return desc2(jobRuns.runAt);
|
|
739
|
-
case "run_at asc":
|
|
740
|
-
return asc(jobRuns.runAt);
|
|
741
|
-
case "created_at desc":
|
|
742
|
-
default:
|
|
743
|
-
return desc2(jobRuns.createdAt);
|
|
744
|
-
}
|
|
745
|
-
})();
|
|
746
|
-
let q = this.db.select().from(jobRuns).where(and2(...conditions)).orderBy(orderCol).$dynamic();
|
|
747
|
-
if (typeof opts.limit === "number") {
|
|
748
|
-
q = q.limit(opts.limit);
|
|
749
|
-
}
|
|
750
|
-
if (typeof opts.offset === "number") {
|
|
751
|
-
q = q.offset(opts.offset);
|
|
752
|
-
}
|
|
753
|
-
const rows = await q;
|
|
754
|
-
return rows;
|
|
755
|
-
}
|
|
756
|
-
async cancelForScope(entityType, entityId, opts = {}) {
|
|
757
|
-
const tenantCond = this.tenantCondition("cancelForScope", opts.tenantId);
|
|
758
|
-
const conditions = [
|
|
759
|
-
eq2(jobRuns.scopeEntityType, entityType),
|
|
760
|
-
eq2(jobRuns.scopeEntityId, entityId),
|
|
761
|
-
inArray2(jobRuns.status, NON_TERMINAL_STATUSES)
|
|
762
|
-
];
|
|
763
|
-
if (tenantCond) conditions.push(tenantCond);
|
|
764
|
-
const rows = await this.db.select({ id: jobRuns.id }).from(jobRuns).where(and2(...conditions));
|
|
765
|
-
for (const { id } of rows) {
|
|
766
|
-
await this.orchestrator.cancel(id, {
|
|
767
|
-
cascade: true,
|
|
768
|
-
tenantId: opts.tenantId
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
async rescheduleForScope(entityType, entityId, newRunAt, opts = {}) {
|
|
773
|
-
const tenantCond = this.tenantCondition("rescheduleForScope", opts.tenantId);
|
|
774
|
-
const conditions = [
|
|
775
|
-
eq2(jobRuns.scopeEntityType, entityType),
|
|
776
|
-
eq2(jobRuns.scopeEntityId, entityId),
|
|
777
|
-
eq2(jobRuns.status, "pending")
|
|
778
|
-
];
|
|
779
|
-
if (tenantCond) conditions.push(tenantCond);
|
|
780
|
-
await this.db.update(jobRuns).set({ runAt: newRunAt, updatedAt: /* @__PURE__ */ new Date() }).where(and2(...conditions));
|
|
781
|
-
}
|
|
782
|
-
async countByPoolAndStatus(tenantId) {
|
|
783
|
-
const tenantCond = this.tenantCondition("countByPoolAndStatus", tenantId);
|
|
784
|
-
const rows = await this.db.select({
|
|
785
|
-
pool: jobRuns.pool,
|
|
786
|
-
status: jobRuns.status,
|
|
787
|
-
count: sql3`count(*)::int`.as("count")
|
|
788
|
-
}).from(jobRuns).where(tenantCond ?? void 0).groupBy(jobRuns.pool, jobRuns.status);
|
|
789
|
-
return rows.map((r) => ({
|
|
790
|
-
pool: r.pool,
|
|
791
|
-
status: r.status,
|
|
792
|
-
count: Number(r.count)
|
|
793
|
-
}));
|
|
794
|
-
}
|
|
795
|
-
async listRecentFailed(limit, tenantId) {
|
|
796
|
-
const conditions = [eq2(jobRuns.status, "failed")];
|
|
797
|
-
const tenantCond = this.tenantCondition("listRecentFailed", tenantId);
|
|
798
|
-
if (tenantCond) conditions.push(tenantCond);
|
|
799
|
-
const rows = await this.db.select().from(jobRuns).where(and2(...conditions)).orderBy(desc2(jobRuns.finishedAt), desc2(jobRuns.updatedAt)).limit(limit);
|
|
800
|
-
return rows.map((r) => ({
|
|
801
|
-
runId: r.id,
|
|
802
|
-
jobType: r.jobType,
|
|
803
|
-
pool: r.pool,
|
|
804
|
-
scopeEntityType: r.scopeEntityType,
|
|
805
|
-
scopeEntityId: r.scopeEntityId,
|
|
806
|
-
tenantId: r.tenantId,
|
|
807
|
-
attempts: r.attempts,
|
|
808
|
-
errorMessage: r.error?.message ?? null,
|
|
809
|
-
failedAt: r.finishedAt ?? r.updatedAt,
|
|
810
|
-
createdAt: r.createdAt
|
|
811
|
-
}));
|
|
812
|
-
}
|
|
813
|
-
async listJobRuns(query = {}) {
|
|
814
|
-
const limit = clampLimit(query.limit);
|
|
815
|
-
const conditions = [];
|
|
816
|
-
const tenantCond = this.tenantCondition("listJobRuns", query.tenantId);
|
|
817
|
-
if (tenantCond) conditions.push(tenantCond);
|
|
818
|
-
if (query.poolId) conditions.push(eq2(jobRuns.pool, query.poolId));
|
|
819
|
-
if (query.rootRunId) conditions.push(eq2(jobRuns.rootRunId, query.rootRunId));
|
|
820
|
-
if (query.status) conditions.push(eq2(jobRuns.status, query.status));
|
|
821
|
-
if (query.since) conditions.push(gte(jobRuns.createdAt, query.since));
|
|
822
|
-
if (query.cursor) {
|
|
823
|
-
const keyset = decodeKeysetCursor(query.cursor);
|
|
824
|
-
if (keyset) {
|
|
825
|
-
conditions.push(
|
|
826
|
-
or(
|
|
827
|
-
lt(jobRuns.createdAt, keyset.createdAt),
|
|
828
|
-
and2(
|
|
829
|
-
eq2(jobRuns.createdAt, keyset.createdAt),
|
|
830
|
-
lt(jobRuns.id, keyset.id)
|
|
831
|
-
)
|
|
832
|
-
)
|
|
833
|
-
);
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
const rows = await this.db.select().from(jobRuns).where(conditions.length > 0 ? and2(...conditions) : void 0).orderBy(desc2(jobRuns.createdAt), desc2(jobRuns.id)).limit(limit + 1);
|
|
837
|
-
const hasMore = rows.length > limit;
|
|
838
|
-
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
839
|
-
const items = page.map(toJobRunSummary);
|
|
840
|
-
const last = page[page.length - 1];
|
|
841
|
-
const nextCursor = hasMore && last ? encodeKeysetCursor({ createdAt: last.createdAt, id: last.id }) : null;
|
|
842
|
-
return { items, nextCursor };
|
|
843
|
-
}
|
|
844
|
-
/**
|
|
845
|
-
* Internal helper used by cascade paths (not on the public protocol).
|
|
846
|
-
* Exposed as a public method on the concrete class so infrastructure
|
|
847
|
-
* code (cascade tests, debug tools) can call it without a cast.
|
|
848
|
-
*/
|
|
849
|
-
async findByRootRunId(rootRunId) {
|
|
850
|
-
const rows = await this.db.select().from(jobRuns).where(eq2(jobRuns.rootRunId, rootRunId));
|
|
851
|
-
return rows;
|
|
852
|
-
}
|
|
853
|
-
};
|
|
854
|
-
DrizzleJobRunService = __decorateClass([
|
|
855
|
-
Injectable2(),
|
|
856
|
-
__decorateParam(0, Inject2(DRIZZLE)),
|
|
857
|
-
__decorateParam(1, Inject2(JOB_ORCHESTRATOR)),
|
|
858
|
-
__decorateParam(2, Inject2(JOBS_MULTI_TENANT))
|
|
859
|
-
], DrizzleJobRunService);
|
|
860
|
-
|
|
861
|
-
// runtime/subsystems/jobs/job-step-service.drizzle-backend.ts
|
|
862
|
-
import { Inject as Inject3, Injectable as Injectable3 } from "@nestjs/common";
|
|
863
|
-
import { and as and3, eq as eq3 } from "drizzle-orm";
|
|
864
|
-
var DrizzleJobStepService = class {
|
|
865
|
-
constructor(db) {
|
|
866
|
-
this.db = db;
|
|
867
|
-
}
|
|
868
|
-
db;
|
|
869
|
-
async recordStep(input) {
|
|
870
|
-
const values = {
|
|
871
|
-
jobRunId: input.jobRunId,
|
|
872
|
-
stepId: input.stepId,
|
|
873
|
-
kind: input.kind,
|
|
874
|
-
seq: input.seq,
|
|
875
|
-
status: input.status,
|
|
876
|
-
input: input.input ?? null,
|
|
877
|
-
output: input.output ?? null,
|
|
878
|
-
error: input.error ?? null,
|
|
879
|
-
attempts: input.attempts ?? 0,
|
|
880
|
-
startedAt: input.startedAt ?? null,
|
|
881
|
-
finishedAt: input.finishedAt ?? null
|
|
882
|
-
};
|
|
883
|
-
const [row] = await this.db.insert(jobSteps).values(values).onConflictDoUpdate({
|
|
884
|
-
target: [jobSteps.jobRunId, jobSteps.stepId],
|
|
885
|
-
set: {
|
|
886
|
-
status: values.status,
|
|
887
|
-
output: values.output,
|
|
888
|
-
error: values.error,
|
|
889
|
-
finishedAt: values.finishedAt,
|
|
890
|
-
attempts: values.attempts
|
|
891
|
-
}
|
|
892
|
-
}).returning();
|
|
893
|
-
return row;
|
|
894
|
-
}
|
|
895
|
-
async findStep(runId, stepId) {
|
|
896
|
-
const [row] = await this.db.select().from(jobSteps).where(and3(eq3(jobSteps.jobRunId, runId), eq3(jobSteps.stepId, stepId))).limit(1);
|
|
897
|
-
return row ?? null;
|
|
898
|
-
}
|
|
899
|
-
};
|
|
900
|
-
DrizzleJobStepService = __decorateClass([
|
|
901
|
-
Injectable3(),
|
|
902
|
-
__decorateParam(0, Inject3(DRIZZLE))
|
|
903
|
-
], DrizzleJobStepService);
|
|
904
|
-
|
|
905
|
-
// runtime/subsystems/jobs/pool-config.loader.ts
|
|
906
|
-
import { existsSync, readFileSync } from "fs";
|
|
907
|
-
import { resolve } from "path";
|
|
908
|
-
import { parse as parseYaml } from "yaml";
|
|
909
|
-
var FRAMEWORK_POOLS = Object.freeze({
|
|
910
|
-
events_inbound: Object.freeze({
|
|
911
|
-
queue: "jobs-events-inbound",
|
|
912
|
-
concurrency: 20,
|
|
913
|
-
reserved: true,
|
|
914
|
-
description: "Inbound events drain (events subsystem outbox)."
|
|
915
|
-
}),
|
|
916
|
-
events_change: Object.freeze({
|
|
917
|
-
queue: "jobs-events-change",
|
|
918
|
-
concurrency: 30,
|
|
919
|
-
reserved: true,
|
|
920
|
-
description: "Change events drain (events subsystem outbox)."
|
|
921
|
-
}),
|
|
922
|
-
events_outbound: Object.freeze({
|
|
923
|
-
queue: "jobs-events-outbound",
|
|
924
|
-
concurrency: 10,
|
|
925
|
-
reserved: true,
|
|
926
|
-
description: "Outbound events drain (events subsystem outbox)."
|
|
927
|
-
}),
|
|
928
|
-
interactive: Object.freeze({
|
|
929
|
-
queue: "jobs-interactive",
|
|
930
|
-
concurrency: 20,
|
|
931
|
-
reserved: false,
|
|
932
|
-
description: "User-facing latency-sensitive jobs."
|
|
933
|
-
}),
|
|
934
|
-
batch: Object.freeze({
|
|
935
|
-
queue: "jobs-batch",
|
|
936
|
-
concurrency: 5,
|
|
937
|
-
reserved: false,
|
|
938
|
-
description: "Default pool for background jobs."
|
|
939
|
-
})
|
|
940
|
-
});
|
|
941
|
-
var RESERVED_POOL_NAMES = new Set(
|
|
942
|
-
Object.entries(FRAMEWORK_POOLS).filter(([, def]) => def.reserved).map(([name]) => name)
|
|
943
|
-
);
|
|
944
|
-
var cache = /* @__PURE__ */ new Map();
|
|
945
|
-
function loadPoolConfig(configPath) {
|
|
946
|
-
const resolved = resolve(configPath ?? `${process.cwd()}/codegen.config.yaml`);
|
|
947
|
-
const cached = cache.get(resolved);
|
|
948
|
-
if (cached) return cached;
|
|
949
|
-
const merged = /* @__PURE__ */ new Map();
|
|
950
|
-
for (const [name, def] of Object.entries(FRAMEWORK_POOLS)) {
|
|
951
|
-
merged.set(name, { ...def });
|
|
952
|
-
}
|
|
953
|
-
if (!existsSync(resolved)) {
|
|
954
|
-
cache.set(resolved, merged);
|
|
955
|
-
return merged;
|
|
956
|
-
}
|
|
957
|
-
let raw;
|
|
958
|
-
try {
|
|
959
|
-
raw = parseYaml(readFileSync(resolved, "utf8"));
|
|
960
|
-
} catch (err) {
|
|
961
|
-
throw new Error(
|
|
962
|
-
`pool-config.loader: failed to parse YAML at ${resolved}: ${err.message}`
|
|
963
|
-
);
|
|
964
|
-
}
|
|
965
|
-
const userPools = extractUserPools(raw);
|
|
966
|
-
for (const [name, userDef] of Object.entries(userPools)) {
|
|
967
|
-
const existing = merged.get(name);
|
|
968
|
-
if (existing) {
|
|
969
|
-
const next = {
|
|
970
|
-
queue: existing.queue,
|
|
971
|
-
concurrency: typeof userDef.concurrency === "number" ? userDef.concurrency : existing.concurrency,
|
|
972
|
-
reserved: existing.reserved,
|
|
973
|
-
description: userDef.description ?? existing.description
|
|
974
|
-
};
|
|
975
|
-
merged.set(name, next);
|
|
976
|
-
continue;
|
|
977
|
-
}
|
|
978
|
-
if (typeof userDef.queue !== "string" || userDef.queue.length === 0) {
|
|
979
|
-
throw new Error(
|
|
980
|
-
`pool-config.loader: pool '${name}' must declare a non-empty 'queue'.`
|
|
981
|
-
);
|
|
982
|
-
}
|
|
983
|
-
if (typeof userDef.concurrency !== "number" || userDef.concurrency <= 0) {
|
|
984
|
-
throw new Error(
|
|
985
|
-
`pool-config.loader: pool '${name}' must declare a positive 'concurrency'.`
|
|
986
|
-
);
|
|
987
|
-
}
|
|
988
|
-
if (userDef.reserved === true) {
|
|
989
|
-
throw new Error(
|
|
990
|
-
`pool-config.loader: user-defined pool '${name}' cannot set 'reserved: true' \u2014 reserved is framework-only.`
|
|
991
|
-
);
|
|
992
|
-
}
|
|
993
|
-
merged.set(name, {
|
|
994
|
-
queue: userDef.queue,
|
|
995
|
-
concurrency: userDef.concurrency,
|
|
996
|
-
reserved: false,
|
|
997
|
-
description: userDef.description
|
|
998
|
-
});
|
|
999
|
-
}
|
|
1000
|
-
cache.set(resolved, merged);
|
|
1001
|
-
return merged;
|
|
1002
|
-
}
|
|
1003
|
-
function allNonReservedPoolNames(config) {
|
|
1004
|
-
const out = [];
|
|
1005
|
-
for (const [name, def] of config) {
|
|
1006
|
-
if (!def.reserved) out.push(name);
|
|
1007
|
-
}
|
|
1008
|
-
return out;
|
|
1009
|
-
}
|
|
1010
|
-
function allPoolNames(config) {
|
|
1011
|
-
return [...config.keys()];
|
|
1012
|
-
}
|
|
1013
|
-
function extractUserPools(raw) {
|
|
1014
|
-
if (!raw || typeof raw !== "object") return {};
|
|
1015
|
-
const jobs2 = raw.jobs;
|
|
1016
|
-
if (!jobs2 || typeof jobs2 !== "object") return {};
|
|
1017
|
-
const pools = jobs2.pools;
|
|
1018
|
-
if (!pools || typeof pools !== "object") return {};
|
|
1019
|
-
const out = {};
|
|
1020
|
-
for (const [name, def] of Object.entries(pools)) {
|
|
1021
|
-
if (!def || typeof def !== "object") continue;
|
|
1022
|
-
out[name] = def;
|
|
1023
|
-
}
|
|
1024
|
-
return out;
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
// runtime/subsystems/jobs/bullmq.config.ts
|
|
1028
|
-
var BULLMQ_CONNECTION = Symbol.for(tokenKey("jobs", "bullmq-connection"));
|
|
1029
|
-
var BULLMQ_RESOLVED_CONFIG = Symbol.for(tokenKey("jobs", "bullmq-resolved-config"));
|
|
1030
|
-
var DEFAULT_REDIS_URL = "redis://localhost:6379";
|
|
1031
|
-
var DEFAULT_BULL_BOARD_MOUNT = "/admin/queues";
|
|
1032
|
-
function resolveBullMqConfig(ext) {
|
|
1033
|
-
const url = ext?.redis_url ?? process.env.REDIS_URL ?? DEFAULT_REDIS_URL;
|
|
1034
|
-
const resolved = {
|
|
1035
|
-
connection: { url },
|
|
1036
|
-
queuePrefix: ext?.queue_prefix
|
|
1037
|
-
};
|
|
1038
|
-
if (ext?.bull_board?.enabled) {
|
|
1039
|
-
resolved.bullBoard = {
|
|
1040
|
-
enabled: true,
|
|
1041
|
-
mountPath: ext.bull_board.mount_path ?? DEFAULT_BULL_BOARD_MOUNT
|
|
1042
|
-
};
|
|
1043
|
-
}
|
|
1044
|
-
return resolved;
|
|
1045
|
-
}
|
|
1046
|
-
function resolvePoolQueueName(pool, config, poolConfig = loadPoolConfig()) {
|
|
1047
|
-
const alias = poolConfig.get(pool)?.queue ?? pool;
|
|
1048
|
-
const prefix = config?.queuePrefix;
|
|
1049
|
-
return prefix ? `${prefix}:${alias}` : alias;
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
// runtime/subsystems/jobs/job-worker.ts
|
|
1053
|
-
import { Inject as Inject4, Injectable as Injectable4, Logger as Logger2 } from "@nestjs/common";
|
|
1054
|
-
import { and as and4, asc as asc2, desc as desc3, eq as eq4, inArray as inArray3, lt as lt2, lte, sql as sql4 } from "drizzle-orm";
|
|
1055
|
-
var JOB_WORKER_OPTIONS = Symbol.for(tokenKey("jobs", "worker-options"));
|
|
1056
|
-
var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
1057
|
-
var DEFAULT_STALE_SWEEPER_INTERVAL_MS = 6e4;
|
|
1058
|
-
var DEFAULT_STALE_THRESHOLD_MS = 5 * 6e4;
|
|
1059
|
-
var DEFAULT_SHUTDOWN_TIMEOUT_MS = 3e4;
|
|
1060
|
-
function computeBackoff(policy, attempts) {
|
|
1061
|
-
const base = Math.max(policy.baseMs, 0);
|
|
1062
|
-
if (policy.backoff === "fixed") {
|
|
1063
|
-
return base;
|
|
1064
|
-
}
|
|
1065
|
-
const exponent = Math.max(attempts - 1, 0);
|
|
1066
|
-
if (exponent >= 53) return Number.MAX_SAFE_INTEGER;
|
|
1067
|
-
const raw = base * Math.pow(2, exponent);
|
|
1068
|
-
if (!Number.isFinite(raw) || raw >= Number.MAX_SAFE_INTEGER) {
|
|
1069
|
-
return Number.MAX_SAFE_INTEGER;
|
|
1070
|
-
}
|
|
1071
|
-
return raw;
|
|
1072
|
-
}
|
|
1073
|
-
function classifyError(err, policy, currentAttempts) {
|
|
1074
|
-
if (!policy) return "fail";
|
|
1075
|
-
const errObj = err;
|
|
1076
|
-
const name = errObj?.name;
|
|
1077
|
-
const code = errObj?.code;
|
|
1078
|
-
const nonRetryable = policy.nonRetryableErrors ?? [];
|
|
1079
|
-
if (nonRetryable.some((n) => n === name || n === code)) return "fail";
|
|
1080
|
-
if (currentAttempts + 1 >= policy.attempts) return "fail";
|
|
1081
|
-
return "retry";
|
|
1082
|
-
}
|
|
1083
|
-
function buildClaimQuery(db, pool) {
|
|
1084
|
-
return db.select({ id: jobRuns.id }).from(jobRuns).where(
|
|
1085
|
-
and4(
|
|
1086
|
-
eq4(jobRuns.status, "pending"),
|
|
1087
|
-
eq4(jobRuns.pool, pool),
|
|
1088
|
-
lte(jobRuns.runAt, /* @__PURE__ */ new Date())
|
|
1089
|
-
)
|
|
1090
|
-
).orderBy(desc3(jobRuns.priority), asc2(jobRuns.runAt)).limit(1).for("update", { skipLocked: true });
|
|
1091
|
-
}
|
|
1092
|
-
function buildStaleSweepQuery(db, staleThresholdMs) {
|
|
1093
|
-
const threshold = new Date(Date.now() - staleThresholdMs);
|
|
1094
|
-
return db.select({ id: jobRuns.id }).from(jobRuns).where(
|
|
1095
|
-
and4(
|
|
1096
|
-
eq4(jobRuns.status, "running"),
|
|
1097
|
-
lt2(jobRuns.claimedAt, threshold)
|
|
1098
|
-
)
|
|
1099
|
-
).for("update", { skipLocked: true });
|
|
1100
|
-
}
|
|
1101
|
-
function serialiseError(err, attempt, retryable) {
|
|
1102
|
-
const e = err;
|
|
1103
|
-
return {
|
|
1104
|
-
message: e?.message ?? String(err),
|
|
1105
|
-
stack: e?.stack,
|
|
1106
|
-
retryable,
|
|
1107
|
-
attempt
|
|
1108
|
-
};
|
|
1109
|
-
}
|
|
1110
|
-
var JobWorker = class {
|
|
1111
|
-
constructor(db, orchestrator, runService, stepService, options, moduleRef) {
|
|
1112
|
-
this.db = db;
|
|
1113
|
-
this.orchestrator = orchestrator;
|
|
1114
|
-
this.runService = runService;
|
|
1115
|
-
this.stepService = stepService;
|
|
1116
|
-
this.options = options;
|
|
1117
|
-
this.moduleRef = moduleRef;
|
|
1118
|
-
this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
1119
|
-
this.staleSweeperIntervalMs = options.staleSweeperIntervalMs ?? DEFAULT_STALE_SWEEPER_INTERVAL_MS;
|
|
1120
|
-
this.staleThresholdMs = options.staleThresholdMs ?? DEFAULT_STALE_THRESHOLD_MS;
|
|
1121
|
-
this.shutdownTimeoutMs = options.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
|
|
1122
|
-
this.sigtermHandler = () => {
|
|
1123
|
-
if (this.sigtermHandled) return;
|
|
1124
|
-
this.sigtermHandled = true;
|
|
1125
|
-
void this.onModuleDestroy();
|
|
1126
|
-
};
|
|
1127
|
-
void this.runService;
|
|
1128
|
-
}
|
|
1129
|
-
db;
|
|
1130
|
-
orchestrator;
|
|
1131
|
-
runService;
|
|
1132
|
-
stepService;
|
|
1133
|
-
options;
|
|
1134
|
-
moduleRef;
|
|
1135
|
-
logger = new Logger2(JobWorker.name);
|
|
1136
|
-
shuttingDown = false;
|
|
1137
|
-
inFlight = /* @__PURE__ */ new Set();
|
|
1138
|
-
pollTimer = null;
|
|
1139
|
-
sweeperTimer = null;
|
|
1140
|
-
sigtermHandled = false;
|
|
1141
|
-
sigtermHandler;
|
|
1142
|
-
pollIntervalMs;
|
|
1143
|
-
staleSweeperIntervalMs;
|
|
1144
|
-
staleThresholdMs;
|
|
1145
|
-
shutdownTimeoutMs;
|
|
1146
|
-
// ============================================================================
|
|
1147
|
-
// Lifecycle
|
|
1148
|
-
// ============================================================================
|
|
1149
|
-
onModuleInit() {
|
|
1150
|
-
this.pollTimer = setInterval(() => {
|
|
1151
|
-
void this.pollAndProcess();
|
|
1152
|
-
}, this.pollIntervalMs);
|
|
1153
|
-
this.sweeperTimer = setInterval(() => {
|
|
1154
|
-
void this.sweepStaleClaims();
|
|
1155
|
-
}, this.staleSweeperIntervalMs);
|
|
1156
|
-
process.on("SIGTERM", this.sigtermHandler);
|
|
1157
|
-
}
|
|
1158
|
-
async onModuleDestroy() {
|
|
1159
|
-
if (this.shuttingDown) {
|
|
1160
|
-
await this.drainInFlight();
|
|
1161
|
-
return;
|
|
1162
|
-
}
|
|
1163
|
-
this.shuttingDown = true;
|
|
1164
|
-
if (this.pollTimer) {
|
|
1165
|
-
clearInterval(this.pollTimer);
|
|
1166
|
-
this.pollTimer = null;
|
|
1167
|
-
}
|
|
1168
|
-
if (this.sweeperTimer) {
|
|
1169
|
-
clearInterval(this.sweeperTimer);
|
|
1170
|
-
this.sweeperTimer = null;
|
|
1171
|
-
}
|
|
1172
|
-
process.removeListener("SIGTERM", this.sigtermHandler);
|
|
1173
|
-
await this.drainInFlight();
|
|
1174
|
-
try {
|
|
1175
|
-
await this.db.update(jobRuns).set({ status: "pending", claimedAt: null, startedAt: null }).where(
|
|
1176
|
-
and4(eq4(jobRuns.status, "running"), eq4(jobRuns.pool, this.options.pool))
|
|
1177
|
-
);
|
|
1178
|
-
} catch (err) {
|
|
1179
|
-
this.logger.error(`shutdown reset failed: ${err.message}`);
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
async drainInFlight() {
|
|
1183
|
-
if (this.inFlight.size === 0) return;
|
|
1184
|
-
const timeout = new Promise(
|
|
1185
|
-
(resolve2) => setTimeout(resolve2, this.shutdownTimeoutMs)
|
|
1186
|
-
);
|
|
1187
|
-
await Promise.race([
|
|
1188
|
-
Promise.allSettled([...this.inFlight]).then(() => void 0),
|
|
1189
|
-
timeout
|
|
1190
|
-
]);
|
|
1191
|
-
}
|
|
1192
|
-
// ============================================================================
|
|
1193
|
-
// Poll loop
|
|
1194
|
-
// ============================================================================
|
|
1195
|
-
async pollAndProcess() {
|
|
1196
|
-
if (this.shuttingDown) return;
|
|
1197
|
-
if (this.inFlight.size >= this.options.concurrency) return;
|
|
1198
|
-
let claimed;
|
|
1199
|
-
try {
|
|
1200
|
-
claimed = await this.claimNext(this.options.pool);
|
|
1201
|
-
} catch (err) {
|
|
1202
|
-
this.logger.error(`claimNext failed: ${err.message}`);
|
|
1203
|
-
return;
|
|
1204
|
-
}
|
|
1205
|
-
if (!claimed) return;
|
|
1206
|
-
const run = claimed;
|
|
1207
|
-
const promise = this.processRun(run).catch((err) => {
|
|
1208
|
-
this.logger.error(
|
|
1209
|
-
`processRun(${run.id}) unhandled: ${err.message}`
|
|
1210
|
-
);
|
|
1211
|
-
});
|
|
1212
|
-
this.inFlight.add(promise);
|
|
1213
|
-
promise.finally(() => {
|
|
1214
|
-
this.inFlight.delete(promise);
|
|
1215
|
-
});
|
|
1216
|
-
}
|
|
1217
|
-
/**
|
|
1218
|
-
* Claim the next runnable row from the pool. Transaction ensures the
|
|
1219
|
-
* select-candidate + update-to-running pair is atomic; FOR UPDATE SKIP
|
|
1220
|
-
* LOCKED lets multiple workers share the table without serialising.
|
|
1221
|
-
*/
|
|
1222
|
-
async claimNext(pool) {
|
|
1223
|
-
return this.db.transaction(async (tx) => {
|
|
1224
|
-
const candidates = await tx.select({ id: jobRuns.id }).from(jobRuns).where(
|
|
1225
|
-
and4(
|
|
1226
|
-
eq4(jobRuns.status, "pending"),
|
|
1227
|
-
eq4(jobRuns.pool, pool),
|
|
1228
|
-
lte(jobRuns.runAt, /* @__PURE__ */ new Date())
|
|
1229
|
-
)
|
|
1230
|
-
).orderBy(desc3(jobRuns.priority), asc2(jobRuns.runAt)).limit(1).for("update", { skipLocked: true });
|
|
1231
|
-
const candidate = candidates[0];
|
|
1232
|
-
if (!candidate) return null;
|
|
1233
|
-
const [claimed] = await tx.update(jobRuns).set({
|
|
1234
|
-
status: "running",
|
|
1235
|
-
claimedAt: /* @__PURE__ */ new Date(),
|
|
1236
|
-
startedAt: /* @__PURE__ */ new Date(),
|
|
1237
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1238
|
-
}).where(eq4(jobRuns.id, candidate.id)).returning();
|
|
1239
|
-
return claimed ?? null;
|
|
1240
|
-
});
|
|
1241
|
-
}
|
|
1242
|
-
// ============================================================================
|
|
1243
|
-
// Stale claim sweeper
|
|
1244
|
-
// ============================================================================
|
|
1245
|
-
/**
|
|
1246
|
-
* Release rows whose `claimed_at` is older than the threshold. Safe to
|
|
1247
|
-
* run concurrently across workers — the two-phase tx (select-for-update
|
|
1248
|
-
* then update) guarantees each stranded row is only reset once.
|
|
1249
|
-
*/
|
|
1250
|
-
async sweepStaleClaims() {
|
|
1251
|
-
if (this.shuttingDown) return;
|
|
1252
|
-
try {
|
|
1253
|
-
await this.db.transaction(async (tx) => {
|
|
1254
|
-
const threshold = new Date(Date.now() - this.staleThresholdMs);
|
|
1255
|
-
const stale = await tx.select({ id: jobRuns.id }).from(jobRuns).where(
|
|
1256
|
-
and4(eq4(jobRuns.status, "running"), lt2(jobRuns.claimedAt, threshold))
|
|
1257
|
-
).for("update", { skipLocked: true });
|
|
1258
|
-
if (stale.length === 0) return;
|
|
1259
|
-
const ids = stale.map((r) => r.id);
|
|
1260
|
-
await tx.update(jobRuns).set({ status: "pending", claimedAt: null, startedAt: null }).where(inArray3(jobRuns.id, ids));
|
|
1261
|
-
for (const id of ids) {
|
|
1262
|
-
this.logger.warn(`Recovered stale claim on run ${id}`);
|
|
1263
|
-
}
|
|
1264
|
-
});
|
|
1265
|
-
} catch (err) {
|
|
1266
|
-
this.logger.error(`sweepStaleClaims failed: ${err.message}`);
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
// ============================================================================
|
|
1270
|
-
// processRun
|
|
1271
|
-
// ============================================================================
|
|
1272
|
-
async processRun(claimed) {
|
|
1273
|
-
const registryEntry = JOB_HANDLER_REGISTRY.get(claimed.jobType);
|
|
1274
|
-
if (!registryEntry) {
|
|
1275
|
-
this.logger.error(
|
|
1276
|
-
`No handler registered for jobType='${claimed.jobType}' (run ${claimed.id})`
|
|
1277
|
-
);
|
|
1278
|
-
await this.markFailed(
|
|
1279
|
-
claimed,
|
|
1280
|
-
new Error(`No handler registered for jobType='${claimed.jobType}'`),
|
|
1281
|
-
/*finalAttempts*/
|
|
1282
|
-
(claimed.attempts ?? 0) + 1
|
|
1283
|
-
);
|
|
1284
|
-
return;
|
|
1285
|
-
}
|
|
1286
|
-
if (claimed.concurrencyKey) {
|
|
1287
|
-
const inflight = await this.db.select({ id: jobRuns.id }).from(jobRuns).where(
|
|
1288
|
-
and4(
|
|
1289
|
-
eq4(jobRuns.concurrencyKey, claimed.concurrencyKey),
|
|
1290
|
-
eq4(jobRuns.status, "running")
|
|
1291
|
-
)
|
|
1292
|
-
);
|
|
1293
|
-
const other = inflight.find((r) => r.id !== claimed.id);
|
|
1294
|
-
if (other) {
|
|
1295
|
-
await this.db.update(jobRuns).set({
|
|
1296
|
-
status: "pending",
|
|
1297
|
-
claimedAt: null,
|
|
1298
|
-
startedAt: null,
|
|
1299
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1300
|
-
}).where(eq4(jobRuns.id, claimed.id));
|
|
1301
|
-
return;
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
const meta = registryEntry.meta;
|
|
1305
|
-
const HandlerClass = registryEntry.handlerClass;
|
|
1306
|
-
const handler = this.moduleRef.get(
|
|
1307
|
-
HandlerClass,
|
|
1308
|
-
{ strict: false }
|
|
1309
|
-
);
|
|
1310
|
-
const ctx = {
|
|
1311
|
-
input: claimed.input,
|
|
1312
|
-
run: claimed,
|
|
1313
|
-
step: this.makeStepFn(claimed),
|
|
1314
|
-
spawnChild: this.makeSpawnFn(claimed),
|
|
1315
|
-
logger: new Logger2(`JobRun:${claimed.id}`)
|
|
1316
|
-
};
|
|
1317
|
-
const attemptsBefore = claimed.attempts ?? 0;
|
|
1318
|
-
try {
|
|
1319
|
-
const output = await handler.run(ctx);
|
|
1320
|
-
await this.db.update(jobRuns).set({
|
|
1321
|
-
status: "completed",
|
|
1322
|
-
output: output ?? {},
|
|
1323
|
-
finishedAt: /* @__PURE__ */ new Date(),
|
|
1324
|
-
updatedAt: /* @__PURE__ */ new Date(),
|
|
1325
|
-
attempts: attemptsBefore + 1
|
|
1326
|
-
}).where(eq4(jobRuns.id, claimed.id));
|
|
1327
|
-
} catch (err) {
|
|
1328
|
-
const policy = meta.retry;
|
|
1329
|
-
const decision = classifyError(err, policy, attemptsBefore);
|
|
1330
|
-
const nextAttempts = attemptsBefore + 1;
|
|
1331
|
-
if (decision === "retry" && policy) {
|
|
1332
|
-
const delay = computeBackoff(policy, nextAttempts);
|
|
1333
|
-
await this.db.update(jobRuns).set({
|
|
1334
|
-
status: "pending",
|
|
1335
|
-
attempts: nextAttempts,
|
|
1336
|
-
runAt: new Date(Date.now() + delay),
|
|
1337
|
-
startedAt: null,
|
|
1338
|
-
claimedAt: null,
|
|
1339
|
-
error: serialiseError(err, nextAttempts, true),
|
|
1340
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1341
|
-
}).where(eq4(jobRuns.id, claimed.id));
|
|
1342
|
-
} else {
|
|
1343
|
-
await this.markFailed(claimed, err, nextAttempts);
|
|
1344
|
-
}
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
async markFailed(claimed, err, finalAttempts) {
|
|
1348
|
-
await this.db.update(jobRuns).set({
|
|
1349
|
-
status: "failed",
|
|
1350
|
-
attempts: finalAttempts,
|
|
1351
|
-
finishedAt: /* @__PURE__ */ new Date(),
|
|
1352
|
-
error: serialiseError(err, finalAttempts, false),
|
|
1353
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1354
|
-
}).where(eq4(jobRuns.id, claimed.id));
|
|
1355
|
-
if (claimed.parentClosePolicy === "terminate") {
|
|
1356
|
-
try {
|
|
1357
|
-
await this.orchestrator.cancel(claimed.id, {
|
|
1358
|
-
cascade: true,
|
|
1359
|
-
reason: "parent-failed",
|
|
1360
|
-
tenantId: claimed.tenantId
|
|
1361
|
-
});
|
|
1362
|
-
} catch (cascadeErr) {
|
|
1363
|
-
this.logger.warn(
|
|
1364
|
-
`cascade on failed run ${claimed.id}: ${cascadeErr.message}`
|
|
1365
|
-
);
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
// ============================================================================
|
|
1370
|
-
// ctx.step / ctx.spawnChild builders
|
|
1371
|
-
// ============================================================================
|
|
1372
|
-
makeStepFn(run) {
|
|
1373
|
-
return async (stepId, fn, _opts) => {
|
|
1374
|
-
void _opts;
|
|
1375
|
-
const existing = await this.stepService.findStep(run.id, stepId);
|
|
1376
|
-
if (existing?.status === "completed") {
|
|
1377
|
-
return existing.output;
|
|
1378
|
-
}
|
|
1379
|
-
const seq = await this.nextStepSeq(run.id);
|
|
1380
|
-
const startedAt = /* @__PURE__ */ new Date();
|
|
1381
|
-
const nextAttempts = (existing?.attempts ?? 0) + 1;
|
|
1382
|
-
await this.stepService.recordStep({
|
|
1383
|
-
jobRunId: run.id,
|
|
1384
|
-
stepId,
|
|
1385
|
-
kind: "task",
|
|
1386
|
-
seq,
|
|
1387
|
-
status: "running",
|
|
1388
|
-
startedAt,
|
|
1389
|
-
attempts: nextAttempts
|
|
1390
|
-
});
|
|
1391
|
-
try {
|
|
1392
|
-
const output = await fn();
|
|
1393
|
-
await this.stepService.recordStep({
|
|
1394
|
-
jobRunId: run.id,
|
|
1395
|
-
stepId,
|
|
1396
|
-
kind: "task",
|
|
1397
|
-
seq,
|
|
1398
|
-
status: "completed",
|
|
1399
|
-
output,
|
|
1400
|
-
finishedAt: /* @__PURE__ */ new Date(),
|
|
1401
|
-
attempts: nextAttempts
|
|
1402
|
-
});
|
|
1403
|
-
return output;
|
|
1404
|
-
} catch (err) {
|
|
1405
|
-
await this.stepService.recordStep({
|
|
1406
|
-
jobRunId: run.id,
|
|
1407
|
-
stepId,
|
|
1408
|
-
kind: "task",
|
|
1409
|
-
seq,
|
|
1410
|
-
status: "failed",
|
|
1411
|
-
error: serialiseError(err, nextAttempts, false),
|
|
1412
|
-
finishedAt: /* @__PURE__ */ new Date(),
|
|
1413
|
-
attempts: nextAttempts
|
|
1414
|
-
});
|
|
1415
|
-
throw err;
|
|
1416
|
-
}
|
|
1417
|
-
};
|
|
1418
|
-
}
|
|
1419
|
-
makeSpawnFn(run) {
|
|
1420
|
-
return async (type, input, opts) => {
|
|
1421
|
-
return this.orchestrator.start(type, input, {
|
|
1422
|
-
parentRunId: run.id,
|
|
1423
|
-
parentClosePolicy: opts?.closePolicy,
|
|
1424
|
-
runAt: opts?.runAt,
|
|
1425
|
-
priority: opts?.priority,
|
|
1426
|
-
tags: opts?.tags,
|
|
1427
|
-
triggerSource: "parent",
|
|
1428
|
-
triggerRef: run.id
|
|
1429
|
-
});
|
|
1430
|
-
};
|
|
1431
|
-
}
|
|
1432
|
-
/**
|
|
1433
|
-
* Allocate the next `seq` for a given run. SELECT-max approach — runs
|
|
1434
|
-
* typically have <100 steps so the scan is cheap, and correctness across
|
|
1435
|
-
* retries is more important than the microseconds saved by an in-memory
|
|
1436
|
-
* counter (which would drift if the worker crashes mid-run and another
|
|
1437
|
-
* worker resumes via stale-claim sweep).
|
|
1438
|
-
*/
|
|
1439
|
-
async nextStepSeq(runId) {
|
|
1440
|
-
const [row] = await this.db.execute(
|
|
1441
|
-
sql4`SELECT COALESCE(MAX(seq), 0) + 1 AS next FROM job_step WHERE job_run_id = ${runId}`
|
|
1442
|
-
);
|
|
1443
|
-
const maybeRows = row?.rows;
|
|
1444
|
-
if (Array.isArray(maybeRows) && maybeRows.length > 0) {
|
|
1445
|
-
return Number(maybeRows[0].next ?? 1);
|
|
1446
|
-
}
|
|
1447
|
-
if (row && typeof row.next !== "undefined") {
|
|
1448
|
-
return Number(row.next);
|
|
1449
|
-
}
|
|
1450
|
-
return 1;
|
|
1451
|
-
}
|
|
1452
|
-
// ============================================================================
|
|
1453
|
-
// (suppress unused-import noise)
|
|
1454
|
-
// ============================================================================
|
|
1455
|
-
};
|
|
1456
|
-
JobWorker = __decorateClass([
|
|
1457
|
-
Injectable4(),
|
|
1458
|
-
__decorateParam(0, Inject4(DRIZZLE)),
|
|
1459
|
-
__decorateParam(1, Inject4(JOB_ORCHESTRATOR)),
|
|
1460
|
-
__decorateParam(2, Inject4(JOB_RUN_SERVICE)),
|
|
1461
|
-
__decorateParam(3, Inject4(JOB_STEP_SERVICE)),
|
|
1462
|
-
__decorateParam(4, Inject4(JOB_WORKER_OPTIONS))
|
|
1463
|
-
], JobWorker);
|
|
1464
|
-
|
|
1465
|
-
// runtime/subsystems/jobs/memory-job-store.ts
|
|
1466
|
-
var MemoryJobStore = class {
|
|
1467
|
-
/** Runs keyed by `id` (single source of truth for status/scope/lineage). */
|
|
1468
|
-
runs = /* @__PURE__ */ new Map();
|
|
1469
|
-
/** Steps keyed by `job_run_id`; array order matches insertion order. */
|
|
1470
|
-
steps = /* @__PURE__ */ new Map();
|
|
1471
|
-
/** Job definitions keyed by `type` — memory mirror of the `job` table. */
|
|
1472
|
-
jobs = /* @__PURE__ */ new Map();
|
|
1473
|
-
/** Reset everything. Tests call this in `beforeEach`. */
|
|
1474
|
-
clear() {
|
|
1475
|
-
this.runs.clear();
|
|
1476
|
-
this.steps.clear();
|
|
1477
|
-
this.jobs.clear();
|
|
1478
|
-
}
|
|
1479
|
-
};
|
|
1480
|
-
|
|
1481
|
-
// runtime/subsystems/jobs/job-orchestrator.memory-backend.ts
|
|
1482
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
1483
|
-
import { Inject as Inject6, Injectable as Injectable6, Logger as Logger3, Optional } from "@nestjs/common";
|
|
1484
|
-
import { ModuleRef } from "@nestjs/core";
|
|
1485
|
-
|
|
1486
|
-
// runtime/subsystems/jobs/job-step-service.memory-backend.ts
|
|
1487
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
1488
|
-
import { Inject as Inject5, Injectable as Injectable5 } from "@nestjs/common";
|
|
1489
|
-
var MemoryJobStepService = class {
|
|
1490
|
-
// ADR-037 (package-mode DI): explicit `@Inject(MemoryJobStore)` — the
|
|
1491
|
-
// published bundle carries no `design:paramtypes`, so a by-type inject
|
|
1492
|
-
// would resolve to `undefined` in package mode.
|
|
1493
|
-
constructor(store) {
|
|
1494
|
-
this.store = store;
|
|
1495
|
-
}
|
|
1496
|
-
store;
|
|
1497
|
-
async findStep(runId, stepId) {
|
|
1498
|
-
const rows = this.store.steps.get(runId);
|
|
1499
|
-
if (!rows) return null;
|
|
1500
|
-
const match = rows.find(
|
|
1501
|
-
(r) => r.stepId === stepId && r.status === "completed"
|
|
1502
|
-
);
|
|
1503
|
-
return match ?? null;
|
|
1504
|
-
}
|
|
1505
|
-
async recordStep(input) {
|
|
1506
|
-
const rows = this.getOrCreateRows(input.jobRunId);
|
|
1507
|
-
const existingIdx = rows.findIndex((r) => r.stepId === input.stepId);
|
|
1508
|
-
const normalisedInput = input.input ?? null;
|
|
1509
|
-
const normalisedOutput = input.output ?? null;
|
|
1510
|
-
if (existingIdx >= 0) {
|
|
1511
|
-
const prev = rows[existingIdx];
|
|
1512
|
-
const next = {
|
|
1513
|
-
...prev,
|
|
1514
|
-
status: input.status,
|
|
1515
|
-
input: normalisedInput ?? prev.input,
|
|
1516
|
-
output: normalisedOutput ?? prev.output,
|
|
1517
|
-
error: input.error ?? prev.error,
|
|
1518
|
-
attempts: input.attempts ?? prev.attempts,
|
|
1519
|
-
startedAt: input.startedAt ?? prev.startedAt,
|
|
1520
|
-
finishedAt: input.finishedAt ?? prev.finishedAt
|
|
1521
|
-
};
|
|
1522
|
-
rows[existingIdx] = next;
|
|
1523
|
-
return next;
|
|
1524
|
-
}
|
|
1525
|
-
const seq = input.seq ?? this.nextSeq(rows);
|
|
1526
|
-
const row = {
|
|
1527
|
-
id: randomUUID2(),
|
|
1528
|
-
jobRunId: input.jobRunId,
|
|
1529
|
-
stepId: input.stepId,
|
|
1530
|
-
kind: input.kind,
|
|
1531
|
-
seq,
|
|
1532
|
-
status: input.status,
|
|
1533
|
-
input: normalisedInput,
|
|
1534
|
-
output: normalisedOutput,
|
|
1535
|
-
error: input.error ?? null,
|
|
1536
|
-
attempts: input.attempts ?? 0,
|
|
1537
|
-
startedAt: input.startedAt ?? null,
|
|
1538
|
-
finishedAt: input.finishedAt ?? null
|
|
1539
|
-
};
|
|
1540
|
-
rows.push(row);
|
|
1541
|
-
return row;
|
|
1542
|
-
}
|
|
1543
|
-
/**
|
|
1544
|
-
* Replay helper — wipe every step row for a run. Mirrors the `scratch`
|
|
1545
|
-
* replay mode of the Drizzle backend (`DELETE FROM job_step WHERE job_run_id = …`).
|
|
1546
|
-
*/
|
|
1547
|
-
clearStepsForRun(runId) {
|
|
1548
|
-
this.store.steps.delete(runId);
|
|
1549
|
-
}
|
|
1550
|
-
/**
|
|
1551
|
-
* Remove every non-`completed` row for the run. Memoized (`completed`)
|
|
1552
|
-
* rows are preserved — this is the `last_checkpoint` / `last_step`
|
|
1553
|
-
* semantics the Drizzle backend implements via
|
|
1554
|
-
* `DELETE … WHERE status != 'completed'`. Both replay modes route here
|
|
1555
|
-
* (Phase 1 collapses `last_step` onto this behaviour; see JOB-3 notes).
|
|
1556
|
-
*/
|
|
1557
|
-
clearIncompleteSteps(runId) {
|
|
1558
|
-
const rows = this.store.steps.get(runId);
|
|
1559
|
-
if (!rows) return;
|
|
1560
|
-
const kept = rows.filter((r) => r.status === "completed");
|
|
1561
|
-
if (kept.length === 0) {
|
|
1562
|
-
this.store.steps.delete(runId);
|
|
1563
|
-
} else {
|
|
1564
|
-
this.store.steps.set(runId, kept);
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1567
|
-
getOrCreateRows(runId) {
|
|
1568
|
-
let rows = this.store.steps.get(runId);
|
|
1569
|
-
if (!rows) {
|
|
1570
|
-
rows = [];
|
|
1571
|
-
this.store.steps.set(runId, rows);
|
|
1572
|
-
}
|
|
1573
|
-
return rows;
|
|
1574
|
-
}
|
|
1575
|
-
nextSeq(rows) {
|
|
1576
|
-
let max = 0;
|
|
1577
|
-
for (const r of rows) {
|
|
1578
|
-
if (r.seq > max) max = r.seq;
|
|
1579
|
-
}
|
|
1580
|
-
return max + 1;
|
|
1581
|
-
}
|
|
1582
|
-
};
|
|
1583
|
-
MemoryJobStepService = __decorateClass([
|
|
1584
|
-
Injectable5(),
|
|
1585
|
-
__decorateParam(0, Inject5(MemoryJobStore))
|
|
1586
|
-
], MemoryJobStepService);
|
|
1587
|
-
|
|
1588
|
-
// runtime/subsystems/jobs/job-orchestrator.memory-backend.ts
|
|
1589
|
-
var QUEUED_RUN_AT = /* @__PURE__ */ new Date(864e13);
|
|
1590
|
-
var TERMINAL_STATUSES2 = [
|
|
1591
|
-
"completed",
|
|
1592
|
-
"failed",
|
|
1593
|
-
"timed_out",
|
|
1594
|
-
"canceled"
|
|
1595
|
-
];
|
|
1596
|
-
var DEDUPE_EXCLUDED_STATUSES2 = ["canceled", "failed"];
|
|
1597
|
-
var IN_FLIGHT_STATUSES2 = ["pending", "running"];
|
|
1598
|
-
function isTerminal(status) {
|
|
1599
|
-
return TERMINAL_STATUSES2.includes(status);
|
|
1600
|
-
}
|
|
1601
|
-
function evaluateKeyTemplate2(template, input) {
|
|
1602
|
-
return template.replace(
|
|
1603
|
-
/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g,
|
|
1604
|
-
(_m, field) => {
|
|
1605
|
-
const value = input[field];
|
|
1606
|
-
if (value === void 0 || value === null) {
|
|
1607
|
-
throw new JobTemplateFieldMissingError(template, field);
|
|
1608
|
-
}
|
|
1609
|
-
return String(value);
|
|
1610
|
-
}
|
|
1611
|
-
);
|
|
1612
|
-
}
|
|
1613
|
-
var PromiseMutex = class {
|
|
1614
|
-
queue = Promise.resolve();
|
|
1615
|
-
async run(fn) {
|
|
1616
|
-
const next = this.queue.then(() => fn());
|
|
1617
|
-
this.queue = next.then(
|
|
1618
|
-
() => void 0,
|
|
1619
|
-
() => void 0
|
|
1620
|
-
);
|
|
1621
|
-
return next;
|
|
1622
|
-
}
|
|
1623
|
-
};
|
|
1624
|
-
var MemoryJobOrchestrator = class {
|
|
1625
|
-
constructor(store, stepService, multiTenant, moduleRef) {
|
|
1626
|
-
this.store = store;
|
|
1627
|
-
this.stepService = stepService;
|
|
1628
|
-
this.multiTenant = multiTenant;
|
|
1629
|
-
this.moduleRef = moduleRef;
|
|
1630
|
-
}
|
|
1631
|
-
store;
|
|
1632
|
-
stepService;
|
|
1633
|
-
multiTenant;
|
|
1634
|
-
moduleRef;
|
|
1635
|
-
logger = new Logger3(MemoryJobOrchestrator.name);
|
|
1636
|
-
mutex = new PromiseMutex();
|
|
1637
|
-
handlerRegistry = /* @__PURE__ */ new Map();
|
|
1638
|
-
/**
|
|
1639
|
-
* `runId → dependent runId[]` — when a run with `concurrencyKey = K`
|
|
1640
|
-
* blocks on an incumbent, its id is added here under the incumbent's id.
|
|
1641
|
-
* On incumbent terminal transition we advance every dependent's `runAt`
|
|
1642
|
-
* back to `now()` so it becomes claimable.
|
|
1643
|
-
*/
|
|
1644
|
-
queueBlockers = /* @__PURE__ */ new Map();
|
|
1645
|
-
/**
|
|
1646
|
-
* JOB-8 — mirror of the Drizzle backend's `resolveTenantId`. Returns the
|
|
1647
|
-
* value to stamp on `tenant_id` / compare against in memory predicates.
|
|
1648
|
-
* Off → always `null`. On + `undefined` → throw. On + `null`/string → pass.
|
|
1649
|
-
*/
|
|
1650
|
-
resolveTenantId(method, tenantId) {
|
|
1651
|
-
if (!this.multiTenant) return null;
|
|
1652
|
-
if (tenantId === void 0) throw new MissingTenantIdError(method);
|
|
1653
|
-
return tenantId;
|
|
1654
|
-
}
|
|
1655
|
-
// ==========================================================================
|
|
1656
|
-
// registerHandler — replaces Drizzle's `job` table upsert
|
|
1657
|
-
// ==========================================================================
|
|
1658
|
-
/**
|
|
1659
|
-
* Populate the in-memory job definition row plus handler class lookup.
|
|
1660
|
-
* Called by `JobWorkerModule.onModuleInit` in memory mode, or directly by
|
|
1661
|
-
* unit tests that want to seed the registry without NestJS.
|
|
1662
|
-
*/
|
|
1663
|
-
registerHandler(type, meta, handlerClass) {
|
|
1664
|
-
const concurrencyKeyTemplate = meta.concurrency?.key ?? null;
|
|
1665
|
-
const dedupeKeyTemplate = meta.dedupe?.key ?? null;
|
|
1666
|
-
const dedupeWindowMs = meta.dedupe?.windowMs ?? null;
|
|
1667
|
-
const now = /* @__PURE__ */ new Date();
|
|
1668
|
-
const def = {
|
|
1669
|
-
type,
|
|
1670
|
-
version: 1,
|
|
1671
|
-
pool: meta.pool ?? "batch",
|
|
1672
|
-
scopeEntityType: meta.scope?.entity ?? null,
|
|
1673
|
-
retryPolicy: meta.retry ?? {
|
|
1674
|
-
attempts: 1,
|
|
1675
|
-
backoff: "fixed",
|
|
1676
|
-
baseMs: 0
|
|
1677
|
-
},
|
|
1678
|
-
timeoutMs: meta.timeoutMs ?? null,
|
|
1679
|
-
concurrencyKeyTemplate: typeof concurrencyKeyTemplate === "string" ? concurrencyKeyTemplate : null,
|
|
1680
|
-
collisionMode: meta.concurrency?.collisionMode ?? "queue",
|
|
1681
|
-
dedupeKeyTemplate: typeof dedupeKeyTemplate === "string" ? dedupeKeyTemplate : null,
|
|
1682
|
-
dedupeWindowMs,
|
|
1683
|
-
priorityDefault: 0,
|
|
1684
|
-
replayFrom: meta.replayFrom ?? "last_checkpoint",
|
|
1685
|
-
createdAt: now,
|
|
1686
|
-
updatedAt: now
|
|
1687
|
-
};
|
|
1688
|
-
this.store.jobs.set(type, def);
|
|
1689
|
-
this.handlerRegistry.set(type, {
|
|
1690
|
-
type,
|
|
1691
|
-
meta,
|
|
1692
|
-
handlerClass
|
|
1693
|
-
});
|
|
1694
|
-
}
|
|
1695
|
-
/** Test helper — look up a registered handler without exposing the map. */
|
|
1696
|
-
getHandlerRegistration(type) {
|
|
1697
|
-
return this.handlerRegistry.get(type);
|
|
1698
|
-
}
|
|
1699
|
-
/**
|
|
1700
|
-
* Boot-time upsert per `IJobOrchestrator.upsertJobRows`. Memory backend
|
|
1701
|
-
* just funnels each entry through `registerHandler`. The validator is
|
|
1702
|
-
* skipped entirely in memory mode (Q4 resolution 2026-04-19), so the
|
|
1703
|
-
* orphaned list is always empty — there are no DB rows to compare against.
|
|
1704
|
-
*/
|
|
1705
|
-
async upsertJobRows(entries, poolConfig) {
|
|
1706
|
-
void poolConfig;
|
|
1707
|
-
for (const entry of entries) {
|
|
1708
|
-
this.registerHandler(
|
|
1709
|
-
entry.type,
|
|
1710
|
-
entry.meta,
|
|
1711
|
-
entry.handlerClass
|
|
1712
|
-
);
|
|
1713
|
-
}
|
|
1714
|
-
return { orphaned: [] };
|
|
1715
|
-
}
|
|
1716
|
-
// ==========================================================================
|
|
1717
|
-
// start
|
|
1718
|
-
// ==========================================================================
|
|
1719
|
-
async start(type, input, opts = {}, _tx) {
|
|
1720
|
-
const tenantId = this.resolveTenantId("start", opts.tenantId);
|
|
1721
|
-
return this.mutex.run(async () => {
|
|
1722
|
-
const payload = input ?? {};
|
|
1723
|
-
const definition = this.store.jobs.get(type);
|
|
1724
|
-
if (!definition) throw new JobTypeNotFoundError(type);
|
|
1725
|
-
if (definition.dedupeKeyTemplate && definition.dedupeWindowMs) {
|
|
1726
|
-
const dedupeKey2 = evaluateKeyTemplate2(
|
|
1727
|
-
definition.dedupeKeyTemplate,
|
|
1728
|
-
payload
|
|
1729
|
-
);
|
|
1730
|
-
const windowStart = Date.now() - definition.dedupeWindowMs;
|
|
1731
|
-
const existing = this.findDedupeCandidate(type, dedupeKey2, windowStart);
|
|
1732
|
-
if (existing) return existing;
|
|
1733
|
-
}
|
|
1734
|
-
let concurrencyKey = null;
|
|
1735
|
-
let queueBlockedBy = null;
|
|
1736
|
-
if (definition.concurrencyKeyTemplate) {
|
|
1737
|
-
concurrencyKey = evaluateKeyTemplate2(
|
|
1738
|
-
definition.concurrencyKeyTemplate,
|
|
1739
|
-
payload
|
|
1740
|
-
);
|
|
1741
|
-
const incumbent = this.findInFlightByConcurrencyKey(concurrencyKey);
|
|
1742
|
-
if (incumbent) {
|
|
1743
|
-
switch (definition.collisionMode) {
|
|
1744
|
-
case "reject":
|
|
1745
|
-
throw new JobCollisionError(type, concurrencyKey, incumbent);
|
|
1746
|
-
case "replace":
|
|
1747
|
-
this.cancelLocked(
|
|
1748
|
-
incumbent.id,
|
|
1749
|
-
{ cascade: true, reason: "replaced" },
|
|
1750
|
-
incumbent.tenantId
|
|
1751
|
-
);
|
|
1752
|
-
break;
|
|
1753
|
-
case "queue":
|
|
1754
|
-
queueBlockedBy = incumbent.id;
|
|
1755
|
-
break;
|
|
1756
|
-
}
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
const newId = randomUUID3();
|
|
1760
|
-
let rootRunId = newId;
|
|
1761
|
-
if (opts.parentRunId) {
|
|
1762
|
-
const parent = this.store.runs.get(opts.parentRunId);
|
|
1763
|
-
if (!parent) {
|
|
1764
|
-
throw new Error(
|
|
1765
|
-
`parentRunId ${opts.parentRunId} does not reference an existing job_run`
|
|
1766
|
-
);
|
|
1767
|
-
}
|
|
1768
|
-
rootRunId = parent.rootRunId;
|
|
1769
|
-
}
|
|
1770
|
-
const dedupeKey = definition.dedupeKeyTemplate ? evaluateKeyTemplate2(definition.dedupeKeyTemplate, payload) : null;
|
|
1771
|
-
const now = /* @__PURE__ */ new Date();
|
|
1772
|
-
const runAt = queueBlockedBy ? QUEUED_RUN_AT : opts.runAt ?? now;
|
|
1773
|
-
const row = {
|
|
1774
|
-
id: newId,
|
|
1775
|
-
jobType: type,
|
|
1776
|
-
jobVersion: definition.version,
|
|
1777
|
-
parentRunId: opts.parentRunId ?? null,
|
|
1778
|
-
rootRunId,
|
|
1779
|
-
parentClosePolicy: opts.parentClosePolicy ?? "terminate",
|
|
1780
|
-
scopeEntityType: opts.scope?.entityType ?? null,
|
|
1781
|
-
scopeEntityId: opts.scope?.entityId ?? null,
|
|
1782
|
-
tenantId,
|
|
1783
|
-
tags: opts.tags ?? {},
|
|
1784
|
-
pool: opts.pool ?? definition.pool,
|
|
1785
|
-
priority: opts.priority ?? definition.priorityDefault,
|
|
1786
|
-
concurrencyKey,
|
|
1787
|
-
dedupeKey,
|
|
1788
|
-
status: "pending",
|
|
1789
|
-
input: payload,
|
|
1790
|
-
output: null,
|
|
1791
|
-
error: null,
|
|
1792
|
-
triggerSource: opts.triggerSource ?? "manual",
|
|
1793
|
-
triggerRef: opts.triggerRef ?? null,
|
|
1794
|
-
runAt,
|
|
1795
|
-
startedAt: null,
|
|
1796
|
-
finishedAt: null,
|
|
1797
|
-
claimedAt: null,
|
|
1798
|
-
attempts: 0,
|
|
1799
|
-
waitKind: null,
|
|
1800
|
-
resumeToken: null,
|
|
1801
|
-
waitDeadline: null,
|
|
1802
|
-
createdAt: now,
|
|
1803
|
-
updatedAt: now
|
|
1804
|
-
};
|
|
1805
|
-
this.store.runs.set(newId, row);
|
|
1806
|
-
if (queueBlockedBy) {
|
|
1807
|
-
const list = this.queueBlockers.get(queueBlockedBy) ?? [];
|
|
1808
|
-
list.push(newId);
|
|
1809
|
-
this.queueBlockers.set(queueBlockedBy, list);
|
|
1810
|
-
}
|
|
1811
|
-
return row;
|
|
1812
|
-
});
|
|
1813
|
-
}
|
|
1814
|
-
// ==========================================================================
|
|
1815
|
-
// cancel
|
|
1816
|
-
// ==========================================================================
|
|
1817
|
-
async cancel(runId, opts = {}) {
|
|
1818
|
-
const tenantId = this.resolveTenantId("cancel", opts.tenantId);
|
|
1819
|
-
await this.mutex.run(async () => {
|
|
1820
|
-
this.cancelLocked(runId, opts, tenantId);
|
|
1821
|
-
});
|
|
1822
|
-
}
|
|
1823
|
-
/**
|
|
1824
|
-
* Internal cancel that assumes the caller already holds the mutex.
|
|
1825
|
-
* Synchronous because all store ops are in-memory. Idempotent.
|
|
1826
|
-
*
|
|
1827
|
-
* `tenantForGate` is the already-validated tenant id (or `null`). When
|
|
1828
|
-
* non-null it gates the initial cancellation to that tenant's run; the
|
|
1829
|
-
* cascade step then sweeps descendants on the same `rootRunId` without
|
|
1830
|
-
* re-checking — children of a tenant-gated parent always share the
|
|
1831
|
-
* tenant (enforced at `start` time).
|
|
1832
|
-
*/
|
|
1833
|
-
cancelLocked(runId, opts, tenantForGate) {
|
|
1834
|
-
const run = this.store.runs.get(runId);
|
|
1835
|
-
if (!run) return;
|
|
1836
|
-
if (this.multiTenant && run.tenantId !== tenantForGate) return;
|
|
1837
|
-
if (isTerminal(run.status)) return;
|
|
1838
|
-
const now = /* @__PURE__ */ new Date();
|
|
1839
|
-
const descendants = opts.cascade === false ? [] : Array.from(this.store.runs.values()).filter(
|
|
1840
|
-
(r) => r.rootRunId === run.rootRunId && r.id !== runId && !isTerminal(r.status)
|
|
1841
|
-
);
|
|
1842
|
-
const terminateChildren = descendants.filter(
|
|
1843
|
-
(d) => d.parentClosePolicy === "terminate" /* Terminate */
|
|
1844
|
-
);
|
|
1845
|
-
const cancelChildren = descendants.filter(
|
|
1846
|
-
(d) => d.parentClosePolicy === "cancel" /* Cancel */
|
|
1847
|
-
);
|
|
1848
|
-
for (const child of terminateChildren) {
|
|
1849
|
-
this.transitionToCanceled(child.id, now);
|
|
1850
|
-
}
|
|
1851
|
-
for (const child of cancelChildren) {
|
|
1852
|
-
this.transitionToCanceled(child.id, now);
|
|
1853
|
-
}
|
|
1854
|
-
this.transitionToCanceled(runId, now);
|
|
1855
|
-
void opts.reason;
|
|
1856
|
-
}
|
|
1857
|
-
transitionToCanceled(runId, at) {
|
|
1858
|
-
const run = this.store.runs.get(runId);
|
|
1859
|
-
if (!run) return;
|
|
1860
|
-
if (isTerminal(run.status)) return;
|
|
1861
|
-
const next = {
|
|
1862
|
-
...run,
|
|
1863
|
-
status: "canceled",
|
|
1864
|
-
finishedAt: at,
|
|
1865
|
-
updatedAt: at
|
|
1866
|
-
};
|
|
1867
|
-
this.store.runs.set(runId, next);
|
|
1868
|
-
this.unblockQueuedDependents(runId);
|
|
1869
|
-
}
|
|
1870
|
-
/**
|
|
1871
|
-
* When `runId` transitions to a terminal state, advance every dependent
|
|
1872
|
-
* `queue`-blocked run's `run_at` back to `now()` so `claimNext` picks
|
|
1873
|
-
* them up.
|
|
1874
|
-
*/
|
|
1875
|
-
unblockQueuedDependents(runId) {
|
|
1876
|
-
const dependents = this.queueBlockers.get(runId);
|
|
1877
|
-
if (!dependents || dependents.length === 0) return;
|
|
1878
|
-
const now = /* @__PURE__ */ new Date();
|
|
1879
|
-
for (const dep of dependents) {
|
|
1880
|
-
const depRun = this.store.runs.get(dep);
|
|
1881
|
-
if (!depRun) continue;
|
|
1882
|
-
if (depRun.status !== "pending") continue;
|
|
1883
|
-
this.store.runs.set(dep, { ...depRun, runAt: now, updatedAt: now });
|
|
1884
|
-
}
|
|
1885
|
-
this.queueBlockers.delete(runId);
|
|
1886
|
-
}
|
|
1887
|
-
// ==========================================================================
|
|
1888
|
-
// claimNext — consumed by JobWorker in memory mode (tests exercise directly)
|
|
1889
|
-
// ==========================================================================
|
|
1890
|
-
async claimNext(pool) {
|
|
1891
|
-
return this.mutex.run(async () => {
|
|
1892
|
-
const now = Date.now();
|
|
1893
|
-
const candidates = Array.from(this.store.runs.values()).filter(
|
|
1894
|
-
(r) => r.status === "pending" && r.pool === pool && r.runAt.getTime() <= now
|
|
1895
|
-
);
|
|
1896
|
-
if (candidates.length === 0) return null;
|
|
1897
|
-
candidates.sort((a, b) => {
|
|
1898
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1899
|
-
return a.runAt.getTime() - b.runAt.getTime();
|
|
1900
|
-
});
|
|
1901
|
-
const winner = candidates[0];
|
|
1902
|
-
const claimedAt = /* @__PURE__ */ new Date();
|
|
1903
|
-
const next = {
|
|
1904
|
-
...winner,
|
|
1905
|
-
status: "running",
|
|
1906
|
-
claimedAt,
|
|
1907
|
-
startedAt: claimedAt,
|
|
1908
|
-
updatedAt: claimedAt
|
|
1909
|
-
};
|
|
1910
|
-
this.store.runs.set(winner.id, next);
|
|
1911
|
-
return next;
|
|
1912
|
-
});
|
|
1913
|
-
}
|
|
1914
|
-
// ==========================================================================
|
|
1915
|
-
// replay
|
|
1916
|
-
// ==========================================================================
|
|
1917
|
-
async replay(runId) {
|
|
1918
|
-
return this.mutex.run(async () => {
|
|
1919
|
-
const run = this.store.runs.get(runId);
|
|
1920
|
-
if (!run) throw new Error(`replay: run ${runId} not found`);
|
|
1921
|
-
if (!isTerminal(run.status)) {
|
|
1922
|
-
throw new JobNotReplayableError(runId, run.status);
|
|
1923
|
-
}
|
|
1924
|
-
const def = this.store.jobs.get(run.jobType);
|
|
1925
|
-
if (!def) throw new JobTypeNotFoundError(run.jobType);
|
|
1926
|
-
const mode = def.replayFrom;
|
|
1927
|
-
if (mode === "scratch") {
|
|
1928
|
-
this.stepService.clearStepsForRun(runId);
|
|
1929
|
-
} else {
|
|
1930
|
-
this.stepService.clearIncompleteSteps(runId);
|
|
1931
|
-
}
|
|
1932
|
-
const now = /* @__PURE__ */ new Date();
|
|
1933
|
-
const next = {
|
|
1934
|
-
...run,
|
|
1935
|
-
status: "pending",
|
|
1936
|
-
attempts: 0,
|
|
1937
|
-
runAt: now,
|
|
1938
|
-
startedAt: null,
|
|
1939
|
-
finishedAt: null,
|
|
1940
|
-
claimedAt: null,
|
|
1941
|
-
error: null,
|
|
1942
|
-
output: null,
|
|
1943
|
-
updatedAt: now
|
|
1944
|
-
};
|
|
1945
|
-
this.store.runs.set(runId, next);
|
|
1946
|
-
return next;
|
|
1947
|
-
});
|
|
1948
|
-
}
|
|
1949
|
-
// ==========================================================================
|
|
1950
|
-
// tick — used by unit tests + memory-mode JobWorker
|
|
1951
|
-
// ==========================================================================
|
|
1952
|
-
/**
|
|
1953
|
-
* Execute a single claimed run to completion, retry, or failure. Not on
|
|
1954
|
-
* `IJobOrchestrator` — it's the memory equivalent of the Drizzle
|
|
1955
|
-
* `JobWorker.processRun` code path. The unit tests drive it directly so
|
|
1956
|
-
* they can assert memoization across ticks without spinning up a worker.
|
|
1957
|
-
*/
|
|
1958
|
-
async tick(runId) {
|
|
1959
|
-
const run = this.store.runs.get(runId);
|
|
1960
|
-
if (!run) throw new Error(`tick: run ${runId} not found`);
|
|
1961
|
-
if (run.status !== "running") {
|
|
1962
|
-
throw new Error(
|
|
1963
|
-
`tick: run ${runId} must be 'running' (got '${run.status}')`
|
|
1964
|
-
);
|
|
1965
|
-
}
|
|
1966
|
-
const registration = this.handlerRegistry.get(run.jobType);
|
|
1967
|
-
if (!registration) {
|
|
1968
|
-
await this.markFailed(run, new Error(
|
|
1969
|
-
`No handler registered for jobType='${run.jobType}'`
|
|
1970
|
-
), (run.attempts ?? 0) + 1);
|
|
1971
|
-
return;
|
|
1972
|
-
}
|
|
1973
|
-
const meta = registration.meta;
|
|
1974
|
-
const HandlerClass = registration.handlerClass;
|
|
1975
|
-
const handler = this.moduleRef ? this.moduleRef.get(
|
|
1976
|
-
HandlerClass,
|
|
1977
|
-
{ strict: false }
|
|
1978
|
-
) : new HandlerClass();
|
|
1979
|
-
const ctx = {
|
|
1980
|
-
input: run.input,
|
|
1981
|
-
run,
|
|
1982
|
-
step: this.makeStepFn(run),
|
|
1983
|
-
spawnChild: this.makeSpawnFn(run),
|
|
1984
|
-
logger: new Logger3(`JobRun:${run.id}`)
|
|
1985
|
-
};
|
|
1986
|
-
const attemptsBefore = run.attempts ?? 0;
|
|
1987
|
-
try {
|
|
1988
|
-
const output = await handler.run(ctx);
|
|
1989
|
-
await this.markCompleted(run, output ?? {}, attemptsBefore + 1);
|
|
1990
|
-
} catch (err) {
|
|
1991
|
-
const policy = meta.retry;
|
|
1992
|
-
const decision = classifyError2(err, policy, attemptsBefore);
|
|
1993
|
-
const nextAttempts = attemptsBefore + 1;
|
|
1994
|
-
if (decision === "retry" && policy) {
|
|
1995
|
-
const delay = computeBackoff2(policy, nextAttempts);
|
|
1996
|
-
await this.rescheduleForRetry(run, err, nextAttempts, delay);
|
|
1997
|
-
} else {
|
|
1998
|
-
await this.markFailed(run, err, nextAttempts);
|
|
1999
|
-
}
|
|
2000
|
-
}
|
|
2001
|
-
}
|
|
2002
|
-
makeStepFn(run) {
|
|
2003
|
-
return async (stepId, fn, _opts) => {
|
|
2004
|
-
void _opts;
|
|
2005
|
-
const existing = await this.stepService.findStep(run.id, stepId);
|
|
2006
|
-
if (existing?.status === "completed") {
|
|
2007
|
-
return existing.output;
|
|
2008
|
-
}
|
|
2009
|
-
const seq = this.nextStepSeq(run.id);
|
|
2010
|
-
const startedAt = /* @__PURE__ */ new Date();
|
|
2011
|
-
const nextAttempts = (existing?.attempts ?? 0) + 1;
|
|
2012
|
-
await this.stepService.recordStep({
|
|
2013
|
-
jobRunId: run.id,
|
|
2014
|
-
stepId,
|
|
2015
|
-
kind: "task",
|
|
2016
|
-
seq,
|
|
2017
|
-
status: "running",
|
|
2018
|
-
startedAt,
|
|
2019
|
-
attempts: nextAttempts
|
|
2020
|
-
});
|
|
2021
|
-
try {
|
|
2022
|
-
const output = await fn();
|
|
2023
|
-
await this.stepService.recordStep({
|
|
2024
|
-
jobRunId: run.id,
|
|
2025
|
-
stepId,
|
|
2026
|
-
kind: "task",
|
|
2027
|
-
seq,
|
|
2028
|
-
status: "completed",
|
|
2029
|
-
output,
|
|
2030
|
-
finishedAt: /* @__PURE__ */ new Date(),
|
|
2031
|
-
attempts: nextAttempts
|
|
2032
|
-
});
|
|
2033
|
-
return output;
|
|
2034
|
-
} catch (err) {
|
|
2035
|
-
await this.stepService.recordStep({
|
|
2036
|
-
jobRunId: run.id,
|
|
2037
|
-
stepId,
|
|
2038
|
-
kind: "task",
|
|
2039
|
-
seq,
|
|
2040
|
-
status: "failed",
|
|
2041
|
-
error: serialiseError2(err, nextAttempts, false),
|
|
2042
|
-
finishedAt: /* @__PURE__ */ new Date(),
|
|
2043
|
-
attempts: nextAttempts
|
|
2044
|
-
});
|
|
2045
|
-
throw err;
|
|
2046
|
-
}
|
|
2047
|
-
};
|
|
2048
|
-
}
|
|
2049
|
-
makeSpawnFn(run) {
|
|
2050
|
-
return async (type, input, opts) => {
|
|
2051
|
-
return this.start(type, input, {
|
|
2052
|
-
parentRunId: run.id,
|
|
2053
|
-
parentClosePolicy: opts?.closePolicy,
|
|
2054
|
-
runAt: opts?.runAt,
|
|
2055
|
-
priority: opts?.priority,
|
|
2056
|
-
tags: opts?.tags,
|
|
2057
|
-
triggerSource: "parent",
|
|
2058
|
-
triggerRef: run.id
|
|
2059
|
-
});
|
|
2060
|
-
};
|
|
2061
|
-
}
|
|
2062
|
-
nextStepSeq(runId) {
|
|
2063
|
-
const rows = this.store.steps.get(runId);
|
|
2064
|
-
if (!rows || rows.length === 0) return 1;
|
|
2065
|
-
let max = 0;
|
|
2066
|
-
for (const r of rows) if (r.seq > max) max = r.seq;
|
|
2067
|
-
return max + 1;
|
|
2068
|
-
}
|
|
2069
|
-
async markCompleted(run, output, attempts) {
|
|
2070
|
-
await this.mutex.run(async () => {
|
|
2071
|
-
const current = this.store.runs.get(run.id);
|
|
2072
|
-
if (!current || isTerminal(current.status)) return;
|
|
2073
|
-
const now = /* @__PURE__ */ new Date();
|
|
2074
|
-
this.store.runs.set(run.id, {
|
|
2075
|
-
...current,
|
|
2076
|
-
status: "completed",
|
|
2077
|
-
output,
|
|
2078
|
-
finishedAt: now,
|
|
2079
|
-
updatedAt: now,
|
|
2080
|
-
attempts
|
|
2081
|
-
});
|
|
2082
|
-
this.unblockQueuedDependents(run.id);
|
|
2083
|
-
});
|
|
2084
|
-
}
|
|
2085
|
-
async markFailed(run, err, attempts) {
|
|
2086
|
-
await this.mutex.run(async () => {
|
|
2087
|
-
const current = this.store.runs.get(run.id);
|
|
2088
|
-
if (!current || isTerminal(current.status)) return;
|
|
2089
|
-
const now = /* @__PURE__ */ new Date();
|
|
2090
|
-
this.store.runs.set(run.id, {
|
|
2091
|
-
...current,
|
|
2092
|
-
status: "failed",
|
|
2093
|
-
finishedAt: now,
|
|
2094
|
-
updatedAt: now,
|
|
2095
|
-
attempts,
|
|
2096
|
-
error: serialiseError2(err, attempts, false)
|
|
2097
|
-
});
|
|
2098
|
-
this.unblockQueuedDependents(run.id);
|
|
2099
|
-
});
|
|
2100
|
-
if (run.parentClosePolicy === "terminate") {
|
|
2101
|
-
try {
|
|
2102
|
-
await this.cancel(run.id, {
|
|
2103
|
-
cascade: true,
|
|
2104
|
-
reason: "parent-failed",
|
|
2105
|
-
tenantId: run.tenantId
|
|
2106
|
-
});
|
|
2107
|
-
} catch (cascadeErr) {
|
|
2108
|
-
this.logger.warn(
|
|
2109
|
-
`cascade on failed run ${run.id}: ${cascadeErr.message}`
|
|
2110
|
-
);
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2113
|
-
}
|
|
2114
|
-
async rescheduleForRetry(run, err, attempts, delayMs) {
|
|
2115
|
-
await this.mutex.run(async () => {
|
|
2116
|
-
const current = this.store.runs.get(run.id);
|
|
2117
|
-
if (!current || isTerminal(current.status)) return;
|
|
2118
|
-
const now = /* @__PURE__ */ new Date();
|
|
2119
|
-
this.store.runs.set(run.id, {
|
|
2120
|
-
...current,
|
|
2121
|
-
status: "pending",
|
|
2122
|
-
attempts,
|
|
2123
|
-
runAt: new Date(Date.now() + delayMs),
|
|
2124
|
-
startedAt: null,
|
|
2125
|
-
claimedAt: null,
|
|
2126
|
-
updatedAt: now,
|
|
2127
|
-
error: serialiseError2(err, attempts, true)
|
|
2128
|
-
});
|
|
2129
|
-
});
|
|
2130
|
-
}
|
|
2131
|
-
// ==========================================================================
|
|
2132
|
-
// Internal queries — used by start / cancel
|
|
2133
|
-
// ==========================================================================
|
|
2134
|
-
findDedupeCandidate(jobType, dedupeKey, windowStartMs) {
|
|
2135
|
-
let best = null;
|
|
2136
|
-
for (const r of this.store.runs.values()) {
|
|
2137
|
-
if (r.jobType !== jobType) continue;
|
|
2138
|
-
if (r.dedupeKey !== dedupeKey) continue;
|
|
2139
|
-
if (DEDUPE_EXCLUDED_STATUSES2.includes(r.status)) continue;
|
|
2140
|
-
if (r.createdAt.getTime() <= windowStartMs) continue;
|
|
2141
|
-
if (!best || r.createdAt.getTime() > best.createdAt.getTime()) {
|
|
2142
|
-
best = r;
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
return best;
|
|
2146
|
-
}
|
|
2147
|
-
findInFlightByConcurrencyKey(key) {
|
|
2148
|
-
for (const r of this.store.runs.values()) {
|
|
2149
|
-
if (r.concurrencyKey !== key) continue;
|
|
2150
|
-
if (!IN_FLIGHT_STATUSES2.includes(r.status)) continue;
|
|
2151
|
-
return r;
|
|
2152
|
-
}
|
|
2153
|
-
return null;
|
|
2154
|
-
}
|
|
2155
|
-
};
|
|
2156
|
-
MemoryJobOrchestrator = __decorateClass([
|
|
2157
|
-
Injectable6(),
|
|
2158
|
-
__decorateParam(0, Inject6(MemoryJobStore)),
|
|
2159
|
-
__decorateParam(1, Inject6(MemoryJobStepService)),
|
|
2160
|
-
__decorateParam(2, Inject6(JOBS_MULTI_TENANT)),
|
|
2161
|
-
__decorateParam(3, Optional()),
|
|
2162
|
-
__decorateParam(3, Inject6(ModuleRef))
|
|
2163
|
-
], MemoryJobOrchestrator);
|
|
2164
|
-
function classifyError2(err, policy, currentAttempts) {
|
|
2165
|
-
if (!policy) return "fail";
|
|
2166
|
-
const errObj = err;
|
|
2167
|
-
const name = errObj?.name;
|
|
2168
|
-
const code = errObj?.code;
|
|
2169
|
-
const nonRetryable = policy.nonRetryableErrors ?? [];
|
|
2170
|
-
if (nonRetryable.some((n) => n === name || n === code)) return "fail";
|
|
2171
|
-
if (currentAttempts + 1 >= policy.attempts) return "fail";
|
|
2172
|
-
return "retry";
|
|
2173
|
-
}
|
|
2174
|
-
function computeBackoff2(policy, attempts) {
|
|
2175
|
-
const base = Math.max(policy.baseMs, 0);
|
|
2176
|
-
if (policy.backoff === "fixed") return base;
|
|
2177
|
-
const exponent = Math.max(attempts - 1, 0);
|
|
2178
|
-
if (exponent >= 53) return Number.MAX_SAFE_INTEGER;
|
|
2179
|
-
const raw = base * Math.pow(2, exponent);
|
|
2180
|
-
if (!Number.isFinite(raw) || raw >= Number.MAX_SAFE_INTEGER) {
|
|
2181
|
-
return Number.MAX_SAFE_INTEGER;
|
|
2182
|
-
}
|
|
2183
|
-
return raw;
|
|
2184
|
-
}
|
|
2185
|
-
function serialiseError2(err, attempt, retryable) {
|
|
2186
|
-
const e = err;
|
|
2187
|
-
return {
|
|
2188
|
-
message: e?.message ?? String(err),
|
|
2189
|
-
stack: e?.stack,
|
|
2190
|
-
retryable,
|
|
2191
|
-
attempt
|
|
2192
|
-
};
|
|
2193
|
-
}
|
|
2194
|
-
|
|
2195
|
-
// runtime/subsystems/jobs/job-run-service.memory-backend.ts
|
|
2196
|
-
import { Inject as Inject7, Injectable as Injectable7 } from "@nestjs/common";
|
|
2197
|
-
var NON_TERMINAL_STATUSES2 = [
|
|
2198
|
-
"pending",
|
|
2199
|
-
"running",
|
|
2200
|
-
"waiting"
|
|
2201
|
-
];
|
|
2202
|
-
var MemoryJobRunService = class {
|
|
2203
|
-
constructor(store, orchestrator, multiTenant) {
|
|
2204
|
-
this.store = store;
|
|
2205
|
-
this.orchestrator = orchestrator;
|
|
2206
|
-
this.multiTenant = multiTenant;
|
|
2207
|
-
}
|
|
2208
|
-
store;
|
|
2209
|
-
orchestrator;
|
|
2210
|
-
multiTenant;
|
|
2211
|
-
/**
|
|
2212
|
-
* JOB-8 — produce a per-row predicate for the tenant gate.
|
|
2213
|
-
* Returns `null` when multi-tenancy is off (caller doesn't check).
|
|
2214
|
-
* Throws when on + `undefined`; matches `tenant_id IS NULL` on explicit
|
|
2215
|
-
* `null` to support cross-tenant background work.
|
|
2216
|
-
*/
|
|
2217
|
-
tenantPredicate(method, tenantId) {
|
|
2218
|
-
if (!this.multiTenant) return null;
|
|
2219
|
-
if (tenantId === void 0) throw new MissingTenantIdError(method);
|
|
2220
|
-
return (r) => r.tenantId === tenantId;
|
|
2221
|
-
}
|
|
2222
|
-
async listForScope(entityType, entityId, opts = {}) {
|
|
2223
|
-
const statusFilter = opts.status ? Array.isArray(opts.status) ? new Set(opts.status) : /* @__PURE__ */ new Set([opts.status]) : null;
|
|
2224
|
-
const tenantCheck = this.tenantPredicate("listForScope", opts.tenantId);
|
|
2225
|
-
const rows = [];
|
|
2226
|
-
for (const r of this.store.runs.values()) {
|
|
2227
|
-
if (r.scopeEntityType !== entityType) continue;
|
|
2228
|
-
if (r.scopeEntityId !== entityId) continue;
|
|
2229
|
-
if (statusFilter && !statusFilter.has(r.status)) continue;
|
|
2230
|
-
if (opts.jobType && r.jobType !== opts.jobType) continue;
|
|
2231
|
-
if (tenantCheck && !tenantCheck(r)) continue;
|
|
2232
|
-
rows.push(r);
|
|
2233
|
-
}
|
|
2234
|
-
const orderBy = opts.orderBy ?? "created_at desc";
|
|
2235
|
-
rows.sort((a, b) => compareBy(a, b, orderBy));
|
|
2236
|
-
const offset = opts.offset ?? 0;
|
|
2237
|
-
const limit = opts.limit;
|
|
2238
|
-
const sliced = typeof limit === "number" ? rows.slice(offset, offset + limit) : rows.slice(offset);
|
|
2239
|
-
return sliced;
|
|
2240
|
-
}
|
|
2241
|
-
async cancelForScope(entityType, entityId, opts = {}) {
|
|
2242
|
-
const tenantCheck = this.tenantPredicate("cancelForScope", opts.tenantId);
|
|
2243
|
-
const ids = [];
|
|
2244
|
-
for (const r of this.store.runs.values()) {
|
|
2245
|
-
if (r.scopeEntityType !== entityType) continue;
|
|
2246
|
-
if (r.scopeEntityId !== entityId) continue;
|
|
2247
|
-
if (!NON_TERMINAL_STATUSES2.includes(r.status)) continue;
|
|
2248
|
-
if (tenantCheck && !tenantCheck(r)) continue;
|
|
2249
|
-
ids.push(r.id);
|
|
2250
|
-
}
|
|
2251
|
-
for (const id of ids) {
|
|
2252
|
-
await this.orchestrator.cancel(id, {
|
|
2253
|
-
cascade: true,
|
|
2254
|
-
tenantId: opts.tenantId
|
|
2255
|
-
});
|
|
2256
|
-
}
|
|
2257
|
-
}
|
|
2258
|
-
async rescheduleForScope(entityType, entityId, newRunAt, opts = {}) {
|
|
2259
|
-
const tenantCheck = this.tenantPredicate("rescheduleForScope", opts.tenantId);
|
|
2260
|
-
for (const r of this.store.runs.values()) {
|
|
2261
|
-
if (r.scopeEntityType !== entityType) continue;
|
|
2262
|
-
if (r.scopeEntityId !== entityId) continue;
|
|
2263
|
-
if (r.status !== "pending") continue;
|
|
2264
|
-
if (tenantCheck && !tenantCheck(r)) continue;
|
|
2265
|
-
this.store.runs.set(r.id, {
|
|
2266
|
-
...r,
|
|
2267
|
-
runAt: newRunAt,
|
|
2268
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
2269
|
-
});
|
|
2270
|
-
}
|
|
2271
|
-
}
|
|
2272
|
-
async countByPoolAndStatus(tenantId) {
|
|
2273
|
-
const tenantCheck = this.tenantPredicate("countByPoolAndStatus", tenantId);
|
|
2274
|
-
const map = /* @__PURE__ */ new Map();
|
|
2275
|
-
for (const r of this.store.runs.values()) {
|
|
2276
|
-
if (tenantCheck && !tenantCheck(r)) continue;
|
|
2277
|
-
const key = `${r.pool}\0${r.status}`;
|
|
2278
|
-
const cur = map.get(key);
|
|
2279
|
-
if (cur) {
|
|
2280
|
-
cur.count += 1;
|
|
2281
|
-
} else {
|
|
2282
|
-
map.set(key, { pool: r.pool, status: r.status, count: 1 });
|
|
2283
|
-
}
|
|
2284
|
-
}
|
|
2285
|
-
return Array.from(map.values());
|
|
2286
|
-
}
|
|
2287
|
-
async listRecentFailed(limit, tenantId) {
|
|
2288
|
-
const tenantCheck = this.tenantPredicate("listRecentFailed", tenantId);
|
|
2289
|
-
const failed = [];
|
|
2290
|
-
for (const r of this.store.runs.values()) {
|
|
2291
|
-
if (r.status !== "failed") continue;
|
|
2292
|
-
if (tenantCheck && !tenantCheck(r)) continue;
|
|
2293
|
-
failed.push(r);
|
|
2294
|
-
}
|
|
2295
|
-
failed.sort((a, b) => {
|
|
2296
|
-
const at = (a.finishedAt ?? a.updatedAt).getTime();
|
|
2297
|
-
const bt = (b.finishedAt ?? b.updatedAt).getTime();
|
|
2298
|
-
return bt - at;
|
|
2299
|
-
});
|
|
2300
|
-
return failed.slice(0, limit).map((r) => ({
|
|
2301
|
-
runId: r.id,
|
|
2302
|
-
jobType: r.jobType,
|
|
2303
|
-
pool: r.pool,
|
|
2304
|
-
scopeEntityType: r.scopeEntityType,
|
|
2305
|
-
scopeEntityId: r.scopeEntityId,
|
|
2306
|
-
tenantId: r.tenantId,
|
|
2307
|
-
attempts: r.attempts,
|
|
2308
|
-
errorMessage: r.error?.message ?? null,
|
|
2309
|
-
failedAt: r.finishedAt ?? r.updatedAt,
|
|
2310
|
-
createdAt: r.createdAt
|
|
2311
|
-
}));
|
|
2312
|
-
}
|
|
2313
|
-
async listJobRuns(query = {}) {
|
|
2314
|
-
const limit = clampLimit(query.limit);
|
|
2315
|
-
const tenantCheck = this.tenantPredicate("listJobRuns", query.tenantId);
|
|
2316
|
-
const keyset = query.cursor ? decodeKeysetCursor(query.cursor) : null;
|
|
2317
|
-
const matched = [];
|
|
2318
|
-
for (const r of this.store.runs.values()) {
|
|
2319
|
-
if (tenantCheck && !tenantCheck(r)) continue;
|
|
2320
|
-
if (query.poolId && r.pool !== query.poolId) continue;
|
|
2321
|
-
if (query.rootRunId && r.rootRunId !== query.rootRunId) continue;
|
|
2322
|
-
if (query.status && r.status !== query.status) continue;
|
|
2323
|
-
if (query.since && r.createdAt.getTime() < query.since.getTime()) continue;
|
|
2324
|
-
matched.push(r);
|
|
2325
|
-
}
|
|
2326
|
-
matched.sort((a, b) => {
|
|
2327
|
-
const dt = b.createdAt.getTime() - a.createdAt.getTime();
|
|
2328
|
-
if (dt !== 0) return dt;
|
|
2329
|
-
return a.id < b.id ? 1 : a.id > b.id ? -1 : 0;
|
|
2330
|
-
});
|
|
2331
|
-
const seeked = keyset ? matched.filter((r) => {
|
|
2332
|
-
const ct = r.createdAt.getTime();
|
|
2333
|
-
const kt = keyset.createdAt.getTime();
|
|
2334
|
-
if (ct < kt) return true;
|
|
2335
|
-
if (ct > kt) return false;
|
|
2336
|
-
return r.id < keyset.id;
|
|
2337
|
-
}) : matched;
|
|
2338
|
-
const hasMore = seeked.length > limit;
|
|
2339
|
-
const page = hasMore ? seeked.slice(0, limit) : seeked;
|
|
2340
|
-
const items = page.map(toJobRunSummary);
|
|
2341
|
-
const last = page[page.length - 1];
|
|
2342
|
-
const nextCursor = hasMore && last ? encodeKeysetCursor({ createdAt: last.createdAt, id: last.id }) : null;
|
|
2343
|
-
return { items, nextCursor };
|
|
2344
|
-
}
|
|
2345
|
-
/**
|
|
2346
|
-
* Direct lookup. Not on the protocol — concrete-class convenience for
|
|
2347
|
-
* tests. Matches `DrizzleJobRunService.findByRootRunId` in spirit; both
|
|
2348
|
-
* are debug / test helpers that sidestep the orchestrator.
|
|
2349
|
-
*/
|
|
2350
|
-
findById(runId) {
|
|
2351
|
-
return this.store.runs.get(runId) ?? null;
|
|
2352
|
-
}
|
|
2353
|
-
/** Public counterpart to the Drizzle backend's `findByRootRunId` helper. */
|
|
2354
|
-
findByRootRunId(rootRunId) {
|
|
2355
|
-
const out = [];
|
|
2356
|
-
for (const r of this.store.runs.values()) {
|
|
2357
|
-
if (r.rootRunId === rootRunId) out.push(r);
|
|
2358
|
-
}
|
|
2359
|
-
return out;
|
|
2360
|
-
}
|
|
2361
|
-
};
|
|
2362
|
-
MemoryJobRunService = __decorateClass([
|
|
2363
|
-
Injectable7(),
|
|
2364
|
-
__decorateParam(0, Inject7(MemoryJobStore)),
|
|
2365
|
-
__decorateParam(1, Inject7(JOB_ORCHESTRATOR)),
|
|
2366
|
-
__decorateParam(2, Inject7(JOBS_MULTI_TENANT))
|
|
2367
|
-
], MemoryJobRunService);
|
|
2368
|
-
function compareBy(a, b, order) {
|
|
2369
|
-
switch (order) {
|
|
2370
|
-
case "created_at asc":
|
|
2371
|
-
return a.createdAt.getTime() - b.createdAt.getTime();
|
|
2372
|
-
case "run_at desc":
|
|
2373
|
-
return b.runAt.getTime() - a.runAt.getTime();
|
|
2374
|
-
case "run_at asc":
|
|
2375
|
-
return a.runAt.getTime() - b.runAt.getTime();
|
|
2376
|
-
case "created_at desc":
|
|
2377
|
-
default:
|
|
2378
|
-
return b.createdAt.getTime() - a.createdAt.getTime();
|
|
2379
|
-
}
|
|
2380
|
-
}
|
|
2381
|
-
|
|
2382
|
-
// runtime/subsystems/jobs/jobs-domain.module.ts
|
|
2383
|
-
import { Module } from "@nestjs/common";
|
|
2384
|
-
var JobsDomainModule = class {
|
|
2385
|
-
static forRoot(opts) {
|
|
2386
|
-
const multiTenant = opts.multiTenant ?? false;
|
|
2387
|
-
const providers = [
|
|
2388
|
-
// JOB-8 — boolean provider consumed by the four service-layer backends.
|
|
2389
|
-
// Always provided (even when `multiTenant === false`) so `@Inject`
|
|
2390
|
-
// always resolves; backends short-circuit the enforcement path when
|
|
2391
|
-
// the value is `false`. See `jobs-domain.tokens.ts` for the claim-loop
|
|
2392
|
-
// cross-tenant-by-design decision.
|
|
2393
|
-
{ provide: JOBS_MULTI_TENANT, useValue: multiTenant }
|
|
2394
|
-
];
|
|
2395
|
-
if (opts.backend === "memory") {
|
|
2396
|
-
const store = new MemoryJobStore();
|
|
2397
|
-
providers.push({ provide: MemoryJobStore, useValue: store });
|
|
2398
|
-
providers.push(MemoryJobStepService);
|
|
2399
|
-
providers.push({ provide: JOB_STEP_SERVICE, useExisting: MemoryJobStepService });
|
|
2400
|
-
providers.push(MemoryJobOrchestrator);
|
|
2401
|
-
providers.push({ provide: JOB_ORCHESTRATOR, useExisting: MemoryJobOrchestrator });
|
|
2402
|
-
providers.push(MemoryJobRunService);
|
|
2403
|
-
providers.push({ provide: JOB_RUN_SERVICE, useExisting: MemoryJobRunService });
|
|
2404
|
-
} else if (opts.backend === "bullmq") {
|
|
2405
|
-
const resolved = resolveBullMqConfig(opts.extensions?.bullmq);
|
|
2406
|
-
providers.push({ provide: BULLMQ_CONNECTION, useValue: resolved.connection });
|
|
2407
|
-
providers.push({ provide: BULLMQ_RESOLVED_CONFIG, useValue: resolved });
|
|
2408
|
-
providers.push({
|
|
2409
|
-
provide: JOB_ORCHESTRATOR,
|
|
2410
|
-
useFactory: async (...args) => {
|
|
2411
|
-
const specifier = "./job-orchestrator.bullmq-backend";
|
|
2412
|
-
const mod = await import(specifier);
|
|
2413
|
-
return new mod.BullMQJobOrchestrator(...args);
|
|
2414
|
-
},
|
|
2415
|
-
// The bullmq orchestrator constructor mirrors DrizzleJobOrchestrator's
|
|
2416
|
-
// injection list: DRIZZLE + JOBS_MULTI_TENANT + the resolved BullMQ
|
|
2417
|
-
// tokens. Importing token references would force a static dep on the
|
|
2418
|
-
// tokens file in this module's import graph; using the existing
|
|
2419
|
-
// symbols already in scope is sufficient.
|
|
2420
|
-
inject: [DRIZZLE, JOBS_MULTI_TENANT, BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG]
|
|
2421
|
-
});
|
|
2422
|
-
providers.push({ provide: JOB_RUN_SERVICE, useClass: DrizzleJobRunService });
|
|
2423
|
-
providers.push({ provide: JOB_STEP_SERVICE, useClass: DrizzleJobStepService });
|
|
2424
|
-
} else {
|
|
2425
|
-
providers.push({ provide: JOB_ORCHESTRATOR, useClass: DrizzleJobOrchestrator });
|
|
2426
|
-
providers.push({ provide: JOB_RUN_SERVICE, useClass: DrizzleJobRunService });
|
|
2427
|
-
providers.push({ provide: JOB_STEP_SERVICE, useClass: DrizzleJobStepService });
|
|
2428
|
-
}
|
|
2429
|
-
const exports = [
|
|
2430
|
-
JOB_ORCHESTRATOR,
|
|
2431
|
-
JOB_RUN_SERVICE,
|
|
2432
|
-
JOB_STEP_SERVICE,
|
|
2433
|
-
JOBS_MULTI_TENANT
|
|
2434
|
-
];
|
|
2435
|
-
if (opts.backend === "bullmq") {
|
|
2436
|
-
exports.push(BULLMQ_CONNECTION, BULLMQ_RESOLVED_CONFIG);
|
|
2437
|
-
}
|
|
2438
|
-
return {
|
|
2439
|
-
module: JobsDomainModule,
|
|
2440
|
-
global: true,
|
|
2441
|
-
providers,
|
|
2442
|
-
exports
|
|
2443
|
-
};
|
|
2444
|
-
}
|
|
2445
|
-
};
|
|
2446
|
-
JobsDomainModule = __decorateClass([
|
|
2447
|
-
Module({})
|
|
2448
|
-
], JobsDomainModule);
|
|
2449
|
-
|
|
2450
|
-
// runtime/subsystems/jobs/job-worker.module.ts
|
|
3
|
+
JobWorkerModule,
|
|
4
|
+
JobWorkerOrchestrator
|
|
5
|
+
} from "../../../chunk-WPXNN6QS.js";
|
|
2451
6
|
import {
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
this.logger.error(
|
|
2537
|
-
`BullMQ orchestrator connection close failed: ${err.message}`
|
|
2538
|
-
);
|
|
2539
|
-
}
|
|
2540
|
-
}
|
|
2541
|
-
}
|
|
2542
|
-
// ============================================================================
|
|
2543
|
-
// Internals
|
|
2544
|
-
// ============================================================================
|
|
2545
|
-
/**
|
|
2546
|
-
* Walk every registered handler; collect any whose declared `pool`
|
|
2547
|
-
* targets a reserved pool from the resolved config. If non-empty,
|
|
2548
|
-
* throw `ReservedPoolViolationError` with the offender list so the
|
|
2549
|
-
* operator sees every violating class on a single boot.
|
|
2550
|
-
*/
|
|
2551
|
-
assertNoReservedPoolHandlers(entries, poolConfig) {
|
|
2552
|
-
const offenders = [];
|
|
2553
|
-
for (const entry of entries) {
|
|
2554
|
-
if (entry.type.startsWith("@framework/")) continue;
|
|
2555
|
-
const declaredPool = entry.meta.pool ?? "batch";
|
|
2556
|
-
const def = poolConfig.get(declaredPool);
|
|
2557
|
-
if (def?.reserved) {
|
|
2558
|
-
offenders.push({
|
|
2559
|
-
handlerClass: entry.handlerClass.name,
|
|
2560
|
-
pool: declaredPool
|
|
2561
|
-
});
|
|
2562
|
-
}
|
|
2563
|
-
}
|
|
2564
|
-
if (offenders.length > 0) {
|
|
2565
|
-
throw new ReservedPoolViolationError(offenders);
|
|
2566
|
-
}
|
|
2567
|
-
}
|
|
2568
|
-
/**
|
|
2569
|
-
* Production worker spawn. `JobWorker` requires `DRIZZLE` so this only
|
|
2570
|
-
* succeeds when the module was booted with `backend: 'drizzle'`. Memory
|
|
2571
|
-
* mode tests must supply `workerFactory` — the memory backend has no
|
|
2572
|
-
* polling loop equivalent (`MemoryJobOrchestrator` is direct-invocation
|
|
2573
|
-
* only).
|
|
2574
|
-
*
|
|
2575
|
-
* We instantiate outside the Nest container because the module spawns
|
|
2576
|
-
* N workers from a single options shape, which doesn't fit Nest's
|
|
2577
|
-
* "one provider per token" model. The dependencies are passed
|
|
2578
|
-
* positionally; the constructor's `@Inject` decorators are unused on
|
|
2579
|
-
* this path (Nest still uses them when `JobWorker` is a provider — e.g.
|
|
2580
|
-
* in JOB-6's standalone `worker.ts` entrypoint).
|
|
2581
|
-
*/
|
|
2582
|
-
spawnWorker(workerOptions) {
|
|
2583
|
-
if (!this.db) {
|
|
2584
|
-
throw new Error(
|
|
2585
|
-
`JobWorkerModule: in-process worker spawning requires the Drizzle backend (no DRIZZLE provider available). Memory-mode tests must pass 'workerFactory' to inject a stub.`
|
|
2586
|
-
);
|
|
2587
|
-
}
|
|
2588
|
-
if (!this.moduleRef) {
|
|
2589
|
-
throw new Error(
|
|
2590
|
-
`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).`
|
|
2591
|
-
);
|
|
2592
|
-
}
|
|
2593
|
-
return new JobWorker(
|
|
2594
|
-
this.db,
|
|
2595
|
-
this.orchestrator,
|
|
2596
|
-
this.runService,
|
|
2597
|
-
this.stepService,
|
|
2598
|
-
workerOptions,
|
|
2599
|
-
this.moduleRef
|
|
2600
|
-
);
|
|
2601
|
-
}
|
|
2602
|
-
/**
|
|
2603
|
-
* BULLMQ-1 — spawn a per-pool `BullMQJobWorker`. Requires the Drizzle
|
|
2604
|
-
* client (the worker drives `job_run` as the source of truth) AND the
|
|
2605
|
-
* resolved BullMQ connection (bound by `JobsDomainModule` when
|
|
2606
|
-
* `backend: 'bullmq'`). The queue name is derived identically to the
|
|
2607
|
-
* orchestrator's `dispatch` via `resolvePoolQueueName(pool, …)` so producer
|
|
2608
|
-
* and consumer agree.
|
|
2609
|
-
*/
|
|
2610
|
-
/**
|
|
2611
|
-
* #6 — async + dynamic-import. The `job-worker.bullmq-backend.ts` file is
|
|
2612
|
-
* filtered out of the vendor set for drizzle/memory installs (no `bullmq`
|
|
2613
|
-
* peer dep needed). The non-literal import specifier makes TS treat the
|
|
2614
|
-
* module as `any` so the consumer's tsc never tries to resolve an absent
|
|
2615
|
-
* file. This method is only entered when `backend === 'bullmq'` — at which
|
|
2616
|
-
* point the file IS vendored.
|
|
2617
|
-
*/
|
|
2618
|
-
async spawnBullMQWorker(pool, _queueAlias, concurrency, poolConfig) {
|
|
2619
|
-
if (!this.db) {
|
|
2620
|
-
throw new Error(
|
|
2621
|
-
`JobWorkerModule: BullMQ worker spawning requires the Drizzle client (no DRIZZLE provider available) \u2014 job_run remains the source of truth.`
|
|
2622
|
-
);
|
|
2623
|
-
}
|
|
2624
|
-
if (!this.bullConnection) {
|
|
2625
|
-
throw new Error(
|
|
2626
|
-
`JobWorkerModule: BullMQ worker spawning requires a resolved BULLMQ_CONNECTION. Ensure JobsDomainModule was booted with backend: 'bullmq'.`
|
|
2627
|
-
);
|
|
2628
|
-
}
|
|
2629
|
-
if (!this.moduleRef) {
|
|
2630
|
-
throw new Error(
|
|
2631
|
-
`JobWorkerModule: ModuleRef not available \u2014 cannot construct BullMQJobWorker with handler DI support.`
|
|
2632
|
-
);
|
|
2633
|
-
}
|
|
2634
|
-
const queueName = resolvePoolQueueName(pool, this.bullConfig, poolConfig);
|
|
2635
|
-
const specifier = "./job-worker.bullmq-backend";
|
|
2636
|
-
const mod = await import(specifier);
|
|
2637
|
-
return new mod.BullMQJobWorker(
|
|
2638
|
-
this.db,
|
|
2639
|
-
this.orchestrator,
|
|
2640
|
-
this.stepService,
|
|
2641
|
-
{
|
|
2642
|
-
pool,
|
|
2643
|
-
queueName,
|
|
2644
|
-
concurrency,
|
|
2645
|
-
connection: this.bullConnection
|
|
2646
|
-
},
|
|
2647
|
-
this.moduleRef
|
|
2648
|
-
);
|
|
2649
|
-
}
|
|
2650
|
-
};
|
|
2651
|
-
JobWorkerOrchestrator = __decorateClass([
|
|
2652
|
-
Injectable8(),
|
|
2653
|
-
__decorateParam(0, Inject8(JOB_ORCHESTRATOR)),
|
|
2654
|
-
__decorateParam(1, Inject8(JOB_RUN_SERVICE)),
|
|
2655
|
-
__decorateParam(2, Inject8(JOB_STEP_SERVICE)),
|
|
2656
|
-
__decorateParam(3, Inject8(JOB_WORKER_MODULE_OPTIONS)),
|
|
2657
|
-
__decorateParam(4, Optional2()),
|
|
2658
|
-
__decorateParam(4, Inject8(DRIZZLE)),
|
|
2659
|
-
__decorateParam(5, Inject8(ModuleRef2)),
|
|
2660
|
-
__decorateParam(6, Optional2()),
|
|
2661
|
-
__decorateParam(6, Inject8(BULLMQ_CONNECTION)),
|
|
2662
|
-
__decorateParam(7, Optional2()),
|
|
2663
|
-
__decorateParam(7, Inject8(BULLMQ_RESOLVED_CONFIG))
|
|
2664
|
-
], JobWorkerOrchestrator);
|
|
2665
|
-
var JobWorkerModule = class {
|
|
2666
|
-
static forRoot(opts) {
|
|
2667
|
-
return {
|
|
2668
|
-
module: JobWorkerModule,
|
|
2669
|
-
imports: [
|
|
2670
|
-
JobsDomainModule.forRoot({
|
|
2671
|
-
backend: opts.backend ?? "drizzle",
|
|
2672
|
-
extensions: opts.domainModuleExtensions,
|
|
2673
|
-
multiTenant: opts.multiTenant
|
|
2674
|
-
})
|
|
2675
|
-
],
|
|
2676
|
-
providers: [
|
|
2677
|
-
{ provide: JOB_WORKER_MODULE_OPTIONS, useValue: opts },
|
|
2678
|
-
JobWorkerOrchestrator
|
|
2679
|
-
],
|
|
2680
|
-
// BULLMQ-1 Phase 1 — export the options token so `BridgeModule`'s
|
|
2681
|
-
// reserved-pool guard (`onModuleInit`) can actually inject it.
|
|
2682
|
-
// Previously `exports: []` left the `@Optional()` inject resolving to
|
|
2683
|
-
// `undefined` and the guard silently no-opped (a dead check). With the
|
|
2684
|
-
// token exported the guard fires for real; consumers that omit the
|
|
2685
|
-
// reserved pools (and don't set `allPools`) now fail fast with
|
|
2686
|
-
// `BridgeReservedPoolsNotPolledError` — which is correct.
|
|
2687
|
-
exports: [JOB_WORKER_MODULE_OPTIONS]
|
|
2688
|
-
};
|
|
2689
|
-
}
|
|
2690
|
-
};
|
|
2691
|
-
JobWorkerModule = __decorateClass([
|
|
2692
|
-
Module2({})
|
|
2693
|
-
], JobWorkerModule);
|
|
7
|
+
JOB_WORKER_OPTIONS,
|
|
8
|
+
JobWorker,
|
|
9
|
+
buildClaimQuery,
|
|
10
|
+
buildStaleSweepQuery,
|
|
11
|
+
classifyError,
|
|
12
|
+
computeBackoff
|
|
13
|
+
} from "../../../chunk-RC23QROE.js";
|
|
14
|
+
import {
|
|
15
|
+
JobsDomainModule
|
|
16
|
+
} from "../../../chunk-KMZCQASO.js";
|
|
17
|
+
import {
|
|
18
|
+
DrizzleJobRunService
|
|
19
|
+
} from "../../../chunk-I6MG4M3F.js";
|
|
20
|
+
import {
|
|
21
|
+
MemoryJobRunService
|
|
22
|
+
} from "../../../chunk-JRVNVKN6.js";
|
|
23
|
+
import {
|
|
24
|
+
DrizzleJobStepService
|
|
25
|
+
} from "../../../chunk-DV4RV2DC.js";
|
|
26
|
+
import {
|
|
27
|
+
DrizzleJobOrchestrator
|
|
28
|
+
} from "../../../chunk-5Y7W3XR6.js";
|
|
29
|
+
import {
|
|
30
|
+
MemoryJobOrchestrator
|
|
31
|
+
} from "../../../chunk-4RFHUZXU.js";
|
|
32
|
+
import {
|
|
33
|
+
MemoryJobStepService
|
|
34
|
+
} from "../../../chunk-PNZSGAB2.js";
|
|
35
|
+
import {
|
|
36
|
+
MemoryJobStore
|
|
37
|
+
} from "../../../chunk-SNQ3TOWP.js";
|
|
38
|
+
import {
|
|
39
|
+
BootValidationError,
|
|
40
|
+
JobCollisionError,
|
|
41
|
+
JobNotReplayableError,
|
|
42
|
+
JobTemplateFieldMissingError,
|
|
43
|
+
JobTypeNotFoundError,
|
|
44
|
+
MissingTenantIdError,
|
|
45
|
+
ReservedPoolViolationError
|
|
46
|
+
} from "../../../chunk-T4BIIU5E.js";
|
|
47
|
+
import "../../../chunk-L3LZWWSX.js";
|
|
48
|
+
import {
|
|
49
|
+
BULLMQ_CONNECTION,
|
|
50
|
+
BULLMQ_RESOLVED_CONFIG,
|
|
51
|
+
resolveBullMqConfig,
|
|
52
|
+
resolvePoolQueueName
|
|
53
|
+
} from "../../../chunk-I6MVCB5A.js";
|
|
54
|
+
import {
|
|
55
|
+
FRAMEWORK_POOLS,
|
|
56
|
+
RESERVED_POOL_NAMES,
|
|
57
|
+
allNonReservedPoolNames,
|
|
58
|
+
allPoolNames,
|
|
59
|
+
loadPoolConfig
|
|
60
|
+
} from "../../../chunk-RHVN6NA7.js";
|
|
61
|
+
import {
|
|
62
|
+
HandlerRegistry,
|
|
63
|
+
JOB_HANDLER_METADATA_KEY,
|
|
64
|
+
JOB_HANDLER_REGISTRY,
|
|
65
|
+
JobHandler,
|
|
66
|
+
JobHandlerBase,
|
|
67
|
+
ParentClosePolicy
|
|
68
|
+
} from "../../../chunk-CO6LUM72.js";
|
|
69
|
+
import {
|
|
70
|
+
JOBS_MULTI_TENANT,
|
|
71
|
+
JOB_ORCHESTRATOR,
|
|
72
|
+
JOB_RUN_SERVICE,
|
|
73
|
+
JOB_STEP_SERVICE
|
|
74
|
+
} from "../../../chunk-BIO6F7YI.js";
|
|
75
|
+
import {
|
|
76
|
+
collisionModeEnum,
|
|
77
|
+
jobRunStatusEnum,
|
|
78
|
+
jobRuns,
|
|
79
|
+
jobStepKindEnum,
|
|
80
|
+
jobStepStatusEnum,
|
|
81
|
+
jobSteps,
|
|
82
|
+
jobs,
|
|
83
|
+
parentClosePolicyEnum,
|
|
84
|
+
replayFromEnum,
|
|
85
|
+
triggerSourceEnum,
|
|
86
|
+
waitKindEnum
|
|
87
|
+
} from "../../../chunk-OKXZ63IA.js";
|
|
88
|
+
import "../../../chunk-GYGNEQSC.js";
|
|
89
|
+
import "../../../chunk-U64T4YZE.js";
|
|
90
|
+
import "../../../chunk-2E224ZSN.js";
|
|
2694
91
|
export {
|
|
2695
92
|
BULLMQ_CONNECTION,
|
|
2696
93
|
BULLMQ_RESOLVED_CONFIG,
|