@pattern-stack/codegen 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/CHANGELOG.md +135 -0
  2. package/README.md +5 -5
  3. package/consumer-skills/codegen/SKILL.md +2 -2
  4. package/consumer-skills/events/typed-bus-and-outbox.md +1 -1
  5. package/consumer-skills/{sync → integration}/SKILL.md +29 -29
  6. package/consumer-skills/{sync → integration}/audit-and-detection.md +22 -22
  7. package/consumer-skills/{sync → integration}/change-sources-and-sinks.md +60 -60
  8. package/consumer-skills/subsystems/SKILL.md +64 -8
  9. package/consumer-skills/subsystems/wiring-and-order.md +7 -7
  10. package/dist/runtime/base-classes/index.d.ts +4 -4
  11. package/dist/runtime/base-classes/index.js +35 -35
  12. package/dist/runtime/base-classes/index.js.map +1 -1
  13. package/dist/runtime/base-classes/{synced-entity-repository.d.ts → integrated-entity-repository.d.ts} +15 -15
  14. package/dist/runtime/base-classes/{synced-entity-repository.js → integrated-entity-repository.js} +21 -21
  15. package/dist/runtime/base-classes/integrated-entity-repository.js.map +1 -0
  16. package/dist/runtime/base-classes/{synced-entity-service.d.ts → integrated-entity-service.d.ts} +6 -6
  17. package/dist/runtime/base-classes/{synced-entity-service.js → integrated-entity-service.js} +4 -4
  18. package/dist/runtime/base-classes/integrated-entity-service.js.map +1 -0
  19. package/dist/runtime/base-classes/{sync-upsert-config.d.ts → integration-upsert-config.d.ts} +13 -13
  20. package/dist/runtime/base-classes/integration-upsert-config.js +1 -0
  21. package/dist/runtime/base-classes/{junction-sync-repository.d.ts → junction-integration-repository.d.ts} +11 -11
  22. package/dist/runtime/base-classes/{junction-sync-repository.js → junction-integration-repository.js} +15 -15
  23. package/dist/runtime/base-classes/junction-integration-repository.js.map +1 -0
  24. package/dist/runtime/subsystems/auth/auth-oauth-state.schema.js.map +1 -1
  25. package/dist/runtime/subsystems/auth/auth.module.d.ts +4 -4
  26. package/dist/runtime/subsystems/auth/auth.module.js +3 -3
  27. package/dist/runtime/subsystems/auth/auth.module.js.map +1 -1
  28. package/dist/runtime/subsystems/auth/auth.tokens.d.ts +8 -8
  29. package/dist/runtime/subsystems/auth/auth.tokens.js +6 -6
  30. package/dist/runtime/subsystems/auth/auth.tokens.js.map +1 -1
  31. package/dist/runtime/subsystems/auth/backends/state-store.drizzle-backend.js.map +1 -1
  32. package/dist/runtime/subsystems/auth/controllers/auth.controller.d.ts +2 -2
  33. package/dist/runtime/subsystems/auth/controllers/auth.controller.js +3 -3
  34. package/dist/runtime/subsystems/auth/controllers/auth.controller.js.map +1 -1
  35. package/dist/runtime/subsystems/auth/index.d.ts +3 -3
  36. package/dist/runtime/subsystems/auth/index.js +40 -40
  37. package/dist/runtime/subsystems/auth/index.js.map +1 -1
  38. package/dist/runtime/subsystems/auth/middleware/requester-context.js.map +1 -1
  39. package/dist/runtime/subsystems/auth/protocols/auth-strategy.d.ts +3 -3
  40. package/dist/runtime/subsystems/auth/protocols/{integration-store.d.ts → connection-store.d.ts} +20 -20
  41. package/dist/runtime/subsystems/auth/protocols/connection-store.js +1 -0
  42. package/dist/runtime/subsystems/auth/protocols/provider-strategy.d.ts +3 -3
  43. package/dist/runtime/subsystems/auth/runtime/{integration-broken.error.d.ts → connection-broken.error.d.ts} +5 -5
  44. package/dist/runtime/subsystems/auth/runtime/connection-broken.error.js +19 -0
  45. package/dist/runtime/subsystems/auth/runtime/connection-broken.error.js.map +1 -0
  46. package/dist/runtime/subsystems/auth/runtime/oauth2-refresh.strategy.d.ts +10 -10
  47. package/dist/runtime/subsystems/auth/runtime/oauth2-refresh.strategy.js +28 -28
  48. package/dist/runtime/subsystems/auth/runtime/oauth2-refresh.strategy.js.map +1 -1
  49. package/dist/runtime/subsystems/auth/runtime/with-auth-retry.d.ts +1 -1
  50. package/dist/runtime/subsystems/auth/runtime/with-auth-retry.js +3 -3
  51. package/dist/runtime/subsystems/auth/runtime/with-auth-retry.js.map +1 -1
  52. package/dist/runtime/subsystems/bridge/bridge.module.d.ts +0 -1
  53. package/dist/runtime/subsystems/bridge/bridge.module.js +294 -710
  54. package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
  55. package/dist/runtime/subsystems/bridge/index.d.ts +0 -1
  56. package/dist/runtime/subsystems/bridge/index.js +248 -664
  57. package/dist/runtime/subsystems/bridge/index.js.map +1 -1
  58. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +18 -10
  59. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -1
  60. package/dist/runtime/subsystems/events/events.module.js +43 -244
  61. package/dist/runtime/subsystems/events/events.module.js.map +1 -1
  62. package/dist/runtime/subsystems/events/index.d.ts +0 -1
  63. package/dist/runtime/subsystems/events/index.js +39 -241
  64. package/dist/runtime/subsystems/events/index.js.map +1 -1
  65. package/dist/runtime/subsystems/index.d.ts +7 -7
  66. package/dist/runtime/subsystems/index.js +222 -839
  67. package/dist/runtime/subsystems/index.js.map +1 -1
  68. package/dist/runtime/subsystems/{sync → integration}/build-change-source.d.ts +3 -3
  69. package/dist/runtime/subsystems/{sync → integration}/build-change-source.js +3 -3
  70. package/dist/runtime/subsystems/integration/build-change-source.js.map +1 -0
  71. package/dist/runtime/subsystems/{sync → integration}/deep-equal.differ.d.ts +2 -2
  72. package/dist/runtime/subsystems/{sync → integration}/deep-equal.differ.js +1 -1
  73. package/dist/runtime/subsystems/integration/deep-equal.differ.js.map +1 -0
  74. package/dist/runtime/subsystems/{sync → integration}/detection-config.schema.d.ts +3 -3
  75. package/dist/runtime/subsystems/{sync → integration}/detection-config.schema.js +1 -1
  76. package/dist/runtime/subsystems/integration/detection-config.schema.js.map +1 -0
  77. package/dist/runtime/subsystems/{sync/execute-sync.use-case.d.ts → integration/execute-integration.use-case.d.ts} +13 -13
  78. package/dist/runtime/subsystems/{sync/execute-sync.use-case.js → integration/execute-integration.use-case.js} +30 -30
  79. package/dist/runtime/subsystems/integration/execute-integration.use-case.js.map +1 -0
  80. package/dist/runtime/subsystems/integration/index.d.ts +28 -0
  81. package/dist/runtime/subsystems/{sync → integration}/index.js +171 -171
  82. package/dist/runtime/subsystems/integration/index.js.map +1 -0
  83. package/dist/runtime/subsystems/{sync/sync-audit.schema.d.ts → integration/integration-audit.schema.d.ts} +64 -64
  84. package/dist/runtime/subsystems/{sync/sync-audit.schema.js → integration/integration-audit.schema.js} +47 -47
  85. package/dist/runtime/subsystems/integration/integration-audit.schema.js.map +1 -0
  86. package/dist/runtime/subsystems/{sync/sync-change-source.protocol.d.ts → integration/integration-change-source.protocol.d.ts} +10 -10
  87. package/dist/runtime/subsystems/integration/integration-change-source.protocol.js +1 -0
  88. package/dist/runtime/subsystems/{sync/sync-cursor-store.drizzle-backend.d.ts → integration/integration-cursor-store.drizzle-backend.d.ts} +1 -1
  89. package/dist/runtime/subsystems/{sync/sync-cursor-store.drizzle-backend.js → integration/integration-cursor-store.drizzle-backend.js} +65 -65
  90. package/dist/runtime/subsystems/integration/integration-cursor-store.drizzle-backend.js.map +1 -0
  91. package/dist/runtime/subsystems/{sync/sync-cursor-store.memory-backend.d.ts → integration/integration-cursor-store.memory-backend.d.ts} +6 -6
  92. package/dist/runtime/subsystems/{sync/sync-cursor-store.memory-backend.js → integration/integration-cursor-store.memory-backend.js} +5 -5
  93. package/dist/runtime/subsystems/integration/integration-cursor-store.memory-backend.js.map +1 -0
  94. package/dist/runtime/subsystems/{sync/sync-cursor-store.protocol.d.ts → integration/integration-cursor-store.protocol.d.ts} +13 -13
  95. package/dist/runtime/subsystems/integration/integration-cursor-store.protocol.js +1 -0
  96. package/dist/runtime/subsystems/{sync/sync-errors.d.ts → integration/integration-errors.d.ts} +2 -2
  97. package/dist/runtime/subsystems/{sync/sync-errors.js → integration/integration-errors.js} +3 -3
  98. package/dist/runtime/subsystems/integration/integration-errors.js.map +1 -0
  99. package/dist/runtime/subsystems/{sync/sync-field-diff.protocol.d.ts → integration/integration-field-diff.protocol.d.ts} +2 -2
  100. package/dist/runtime/subsystems/{sync/sync-field-diff.protocol.js → integration/integration-field-diff.protocol.js} +2 -2
  101. package/dist/runtime/subsystems/integration/integration-field-diff.protocol.js.map +1 -0
  102. package/dist/runtime/subsystems/{sync/sync-loopback.protocol.d.ts → integration/integration-loopback.protocol.d.ts} +2 -2
  103. package/dist/runtime/subsystems/integration/integration-loopback.protocol.js +1 -0
  104. package/dist/runtime/subsystems/{sync/sync-middleware.protocol.d.ts → integration/integration-middleware.protocol.d.ts} +5 -5
  105. package/dist/runtime/subsystems/integration/integration-middleware.protocol.js +1 -0
  106. package/dist/runtime/subsystems/{sync/sync-run-recorder.drizzle-backend.d.ts → integration/integration-run-recorder.drizzle-backend.d.ts} +5 -5
  107. package/dist/runtime/subsystems/{sync/sync-run-recorder.drizzle-backend.js → integration/integration-run-recorder.drizzle-backend.js} +73 -73
  108. package/dist/runtime/subsystems/integration/integration-run-recorder.drizzle-backend.js.map +1 -0
  109. package/dist/runtime/subsystems/{sync/sync-run-recorder.memory-backend.d.ts → integration/integration-run-recorder.memory-backend.d.ts} +15 -15
  110. package/dist/runtime/subsystems/{sync/sync-run-recorder.memory-backend.js → integration/integration-run-recorder.memory-backend.js} +11 -11
  111. package/dist/runtime/subsystems/integration/integration-run-recorder.memory-backend.js.map +1 -0
  112. package/dist/runtime/subsystems/{sync/sync-run-recorder.protocol.d.ts → integration/integration-run-recorder.protocol.d.ts} +25 -25
  113. package/dist/runtime/subsystems/integration/integration-run-recorder.protocol.js +1 -0
  114. package/dist/runtime/subsystems/{sync/sync-sink.protocol.d.ts → integration/integration-sink.protocol.d.ts} +5 -5
  115. package/dist/runtime/subsystems/integration/integration-sink.protocol.js +1 -0
  116. package/dist/runtime/subsystems/{sync/sync.module.d.ts → integration/integration.module.d.ts} +24 -24
  117. package/dist/runtime/subsystems/{sync/sync.module.js → integration/integration.module.js} +132 -132
  118. package/dist/runtime/subsystems/integration/integration.module.js.map +1 -0
  119. package/dist/runtime/subsystems/integration/integration.tokens.d.ts +47 -0
  120. package/dist/runtime/subsystems/integration/integration.tokens.js +18 -0
  121. package/dist/runtime/subsystems/integration/integration.tokens.js.map +1 -0
  122. package/dist/runtime/subsystems/{sync → integration}/loopback.middleware.d.ts +5 -5
  123. package/dist/runtime/subsystems/{sync → integration}/loopback.middleware.js +1 -1
  124. package/dist/runtime/subsystems/integration/loopback.middleware.js.map +1 -0
  125. package/dist/runtime/subsystems/{sync → integration}/poll-change-source.d.ts +5 -5
  126. package/dist/runtime/subsystems/{sync → integration}/poll-change-source.js +1 -1
  127. package/dist/runtime/subsystems/integration/poll-change-source.js.map +1 -0
  128. package/dist/runtime/subsystems/{sync → integration}/webhook-change-source.d.ts +5 -5
  129. package/dist/runtime/subsystems/{sync → integration}/webhook-change-source.js +1 -1
  130. package/dist/runtime/subsystems/integration/webhook-change-source.js.map +1 -0
  131. package/dist/runtime/subsystems/jobs/bullmq.config.d.ts +22 -3
  132. package/dist/runtime/subsystems/jobs/bullmq.config.js.map +1 -1
  133. package/dist/runtime/subsystems/jobs/index.d.ts +1 -4
  134. package/dist/runtime/subsystems/jobs/index.js +87 -506
  135. package/dist/runtime/subsystems/jobs/index.js.map +1 -1
  136. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -1
  137. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +3 -0
  138. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js.map +1 -1
  139. package/dist/runtime/subsystems/jobs/job-worker.module.d.ts +10 -3
  140. package/dist/runtime/subsystems/jobs/job-worker.module.js +248 -664
  141. package/dist/runtime/subsystems/jobs/job-worker.module.js.map +1 -1
  142. package/dist/runtime/subsystems/jobs/jobs-domain.module.d.ts +0 -1
  143. package/dist/runtime/subsystems/jobs/jobs-domain.module.js +89 -391
  144. package/dist/runtime/subsystems/jobs/jobs-domain.module.js.map +1 -1
  145. package/dist/runtime/subsystems/observability/index.d.ts +4 -4
  146. package/dist/runtime/subsystems/observability/index.js +11 -11
  147. package/dist/runtime/subsystems/observability/index.js.map +1 -1
  148. package/dist/runtime/subsystems/observability/observability.module.d.ts +2 -2
  149. package/dist/runtime/subsystems/observability/observability.module.js +11 -11
  150. package/dist/runtime/subsystems/observability/observability.module.js.map +1 -1
  151. package/dist/runtime/subsystems/observability/observability.protocol.d.ts +11 -11
  152. package/dist/runtime/subsystems/observability/observability.service.d.ts +6 -6
  153. package/dist/runtime/subsystems/observability/observability.service.js +11 -11
  154. package/dist/runtime/subsystems/observability/observability.service.js.map +1 -1
  155. package/dist/runtime/subsystems/observability/observability.tokens.d.ts +1 -1
  156. package/dist/runtime/subsystems/observability/observability.tokens.js.map +1 -1
  157. package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.d.ts +3 -3
  158. package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.js.map +1 -1
  159. package/dist/runtime/subsystems/observability/reporters/index.d.ts +3 -3
  160. package/dist/runtime/subsystems/observability/reporters/index.js.map +1 -1
  161. package/dist/src/cli/index.js +412 -302
  162. package/dist/src/cli/index.js.map +1 -1
  163. package/dist/src/index.d.ts +22 -22
  164. package/dist/src/index.js +191 -191
  165. package/dist/src/index.js.map +1 -1
  166. package/examples/auth-integrations/README.md +32 -32
  167. package/examples/auth-integrations/definitions/entities/{integration.yaml → connection.yaml} +10 -10
  168. package/examples/auth-integrations/runtime/{integrations/adapters/integration-grant-sink.adapter.ts → connections/adapters/connection-grant-sink.adapter.ts} +7 -7
  169. package/examples/auth-integrations/runtime/{integrations/adapters/integration-reader.adapter.ts → connections/adapters/connection-reader.adapter.ts} +10 -10
  170. package/examples/auth-integrations/runtime/{integrations/adapters/integration-token-writer.adapter.ts → connections/adapters/connection-token-writer.adapter.ts} +11 -11
  171. package/examples/auth-integrations/runtime/connections/connections-auth.module.ts +81 -0
  172. package/examples/auth-integrations/runtime/{integrations/facade/integrations.service.ts → connections/facade/connections.service.ts} +35 -35
  173. package/examples/auth-integrations/runtime/{integrations → connections}/oauth/use-cases/create-or-update-from-oauth-grant.use-case.ts +11 -11
  174. package/examples/auth-integrations/runtime/{integrations/oauth/use-cases/disconnect-integration.use-case.ts → connections/oauth/use-cases/disconnect-connection.use-case.ts} +6 -6
  175. package/examples/auth-integrations/runtime/connections/oauth/use-cases/list-user-connections.use-case.ts +21 -0
  176. package/examples/auth-integrations/runtime/connections/oauth/use-cases/mark-connection-requires-reauth.use-case.ts +21 -0
  177. package/package.json +1 -1
  178. package/runtime/base-classes/index.ts +8 -8
  179. package/runtime/base-classes/{synced-entity-repository.ts → integrated-entity-repository.ts} +36 -36
  180. package/runtime/base-classes/{synced-entity-service.ts → integrated-entity-service.ts} +6 -6
  181. package/runtime/base-classes/{sync-upsert-config.ts → integration-upsert-config.ts} +12 -12
  182. package/runtime/base-classes/{junction-sync-repository.ts → junction-integration-repository.ts} +28 -28
  183. package/runtime/subsystems/auth/auth-oauth-state.schema.ts +1 -1
  184. package/runtime/subsystems/auth/auth.module.ts +4 -4
  185. package/runtime/subsystems/auth/auth.tokens.ts +7 -7
  186. package/runtime/subsystems/auth/controllers/auth.controller.ts +7 -7
  187. package/runtime/subsystems/auth/index.ts +19 -19
  188. package/runtime/subsystems/auth/protocols/auth-strategy.ts +3 -3
  189. package/runtime/subsystems/auth/protocols/{integration-store.ts → connection-store.ts} +19 -19
  190. package/runtime/subsystems/auth/protocols/provider-strategy.ts +2 -2
  191. package/runtime/subsystems/auth/runtime/{integration-broken.error.ts → connection-broken.error.ts} +5 -5
  192. package/runtime/subsystems/auth/runtime/oauth2-refresh.strategy.ts +35 -35
  193. package/runtime/subsystems/auth/runtime/with-auth-retry.ts +3 -3
  194. package/runtime/subsystems/events/event-bus.drizzle-backend.ts +32 -10
  195. package/runtime/subsystems/events/events.module.ts +38 -6
  196. package/runtime/subsystems/events/index.ts +7 -1
  197. package/runtime/subsystems/index.ts +11 -11
  198. package/runtime/subsystems/{sync → integration}/build-change-source.ts +3 -3
  199. package/runtime/subsystems/{sync → integration}/deep-equal.differ.ts +7 -7
  200. package/runtime/subsystems/{sync → integration}/detection-config.schema.ts +3 -3
  201. package/runtime/subsystems/{sync/execute-sync.use-case.ts → integration/execute-integration.use-case.ts} +40 -40
  202. package/runtime/subsystems/{sync → integration}/index.ts +47 -47
  203. package/runtime/subsystems/{sync/sync-audit.schema.ts → integration/integration-audit.schema.ts} +61 -61
  204. package/runtime/subsystems/{sync/sync-change-source.protocol.ts → integration/integration-change-source.protocol.ts} +9 -9
  205. package/runtime/subsystems/{sync/sync-cursor-store.drizzle-backend.ts → integration/integration-cursor-store.drizzle-backend.ts} +30 -30
  206. package/runtime/subsystems/{sync/sync-cursor-store.memory-backend.ts → integration/integration-cursor-store.memory-backend.ts} +9 -9
  207. package/runtime/subsystems/{sync/sync-cursor-store.protocol.ts → integration/integration-cursor-store.protocol.ts} +13 -13
  208. package/runtime/subsystems/{sync/sync-errors.ts → integration/integration-errors.ts} +3 -3
  209. package/runtime/subsystems/{sync/sync-field-diff.protocol.ts → integration/integration-field-diff.protocol.ts} +2 -2
  210. package/runtime/subsystems/{sync/sync-loopback.protocol.ts → integration/integration-loopback.protocol.ts} +2 -2
  211. package/runtime/subsystems/{sync/sync-middleware.protocol.ts → integration/integration-middleware.protocol.ts} +6 -6
  212. package/runtime/subsystems/{sync/sync-run-recorder.drizzle-backend.ts → integration/integration-run-recorder.drizzle-backend.ts} +39 -39
  213. package/runtime/subsystems/{sync/sync-run-recorder.memory-backend.ts → integration/integration-run-recorder.memory-backend.ts} +23 -23
  214. package/runtime/subsystems/{sync/sync-run-recorder.protocol.ts → integration/integration-run-recorder.protocol.ts} +25 -25
  215. package/runtime/subsystems/{sync/sync-sink.protocol.ts → integration/integration-sink.protocol.ts} +4 -4
  216. package/runtime/subsystems/{sync/sync.module.ts → integration/integration.module.ts} +48 -48
  217. package/runtime/subsystems/integration/integration.tokens.ts +49 -0
  218. package/runtime/subsystems/{sync → integration}/loopback.middleware.ts +5 -5
  219. package/runtime/subsystems/{sync → integration}/poll-change-source.ts +7 -7
  220. package/runtime/subsystems/{sync → integration}/webhook-change-source.ts +7 -7
  221. package/runtime/subsystems/jobs/bullmq.config.ts +23 -3
  222. package/runtime/subsystems/jobs/index.ts +13 -8
  223. package/runtime/subsystems/jobs/job-worker.bullmq-backend.ts +5 -2
  224. package/runtime/subsystems/jobs/job-worker.module.ts +27 -7
  225. package/runtime/subsystems/jobs/jobs-domain.module.ts +27 -2
  226. package/runtime/subsystems/observability/index.ts +1 -1
  227. package/runtime/subsystems/observability/observability.module.ts +2 -2
  228. package/runtime/subsystems/observability/observability.protocol.ts +11 -11
  229. package/runtime/subsystems/observability/observability.service.ts +13 -13
  230. package/runtime/subsystems/observability/observability.tokens.ts +1 -1
  231. package/src/patterns/library/index.ts +4 -4
  232. package/src/patterns/library/{synced.pattern.ts → integrated.pattern.ts} +12 -12
  233. package/src/patterns/library/junction.pattern.ts +1 -1
  234. package/src/patterns/pattern-definition.ts +3 -3
  235. package/templates/entity/new/backend/modules/core/{sync-source.ejs.t → integration-source.ejs.t} +6 -6
  236. package/templates/entity/new/backend/modules/core/{sync-source.providers.ejs.t → integration-source.providers.ejs.t} +2 -2
  237. package/templates/entity/new/clean-lite-ps/entity.ejs.t +1 -1
  238. package/templates/entity/new/clean-lite-ps/module.ejs.t +1 -1
  239. package/templates/entity/new/clean-lite-ps/prompt-extension.js +33 -33
  240. package/templates/entity/new/clean-lite-ps/repository.ejs.t +27 -27
  241. package/templates/entity/new/frontend/collections/collection.ejs.t +26 -1
  242. package/templates/entity/new/frontend/collections/collections-base.ejs.t +11 -0
  243. package/templates/entity/new/frontend/entity/combined.ejs.t +31 -1
  244. package/templates/entity/new/prompt.js +27 -15
  245. package/templates/junction/new/entity.ejs.t +1 -1
  246. package/templates/junction/new/prompt.js +24 -24
  247. package/templates/junction/new/repository.ejs.t +19 -19
  248. package/templates/subsystem/auth/auth-oauth-state.schema.ejs.t +2 -2
  249. package/templates/subsystem/auth-config/prompt.js +1 -1
  250. package/templates/subsystem/auth-integrations/app-module-hook.ejs.t +5 -5
  251. package/templates/subsystem/bridge/prompt.js +1 -1
  252. package/templates/subsystem/events/domain-events.schema.ejs.t +43 -2
  253. package/templates/subsystem/integration/integration-audit.schema.ejs.t +192 -0
  254. package/templates/subsystem/{sync → integration}/prompt.js +12 -12
  255. package/templates/subsystem/{sync-config/codegen-config-sync-block.ejs.t → integration-config/codegen-config-integration-block.ejs.t} +7 -7
  256. package/templates/subsystem/integration-config/prompt.js +22 -0
  257. package/templates/subsystem/jobs/worker.ejs.t +2 -2
  258. package/templates/subsystem/observability/main-hook.ejs.t +1 -1
  259. package/templates/subsystem/observability/prompt.js +1 -1
  260. package/templates/subsystem/openapi-config/prompt.js +1 -1
  261. package/dist/runtime/base-classes/junction-sync-repository.js.map +0 -1
  262. package/dist/runtime/base-classes/sync-upsert-config.js +0 -1
  263. package/dist/runtime/base-classes/synced-entity-repository.js.map +0 -1
  264. package/dist/runtime/base-classes/synced-entity-service.js.map +0 -1
  265. package/dist/runtime/subsystems/auth/protocols/integration-store.js +0 -1
  266. package/dist/runtime/subsystems/auth/runtime/integration-broken.error.js +0 -19
  267. package/dist/runtime/subsystems/auth/runtime/integration-broken.error.js.map +0 -1
  268. package/dist/runtime/subsystems/sync/build-change-source.js.map +0 -1
  269. package/dist/runtime/subsystems/sync/deep-equal.differ.js.map +0 -1
  270. package/dist/runtime/subsystems/sync/detection-config.schema.js.map +0 -1
  271. package/dist/runtime/subsystems/sync/execute-sync.use-case.js.map +0 -1
  272. package/dist/runtime/subsystems/sync/index.d.ts +0 -28
  273. package/dist/runtime/subsystems/sync/index.js.map +0 -1
  274. package/dist/runtime/subsystems/sync/loopback.middleware.js.map +0 -1
  275. package/dist/runtime/subsystems/sync/poll-change-source.js.map +0 -1
  276. package/dist/runtime/subsystems/sync/sync-audit.schema.js.map +0 -1
  277. package/dist/runtime/subsystems/sync/sync-change-source.protocol.js +0 -1
  278. package/dist/runtime/subsystems/sync/sync-cursor-store.drizzle-backend.js.map +0 -1
  279. package/dist/runtime/subsystems/sync/sync-cursor-store.memory-backend.js.map +0 -1
  280. package/dist/runtime/subsystems/sync/sync-cursor-store.protocol.js +0 -1
  281. package/dist/runtime/subsystems/sync/sync-errors.js.map +0 -1
  282. package/dist/runtime/subsystems/sync/sync-field-diff.protocol.js.map +0 -1
  283. package/dist/runtime/subsystems/sync/sync-loopback.protocol.js +0 -1
  284. package/dist/runtime/subsystems/sync/sync-middleware.protocol.js +0 -1
  285. package/dist/runtime/subsystems/sync/sync-run-recorder.drizzle-backend.js.map +0 -1
  286. package/dist/runtime/subsystems/sync/sync-run-recorder.memory-backend.js.map +0 -1
  287. package/dist/runtime/subsystems/sync/sync-run-recorder.protocol.js +0 -1
  288. package/dist/runtime/subsystems/sync/sync-sink.protocol.js +0 -1
  289. package/dist/runtime/subsystems/sync/sync.module.js.map +0 -1
  290. package/dist/runtime/subsystems/sync/sync.tokens.d.ts +0 -47
  291. package/dist/runtime/subsystems/sync/sync.tokens.js +0 -18
  292. package/dist/runtime/subsystems/sync/sync.tokens.js.map +0 -1
  293. package/dist/runtime/subsystems/sync/webhook-change-source.js.map +0 -1
  294. package/examples/auth-integrations/runtime/integrations/integrations-auth.module.ts +0 -81
  295. package/examples/auth-integrations/runtime/integrations/oauth/use-cases/list-user-integrations.use-case.ts +0 -21
  296. package/examples/auth-integrations/runtime/integrations/oauth/use-cases/mark-integration-requires-reauth.use-case.ts +0 -21
  297. package/runtime/subsystems/sync/sync.tokens.ts +0 -49
  298. package/templates/subsystem/sync/sync-audit.schema.ejs.t +0 -192
  299. package/templates/subsystem/sync-config/prompt.js +0 -22
  300. /package/dist/runtime/base-classes/{sync-upsert-config.js.map → integration-upsert-config.js.map} +0 -0
  301. /package/dist/runtime/subsystems/auth/protocols/{integration-store.js.map → connection-store.js.map} +0 -0
  302. /package/dist/runtime/subsystems/{sync/sync-change-source.protocol.js.map → integration/integration-change-source.protocol.js.map} +0 -0
  303. /package/dist/runtime/subsystems/{sync/sync-cursor-store.protocol.js.map → integration/integration-cursor-store.protocol.js.map} +0 -0
  304. /package/dist/runtime/subsystems/{sync/sync-loopback.protocol.js.map → integration/integration-loopback.protocol.js.map} +0 -0
  305. /package/dist/runtime/subsystems/{sync/sync-middleware.protocol.js.map → integration/integration-middleware.protocol.js.map} +0 -0
  306. /package/dist/runtime/subsystems/{sync/sync-run-recorder.protocol.js.map → integration/integration-run-recorder.protocol.js.map} +0 -0
  307. /package/dist/runtime/subsystems/{sync/sync-sink.protocol.js.map → integration/integration-sink.protocol.js.map} +0 -0
@@ -131,13 +131,12 @@ var BRIDGE_OUTBOX_DRAIN_HOOK = "BRIDGE_OUTBOX_DRAIN_HOOK";
131
131
  // runtime/subsystems/events/event-bus.drizzle-backend.ts
132
132
  var POLL_INTERVAL_MS = 1e3;
133
133
  var POLL_BATCH_SIZE = 50;
134
- function toInsertValues(event) {
134
+ function toInsertValues(event, multiTenant) {
135
135
  const metadata = event.metadata ?? void 0;
136
136
  const pool = metadata?.["pool"] ?? null;
137
137
  const direction = metadata?.["direction"] ?? null;
138
- const tenantId = metadata?.["tenantId"] ?? null;
139
138
  const tier = metadata?.["tier"] ?? "domain";
140
- return {
139
+ const base = {
141
140
  id: event.id,
142
141
  type: event.type,
143
142
  aggregateId: event.aggregateId,
@@ -149,9 +148,11 @@ function toInsertValues(event) {
149
148
  metadata: event.metadata,
150
149
  pool,
151
150
  direction,
152
- tier,
153
- tenantId
151
+ tier
154
152
  };
153
+ if (!multiTenant) return base;
154
+ const tenantId = metadata?.["tenantId"] ?? null;
155
+ return { ...base, tenantId };
155
156
  }
156
157
  function toEventSummary(r) {
157
158
  const metadata = r.metadata ?? void 0;
@@ -166,7 +167,11 @@ function toEventSummary(r) {
166
167
  direction: r.direction,
167
168
  tier: r.tier,
168
169
  rootRunId: typeof rootRunId === "string" ? rootRunId : null,
169
- tenantId: r.tenantId,
170
+ // EVT-8: `tenant_id` is a scaffold-time conditional column. Read it
171
+ // structurally so this projection typechecks against both the
172
+ // multi-tenant schema (column present) and the single-tenant schema
173
+ // (column absent → undefined → null).
174
+ tenantId: r.tenantId ?? null,
170
175
  occurredAt: r.occurredAt instanceof Date ? r.occurredAt : new Date(r.occurredAt),
171
176
  processedAt: r.processedAt == null ? null : r.processedAt instanceof Date ? r.processedAt : new Date(r.processedAt)
172
177
  };
@@ -203,12 +208,14 @@ var DrizzleEventBus = class {
203
208
  // ============================================================================
204
209
  async publish(event, tx) {
205
210
  const client = tx ?? this.db;
206
- await client.insert(domainEvents).values(toInsertValues(event));
211
+ const multiTenant = this.opts.multiTenant ?? false;
212
+ await client.insert(domainEvents).values(toInsertValues(event, multiTenant));
207
213
  }
208
214
  async publishMany(events, tx) {
209
215
  if (events.length === 0) return;
210
216
  const client = tx ?? this.db;
211
- await client.insert(domainEvents).values(events.map(toInsertValues));
217
+ const multiTenant = this.opts.multiTenant ?? false;
218
+ await client.insert(domainEvents).values(events.map((e) => toInsertValues(e, multiTenant)));
212
219
  }
213
220
  async findById(eventId) {
214
221
  const rows = await this.db.select().from(domainEvents).where(eq(domainEvents.id, eventId)).limit(1);
@@ -250,9 +257,10 @@ var DrizzleEventBus = class {
250
257
  sql2`${domainEvents.metadata}->>'rootRunId' = ${query.rootRunId}`
251
258
  );
252
259
  }
253
- if (query.tenantId !== void 0) {
260
+ if (this.opts.multiTenant && query.tenantId !== void 0) {
261
+ const tenantIdColumn = domainEvents.tenantId;
254
262
  conditions.push(
255
- query.tenantId === null ? sql2`${domainEvents.tenantId} is null` : eq(domainEvents.tenantId, query.tenantId)
263
+ query.tenantId === null ? sql2`${tenantIdColumn} is null` : eq(tenantIdColumn, query.tenantId)
256
264
  );
257
265
  }
258
266
  if (query.cursor) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../runtime/subsystems/events/event-bus.drizzle-backend.ts","../../../../runtime/subsystems/events/event-keyset-cursor.ts","../../../../runtime/subsystems/events/domain-events.schema.ts","../../../../runtime/constants/tokens.ts","../../../../runtime/subsystems/events/events.tokens.ts","../../../../runtime/subsystems/bridge/bridge.tokens.ts"],"sourcesContent":["/**\n * DrizzleEventBus — Postgres-backed event bus using the transactional outbox pattern.\n *\n * Events are inserted into the `domain_events` table within the caller's\n * Drizzle transaction. A background polling loop (started on module init)\n * reads unprocessed events and dispatches them to registered subscribers.\n *\n * When the transaction rolls back, the event is never persisted — no\n * phantom events.\n *\n * Pool awareness (EVT-4):\n * - On `publish`/`publishMany` the backend writes `metadata.pool`,\n * `metadata.direction`, and `metadata.tenantId` into the first-class\n * `pool` / `direction` / `tenant_id` columns (metadata JSON is still\n * written unchanged for protocol stability).\n * - The drain loop filters by `opts.pools` when provided, so separate\n * processes (e.g. one per `events_inbound` / `events_change` /\n * `events_outbound`) can claim only their own lane. `pools: undefined`\n * drains all pending rows (backwards-compatible behaviour).\n *\n * EVT-Q7: No stale-event sweeper. `FOR UPDATE SKIP LOCKED` is\n * self-healing — the row is only locked for the duration of the\n * enclosing polling transaction; the `status='processed'` update happens\n * within that same transaction. There is no `claimed_at` semantic (unlike\n * jobs), so no stale rows can exist.\n *\n * This backend is suitable until you need real-time fan-out or very high\n * throughput. At that point, swap the backend for Redis Streams or similar\n * via EventsModule.forRoot({ backend: '...' }) without touching use cases.\n */\nimport { Injectable, OnModuleDestroy, OnModuleInit, Inject, Logger, Optional } from '@nestjs/common';\nimport { eq, and, inArray, asc, desc, gte, lt, or, sql, type SQL } from 'drizzle-orm';\nimport type { DomainEvent, DrizzleTransaction, IEventBus } from './event-bus.protocol';\nimport type {\n EventPage,\n IEventReadPort,\n ListEventsQuery,\n} from './event-read.protocol';\nimport {\n clampEventLimit,\n decodeEventCursor,\n encodeEventCursor,\n} from './event-keyset-cursor';\nimport type { DrizzleClient } from '../../types/drizzle';\nimport { domainEvents, type DomainEventRecord } from './domain-events.schema';\nimport { DRIZZLE } from '../../constants/tokens';\nimport { EVENTS_MODULE_OPTIONS } from './events.tokens';\nimport type { EventsModuleOptions } from './events.module';\nimport { BRIDGE_OUTBOX_DRAIN_HOOK } from '../bridge/bridge.tokens';\nimport type { IBridgeOutboxDrainHook } from '../bridge/bridge.protocol';\n\n/** How long to wait between polling cycles (ms). */\nconst POLL_INTERVAL_MS = 1_000;\n/** Max events claimed per polling cycle to bound memory usage. */\nconst POLL_BATCH_SIZE = 50;\n\n/**\n * Row shape built from `metadata` for writing into `domain_events`. Keeps\n * the per-event extraction logic in one place so publish/publishMany stay\n * in sync.\n */\nfunction toInsertValues(event: DomainEvent) {\n const metadata = event.metadata ?? undefined;\n const pool = (metadata?.['pool'] as string | undefined) ?? null;\n const direction = (metadata?.['direction'] as string | undefined) ?? null;\n const tenantId = (metadata?.['tenantId'] as string | undefined) ?? null;\n // AUDIT-1: tier defaults to 'domain' when absent. The DB CHECK\n // constraint (`domain_events_tier_routing_check`) enforces the\n // tier ⇔ routing-fields invariant at the storage boundary; no\n // JS-side assertion is needed here.\n const tier = (metadata?.['tier'] as string | undefined) ?? 'domain';\n return {\n id: event.id,\n type: event.type,\n aggregateId: event.aggregateId,\n aggregateType: event.aggregateType,\n payload: event.payload,\n occurredAt: event.occurredAt,\n processedAt: null,\n status: 'pending' as const,\n metadata: event.metadata,\n pool,\n direction,\n tier,\n tenantId,\n };\n}\n\n/**\n * Project a raw `domain_events` row into the narrow `EventSummary` shape.\n * Shared with the memory backend via this helper kept module-local to each\n * backend (the events subsystem has no cross-backend projection file yet;\n * the two are byte-identical and small).\n */\nfunction toEventSummary(r: DomainEventRecord) {\n const metadata = (r.metadata ?? undefined) as\n | Record<string, unknown>\n | undefined;\n const rootRunId = metadata?.['rootRunId'];\n return {\n id: r.id,\n type: r.type,\n aggregateId: r.aggregateId,\n aggregateType: r.aggregateType,\n status: r.status,\n pool: r.pool,\n direction: r.direction,\n tier: r.tier,\n rootRunId: typeof rootRunId === 'string' ? rootRunId : null,\n tenantId: r.tenantId,\n occurredAt:\n r.occurredAt instanceof Date\n ? r.occurredAt\n : new Date(r.occurredAt as unknown as string),\n processedAt:\n r.processedAt == null\n ? null\n : r.processedAt instanceof Date\n ? r.processedAt\n : new Date(r.processedAt as unknown as string),\n };\n}\n\n@Injectable()\nexport class DrizzleEventBus implements IEventBus, IEventReadPort, OnModuleInit, OnModuleDestroy {\n private readonly logger = new Logger(DrizzleEventBus.name);\n private polling = false;\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\n private readonly handlers = new Map<string, Set<(event: DomainEvent) => Promise<void>>>();\n private readonly opts: EventsModuleOptions;\n\n constructor(\n @Inject(DRIZZLE) private readonly db: DrizzleClient,\n @Optional() @Inject(EVENTS_MODULE_OPTIONS) opts?: EventsModuleOptions,\n /**\n * Bridge subsystem hook (BRIDGE-4). Optional — when the bridge\n * subsystem is not installed in the consuming app, this token is\n * undefined and the drain skips the bridge block entirely (preserves\n * EVT-4 baseline behaviour).\n *\n * When provided, `processEvent` is invoked once per drained event\n * INSIDE the per-event tx, before `processed_at` is stamped. The\n * hook owns all knowledge of `bridge_delivery + wrapper job_run`\n * shapes; the events subsystem stays unaware of bridge schemas.\n */\n @Optional()\n @Inject(BRIDGE_OUTBOX_DRAIN_HOOK)\n private readonly bridgeHook: IBridgeOutboxDrainHook | null = null,\n ) {\n // Default so direct construction (e.g. integration tests not going\n // through Nest DI) keeps working without an explicit options object.\n this.opts = opts ?? { backend: 'drizzle' };\n }\n\n // ============================================================================\n // Lifecycle\n // ============================================================================\n\n async onModuleInit(): Promise<void> {\n this.polling = true;\n this.schedulePoll();\n }\n\n async onModuleDestroy(): Promise<void> {\n this.polling = false;\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n }\n\n // ============================================================================\n // IEventBus\n // ============================================================================\n\n async publish(event: DomainEvent, tx?: DrizzleTransaction): Promise<void> {\n const client = (tx ?? this.db) as DrizzleClient;\n await client.insert(domainEvents).values(toInsertValues(event));\n }\n\n async publishMany(events: DomainEvent[], tx?: DrizzleTransaction): Promise<void> {\n if (events.length === 0) return;\n const client = (tx ?? this.db) as DrizzleClient;\n await client.insert(domainEvents).values(events.map(toInsertValues));\n }\n\n async findById(eventId: string): Promise<DomainEvent | null> {\n const rows = await this.db\n .select()\n .from(domainEvents)\n .where(eq(domainEvents.id, eventId))\n .limit(1);\n const row = rows[0];\n if (!row) return null;\n return {\n id: row.id,\n type: row.type,\n aggregateId: row.aggregateId,\n aggregateType: row.aggregateType,\n payload: row.payload as Record<string, unknown>,\n occurredAt:\n row.occurredAt instanceof Date\n ? row.occurredAt\n : new Date(row.occurredAt as unknown as string),\n metadata: (row.metadata ?? undefined) as\n | Record<string, unknown>\n | undefined,\n };\n }\n\n subscribe<T extends DomainEvent = DomainEvent>(\n eventType: string,\n handler: (event: T) => Promise<void>,\n ): () => void {\n if (!this.handlers.has(eventType)) {\n this.handlers.set(eventType, new Set());\n }\n const set = this.handlers.get(eventType)!;\n const h = handler as (event: DomainEvent) => Promise<void>;\n set.add(h);\n return () => {\n set.delete(h);\n };\n }\n\n // ============================================================================\n // IEventReadPort (OBS-LIST-1)\n // ============================================================================\n\n async listEvents(query: ListEventsQuery = {}): Promise<EventPage> {\n const limit = clampEventLimit(query.limit);\n const conditions: SQL<unknown>[] = [];\n\n if (query.poolId) conditions.push(eq(domainEvents.pool, query.poolId));\n if (query.direction)\n conditions.push(eq(domainEvents.direction, query.direction));\n if (query.since) conditions.push(gte(domainEvents.occurredAt, query.since));\n if (query.rootRunId) {\n // Filter on the JSON correlation id: metadata->>'rootRunId'.\n conditions.push(\n sql`${domainEvents.metadata}->>'rootRunId' = ${query.rootRunId}`,\n );\n }\n if (query.tenantId !== undefined) {\n conditions.push(\n query.tenantId === null\n ? (sql`${domainEvents.tenantId} is null` as SQL<unknown>)\n : eq(domainEvents.tenantId, query.tenantId),\n );\n }\n\n // Keyset seek: WHERE (occurred_at, id) < (cursorOccurredAt, cursorId).\n if (query.cursor) {\n const keyset = decodeEventCursor(query.cursor);\n if (keyset) {\n conditions.push(\n or(\n lt(domainEvents.occurredAt, keyset.occurredAt),\n and(\n eq(domainEvents.occurredAt, keyset.occurredAt),\n lt(domainEvents.id, keyset.id),\n ),\n )!,\n );\n }\n }\n\n const rows = (await this.db\n .select()\n .from(domainEvents)\n .where(conditions.length > 0 ? and(...conditions) : undefined)\n .orderBy(desc(domainEvents.occurredAt), desc(domainEvents.id))\n .limit(limit + 1)) as DomainEventRecord[];\n\n const hasMore = rows.length > limit;\n const page = hasMore ? rows.slice(0, limit) : rows;\n const items = page.map(toEventSummary);\n const last = page[page.length - 1];\n const nextCursor =\n hasMore && last\n ? encodeEventCursor({ occurredAt: last.occurredAt, id: last.id })\n : null;\n\n return { items, nextCursor };\n }\n\n // ============================================================================\n // Polling\n // ============================================================================\n\n /**\n * Test-only hook. Runs exactly one drain cycle and returns. Production\n * code goes through `onModuleInit` → `schedulePoll`, which calls the\n * same `processBatch` under a timer.\n */\n async drainOnce(): Promise<void> {\n await this.processBatch();\n }\n\n private schedulePoll(): void {\n if (!this.polling) return;\n this.pollTimer = setTimeout(async () => {\n try {\n await this.processBatch();\n } catch (err) {\n this.logger.error(`Poll cycle error: ${err}`);\n } finally {\n this.schedulePoll();\n }\n }, POLL_INTERVAL_MS);\n }\n\n /**\n * Drain one batch (BRIDGE-4 restructure of EVT-4).\n *\n * Two-phase per drained event:\n *\n * 1. **Per-event transaction** — bridge fanout (`bridgeHook.processEvent`)\n * + `processed_at` stamp. Both write through the same `tx`. A throw\n * inside the tx (only infra-level failures should reach here, since\n * the hook tolerates null direction and registry misses inline)\n * rolls back the bridge inserts AND the `processed_at` stamp; the\n * event re-claims on the next drain cycle. Bridge `UNIQUE\n * (event_id, trigger_id)` makes the retry idempotent.\n *\n * 2. **After commit** — dispatch in-process subscribers (`IEventBus.subscribe`\n * handlers). This deliberately runs OUTSIDE the per-event tx (lead\n * decision 2026-04-22): subscribers are best-effort and must not\n * gate forward progress or roll back bridge fanout. Subscriber\n * errors are caught + logged; `processed_at` is already committed.\n * The old `MAX_RETRIES=3` in-process retry loop and the\n * `failed`-stamping path were removed in BRIDGE-4 along with their\n * coupling.\n *\n * The `processed_at` UPDATE carries `AND status='pending'` (BRIDGE-4\n * tightening — without it, a hypothetical double-claim could double-stamp\n * the timestamp). The per-event tx + `FOR UPDATE SKIP LOCKED` claim\n * make this defensive belt-and-suspenders.\n */\n private async processBatch(): Promise<void> {\n const pools = this.opts.pools;\n\n // Build WHERE: status='pending' [AND pool IN (...)]\n const whereClause: SQL<unknown> = pools && pools.length > 0\n ? (and(eq(domainEvents.status, 'pending'), inArray(domainEvents.pool, pools)) as SQL<unknown>)\n : eq(domainEvents.status, 'pending');\n\n // Claim a batch with FOR UPDATE SKIP LOCKED so multiple pollers don't\n // double-dispatch. The lock is released when the outer transaction\n // commits — which is fine because the immediately-following per-event\n // tx flips status='processed' under its own `AND status='pending'`\n // guard, so a re-claim of the same row in a subsequent batch is a\n // no-op UPDATE.\n const rows = await this.db.transaction(async (tx) => {\n return tx\n .select()\n .from(domainEvents)\n .where(whereClause)\n .orderBy(asc(domainEvents.occurredAt))\n .limit(POLL_BATCH_SIZE)\n .for('update', { skipLocked: true });\n }) as Array<typeof domainEvents.$inferSelect>;\n\n for (const row of rows) {\n const event: DomainEvent = {\n id: row.id,\n type: row.type,\n aggregateId: row.aggregateId,\n aggregateType: row.aggregateType,\n payload: row.payload as Record<string, unknown>,\n occurredAt: row.occurredAt instanceof Date ? row.occurredAt : new Date(row.occurredAt as unknown as string),\n metadata: (row.metadata ?? undefined) as Record<string, unknown> | undefined,\n };\n\n // Phase 1 — per-event tx: bridge fanout + processed_at stamp.\n try {\n await this.db.transaction(async (tx) => {\n if (this.bridgeHook) {\n await this.bridgeHook.processEvent(event, tx);\n }\n await tx\n .update(domainEvents)\n .set({ status: 'processed', processedAt: new Date() })\n .where(\n and(\n eq(domainEvents.id, event.id),\n eq(domainEvents.status, 'pending'),\n ),\n );\n });\n } catch (err) {\n // Infra-level failure inside the per-event tx — bridge inserts\n // and processed_at both rolled back. Log and move on; the next\n // drain cycle re-claims the row. UNIQUE on bridge_delivery makes\n // the retry idempotent.\n this.logger.error(\n `Per-event tx failed for event id=${event.id} type=${event.type}: ${err}`,\n );\n continue;\n }\n\n // Phase 2 — best-effort subscriber dispatch. Errors are logged\n // and discarded; processed_at is already committed. Subscribers\n // are observability + cache-busts + small ancillary work; they\n // must not gate forward progress.\n try {\n await this.dispatch(event);\n } catch (err) {\n this.logger.error(\n `Subscriber dispatch failed for event id=${event.id} type=${event.type} ` +\n `(processed_at already committed; failure does not retry): ${err}`,\n );\n }\n }\n }\n\n private async dispatch(event: DomainEvent): Promise<void> {\n const set = this.handlers.get(event.type);\n if (!set) return;\n\n let firstError: unknown;\n for (const handler of set) {\n try {\n await handler(event);\n } catch (err) {\n this.logger.error(\n `Handler error for event type \"${event.type}\" (id: ${event.id}): ${err}`,\n );\n if (firstError === undefined) {\n firstError = err;\n }\n }\n }\n\n if (firstError !== undefined) {\n throw firstError;\n }\n }\n}\n","/**\n * Keyset (seek) cursor codec for `IEventReadPort.listEvents` (OBS-LIST-1).\n *\n * The list is ordered `occurred_at DESC, id DESC`. The cursor encodes the\n * `(occurredAt, id)` of the last row on the previous page so the next page\n * seeks with `WHERE (occurred_at, id) < (cursorOccurredAt, cursorId)`.\n *\n * The cursor is opaque to consumers: a base64url-encoded JSON tuple. Shape\n * is an implementation detail — never parse it outside this module.\n *\n * Mirrors the jobs keyset codec; kept separate because the events subsystem\n * must not depend on `runtime/subsystems/jobs/`.\n */\n\nexport interface EventKeyset {\n occurredAt: Date;\n id: string;\n}\n\n/** Default page size when `limit` is omitted. */\nexport const DEFAULT_EVENT_LIST_LIMIT = 50;\n/** Hard upper bound on page size. */\nexport const MAX_EVENT_LIST_LIMIT = 200;\n\n/** Clamp a caller-supplied `limit` into `[1, MAX_EVENT_LIST_LIMIT]`. */\nexport function clampEventLimit(limit: number | undefined): number {\n if (typeof limit !== 'number' || !Number.isFinite(limit)) {\n return DEFAULT_EVENT_LIST_LIMIT;\n }\n const floored = Math.floor(limit);\n if (floored < 1) return 1;\n if (floored > MAX_EVENT_LIST_LIMIT) return MAX_EVENT_LIST_LIMIT;\n return floored;\n}\n\nexport function encodeEventCursor(keyset: EventKeyset): string {\n const tuple = [keyset.occurredAt.toISOString(), keyset.id];\n return Buffer.from(JSON.stringify(tuple), 'utf8').toString('base64url');\n}\n\n/**\n * Decode an opaque cursor back into its `(occurredAt, id)` keyset. Returns\n * `null` for malformed input so user-supplied garbage is treated as \"start\n * from the beginning\" rather than throwing.\n */\nexport function decodeEventCursor(cursor: string): EventKeyset | null {\n try {\n const json = Buffer.from(cursor, 'base64url').toString('utf8');\n const parsed = JSON.parse(json) as unknown;\n if (!Array.isArray(parsed) || parsed.length !== 2) return null;\n const [iso, id] = parsed;\n if (typeof iso !== 'string' || typeof id !== 'string') return null;\n const occurredAt = new Date(iso);\n if (Number.isNaN(occurredAt.getTime())) return null;\n return { occurredAt, id };\n } catch {\n return null;\n }\n}\n","/**\n * Drizzle schema for the domain_events outbox table.\n *\n * This table backs the DrizzleEventBus. Events are inserted within the\n * same database transaction as the domain write (outbox pattern). A\n * polling process reads unprocessed rows and dispatches to subscribers.\n *\n * First-class routing columns (EVT-1):\n * - `pool` — populated by DrizzleEventBus.publish() (EVT-4); enables\n * pool-filtered drain queries without unpacking metadata JSON.\n * NULL when `tier='audit'` (audit events are not routed).\n * - `direction` — `inbound` | `change` | `outbound`; mirrors the routing\n * dimension used by jobs' reserved `events_inbound` /\n * `events_change` / `events_outbound` pools.\n * NULL when `tier='audit'`.\n * - `tenant_id` — conditional: emitted only when `events.multi_tenant: true`\n * in `codegen.config.yaml`. The runtime source declares it\n * unconditionally; EVT-8's scaffold template handles the\n * config-driven include/exclude.\n *\n * Audit-tier column (AUDIT-1):\n * - `tier` — `'domain'` | `'audit'`. Defaults to `'domain'`. Audit-tier\n * rows are observability-only (subscribers may observe but\n * the bridge MUST NOT spawn jobs from them); they have null\n * `pool` and `direction` by construction. The CHECK\n * constraint `domain_events_tier_routing_check` enforces\n * `tier='audit' ⇔ (pool IS NULL AND direction IS NULL)`.\n *\n * The `metadata` JSON column continues to carry these values for protocol\n * stability; the first-class columns are an optimization for drain filtering.\n *\n * Indexes (declared below in the index callback):\n * - (status, occurred_at) — polling drain filter\n * - (aggregate_id, aggregate_type) — event replay per aggregate\n * - (pool, status, occurred_at) — per-pool drain filter (EVT-1)\n * - (tier, status, occurred_at) — per-tier filter for the observability\n * viewer's tier toggle (AUDIT-1).\n */\nimport {\n check,\n index,\n jsonb,\n pgTable,\n text,\n timestamp,\n uuid,\n} from 'drizzle-orm/pg-core';\nimport { sql } from 'drizzle-orm';\nimport type { InferSelectModel } from 'drizzle-orm';\n\nexport const domainEvents = pgTable(\n 'domain_events',\n {\n id: uuid('id').primaryKey(),\n type: text('type').notNull(),\n aggregateId: text('aggregate_id').notNull(),\n aggregateType: text('aggregate_type').notNull(),\n payload: jsonb('payload').notNull().$type<Record<string, unknown>>(),\n occurredAt: timestamp('occurred_at', { withTimezone: true }).notNull(),\n processedAt: timestamp('processed_at', { withTimezone: true }),\n /** Lifecycle status: pending | processed | failed */\n status: text('status').notNull().default('pending'),\n /** Error message from the last failed dispatch attempt. */\n error: text('error'),\n metadata: jsonb('metadata').$type<Record<string, unknown>>(),\n /** Routing pool (e.g. `events_inbound`, `events_change`, `events_outbound`). Populated by DrizzleEventBus.publish() in EVT-4. NULL when `tier='audit'`. */\n pool: text('pool'),\n /** Routing direction: `inbound` | `change` | `outbound`. Populated by DrizzleEventBus.publish() in EVT-4. NULL when `tier='audit'`. */\n direction: text('direction'),\n /**\n * Event tier: `'domain'` (default) or `'audit'`. Audit-tier rows are\n * observability-only and have null `pool`/`direction` by construction —\n * enforced by the `domain_events_tier_routing_check` CHECK constraint\n * declared below. (AUDIT-1)\n */\n tier: text('tier').notNull().default('domain'),\n // conditional: emitted only when events.multi_tenant: true\n tenantId: text('tenant_id'),\n },\n (t) => ({\n /** Polling drain filter (existing — promoted from comment to declaration in EVT-1). */\n idxDomainEventsStatusOccurredAt: index('idx_domain_events_status_occurred_at').on(\n t.status,\n t.occurredAt,\n ),\n /** Event replay per aggregate (existing — promoted from comment to declaration in EVT-1). */\n idxDomainEventsAggregate: index('idx_domain_events_aggregate').on(\n t.aggregateId,\n t.aggregateType,\n ),\n /** Per-pool drain filter (EVT-1). Enables DrizzleEventBus to drain a single pool without scanning all events. */\n idxDomainEventsPoolStatusOccurredAt: index(\n 'idx_domain_events_pool_status_occurred_at',\n ).on(t.pool, t.status, t.occurredAt),\n /** Per-tier filter (AUDIT-1). Backs the observability viewer's tier toggle. */\n idxDomainEventsTierStatusOccurredAt: index(\n 'idx_domain_events_tier_status_occurred_at',\n ).on(t.tier, t.status, t.occurredAt),\n /**\n * Tier ↔ routing-fields invariant (AUDIT-1):\n * - `tier` is one of `'domain' | 'audit'`.\n * - `tier='audit'` ⇔ `pool IS NULL AND direction IS NULL`.\n * - `tier='domain'` ⇒ `pool` and `direction` are populated (the\n * DrizzleEventBus inserts always supply them; the bus stamps them\n * in AUDIT-3).\n */\n tierRoutingCheck: check(\n 'domain_events_tier_routing_check',\n sql`${t.tier} in ('domain','audit') AND ((${t.tier} = 'audit') = (${t.pool} is null and ${t.direction} is null))`,\n ),\n }),\n);\n\nexport type DomainEventRecord = InferSelectModel<typeof domainEvents>;\n","/**\n * NestJS injection tokens\n *\n * Used with @Inject() decorator in concrete repository constructors.\n */\n\n/**\n * Injection token for the Drizzle ORM database client.\n *\n * Usage in concrete repositories:\n * ```typescript\n * constructor(@Inject(DRIZZLE) db: DrizzleClient) { super(db); }\n * ```\n */\nexport const DRIZZLE = 'DRIZZLE' as const;\n\n/**\n * Injection token for the event bus (IEventBus).\n *\n * Optional — only resolved when EventsModule.forRoot() is registered.\n * BaseService uses this with @Optional() to emit lifecycle events\n * without requiring the events subsystem to be installed.\n *\n * Usage in services/use cases:\n * ```typescript\n * @Optional() @Inject(EVENT_BUS) eventBus?: IEventBus\n * ```\n */\nexport const EVENT_BUS = 'EVENT_BUS' as const;\n","/**\n * Injection token for the event bus.\n *\n * String constant (not Symbol) so it matches by value across import boundaries.\n * Matches the token in runtime/constants/tokens.ts — both are 'EVENT_BUS'.\n *\n * Usage in use cases:\n * ```typescript\n * constructor(@Inject(EVENT_BUS) private readonly eventBus: IEventBus) {}\n * ```\n */\nexport const EVENT_BUS = 'EVENT_BUS' as const;\n\n/**\n * Injection token for the read-side `IEventReadPort` over `domain_events`\n * (OBS-LIST-1).\n *\n * Bound by `EventsModule.forRoot` to the same backend instance as\n * `EVENT_BUS` for the `drizzle` and `memory` backends (both implement\n * `IEventReadPort`). The `redis` backend retains no history and therefore\n * does NOT provide this token — consumers composing it (e.g. the\n * observability combiner) inject it `@Optional()` and degrade to empty\n * results.\n *\n * String constant (not Symbol) so it matches by value across import\n * boundaries — same convention as `EVENT_BUS`.\n */\nexport const EVENT_READ_PORT = 'EVENT_READ_PORT' as const;\n\n/**\n * Injection token for the generated `TypedEventBus` facade.\n *\n * `TypedEventBus` lives in `runtime/subsystems/events/generated/bus.ts` and\n * wraps `IEventBus` with project-specific `AppDomainEvent`-typed `publish<T>()`\n * and `subscribe<T>()`. Use cases inject this token in preference to\n * `EVENT_BUS` when they want compile-time type safety on event shapes.\n *\n * String constant (not Symbol) so it matches by value across import\n * boundaries — same convention as `EVENT_BUS`.\n *\n * Provider registration lands in EVT-6 (EventsModule wiring); the token is\n * declared here so generated code can import it without depending on the\n * still-being-formalised module.\n */\nexport const TYPED_EVENT_BUS = 'TYPED_EVENT_BUS' as const;\n\n/**\n * Injection token for the resolved multi-tenancy flag.\n *\n * Provided by `EventsModule.forRoot(...)` as `options.multiTenant ?? false`.\n * Consumed by `TypedEventBus` to enforce the tenantId-is-required rule at\n * publish time.\n *\n * String constant (not Symbol) so it matches by value across import\n * boundaries — same convention as the other events tokens. (The jobs\n * subsystem uses Symbols for the analogous token; events chose strings\n * from the start and we keep the file internally consistent.)\n */\nexport const EVENTS_MULTI_TENANT = 'EVENTS_MULTI_TENANT' as const;\n\n/**\n * Injection token for the Redis connection URL used by RedisEventBus.\n * Provided automatically by EventsModule.forRoot({ backend: 'redis' }).\n */\nexport const REDIS_URL = Symbol('REDIS_URL');\n\n/**\n * Injection token for the resolved `EventsModuleOptions` object.\n *\n * Provided automatically by `EventsModule.forRoot(...)` /\n * `EventsModule.forRootAsync(...)`. Backends that need to observe module\n * configuration (e.g. `DrizzleEventBus` reading `opts.pools` for\n * pool-filtered drain) inject via this token.\n *\n * String-valued (not `Symbol`) so it matches by value across import\n * boundaries — same convention as `EVENT_BUS`.\n */\nexport const EVENTS_MODULE_OPTIONS = 'EVENTS_MODULE_OPTIONS' as const;\n","/**\n * Injection tokens for the bridge subsystem (ADR-023 Phase 2, BRIDGE-2).\n *\n * String constants (not Symbols) so they match by value across import\n * boundaries — same convention as `EVENT_BUS` / `EVENTS_MULTI_TENANT` in the\n * events subsystem (per EVT-6 §Implementation Notes). The jobs subsystem\n * uses Symbols for its analogous tokens; we keep the bridge file internally\n * consistent with the events convention because the bridge is conceptually\n * downstream of (and imports from) the events module.\n */\n\n/**\n * Token for the `IJobBridge` repo backend (memory in BRIDGE-3, Drizzle in\n * BRIDGE-4). Consumed by `BridgeDeliveryHandler` (BRIDGE-5), the outbox\n * drain (BRIDGE-4 modification), and `EventFlowService` (BRIDGE-7).\n */\nexport const BRIDGE_DELIVERY_REPO = 'BRIDGE_DELIVERY_REPO' as const;\n\n/**\n * Token for the `IEventFlow` facade implementation (BRIDGE-7). Use cases\n * inject this in preference to `EVENT_BUS` / `TYPED_EVENT_BUS` — calling\n * `eventFlow.publish(...)` / `eventFlow.publishAndStart(...)` is the\n * sanctioned authoring surface (ADR-023 §Decision 7).\n */\nexport const EVENT_FLOW = 'EVENT_FLOW' as const;\n\n/**\n * Token for the resolved multi-tenancy flag, provided by\n * `BridgeModule.forRoot({ multiTenant })` in BRIDGE-8. Consumed by\n * `EventFlowService.publishAndStart` (entry), `BridgeDeliveryHandler.handle`\n * (entry), and `DrizzleBridgeDeliveryRepo.insertDelivery` (pre-write) — the\n * three enforcement sites called out in ADR-023 §Multi-tenancy.\n */\nexport const BRIDGE_MULTI_TENANT = 'BRIDGE_MULTI_TENANT' as const;\n\n/**\n * Token for the resolved `BridgeModuleOptions` object. Provided by\n * `BridgeModule.forRoot(...)` / `forRootAsync(...)` in BRIDGE-8.\n * Mirrors `EVENTS_MODULE_OPTIONS` and `JOBS_DOMAIN_OPTIONS` shape — backends\n * inject this when they need to observe additional module configuration\n * (e.g. pool overrides) without each adding a dedicated token.\n */\nexport const BRIDGE_MODULE_OPTIONS = 'BRIDGE_MODULE_OPTIONS' as const;\n\n/**\n * Token for the codegen-emitted `bridgeRegistry` — the\n * `Record<EventTypeName, BridgeTriggerEntry[]>` map that drives\n * outbox-drain trigger lookup (BRIDGE-4) and `EventFlowService` Case B\n * dedup (BRIDGE-7). Provider registration lands in BRIDGE-8; the token is\n * declared here so generated code (BRIDGE-6) can import it without\n * depending on the still-being-formalised module.\n */\nexport const BRIDGE_REGISTRY = 'BRIDGE_REGISTRY' as const;\n\n\n/**\n * Token for the `IBridgeOutboxDrainHook` implementation (BRIDGE-4).\n * Injected `@Optional()` into `DrizzleEventBus` — when the bridge\n * subsystem is not installed the token is undefined and the events\n * outbox drain skips the bridge block entirely (preserves EVT-4\n * baseline behaviour).\n *\n * Resolution order: `BridgeModule.forRoot()` provides this token in\n * BRIDGE-8 alongside the rest of the bridge subsystem. `EventsModule`\n * itself never provides it; the events subsystem stays unaware of the\n * bridge unless the consumer wires it.\n */\nexport const BRIDGE_OUTBOX_DRAIN_HOOK = 'BRIDGE_OUTBOX_DRAIN_HOOK' as const;\n"],"mappings":"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","names":["sql","sql"]}
1
+ {"version":3,"sources":["../../../../runtime/subsystems/events/event-bus.drizzle-backend.ts","../../../../runtime/subsystems/events/event-keyset-cursor.ts","../../../../runtime/subsystems/events/domain-events.schema.ts","../../../../runtime/constants/tokens.ts","../../../../runtime/subsystems/events/events.tokens.ts","../../../../runtime/subsystems/bridge/bridge.tokens.ts"],"sourcesContent":["/**\n * DrizzleEventBus — Postgres-backed event bus using the transactional outbox pattern.\n *\n * Events are inserted into the `domain_events` table within the caller's\n * Drizzle transaction. A background polling loop (started on module init)\n * reads unprocessed events and dispatches them to registered subscribers.\n *\n * When the transaction rolls back, the event is never persisted — no\n * phantom events.\n *\n * Pool awareness (EVT-4):\n * - On `publish`/`publishMany` the backend writes `metadata.pool`,\n * `metadata.direction`, and `metadata.tenantId` into the first-class\n * `pool` / `direction` / `tenant_id` columns (metadata JSON is still\n * written unchanged for protocol stability).\n * - The drain loop filters by `opts.pools` when provided, so separate\n * processes (e.g. one per `events_inbound` / `events_change` /\n * `events_outbound`) can claim only their own lane. `pools: undefined`\n * drains all pending rows (backwards-compatible behaviour).\n *\n * EVT-Q7: No stale-event sweeper. `FOR UPDATE SKIP LOCKED` is\n * self-healing — the row is only locked for the duration of the\n * enclosing polling transaction; the `status='processed'` update happens\n * within that same transaction. There is no `claimed_at` semantic (unlike\n * jobs), so no stale rows can exist.\n *\n * This backend is suitable until you need real-time fan-out or very high\n * throughput. At that point, swap the backend for Redis Streams or similar\n * via EventsModule.forRoot({ backend: '...' }) without touching use cases.\n */\nimport { Injectable, OnModuleDestroy, OnModuleInit, Inject, Logger, Optional } from '@nestjs/common';\nimport { eq, and, inArray, asc, desc, gte, lt, or, sql, type SQL } from 'drizzle-orm';\nimport type { DomainEvent, DrizzleTransaction, IEventBus } from './event-bus.protocol';\nimport type {\n EventPage,\n IEventReadPort,\n ListEventsQuery,\n} from './event-read.protocol';\nimport {\n clampEventLimit,\n decodeEventCursor,\n encodeEventCursor,\n} from './event-keyset-cursor';\nimport type { DrizzleClient } from '../../types/drizzle';\nimport { domainEvents, type DomainEventRecord } from './domain-events.schema';\nimport { DRIZZLE } from '../../constants/tokens';\nimport { EVENTS_MODULE_OPTIONS } from './events.tokens';\nimport type { EventsModuleOptions } from './events.module';\nimport { BRIDGE_OUTBOX_DRAIN_HOOK } from '../bridge/bridge.tokens';\nimport type { IBridgeOutboxDrainHook } from '../bridge/bridge.protocol';\n\n/** How long to wait between polling cycles (ms). */\nconst POLL_INTERVAL_MS = 1_000;\n/** Max events claimed per polling cycle to bound memory usage. */\nconst POLL_BATCH_SIZE = 50;\n\n/**\n * Row shape built from `metadata` for writing into `domain_events`. Keeps\n * the per-event extraction logic in one place so publish/publishMany stay\n * in sync.\n */\nfunction toInsertValues(event: DomainEvent, multiTenant: boolean) {\n const metadata = event.metadata ?? undefined;\n const pool = (metadata?.['pool'] as string | undefined) ?? null;\n const direction = (metadata?.['direction'] as string | undefined) ?? null;\n // AUDIT-1: tier defaults to 'domain' when absent. The DB CHECK\n // constraint (`domain_events_tier_routing_check`) enforces the\n // tier ⇔ routing-fields invariant at the storage boundary; no\n // JS-side assertion is needed here.\n const tier = (metadata?.['tier'] as string | undefined) ?? 'domain';\n const base = {\n id: event.id,\n type: event.type,\n aggregateId: event.aggregateId,\n aggregateType: event.aggregateType,\n payload: event.payload,\n occurredAt: event.occurredAt,\n processedAt: null,\n status: 'pending' as const,\n metadata: event.metadata,\n pool,\n direction,\n tier,\n };\n // EVT-8: `tenant_id` is a scaffold-time conditional column, emitted only\n // when `events.multi_tenant: true`. Only write it when multi-tenancy is\n // on — under single-tenant scaffolds the column does not exist, so the\n // key must be omitted from the insert.\n if (!multiTenant) return base;\n const tenantId = (metadata?.['tenantId'] as string | undefined) ?? null;\n return { ...base, tenantId };\n}\n\n/**\n * Project a raw `domain_events` row into the narrow `EventSummary` shape.\n * Shared with the memory backend via this helper kept module-local to each\n * backend (the events subsystem has no cross-backend projection file yet;\n * the two are byte-identical and small).\n */\nfunction toEventSummary(r: DomainEventRecord) {\n const metadata = (r.metadata ?? undefined) as\n | Record<string, unknown>\n | undefined;\n const rootRunId = metadata?.['rootRunId'];\n return {\n id: r.id,\n type: r.type,\n aggregateId: r.aggregateId,\n aggregateType: r.aggregateType,\n status: r.status,\n pool: r.pool,\n direction: r.direction,\n tier: r.tier,\n rootRunId: typeof rootRunId === 'string' ? rootRunId : null,\n // EVT-8: `tenant_id` is a scaffold-time conditional column. Read it\n // structurally so this projection typechecks against both the\n // multi-tenant schema (column present) and the single-tenant schema\n // (column absent → undefined → null).\n tenantId: (r as { tenantId?: string | null }).tenantId ?? null,\n occurredAt:\n r.occurredAt instanceof Date\n ? r.occurredAt\n : new Date(r.occurredAt as unknown as string),\n processedAt:\n r.processedAt == null\n ? null\n : r.processedAt instanceof Date\n ? r.processedAt\n : new Date(r.processedAt as unknown as string),\n };\n}\n\n@Injectable()\nexport class DrizzleEventBus implements IEventBus, IEventReadPort, OnModuleInit, OnModuleDestroy {\n private readonly logger = new Logger(DrizzleEventBus.name);\n private polling = false;\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\n private readonly handlers = new Map<string, Set<(event: DomainEvent) => Promise<void>>>();\n private readonly opts: EventsModuleOptions;\n\n constructor(\n @Inject(DRIZZLE) private readonly db: DrizzleClient,\n @Optional() @Inject(EVENTS_MODULE_OPTIONS) opts?: EventsModuleOptions,\n /**\n * Bridge subsystem hook (BRIDGE-4). Optional — when the bridge\n * subsystem is not installed in the consuming app, this token is\n * undefined and the drain skips the bridge block entirely (preserves\n * EVT-4 baseline behaviour).\n *\n * When provided, `processEvent` is invoked once per drained event\n * INSIDE the per-event tx, before `processed_at` is stamped. The\n * hook owns all knowledge of `bridge_delivery + wrapper job_run`\n * shapes; the events subsystem stays unaware of bridge schemas.\n */\n @Optional()\n @Inject(BRIDGE_OUTBOX_DRAIN_HOOK)\n private readonly bridgeHook: IBridgeOutboxDrainHook | null = null,\n ) {\n // Default so direct construction (e.g. integration tests not going\n // through Nest DI) keeps working without an explicit options object.\n this.opts = opts ?? { backend: 'drizzle' };\n }\n\n // ============================================================================\n // Lifecycle\n // ============================================================================\n\n async onModuleInit(): Promise<void> {\n this.polling = true;\n this.schedulePoll();\n }\n\n async onModuleDestroy(): Promise<void> {\n this.polling = false;\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n }\n\n // ============================================================================\n // IEventBus\n // ============================================================================\n\n async publish(event: DomainEvent, tx?: DrizzleTransaction): Promise<void> {\n const client = (tx ?? this.db) as DrizzleClient;\n const multiTenant = this.opts.multiTenant ?? false;\n await client.insert(domainEvents).values(toInsertValues(event, multiTenant));\n }\n\n async publishMany(events: DomainEvent[], tx?: DrizzleTransaction): Promise<void> {\n if (events.length === 0) return;\n const client = (tx ?? this.db) as DrizzleClient;\n const multiTenant = this.opts.multiTenant ?? false;\n await client\n .insert(domainEvents)\n .values(events.map((e) => toInsertValues(e, multiTenant)));\n }\n\n async findById(eventId: string): Promise<DomainEvent | null> {\n const rows = await this.db\n .select()\n .from(domainEvents)\n .where(eq(domainEvents.id, eventId))\n .limit(1);\n const row = rows[0];\n if (!row) return null;\n return {\n id: row.id,\n type: row.type,\n aggregateId: row.aggregateId,\n aggregateType: row.aggregateType,\n payload: row.payload as Record<string, unknown>,\n occurredAt:\n row.occurredAt instanceof Date\n ? row.occurredAt\n : new Date(row.occurredAt as unknown as string),\n metadata: (row.metadata ?? undefined) as\n | Record<string, unknown>\n | undefined,\n };\n }\n\n subscribe<T extends DomainEvent = DomainEvent>(\n eventType: string,\n handler: (event: T) => Promise<void>,\n ): () => void {\n if (!this.handlers.has(eventType)) {\n this.handlers.set(eventType, new Set());\n }\n const set = this.handlers.get(eventType)!;\n const h = handler as (event: DomainEvent) => Promise<void>;\n set.add(h);\n return () => {\n set.delete(h);\n };\n }\n\n // ============================================================================\n // IEventReadPort (OBS-LIST-1)\n // ============================================================================\n\n async listEvents(query: ListEventsQuery = {}): Promise<EventPage> {\n const limit = clampEventLimit(query.limit);\n const conditions: SQL<unknown>[] = [];\n\n if (query.poolId) conditions.push(eq(domainEvents.pool, query.poolId));\n if (query.direction)\n conditions.push(eq(domainEvents.direction, query.direction));\n if (query.since) conditions.push(gte(domainEvents.occurredAt, query.since));\n if (query.rootRunId) {\n // Filter on the JSON correlation id: metadata->>'rootRunId'.\n conditions.push(\n sql`${domainEvents.metadata}->>'rootRunId' = ${query.rootRunId}`,\n );\n }\n // EVT-8: `tenant_id` is a scaffold-time conditional column (emitted only\n // under `events.multi_tenant: true`). Guard the filter behind the same\n // `multiTenant` flag, and read the column structurally so this backend\n // typechecks against both the multi-tenant schema (column present) and\n // the single-tenant schema (column absent). When multi-tenancy is off\n // there is no `tenant_id` column to filter on.\n if (this.opts.multiTenant && query.tenantId !== undefined) {\n const tenantIdColumn = (\n domainEvents as unknown as { tenantId: typeof domainEvents.pool }\n ).tenantId;\n conditions.push(\n query.tenantId === null\n ? (sql`${tenantIdColumn} is null` as SQL<unknown>)\n : eq(tenantIdColumn, query.tenantId),\n );\n }\n\n // Keyset seek: WHERE (occurred_at, id) < (cursorOccurredAt, cursorId).\n if (query.cursor) {\n const keyset = decodeEventCursor(query.cursor);\n if (keyset) {\n conditions.push(\n or(\n lt(domainEvents.occurredAt, keyset.occurredAt),\n and(\n eq(domainEvents.occurredAt, keyset.occurredAt),\n lt(domainEvents.id, keyset.id),\n ),\n )!,\n );\n }\n }\n\n const rows = (await this.db\n .select()\n .from(domainEvents)\n .where(conditions.length > 0 ? and(...conditions) : undefined)\n .orderBy(desc(domainEvents.occurredAt), desc(domainEvents.id))\n .limit(limit + 1)) as DomainEventRecord[];\n\n const hasMore = rows.length > limit;\n const page = hasMore ? rows.slice(0, limit) : rows;\n const items = page.map(toEventSummary);\n const last = page[page.length - 1];\n const nextCursor =\n hasMore && last\n ? encodeEventCursor({ occurredAt: last.occurredAt, id: last.id })\n : null;\n\n return { items, nextCursor };\n }\n\n // ============================================================================\n // Polling\n // ============================================================================\n\n /**\n * Test-only hook. Runs exactly one drain cycle and returns. Production\n * code goes through `onModuleInit` → `schedulePoll`, which calls the\n * same `processBatch` under a timer.\n */\n async drainOnce(): Promise<void> {\n await this.processBatch();\n }\n\n private schedulePoll(): void {\n if (!this.polling) return;\n this.pollTimer = setTimeout(async () => {\n try {\n await this.processBatch();\n } catch (err) {\n this.logger.error(`Poll cycle error: ${err}`);\n } finally {\n this.schedulePoll();\n }\n }, POLL_INTERVAL_MS);\n }\n\n /**\n * Drain one batch (BRIDGE-4 restructure of EVT-4).\n *\n * Two-phase per drained event:\n *\n * 1. **Per-event transaction** — bridge fanout (`bridgeHook.processEvent`)\n * + `processed_at` stamp. Both write through the same `tx`. A throw\n * inside the tx (only infra-level failures should reach here, since\n * the hook tolerates null direction and registry misses inline)\n * rolls back the bridge inserts AND the `processed_at` stamp; the\n * event re-claims on the next drain cycle. Bridge `UNIQUE\n * (event_id, trigger_id)` makes the retry idempotent.\n *\n * 2. **After commit** — dispatch in-process subscribers (`IEventBus.subscribe`\n * handlers). This deliberately runs OUTSIDE the per-event tx (lead\n * decision 2026-04-22): subscribers are best-effort and must not\n * gate forward progress or roll back bridge fanout. Subscriber\n * errors are caught + logged; `processed_at` is already committed.\n * The old `MAX_RETRIES=3` in-process retry loop and the\n * `failed`-stamping path were removed in BRIDGE-4 along with their\n * coupling.\n *\n * The `processed_at` UPDATE carries `AND status='pending'` (BRIDGE-4\n * tightening — without it, a hypothetical double-claim could double-stamp\n * the timestamp). The per-event tx + `FOR UPDATE SKIP LOCKED` claim\n * make this defensive belt-and-suspenders.\n */\n private async processBatch(): Promise<void> {\n const pools = this.opts.pools;\n\n // Build WHERE: status='pending' [AND pool IN (...)]\n const whereClause: SQL<unknown> = pools && pools.length > 0\n ? (and(eq(domainEvents.status, 'pending'), inArray(domainEvents.pool, pools)) as SQL<unknown>)\n : eq(domainEvents.status, 'pending');\n\n // Claim a batch with FOR UPDATE SKIP LOCKED so multiple pollers don't\n // double-dispatch. The lock is released when the outer transaction\n // commits — which is fine because the immediately-following per-event\n // tx flips status='processed' under its own `AND status='pending'`\n // guard, so a re-claim of the same row in a subsequent batch is a\n // no-op UPDATE.\n const rows = await this.db.transaction(async (tx) => {\n return tx\n .select()\n .from(domainEvents)\n .where(whereClause)\n .orderBy(asc(domainEvents.occurredAt))\n .limit(POLL_BATCH_SIZE)\n .for('update', { skipLocked: true });\n }) as Array<typeof domainEvents.$inferSelect>;\n\n for (const row of rows) {\n const event: DomainEvent = {\n id: row.id,\n type: row.type,\n aggregateId: row.aggregateId,\n aggregateType: row.aggregateType,\n payload: row.payload as Record<string, unknown>,\n occurredAt: row.occurredAt instanceof Date ? row.occurredAt : new Date(row.occurredAt as unknown as string),\n metadata: (row.metadata ?? undefined) as Record<string, unknown> | undefined,\n };\n\n // Phase 1 — per-event tx: bridge fanout + processed_at stamp.\n try {\n await this.db.transaction(async (tx) => {\n if (this.bridgeHook) {\n await this.bridgeHook.processEvent(event, tx);\n }\n await tx\n .update(domainEvents)\n .set({ status: 'processed', processedAt: new Date() })\n .where(\n and(\n eq(domainEvents.id, event.id),\n eq(domainEvents.status, 'pending'),\n ),\n );\n });\n } catch (err) {\n // Infra-level failure inside the per-event tx — bridge inserts\n // and processed_at both rolled back. Log and move on; the next\n // drain cycle re-claims the row. UNIQUE on bridge_delivery makes\n // the retry idempotent.\n this.logger.error(\n `Per-event tx failed for event id=${event.id} type=${event.type}: ${err}`,\n );\n continue;\n }\n\n // Phase 2 — best-effort subscriber dispatch. Errors are logged\n // and discarded; processed_at is already committed. Subscribers\n // are observability + cache-busts + small ancillary work; they\n // must not gate forward progress.\n try {\n await this.dispatch(event);\n } catch (err) {\n this.logger.error(\n `Subscriber dispatch failed for event id=${event.id} type=${event.type} ` +\n `(processed_at already committed; failure does not retry): ${err}`,\n );\n }\n }\n }\n\n private async dispatch(event: DomainEvent): Promise<void> {\n const set = this.handlers.get(event.type);\n if (!set) return;\n\n let firstError: unknown;\n for (const handler of set) {\n try {\n await handler(event);\n } catch (err) {\n this.logger.error(\n `Handler error for event type \"${event.type}\" (id: ${event.id}): ${err}`,\n );\n if (firstError === undefined) {\n firstError = err;\n }\n }\n }\n\n if (firstError !== undefined) {\n throw firstError;\n }\n }\n}\n","/**\n * Keyset (seek) cursor codec for `IEventReadPort.listEvents` (OBS-LIST-1).\n *\n * The list is ordered `occurred_at DESC, id DESC`. The cursor encodes the\n * `(occurredAt, id)` of the last row on the previous page so the next page\n * seeks with `WHERE (occurred_at, id) < (cursorOccurredAt, cursorId)`.\n *\n * The cursor is opaque to consumers: a base64url-encoded JSON tuple. Shape\n * is an implementation detail — never parse it outside this module.\n *\n * Mirrors the jobs keyset codec; kept separate because the events subsystem\n * must not depend on `runtime/subsystems/jobs/`.\n */\n\nexport interface EventKeyset {\n occurredAt: Date;\n id: string;\n}\n\n/** Default page size when `limit` is omitted. */\nexport const DEFAULT_EVENT_LIST_LIMIT = 50;\n/** Hard upper bound on page size. */\nexport const MAX_EVENT_LIST_LIMIT = 200;\n\n/** Clamp a caller-supplied `limit` into `[1, MAX_EVENT_LIST_LIMIT]`. */\nexport function clampEventLimit(limit: number | undefined): number {\n if (typeof limit !== 'number' || !Number.isFinite(limit)) {\n return DEFAULT_EVENT_LIST_LIMIT;\n }\n const floored = Math.floor(limit);\n if (floored < 1) return 1;\n if (floored > MAX_EVENT_LIST_LIMIT) return MAX_EVENT_LIST_LIMIT;\n return floored;\n}\n\nexport function encodeEventCursor(keyset: EventKeyset): string {\n const tuple = [keyset.occurredAt.toISOString(), keyset.id];\n return Buffer.from(JSON.stringify(tuple), 'utf8').toString('base64url');\n}\n\n/**\n * Decode an opaque cursor back into its `(occurredAt, id)` keyset. Returns\n * `null` for malformed input so user-supplied garbage is treated as \"start\n * from the beginning\" rather than throwing.\n */\nexport function decodeEventCursor(cursor: string): EventKeyset | null {\n try {\n const json = Buffer.from(cursor, 'base64url').toString('utf8');\n const parsed = JSON.parse(json) as unknown;\n if (!Array.isArray(parsed) || parsed.length !== 2) return null;\n const [iso, id] = parsed;\n if (typeof iso !== 'string' || typeof id !== 'string') return null;\n const occurredAt = new Date(iso);\n if (Number.isNaN(occurredAt.getTime())) return null;\n return { occurredAt, id };\n } catch {\n return null;\n }\n}\n","/**\n * Drizzle schema for the domain_events outbox table.\n *\n * This table backs the DrizzleEventBus. Events are inserted within the\n * same database transaction as the domain write (outbox pattern). A\n * polling process reads unprocessed rows and dispatches to subscribers.\n *\n * First-class routing columns (EVT-1):\n * - `pool` — populated by DrizzleEventBus.publish() (EVT-4); enables\n * pool-filtered drain queries without unpacking metadata JSON.\n * NULL when `tier='audit'` (audit events are not routed).\n * - `direction` — `inbound` | `change` | `outbound`; mirrors the routing\n * dimension used by jobs' reserved `events_inbound` /\n * `events_change` / `events_outbound` pools.\n * NULL when `tier='audit'`.\n * - `tenant_id` — conditional: emitted only when `events.multi_tenant: true`\n * in `codegen.config.yaml`. The runtime source declares it\n * unconditionally; EVT-8's scaffold template handles the\n * config-driven include/exclude.\n *\n * Audit-tier column (AUDIT-1):\n * - `tier` — `'domain'` | `'audit'`. Defaults to `'domain'`. Audit-tier\n * rows are observability-only (subscribers may observe but\n * the bridge MUST NOT spawn jobs from them); they have null\n * `pool` and `direction` by construction. The CHECK\n * constraint `domain_events_tier_routing_check` enforces\n * `tier='audit' ⇔ (pool IS NULL AND direction IS NULL)`.\n *\n * The `metadata` JSON column continues to carry these values for protocol\n * stability; the first-class columns are an optimization for drain filtering.\n *\n * Indexes (declared below in the index callback):\n * - (status, occurred_at) — polling drain filter\n * - (aggregate_id, aggregate_type) — event replay per aggregate\n * - (pool, status, occurred_at) — per-pool drain filter (EVT-1)\n * - (tier, status, occurred_at) — per-tier filter for the observability\n * viewer's tier toggle (AUDIT-1).\n */\nimport {\n check,\n index,\n jsonb,\n pgTable,\n text,\n timestamp,\n uuid,\n} from 'drizzle-orm/pg-core';\nimport { sql } from 'drizzle-orm';\nimport type { InferSelectModel } from 'drizzle-orm';\n\nexport const domainEvents = pgTable(\n 'domain_events',\n {\n id: uuid('id').primaryKey(),\n type: text('type').notNull(),\n aggregateId: text('aggregate_id').notNull(),\n aggregateType: text('aggregate_type').notNull(),\n payload: jsonb('payload').notNull().$type<Record<string, unknown>>(),\n occurredAt: timestamp('occurred_at', { withTimezone: true }).notNull(),\n processedAt: timestamp('processed_at', { withTimezone: true }),\n /** Lifecycle status: pending | processed | failed */\n status: text('status').notNull().default('pending'),\n /** Error message from the last failed dispatch attempt. */\n error: text('error'),\n metadata: jsonb('metadata').$type<Record<string, unknown>>(),\n /** Routing pool (e.g. `events_inbound`, `events_change`, `events_outbound`). Populated by DrizzleEventBus.publish() in EVT-4. NULL when `tier='audit'`. */\n pool: text('pool'),\n /** Routing direction: `inbound` | `change` | `outbound`. Populated by DrizzleEventBus.publish() in EVT-4. NULL when `tier='audit'`. */\n direction: text('direction'),\n /**\n * Event tier: `'domain'` (default) or `'audit'`. Audit-tier rows are\n * observability-only and have null `pool`/`direction` by construction —\n * enforced by the `domain_events_tier_routing_check` CHECK constraint\n * declared below. (AUDIT-1)\n */\n tier: text('tier').notNull().default('domain'),\n // conditional: emitted only when events.multi_tenant: true\n tenantId: text('tenant_id'),\n },\n (t) => ({\n /** Polling drain filter (existing — promoted from comment to declaration in EVT-1). */\n idxDomainEventsStatusOccurredAt: index('idx_domain_events_status_occurred_at').on(\n t.status,\n t.occurredAt,\n ),\n /** Event replay per aggregate (existing — promoted from comment to declaration in EVT-1). */\n idxDomainEventsAggregate: index('idx_domain_events_aggregate').on(\n t.aggregateId,\n t.aggregateType,\n ),\n /** Per-pool drain filter (EVT-1). Enables DrizzleEventBus to drain a single pool without scanning all events. */\n idxDomainEventsPoolStatusOccurredAt: index(\n 'idx_domain_events_pool_status_occurred_at',\n ).on(t.pool, t.status, t.occurredAt),\n /** Per-tier filter (AUDIT-1). Backs the observability viewer's tier toggle. */\n idxDomainEventsTierStatusOccurredAt: index(\n 'idx_domain_events_tier_status_occurred_at',\n ).on(t.tier, t.status, t.occurredAt),\n /**\n * Tier ↔ routing-fields invariant (AUDIT-1):\n * - `tier` is one of `'domain' | 'audit'`.\n * - `tier='audit'` ⇔ `pool IS NULL AND direction IS NULL`.\n * - `tier='domain'` ⇒ `pool` and `direction` are populated (the\n * DrizzleEventBus inserts always supply them; the bus stamps them\n * in AUDIT-3).\n */\n tierRoutingCheck: check(\n 'domain_events_tier_routing_check',\n sql`${t.tier} in ('domain','audit') AND ((${t.tier} = 'audit') = (${t.pool} is null and ${t.direction} is null))`,\n ),\n }),\n);\n\nexport type DomainEventRecord = InferSelectModel<typeof domainEvents>;\n","/**\n * NestJS injection tokens\n *\n * Used with @Inject() decorator in concrete repository constructors.\n */\n\n/**\n * Injection token for the Drizzle ORM database client.\n *\n * Usage in concrete repositories:\n * ```typescript\n * constructor(@Inject(DRIZZLE) db: DrizzleClient) { super(db); }\n * ```\n */\nexport const DRIZZLE = 'DRIZZLE' as const;\n\n/**\n * Injection token for the event bus (IEventBus).\n *\n * Optional — only resolved when EventsModule.forRoot() is registered.\n * BaseService uses this with @Optional() to emit lifecycle events\n * without requiring the events subsystem to be installed.\n *\n * Usage in services/use cases:\n * ```typescript\n * @Optional() @Inject(EVENT_BUS) eventBus?: IEventBus\n * ```\n */\nexport const EVENT_BUS = 'EVENT_BUS' as const;\n","/**\n * Injection token for the event bus.\n *\n * String constant (not Symbol) so it matches by value across import boundaries.\n * Matches the token in runtime/constants/tokens.ts — both are 'EVENT_BUS'.\n *\n * Usage in use cases:\n * ```typescript\n * constructor(@Inject(EVENT_BUS) private readonly eventBus: IEventBus) {}\n * ```\n */\nexport const EVENT_BUS = 'EVENT_BUS' as const;\n\n/**\n * Injection token for the read-side `IEventReadPort` over `domain_events`\n * (OBS-LIST-1).\n *\n * Bound by `EventsModule.forRoot` to the same backend instance as\n * `EVENT_BUS` for the `drizzle` and `memory` backends (both implement\n * `IEventReadPort`). The `redis` backend retains no history and therefore\n * does NOT provide this token — consumers composing it (e.g. the\n * observability combiner) inject it `@Optional()` and degrade to empty\n * results.\n *\n * String constant (not Symbol) so it matches by value across import\n * boundaries — same convention as `EVENT_BUS`.\n */\nexport const EVENT_READ_PORT = 'EVENT_READ_PORT' as const;\n\n/**\n * Injection token for the generated `TypedEventBus` facade.\n *\n * `TypedEventBus` lives in `runtime/subsystems/events/generated/bus.ts` and\n * wraps `IEventBus` with project-specific `AppDomainEvent`-typed `publish<T>()`\n * and `subscribe<T>()`. Use cases inject this token in preference to\n * `EVENT_BUS` when they want compile-time type safety on event shapes.\n *\n * String constant (not Symbol) so it matches by value across import\n * boundaries — same convention as `EVENT_BUS`.\n *\n * Provider registration lands in EVT-6 (EventsModule wiring); the token is\n * declared here so generated code can import it without depending on the\n * still-being-formalised module.\n */\nexport const TYPED_EVENT_BUS = 'TYPED_EVENT_BUS' as const;\n\n/**\n * Injection token for the resolved multi-tenancy flag.\n *\n * Provided by `EventsModule.forRoot(...)` as `options.multiTenant ?? false`.\n * Consumed by `TypedEventBus` to enforce the tenantId-is-required rule at\n * publish time.\n *\n * String constant (not Symbol) so it matches by value across import\n * boundaries — same convention as the other events tokens. (The jobs\n * subsystem uses Symbols for the analogous token; events chose strings\n * from the start and we keep the file internally consistent.)\n */\nexport const EVENTS_MULTI_TENANT = 'EVENTS_MULTI_TENANT' as const;\n\n/**\n * Injection token for the Redis connection URL used by RedisEventBus.\n * Provided automatically by EventsModule.forRoot({ backend: 'redis' }).\n */\nexport const REDIS_URL = Symbol('REDIS_URL');\n\n/**\n * Injection token for the resolved `EventsModuleOptions` object.\n *\n * Provided automatically by `EventsModule.forRoot(...)` /\n * `EventsModule.forRootAsync(...)`. Backends that need to observe module\n * configuration (e.g. `DrizzleEventBus` reading `opts.pools` for\n * pool-filtered drain) inject via this token.\n *\n * String-valued (not `Symbol`) so it matches by value across import\n * boundaries — same convention as `EVENT_BUS`.\n */\nexport const EVENTS_MODULE_OPTIONS = 'EVENTS_MODULE_OPTIONS' as const;\n","/**\n * Injection tokens for the bridge subsystem (ADR-023 Phase 2, BRIDGE-2).\n *\n * String constants (not Symbols) so they match by value across import\n * boundaries — same convention as `EVENT_BUS` / `EVENTS_MULTI_TENANT` in the\n * events subsystem (per EVT-6 §Implementation Notes). The jobs subsystem\n * uses Symbols for its analogous tokens; we keep the bridge file internally\n * consistent with the events convention because the bridge is conceptually\n * downstream of (and imports from) the events module.\n */\n\n/**\n * Token for the `IJobBridge` repo backend (memory in BRIDGE-3, Drizzle in\n * BRIDGE-4). Consumed by `BridgeDeliveryHandler` (BRIDGE-5), the outbox\n * drain (BRIDGE-4 modification), and `EventFlowService` (BRIDGE-7).\n */\nexport const BRIDGE_DELIVERY_REPO = 'BRIDGE_DELIVERY_REPO' as const;\n\n/**\n * Token for the `IEventFlow` facade implementation (BRIDGE-7). Use cases\n * inject this in preference to `EVENT_BUS` / `TYPED_EVENT_BUS` — calling\n * `eventFlow.publish(...)` / `eventFlow.publishAndStart(...)` is the\n * sanctioned authoring surface (ADR-023 §Decision 7).\n */\nexport const EVENT_FLOW = 'EVENT_FLOW' as const;\n\n/**\n * Token for the resolved multi-tenancy flag, provided by\n * `BridgeModule.forRoot({ multiTenant })` in BRIDGE-8. Consumed by\n * `EventFlowService.publishAndStart` (entry), `BridgeDeliveryHandler.handle`\n * (entry), and `DrizzleBridgeDeliveryRepo.insertDelivery` (pre-write) — the\n * three enforcement sites called out in ADR-023 §Multi-tenancy.\n */\nexport const BRIDGE_MULTI_TENANT = 'BRIDGE_MULTI_TENANT' as const;\n\n/**\n * Token for the resolved `BridgeModuleOptions` object. Provided by\n * `BridgeModule.forRoot(...)` / `forRootAsync(...)` in BRIDGE-8.\n * Mirrors `EVENTS_MODULE_OPTIONS` and `JOBS_DOMAIN_OPTIONS` shape — backends\n * inject this when they need to observe additional module configuration\n * (e.g. pool overrides) without each adding a dedicated token.\n */\nexport const BRIDGE_MODULE_OPTIONS = 'BRIDGE_MODULE_OPTIONS' as const;\n\n/**\n * Token for the codegen-emitted `bridgeRegistry` — the\n * `Record<EventTypeName, BridgeTriggerEntry[]>` map that drives\n * outbox-drain trigger lookup (BRIDGE-4) and `EventFlowService` Case B\n * dedup (BRIDGE-7). Provider registration lands in BRIDGE-8; the token is\n * declared here so generated code (BRIDGE-6) can import it without\n * depending on the still-being-formalised module.\n */\nexport const BRIDGE_REGISTRY = 'BRIDGE_REGISTRY' as const;\n\n\n/**\n * Token for the `IBridgeOutboxDrainHook` implementation (BRIDGE-4).\n * Injected `@Optional()` into `DrizzleEventBus` — when the bridge\n * subsystem is not installed the token is undefined and the events\n * outbox drain skips the bridge block entirely (preserves EVT-4\n * baseline behaviour).\n *\n * Resolution order: `BridgeModule.forRoot()` provides this token in\n * BRIDGE-8 alongside the rest of the bridge subsystem. `EventsModule`\n * itself never provides it; the events subsystem stays unaware of the\n * bridge unless the consumer wires it.\n */\nexport const BRIDGE_OUTBOX_DRAIN_HOOK = 'BRIDGE_OUTBOX_DRAIN_HOOK' as const;\n"],"mappings":"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","names":["sql","sql"]}
@@ -139,13 +139,12 @@ var BRIDGE_OUTBOX_DRAIN_HOOK = "BRIDGE_OUTBOX_DRAIN_HOOK";
139
139
  // runtime/subsystems/events/event-bus.drizzle-backend.ts
140
140
  var POLL_INTERVAL_MS = 1e3;
141
141
  var POLL_BATCH_SIZE = 50;
142
- function toInsertValues(event) {
142
+ function toInsertValues(event, multiTenant) {
143
143
  const metadata = event.metadata ?? void 0;
144
144
  const pool = metadata?.["pool"] ?? null;
145
145
  const direction = metadata?.["direction"] ?? null;
146
- const tenantId = metadata?.["tenantId"] ?? null;
147
146
  const tier = metadata?.["tier"] ?? "domain";
148
- return {
147
+ const base = {
149
148
  id: event.id,
150
149
  type: event.type,
151
150
  aggregateId: event.aggregateId,
@@ -157,9 +156,11 @@ function toInsertValues(event) {
157
156
  metadata: event.metadata,
158
157
  pool,
159
158
  direction,
160
- tier,
161
- tenantId
159
+ tier
162
160
  };
161
+ if (!multiTenant) return base;
162
+ const tenantId = metadata?.["tenantId"] ?? null;
163
+ return { ...base, tenantId };
163
164
  }
164
165
  function toEventSummary(r) {
165
166
  const metadata = r.metadata ?? void 0;
@@ -174,7 +175,11 @@ function toEventSummary(r) {
174
175
  direction: r.direction,
175
176
  tier: r.tier,
176
177
  rootRunId: typeof rootRunId === "string" ? rootRunId : null,
177
- tenantId: r.tenantId,
178
+ // EVT-8: `tenant_id` is a scaffold-time conditional column. Read it
179
+ // structurally so this projection typechecks against both the
180
+ // multi-tenant schema (column present) and the single-tenant schema
181
+ // (column absent → undefined → null).
182
+ tenantId: r.tenantId ?? null,
178
183
  occurredAt: r.occurredAt instanceof Date ? r.occurredAt : new Date(r.occurredAt),
179
184
  processedAt: r.processedAt == null ? null : r.processedAt instanceof Date ? r.processedAt : new Date(r.processedAt)
180
185
  };
@@ -211,12 +216,14 @@ var DrizzleEventBus = class {
211
216
  // ============================================================================
212
217
  async publish(event, tx) {
213
218
  const client = tx ?? this.db;
214
- await client.insert(domainEvents).values(toInsertValues(event));
219
+ const multiTenant = this.opts.multiTenant ?? false;
220
+ await client.insert(domainEvents).values(toInsertValues(event, multiTenant));
215
221
  }
216
222
  async publishMany(events, tx) {
217
223
  if (events.length === 0) return;
218
224
  const client = tx ?? this.db;
219
- await client.insert(domainEvents).values(events.map(toInsertValues));
225
+ const multiTenant = this.opts.multiTenant ?? false;
226
+ await client.insert(domainEvents).values(events.map((e) => toInsertValues(e, multiTenant)));
220
227
  }
221
228
  async findById(eventId) {
222
229
  const rows = await this.db.select().from(domainEvents).where(eq(domainEvents.id, eventId)).limit(1);
@@ -258,9 +265,10 @@ var DrizzleEventBus = class {
258
265
  sql2`${domainEvents.metadata}->>'rootRunId' = ${query.rootRunId}`
259
266
  );
260
267
  }
261
- if (query.tenantId !== void 0) {
268
+ if (this.opts.multiTenant && query.tenantId !== void 0) {
269
+ const tenantIdColumn = domainEvents.tenantId;
262
270
  conditions.push(
263
- query.tenantId === null ? sql2`${domainEvents.tenantId} is null` : eq(domainEvents.tenantId, query.tenantId)
271
+ query.tenantId === null ? sql2`${tenantIdColumn} is null` : eq(tenantIdColumn, query.tenantId)
264
272
  );
265
273
  }
266
274
  if (query.cursor) {
@@ -591,234 +599,8 @@ MemoryEventBus = __decorateClass([
591
599
  __decorateParam(0, Inject2(EVENTS_MODULE_OPTIONS))
592
600
  ], MemoryEventBus);
593
601
 
594
- // runtime/subsystems/events/event-bus.redis-backend.ts
595
- import { Injectable as Injectable3, Inject as Inject3, Logger as Logger3 } from "@nestjs/common";
596
- var CHANNEL_PREFIX = "events:";
597
- async function createRedisClient(url) {
598
- let Redis;
599
- try {
600
- const mod = await import("ioredis");
601
- Redis = mod.default ?? mod;
602
- } catch {
603
- throw new Error(
604
- 'RedisEventBus requires the "ioredis" package. Install it with: npm install ioredis'
605
- );
606
- }
607
- return new Redis(url);
608
- }
609
- var RedisEventBus = class {
610
- constructor(redisUrl) {
611
- this.redisUrl = redisUrl;
612
- }
613
- redisUrl;
614
- logger = new Logger3(RedisEventBus.name);
615
- publisher = null;
616
- subscriber = null;
617
- connected = false;
618
- /**
619
- * In-process subscriber registry. Handlers registered here are called when
620
- * a message arrives on the subscriber client — keeping fan-out within the
621
- * same process without an extra round-trip through Redis.
622
- */
623
- handlers = /* @__PURE__ */ new Map();
624
- /**
625
- * Track which event types have active Redis subscriptions.
626
- * Used to avoid subscribing multiple times to the same type channel.
627
- */
628
- subscribedTypes = /* @__PURE__ */ new Set();
629
- // ============================================================================
630
- // Lifecycle
631
- // ============================================================================
632
- async onModuleInit() {
633
- this.publisher = await createRedisClient(this.redisUrl);
634
- this.subscriber = await createRedisClient(this.redisUrl);
635
- this.publisher.on(
636
- "error",
637
- (err) => this.logger.error(`Redis publisher error: ${err.message}`, err.stack)
638
- );
639
- this.subscriber.on(
640
- "error",
641
- (err) => this.logger.error(`Redis subscriber error: ${err.message}`, err.stack)
642
- );
643
- this.subscriber.on("message", (channel, message) => {
644
- void this.handleMessage(channel, message);
645
- });
646
- this.connected = true;
647
- this.logger.log(`RedisEventBus connected to ${this.redisUrl}`);
648
- }
649
- async onModuleDestroy() {
650
- this.connected = false;
651
- if (this.subscriber) {
652
- await this.subscriber.unsubscribe();
653
- this.subscriber.disconnect();
654
- this.subscriber = null;
655
- }
656
- if (this.publisher) {
657
- this.publisher.disconnect();
658
- this.publisher = null;
659
- }
660
- this.subscribedTypes.clear();
661
- this.logger.log("RedisEventBus disconnected");
662
- }
663
- // ============================================================================
664
- // IEventBus
665
- // ============================================================================
666
- /**
667
- * Publish a single event.
668
- *
669
- * `tx` is accepted but ignored — see module-level JSDoc for details.
670
- */
671
- async publish(event, tx) {
672
- void tx;
673
- this.assertConnected();
674
- const payload = this.serialize(event);
675
- const channel = `${CHANNEL_PREFIX}${event.type}`;
676
- await this.publisher.publish(channel, payload);
677
- }
678
- /**
679
- * Publish multiple events using a pipeline so all PUBLISH commands are sent
680
- * in a single round-trip.
681
- *
682
- * `tx` is accepted but ignored — see module-level JSDoc for details.
683
- */
684
- async publishMany(events, tx) {
685
- void tx;
686
- if (events.length === 0) return;
687
- this.assertConnected();
688
- const pipeline = this.publisher.pipeline();
689
- for (const event of events) {
690
- const payload = this.serialize(event);
691
- const channel = `${CHANNEL_PREFIX}${event.type}`;
692
- pipeline.publish(channel, payload);
693
- }
694
- await pipeline.exec();
695
- }
696
- /**
697
- * Register a handler for a specific event type.
698
- * Returns an unsubscribe function — call it to remove the handler.
699
- *
700
- * On first handler for a type, subscribes to the per-type Redis channel.
701
- * On removal of the last handler for a type, unsubscribes from the channel.
702
- */
703
- /**
704
- * Lookup by id is unsupported on the Redis Pub/Sub backend — Pub/Sub
705
- * does not retain history. Always returns `null`. Logs a warning the
706
- * first time it's called so a misconfiguration surfaces visibly. Using
707
- * the bridge with the Redis backend is unsupported (the bridge requires
708
- * a durable event store).
709
- */
710
- warnedFindById = false;
711
- async findById(_eventId) {
712
- if (!this.warnedFindById) {
713
- this.warnedFindById = true;
714
- this.logger.warn(
715
- "RedisEventBus.findById is unsupported (Pub/Sub has no history). The bridge subsystem requires a durable event store; switch to DrizzleEventBus if you need bridge fanout."
716
- );
717
- }
718
- return null;
719
- }
720
- subscribe(eventType, handler) {
721
- if (!this.handlers.has(eventType)) {
722
- this.handlers.set(eventType, /* @__PURE__ */ new Set());
723
- void this.subscribeToType(eventType);
724
- }
725
- const set = this.handlers.get(eventType);
726
- const h = handler;
727
- set.add(h);
728
- return () => {
729
- set.delete(h);
730
- if (set.size === 0) {
731
- this.handlers.delete(eventType);
732
- void this.unsubscribeFromType(eventType);
733
- }
734
- };
735
- }
736
- // ============================================================================
737
- // Internal helpers
738
- // ============================================================================
739
- assertConnected() {
740
- if (!this.connected || !this.publisher) {
741
- throw new Error(
742
- "RedisEventBus is not connected. Ensure the module has been initialised before publishing."
743
- );
744
- }
745
- }
746
- serialize(event) {
747
- return JSON.stringify({
748
- ...event,
749
- occurredAt: event.occurredAt.toISOString()
750
- });
751
- }
752
- deserialize(raw) {
753
- const parsed = JSON.parse(raw);
754
- return {
755
- ...parsed,
756
- occurredAt: new Date(parsed.occurredAt)
757
- };
758
- }
759
- async handleMessage(channel, message) {
760
- let event;
761
- try {
762
- event = this.deserialize(message);
763
- } catch (err) {
764
- this.logger.warn(`Failed to deserialize event on channel "${channel}": ${err}`);
765
- return;
766
- }
767
- await this.dispatch(event);
768
- }
769
- async dispatch(event) {
770
- const set = this.handlers.get(event.type);
771
- if (!set) return;
772
- for (const handler of set) {
773
- try {
774
- await handler(event);
775
- } catch (err) {
776
- this.logger.error(
777
- `Handler error for event type "${event.type}" (id: ${event.id}): ${err}`
778
- );
779
- }
780
- }
781
- }
782
- /**
783
- * Subscribe to a per-type Redis channel.
784
- * Called lazily when the first handler is registered for a type.
785
- */
786
- async subscribeToType(eventType) {
787
- if (this.subscribedTypes.has(eventType)) {
788
- return;
789
- }
790
- const channel = `${CHANNEL_PREFIX}${eventType}`;
791
- try {
792
- await this.subscriber.subscribe(channel);
793
- this.subscribedTypes.add(eventType);
794
- } catch (err) {
795
- this.logger.error(`Failed to subscribe to channel "${channel}": ${err}`);
796
- }
797
- }
798
- /**
799
- * Unsubscribe from a per-type Redis channel.
800
- * Called when the last handler for a type is removed.
801
- */
802
- async unsubscribeFromType(eventType) {
803
- if (!this.subscribedTypes.has(eventType)) {
804
- return;
805
- }
806
- const channel = `${CHANNEL_PREFIX}${eventType}`;
807
- try {
808
- await this.subscriber.unsubscribe(channel);
809
- this.subscribedTypes.delete(eventType);
810
- } catch (err) {
811
- this.logger.error(`Failed to unsubscribe from channel "${channel}": ${err}`);
812
- }
813
- }
814
- };
815
- RedisEventBus = __decorateClass([
816
- Injectable3(),
817
- __decorateParam(0, Inject3(REDIS_URL))
818
- ], RedisEventBus);
819
-
820
602
  // runtime/subsystems/events/generated/bus.ts
821
- import { Injectable as Injectable4, Inject as Inject4 } from "@nestjs/common";
603
+ import { Injectable as Injectable3, Inject as Inject3 } from "@nestjs/common";
822
604
  import { randomUUID } from "crypto";
823
605
 
824
606
  // runtime/subsystems/events/events-errors.ts
@@ -1032,12 +814,17 @@ var TypedEventBus = class {
1032
814
  }
1033
815
  };
1034
816
  TypedEventBus = __decorateClass([
1035
- Injectable4(),
1036
- __decorateParam(0, Inject4(EVENT_BUS)),
1037
- __decorateParam(1, Inject4(EVENTS_MULTI_TENANT))
817
+ Injectable3(),
818
+ __decorateParam(0, Inject3(EVENT_BUS)),
819
+ __decorateParam(1, Inject3(EVENTS_MULTI_TENANT))
1038
820
  ], TypedEventBus);
1039
821
 
1040
822
  // runtime/subsystems/events/events.module.ts
823
+ async function loadRedisEventBus() {
824
+ const specifier = "./event-bus.redis-backend";
825
+ const mod = await import(specifier);
826
+ return mod.RedisEventBus;
827
+ }
1041
828
  function buildTypedBusProviders(multiTenant) {
1042
829
  return [
1043
830
  TypedEventBus,
@@ -1045,7 +832,7 @@ function buildTypedBusProviders(multiTenant) {
1045
832
  { provide: EVENTS_MULTI_TENANT, useValue: multiTenant }
1046
833
  ];
1047
834
  }
1048
- function buildEventBusAsync(options, db, redisUrl) {
835
+ async function buildEventBusAsync(options, db, redisUrl) {
1049
836
  if (options.backend === "drizzle") {
1050
837
  if (!db) {
1051
838
  throw new Error(
@@ -1055,6 +842,7 @@ function buildEventBusAsync(options, db, redisUrl) {
1055
842
  return new DrizzleEventBus(db, options);
1056
843
  }
1057
844
  if (options.backend === "redis") {
845
+ const RedisEventBus = await loadRedisEventBus();
1058
846
  return new RedisEventBus(redisUrl);
1059
847
  }
1060
848
  return new MemoryEventBus(options);
@@ -1116,9 +904,20 @@ var EventsModule = class {
1116
904
  providers: [
1117
905
  { provide: EVENTS_MODULE_OPTIONS, useValue: options },
1118
906
  { provide: REDIS_URL, useValue: resolvedUrl },
1119
- { provide: EVENT_BUS, useClass: RedisEventBus },
1120
- // Register concrete class so NestJS can resolve lifecycle hooks
1121
- RedisEventBus,
907
+ {
908
+ // #6: useFactory + dynamic import so the consumer's tsc never
909
+ // needs to resolve `event-bus.redis-backend.ts` for drizzle/
910
+ // memory installs (the file is filtered out by
911
+ // `backendFileFilter`). Nest awaits async factories + manages
912
+ // lifecycle on the returned instance, so we drop the old bare
913
+ // `RedisEventBus` provider entry.
914
+ provide: EVENT_BUS,
915
+ useFactory: async (url) => {
916
+ const RedisEventBus = await loadRedisEventBus();
917
+ return new RedisEventBus(url);
918
+ },
919
+ inject: [REDIS_URL]
920
+ },
1122
921
  ...buildTypedBusProviders(multiTenant)
1123
922
  ],
1124
923
  exports: [EVENT_BUS, TYPED_EVENT_BUS, EVENTS_MULTI_TENANT]