@starci/hfs 4.7.32 → 4.7.34

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 (473) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +13 -6
  3. package/emit/contracts.mjs +4 -3
  4. package/emit/form-bounds-reader.ts.partial +58 -0
  5. package/emit/form-bounds.mjs +155 -0
  6. package/emit/graphql-input-bounds.mjs +97 -0
  7. package/emit/input-bounds.mjs +44 -0
  8. package/emit/operation-schemas.mjs +137 -0
  9. package/emit/operations-worker.mjs +2 -2
  10. package/emit/operations.mjs +52 -16
  11. package/emit/schema-worker.mjs +17 -4
  12. package/emit/type-schema.mjs +17 -5
  13. package/package.json +9 -2
  14. package/release/gate.mjs +18 -23
  15. package/release/index.mjs +1 -1
  16. package/release/push.mjs +4 -5
  17. package/runtime/engine/admission.mjs +331 -0
  18. package/runtime/engine/canonical-json.mjs +14 -0
  19. package/runtime/engine/db/blob.mjs +380 -0
  20. package/runtime/engine/db/event-compact.mjs +73 -0
  21. package/runtime/engine/db/ledger-open.mjs +38 -0
  22. package/runtime/engine/db/ledger-paths.mjs +87 -0
  23. package/runtime/engine/db/ledger.mjs +1068 -0
  24. package/runtime/engine/db/machine-connection.mjs +150 -0
  25. package/runtime/engine/db/machine-open.mjs +60 -0
  26. package/runtime/engine/db/machine-schema.mjs +105 -0
  27. package/runtime/engine/db/machine.mjs +1317 -0
  28. package/runtime/engine/db/provider-reservations.mjs +127 -0
  29. package/runtime/engine/db/provision-authority.mjs +76 -0
  30. package/runtime/engine/db/provision-lock.mjs +29 -0
  31. package/runtime/engine/db/provision-operations.mjs +146 -0
  32. package/runtime/engine/db/ref-value.mjs +67 -0
  33. package/runtime/engine/db/schema/machine.sql +920 -0
  34. package/runtime/engine/db/schema/runtime.sql +1113 -0
  35. package/runtime/engine/refuse.mjs +11 -0
  36. package/runtime/knowledge/hfs/canon-pins.yaml +65 -9
  37. package/runtime/knowledge/hfs/design-truths.yaml +149 -0
  38. package/runtime/knowledge/hfs/fe-stack.yaml +23 -5
  39. package/runtime/knowledge/hfs/rules.yaml +269 -48
  40. package/runtime/knowledge/hfs/slots.yaml +113 -53
  41. package/runtime/knowledge/patterns/be/api.yaml +46 -20
  42. package/runtime/knowledge/patterns/be/event-bus.yaml +19 -12
  43. package/runtime/knowledge/patterns/be/idempotency.yaml +265 -0
  44. package/runtime/knowledge/patterns/be/jobs.yaml +17 -12
  45. package/runtime/knowledge/patterns/be/projections.yaml +2 -2
  46. package/runtime/knowledge/patterns/be/queues.yaml +57 -22
  47. package/runtime/knowledge/patterns/be/reactors.yaml +1 -1
  48. package/runtime/knowledge/patterns/be/realtime.yaml +326 -27
  49. package/runtime/knowledge/patterns/be/saga.yaml +2 -2
  50. package/runtime/knowledge/sonar-gate.yaml +1 -1
  51. package/runtime/modules/kernel/command-policy.yaml +249 -0
  52. package/runtime/modules/kernel/failure-codes.yaml +211 -52
  53. package/runtime/modules/kernel/hook-input.yaml +6 -0
  54. package/runtime/modules/kernel/protected-zone.yaml +81 -0
  55. package/runtime/modules/kernel/removed-vocabulary.yaml +62 -0
  56. package/runtime/modules/kernel/roles.yaml +405 -0
  57. package/runtime/modules/models/registry.yaml +3 -1
  58. package/runtime/modules/models/runtimes.yaml +2 -0
  59. package/runtime/modules/schemas/application-stacks.schema.yaml +10 -2
  60. package/runtime/packages/cli/src/catalog.generated.mjs +46 -59
  61. package/runtime/packages/grammar/scripts/build-stamp.mjs +98 -0
  62. package/runtime/scripts/agent/codex-app-server.mjs +79 -0
  63. package/runtime/scripts/agent/codex-mirror-source.mjs +34 -0
  64. package/runtime/scripts/agent/host-agents.mjs +80 -0
  65. package/runtime/scripts/agent/launch-trust-policy.mjs +53 -0
  66. package/runtime/scripts/agent/trust-launch.mjs +191 -0
  67. package/runtime/scripts/agent/trust.mjs +523 -0
  68. package/runtime/scripts/api/fs/rename-over.mjs +21 -0
  69. package/runtime/scripts/api/fs/temp-path.mjs +8 -0
  70. package/runtime/scripts/api/fs/write-json-file.mjs +10 -0
  71. package/runtime/scripts/api/git/diff-names.mjs +8 -0
  72. package/runtime/scripts/api/git/head-time.mjs +10 -0
  73. package/runtime/scripts/api/git/index-lock.mjs +78 -0
  74. package/runtime/scripts/api/git/is-ancestor.mjs +5 -0
  75. package/runtime/scripts/api/git/log.mjs +5 -0
  76. package/runtime/scripts/api/git/merge-base-query.mjs +5 -0
  77. package/runtime/scripts/api/git/merge-tree.mjs +5 -0
  78. package/runtime/scripts/api/git/rev-list.mjs +5 -0
  79. package/runtime/scripts/api/git/rev-parse.mjs +13 -0
  80. package/runtime/scripts/api/git/status-query.mjs +5 -0
  81. package/runtime/scripts/api/git/worktree-list-query.mjs +5 -0
  82. package/runtime/scripts/api/node/spawn-node.mjs +6 -0
  83. package/runtime/scripts/api/npm/dist-tag.mjs +8 -0
  84. package/runtime/scripts/api/npm/lib.mjs +32 -0
  85. package/runtime/scripts/api/npm/pack-dry-run.mjs +9 -0
  86. package/runtime/scripts/api/npm/run-npm.mjs +6 -0
  87. package/runtime/scripts/api/npm/view.mjs +30 -0
  88. package/runtime/scripts/api/npm/whoami.mjs +8 -0
  89. package/runtime/scripts/api/process/hide-child-windows.mjs +31 -0
  90. package/runtime/scripts/api/process/kill-tree.mjs +22 -0
  91. package/runtime/scripts/api/process/lib.mjs +125 -0
  92. package/runtime/scripts/api/process/probe-guard-command.mjs +74 -0
  93. package/runtime/scripts/api/process/process-list.mjs +28 -0
  94. package/runtime/scripts/api/process/system-tool.mjs +37 -0
  95. package/runtime/scripts/gates/canon-digest.mjs +81 -0
  96. package/runtime/scripts/gates/canon-pins.mjs +16 -0
  97. package/runtime/scripts/gates/commit-proof.mjs +53 -0
  98. package/runtime/scripts/gates/gate-input.mjs +98 -0
  99. package/runtime/scripts/gates/gate.mjs +669 -0
  100. package/runtime/scripts/gates/grammar-dist.mjs +206 -0
  101. package/runtime/scripts/gates/runtime-package-selection.mjs +27 -0
  102. package/runtime/scripts/gates/test-world-run.mjs +254 -0
  103. package/runtime/scripts/gates/type-impact.mjs +277 -0
  104. package/runtime/scripts/gates/type-program.mjs +113 -0
  105. package/runtime/scripts/guards/assigned-command.mjs +12 -0
  106. package/runtime/scripts/guards/call-rights.mjs +47 -0
  107. package/runtime/scripts/guards/command-guard.mjs +468 -0
  108. package/runtime/scripts/guards/command-policy.mjs +342 -0
  109. package/runtime/scripts/guards/critic-reach.mjs +82 -0
  110. package/runtime/scripts/guards/deps-guard.mjs +150 -0
  111. package/runtime/scripts/guards/env-dump-verdict.mjs +89 -0
  112. package/runtime/scripts/guards/git-pathspec.mjs +225 -0
  113. package/runtime/scripts/guards/git-policy.mjs +355 -0
  114. package/runtime/scripts/guards/guards-root.mjs +27 -0
  115. package/runtime/scripts/guards/hook-install.mjs +343 -0
  116. package/runtime/scripts/guards/hook-io.mjs +76 -0
  117. package/runtime/scripts/guards/hook-verdict.mjs +12 -0
  118. package/runtime/scripts/guards/install-verdict.mjs +41 -0
  119. package/runtime/scripts/guards/kernel-seat.mjs +63 -0
  120. package/runtime/scripts/guards/launch-verdict.mjs +74 -0
  121. package/runtime/scripts/guards/op-starci.mjs +16 -0
  122. package/runtime/scripts/guards/op-write-scope.mjs +27 -0
  123. package/runtime/scripts/guards/orca-self-lifecycle.mjs +39 -0
  124. package/runtime/scripts/guards/process-kill-verdict.mjs +106 -0
  125. package/runtime/scripts/guards/protected-zone.mjs +56 -0
  126. package/runtime/scripts/guards/refusals.mjs +27 -0
  127. package/runtime/scripts/guards/release-definition.mjs +185 -0
  128. package/runtime/scripts/guards/release-push-gate.mjs +72 -0
  129. package/runtime/scripts/guards/release-record.mjs +58 -0
  130. package/runtime/scripts/guards/release-suite-mode.mjs +15 -0
  131. package/runtime/scripts/guards/rights.mjs +268 -0
  132. package/runtime/scripts/guards/seat-tools.mjs +47 -0
  133. package/runtime/scripts/guards/shell-commands.mjs +328 -0
  134. package/runtime/scripts/guards/supervisor-seat.mjs +80 -0
  135. package/runtime/scripts/guards/verdict-process.mjs +81 -0
  136. package/runtime/scripts/hfs/architecture/dead-exports.mjs +10 -10
  137. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +1 -1
  138. package/runtime/scripts/hfs/architecture/operation-contract-roots.mjs +53 -0
  139. package/runtime/scripts/hfs/architecture/reachability.mjs +1 -1
  140. package/runtime/scripts/hfs/architecture/sql-owner.mjs +17 -6
  141. package/runtime/scripts/hfs/architecture/sql-token-forms.mjs +105 -0
  142. package/runtime/scripts/hfs/architecture/sql-tokens.mjs +38 -52
  143. package/runtime/scripts/hfs/architecture/supabase-keys.mjs +261 -0
  144. package/runtime/scripts/hfs/architecture/transport-owner.mjs +49 -3
  145. package/runtime/scripts/hfs/architecture/typescript.mjs +1 -1
  146. package/runtime/scripts/hfs/check.mjs +6 -4
  147. package/runtime/scripts/hfs/coverage-scope.mjs +28 -1
  148. package/runtime/scripts/hfs/db-restamp-verb.mjs +56 -16
  149. package/runtime/scripts/hfs/declaration-shape.mjs +4 -4
  150. package/runtime/scripts/hfs/declaration-slots.mjs +14 -3
  151. package/runtime/scripts/hfs/manifest-shape.mjs +10 -6
  152. package/runtime/scripts/hfs/rule-params-shape.mjs +18 -6
  153. package/runtime/scripts/hfs/rules/a15-findings.mjs +4 -1
  154. package/runtime/scripts/hfs/rules/bypass-markers.mjs +22 -7
  155. package/runtime/scripts/hfs/rules/database-migrations.mjs +69 -14
  156. package/runtime/scripts/hfs/rules/database-queue-sources.mjs +9 -7
  157. package/runtime/scripts/hfs/rules/database-queues.mjs +68 -18
  158. package/runtime/scripts/hfs/rules/edition.mjs +56 -12
  159. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +1 -10
  160. package/runtime/scripts/hfs/rules/fe-stack.mjs +12 -7
  161. package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +1 -1
  162. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +75 -9
  163. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +1 -1
  164. package/runtime/scripts/hfs/rules/stacks-swarm.mjs +15 -4
  165. package/runtime/scripts/hfs/rules/text-integrity.mjs +73 -0
  166. package/runtime/scripts/hfs/runtime-rules/hook-shape.mjs +74 -0
  167. package/runtime/scripts/hfs/runtime-rules/release-notes.mjs +44 -0
  168. package/runtime/scripts/hfs/sealed-members.mjs +3 -3
  169. package/runtime/scripts/hfs/slot-side-problems.mjs +3 -3
  170. package/runtime/scripts/hfs/slots.mjs +2 -1
  171. package/runtime/scripts/hfs/stack-deploy-config.mjs +22 -5
  172. package/runtime/scripts/hfs/stacks-gate.mjs +28 -7
  173. package/runtime/scripts/hfs/view.mjs +2 -1
  174. package/runtime/scripts/lib/app-data.mjs +11 -0
  175. package/runtime/scripts/lib/bypass-markers.mjs +272 -75
  176. package/runtime/scripts/lib/check-format.mjs +7 -0
  177. package/runtime/scripts/lib/cli-arg.mjs +120 -0
  178. package/runtime/scripts/lib/css-rules.mjs +220 -0
  179. package/runtime/scripts/lib/dom-tree.mjs +4 -0
  180. package/runtime/scripts/lib/escape.mjs +16 -0
  181. package/runtime/scripts/lib/event-items.mjs +22 -0
  182. package/runtime/scripts/lib/failure-code-template-errors.mjs +161 -0
  183. package/runtime/scripts/lib/git-dir.mjs +120 -0
  184. package/runtime/scripts/lib/grammar-vendor-parity.mjs +377 -0
  185. package/runtime/scripts/lib/guard-command.mjs +24 -0
  186. package/runtime/scripts/lib/hook-limits.mjs +5 -0
  187. package/runtime/scripts/lib/interrupt-cleanup.mjs +38 -0
  188. package/runtime/scripts/lib/js-lex.mjs +200 -0
  189. package/runtime/scripts/lib/json-formats.mjs +0 -1
  190. package/runtime/scripts/lib/language.mjs +94 -1
  191. package/runtime/scripts/lib/loosening-budgets.mjs +44 -0
  192. package/runtime/scripts/lib/loosening-config.mjs +361 -0
  193. package/runtime/scripts/lib/loosening-state.mjs +223 -0
  194. package/runtime/scripts/lib/menu-parts.mjs +11 -0
  195. package/runtime/scripts/lib/normalize.mjs +25 -0
  196. package/runtime/scripts/lib/op-gate.mjs +15 -0
  197. package/runtime/scripts/lib/redact.mjs +208 -0
  198. package/runtime/scripts/lib/runtime-location.mjs +21 -0
  199. package/runtime/scripts/lib/runtime-package-inventory.mjs +21 -0
  200. package/runtime/scripts/lib/runtime-package-manifests.mjs +84 -0
  201. package/runtime/scripts/lib/runtime-publication-tags.mjs +99 -0
  202. package/runtime/scripts/lib/seat-env.mjs +43 -0
  203. package/runtime/scripts/lib/secret-patterns.mjs +58 -9
  204. package/runtime/scripts/lib/shell-source.mjs +4 -0
  205. package/runtime/scripts/lib/source-phrases.mjs +34 -0
  206. package/runtime/scripts/lib/sqlite.mjs +21 -0
  207. package/runtime/scripts/lib/swarm-stack.mjs +2 -1
  208. package/runtime/scripts/lib/tar-files.mjs +39 -0
  209. package/runtime/scripts/lib/ts-analysis.mjs +10 -0
  210. package/runtime/scripts/lib/worktree-exclude.mjs +35 -0
  211. package/runtime/scripts/lib/worktree-record.mjs +14 -0
  212. package/runtime/scripts/machine/artifact-hold.mjs +52 -0
  213. package/runtime/scripts/machine/ask-receipts.mjs +113 -0
  214. package/runtime/scripts/machine/evidence-store.mjs +223 -0
  215. package/runtime/scripts/machine/home.mjs +216 -0
  216. package/runtime/scripts/machine/host-lock.mjs +184 -0
  217. package/runtime/scripts/machine/lock-recovery.mjs +55 -0
  218. package/runtime/scripts/machine/roles-contract.mjs +90 -0
  219. package/runtime/scripts/machine/runtime-change.mjs +20 -0
  220. package/runtime/scripts/machine/workflow-tree.mjs +122 -0
  221. package/runtime/scripts/reconciler/checklist-items.mjs +7 -0
  222. package/runtime/scripts/reconciler/guard-row.mjs +25 -0
  223. package/runtime/scripts/work/asset-slot.mjs +284 -0
  224. package/runtime/scripts/work/brand/brand-colour.mjs +125 -0
  225. package/runtime/scripts/work/brand/brand-tokens.mjs +167 -0
  226. package/runtime/scripts/work/brand/brand-value-source.mjs +106 -0
  227. package/runtime/scripts/work/brand/brand.mjs +892 -0
  228. package/runtime/scripts/work/draw/draw-dna-anatomy.mjs +60 -0
  229. package/runtime/scripts/work/draw/draw-dna-findings.mjs +256 -0
  230. package/runtime/scripts/work/draw/draw-dna-selector.mjs +38 -0
  231. package/runtime/scripts/work/draw/draw-dna.mjs +483 -0
  232. package/runtime/scripts/work/draw/draw-layer.mjs +305 -0
  233. package/runtime/scripts/work/draw/draw-rationale-page.mjs +237 -0
  234. package/runtime/scripts/work/draw/draw-rationale-ref.mjs +14 -0
  235. package/runtime/scripts/work/draw/draw-rationale.mjs +353 -0
  236. package/runtime/scripts/work/draw/draw-source.mjs +412 -0
  237. package/runtime/scripts/work/draw/draw-taste.mjs +278 -0
  238. package/runtime/scripts/work/draw/draw-typecheck.mjs +72 -0
  239. package/runtime/scripts/work/draw/rationale-findings.mjs +205 -0
  240. package/runtime/scripts/work/draw-grammar.mjs +175 -0
  241. package/runtime/scripts/work/draw-render-page.mjs +188 -0
  242. package/runtime/scripts/work/draw-render.mjs +626 -0
  243. package/runtime/scripts/work/grammar-proposal.mjs +279 -0
  244. package/runtime/scripts/work/png.mjs +237 -0
  245. package/runtime/scripts/work/render-tools.mjs +64 -0
  246. package/runtime/scripts/work/work-io.mjs +119 -0
  247. package/scaffold/add-inbox-lite.mjs +2 -2
  248. package/scaffold/add-plan.mjs +40 -13
  249. package/scaffold/add-queue-lite.mjs +372 -0
  250. package/scaffold/add.mjs +5 -2
  251. package/scaffold/app.mjs +1 -2
  252. package/scaffold/edition-gate.mjs +11 -7
  253. package/scaffold/lite-exports.mjs +10 -0
  254. package/scaffold/pattern-file-slots.mjs +33 -0
  255. package/scaffold/test-world.mjs +1 -1
  256. package/scaffold/yaml-block-edit.mjs +117 -0
  257. package/src/main.mjs +5 -4
  258. package/stack/deploy.mjs +7 -3
  259. package/stack/post-deploy.mjs +48 -0
  260. package/sync/index.mjs +10 -0
  261. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +1 -1
  262. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +1 -1
  263. package/templates/app/codegen-graphql.mjs.partial +59 -12
  264. package/templates/app/hooks/husky/pre-commit +2 -2
  265. package/templates/app/hooks/husky/pre-push +10 -3
  266. package/templates/app/package-scripts/redis-dev.json +3 -0
  267. package/templates/app/package-scripts-lite/package.json +1 -0
  268. package/templates/app/skeleton/.starcistacks/application-stacks.yaml +36 -8
  269. package/templates/app/skeleton/.starcistacks/dev/README.md +1 -1
  270. package/templates/app/skeleton/.starcistacks/production/README.md +10 -9
  271. package/templates/app/skeleton/.starcistacks/production/infra/swarm/postgres-init.sh +21 -0
  272. package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +1 -1
  273. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +1 -0
  274. package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +47 -5
  275. package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +5 -3
  276. package/templates/app/skeleton/README.md +2 -0
  277. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +5 -0
  278. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +2 -2
  279. package/templates/app/skeleton-lite/.starcistacks/production/README.md +1 -1
  280. package/templates/app/skeleton-lite/README.md +1 -1
  281. package/templates/be/inbox/platform/client-operation-outcomes.sql.tpl +18 -0
  282. package/templates/be/inbox/platform/event-bus/errors/event-bus.error.ts.tpl +16 -0
  283. package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +10 -0
  284. package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +1 -1
  285. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +15 -3
  286. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +11 -3
  287. package/templates/be/inbox/platform/event-bus/index.ts.tpl +2 -0
  288. package/templates/be/inbox/platform/event-bus/messages/event-bus.messages.ts.tpl +7 -0
  289. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +2 -2
  290. package/templates/be/inbox/platform/idempotency/errors/idempotency.error.ts.tpl +19 -0
  291. package/templates/be/inbox/platform/idempotency/index.ts.tpl +2 -0
  292. package/templates/be/inbox/platform/idempotency/messages/idempotency.messages.ts.tpl +13 -0
  293. package/templates/be/inbox/platform/inbox/client-operation.policy.spec.ts.tpl +32 -0
  294. package/templates/be/inbox/platform/inbox/client-operation.policy.ts.tpl +17 -0
  295. package/templates/be/inbox/platform/inbox/errors/inbox.error.ts.tpl +22 -0
  296. package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +21 -0
  297. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +334 -11
  298. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +55 -4
  299. package/templates/be/inbox/platform/inbox/index.ts.tpl +2 -0
  300. package/templates/be/inbox/platform/inbox/messages/inbox.messages.ts.tpl +15 -0
  301. package/templates/be/inbox/platform/inbox/persistence/inbox.rows.ts.tpl +33 -0
  302. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +26 -0
  303. package/templates/be/inbox/platform/inbox/request-fingerprint.policy.spec.ts.tpl +76 -0
  304. package/templates/be/inbox/platform/inbox/request-fingerprint.policy.ts.tpl +36 -0
  305. package/templates/be/inbox/platform/migration.sql.tpl +1 -0
  306. package/templates/be/inbox/service.spec.ts.tpl +4 -4
  307. package/templates/be/inbox/service.ts.tpl +3 -1
  308. package/templates/be/inbox/webhook-door.spec.ts.tpl +3 -2
  309. package/templates/be/inbox/webhook-door.ts.tpl +3 -2
  310. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +3 -0
  311. package/templates/be/patterns/event-bus/platform/event-bus.contracts.ts.tpl +10 -0
  312. package/templates/be/patterns/event-bus/platform/event-bus.port.ts.tpl +2 -2
  313. package/templates/be/patterns/event-bus/platform/event-bus.service.spec.ts.tpl +16 -3
  314. package/templates/be/patterns/event-bus/platform/event-bus.service.ts.tpl +11 -3
  315. package/templates/be/patterns/event-bus/platform/event-envelope.policy.spec.ts.tpl +139 -50
  316. package/templates/be/patterns/event-bus/platform/event-envelope.policy.ts.tpl +25 -2
  317. package/templates/be/patterns/event-bus/platform/event-relay.service.spec.ts.tpl +139 -78
  318. package/templates/be/patterns/event-bus/platform/event-relay.service.ts.tpl +10 -6
  319. package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +262 -159
  320. package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +32 -5
  321. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +2 -0
  322. package/templates/be/patterns/event-bus/platform/persistence/entities/event-outbox.entity.ts.tpl +5 -1
  323. package/templates/be/patterns/event-bus/platform/persistence/event-bus.rows.ts.tpl +2 -0
  324. package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +4 -4
  325. package/templates/be/patterns/event-bus/platform/persistence/migrations/1789800009000-create-event-outbox.ts.tpl +1 -0
  326. package/templates/be/patterns/idempotency/platform/idempotency/errors/idempotency.error.ts.tpl +19 -0
  327. package/templates/be/patterns/idempotency/platform/idempotency/index.ts.tpl +2 -0
  328. package/templates/be/patterns/idempotency/platform/idempotency/messages/idempotency.messages.ts.tpl +13 -0
  329. package/templates/be/patterns/idempotency/platform/inbox/client-operation.policy.spec.ts.tpl +32 -0
  330. package/templates/be/patterns/idempotency/platform/inbox/client-operation.policy.ts.tpl +17 -0
  331. package/templates/be/patterns/idempotency/platform/inbox/client-outcome-backfill.client.spec.ts.tpl +66 -0
  332. package/templates/be/patterns/idempotency/platform/inbox/client-outcome-backfill.client.ts.tpl +24 -0
  333. package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-backfill.client.spec.ts.tpl +60 -0
  334. package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-backfill.client.ts.tpl +12 -0
  335. package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-release.client.spec.ts.tpl +49 -0
  336. package/templates/be/patterns/idempotency/platform/inbox/delivery-claim-release.client.ts.tpl +12 -0
  337. package/templates/be/patterns/idempotency/platform/inbox/effectful.decorators.ts.tpl +7 -0
  338. package/templates/be/patterns/idempotency/platform/inbox/errors/inbox.error.ts.tpl +22 -0
  339. package/templates/be/patterns/idempotency/platform/inbox/inbox.decorators.ts.tpl +17 -0
  340. package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +7 -0
  341. package/templates/be/patterns/idempotency/platform/inbox/inbox.module.ts.tpl +28 -0
  342. package/templates/be/patterns/idempotency/platform/inbox/inbox.options.ts.tpl +7 -0
  343. package/templates/be/patterns/idempotency/platform/inbox/inbox.port.ts.tpl +44 -0
  344. package/templates/be/patterns/idempotency/platform/inbox/inbox.service.spec.ts.tpl +388 -0
  345. package/templates/be/patterns/idempotency/platform/inbox/inbox.service.ts.tpl +77 -0
  346. package/templates/be/patterns/idempotency/platform/inbox/index.ts.tpl +10 -0
  347. package/templates/be/patterns/idempotency/platform/inbox/messages/inbox.messages.ts.tpl +15 -0
  348. package/templates/be/patterns/idempotency/platform/inbox/persistence/connection.ts.tpl +9 -0
  349. package/templates/be/patterns/idempotency/platform/inbox/persistence/entities/inbox-claim.entity.ts.tpl +25 -0
  350. package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.rows.ts.tpl +33 -0
  351. package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.sql.ts.tpl +39 -0
  352. package/templates/be/patterns/idempotency/platform/inbox/persistence/migrations/1789800005000-create-inbox-claims.ts.tpl +21 -0
  353. package/templates/be/patterns/idempotency/platform/inbox/persistence/migrations/1792800000000-client-operation-outcomes.ts.tpl +21 -0
  354. package/templates/be/patterns/idempotency/platform/inbox/request-fingerprint.policy.spec.ts.tpl +76 -0
  355. package/templates/be/patterns/idempotency/platform/inbox/request-fingerprint.policy.ts.tpl +36 -0
  356. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +1 -0
  357. package/templates/be/patterns/jobs/platform/job-claim.service.spec.ts.tpl +4 -3
  358. package/templates/be/patterns/jobs/platform/job-claim.service.ts.tpl +2 -0
  359. package/templates/be/patterns/jobs/platform/job-runner.service.spec.ts.tpl +20 -2
  360. package/templates/be/patterns/jobs/platform/job-runner.service.ts.tpl +1 -0
  361. package/templates/be/patterns/jobs/platform/jobs.contracts.ts.tpl +4 -0
  362. package/templates/be/patterns/jobs/platform-lite/jobs.index.ts.tpl +10 -0
  363. package/templates/be/patterns/jobs/platform-lite/jobs.sql.tpl +44 -0
  364. package/templates/be/patterns/jobs/platform-lite/persistence.jobs.sql.ts.tpl +33 -0
  365. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.spec.ts.tpl +203 -18
  366. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.ts.tpl +78 -22
  367. package/templates/be/patterns/queues/platform/errors/queue.error.ts.tpl +19 -0
  368. package/templates/be/patterns/queues/platform/messages/queue.messages.ts.tpl +13 -0
  369. package/templates/be/patterns/queues/platform/persistence.migration.ts.tpl +1 -0
  370. package/templates/be/patterns/queues/platform/persistence.queue-outbox.entity.ts.tpl +4 -0
  371. package/templates/be/patterns/queues/platform/persistence.queue.rows.ts.tpl +2 -0
  372. package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +4 -4
  373. package/templates/be/patterns/queues/platform/queue-outbox.service.spec.ts.tpl +14 -2
  374. package/templates/be/patterns/queues/platform/queue-outbox.service.ts.tpl +5 -2
  375. package/templates/be/patterns/queues/platform/queue-relay.service.spec.ts.tpl +72 -74
  376. package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +1 -1
  377. package/templates/be/patterns/queues/platform/queue-transport.port.ts.tpl +23 -8
  378. package/templates/be/patterns/queues/platform/queue-worker.service.spec.ts.tpl +29 -8
  379. package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +7 -3
  380. package/templates/be/patterns/queues/platform/queue.contracts.ts.tpl +5 -0
  381. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +2 -0
  382. package/templates/be/patterns/queues/platform/queue.log-events.ts.tpl +4 -0
  383. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +2 -1
  384. package/templates/be/patterns/queues/platform/queue.port.ts.tpl +1 -1
  385. package/templates/be/patterns/queues/platform-lite/bullmq-queue-transport.client.spec.ts.tpl +417 -0
  386. package/templates/be/patterns/queues/platform-lite/bullmq-queue-transport.client.ts.tpl +172 -0
  387. package/templates/be/patterns/queues/platform-lite/persistence.queue.sql.ts.tpl +12 -0
  388. package/templates/be/patterns/queues/platform-lite/queue-outbox.sql.tpl +32 -0
  389. package/templates/be/patterns/queues/platform-lite/queue-relay.service.spec.ts.tpl +148 -0
  390. package/templates/be/patterns/queues/platform-lite/queue-relay.service.ts.tpl +51 -0
  391. package/templates/be/patterns/queues/platform-lite/queue-transport.port.ts.tpl +163 -0
  392. package/templates/be/patterns/queues/platform-lite/queue.config.ts.tpl +17 -0
  393. package/templates/be/patterns/queues/platform-lite/queue.error.ts.tpl +25 -0
  394. package/templates/be/patterns/queues/platform-lite/queue.index.ts.tpl +8 -0
  395. package/templates/be/patterns/queues/platform-lite/queue.messages.ts.tpl +21 -0
  396. package/templates/be/patterns/queues/platform-lite/queue.module.ts.tpl +74 -0
  397. package/templates/be/patterns/queues/platform-lite/queue.options.ts.tpl +38 -0
  398. package/templates/be/patterns/queues/queue.queue.ts.tpl +2 -2
  399. package/templates/be/patterns/reactors/application.contracts.ts.tpl +2 -0
  400. package/templates/be/patterns/reactors/consumer.ts.tpl +5 -1
  401. package/templates/be/platform/graphql/graphql.contracts.ts +2 -1
  402. package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +1 -1
  403. package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +6 -1
  404. package/templates/be/skeleton/apps/__app__/src/main.ts +7 -4
  405. package/templates/be/skeleton/apps/cli/src/compose/platform.ts +4 -0
  406. package/templates/be/skeleton/apps/cli/src/main.ts +7 -4
  407. package/templates/be/skeleton/src/modules/platform/database/index.ts +1 -1
  408. package/templates/be/skeleton/src/modules/platform/http/errors/http.error.ts +22 -0
  409. package/templates/be/skeleton/src/modules/platform/http/fetch-http-client.client.spec.ts +235 -0
  410. package/templates/be/skeleton/src/modules/platform/http/fetch-http-client.client.ts +85 -0
  411. package/templates/be/skeleton/src/modules/platform/http/graphql-call.mapper.spec.ts +92 -0
  412. package/templates/be/skeleton/src/modules/platform/http/graphql-call.mapper.ts +52 -0
  413. package/templates/be/skeleton/src/modules/platform/http/http.decorators.ts +9 -0
  414. package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +7 -0
  415. package/templates/be/skeleton/src/modules/platform/http/http.module.ts +19 -0
  416. package/templates/be/skeleton/src/modules/platform/http/http.options.ts +2 -0
  417. package/templates/be/skeleton/src/modules/platform/http/http.port.ts +40 -0
  418. package/templates/be/skeleton/src/modules/platform/http/index.ts +7 -0
  419. package/templates/be/skeleton/src/modules/platform/http/messages/http.messages.ts +15 -0
  420. package/templates/be/skeleton/src/modules/platform/ids/ids.decorators.ts +9 -0
  421. package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +7 -0
  422. package/templates/be/skeleton/src/modules/platform/ids/ids.module.ts +19 -0
  423. package/templates/be/skeleton/src/modules/platform/ids/ids.options.ts +2 -0
  424. package/templates/be/skeleton/src/modules/platform/ids/ids.port.ts +5 -0
  425. package/templates/be/skeleton/src/modules/platform/ids/index.ts +4 -0
  426. package/templates/be/skeleton/src/modules/platform/ids/system-ids.service.spec.ts +15 -0
  427. package/templates/be/skeleton/src/modules/platform/ids/system-ids.service.ts +10 -0
  428. package/templates/be/skeleton/src/modules/platform/logging/correlation-request.policy.spec.ts +59 -0
  429. package/templates/be/skeleton/src/modules/platform/logging/correlation-request.policy.ts +21 -0
  430. package/templates/be/skeleton/src/modules/platform/logging/correlation.contracts.ts +16 -0
  431. package/templates/be/skeleton/src/modules/platform/logging/correlation.policy.spec.ts +150 -0
  432. package/templates/be/skeleton/src/modules/platform/logging/correlation.policy.ts +42 -0
  433. package/templates/be/skeleton/src/modules/platform/logging/correlation.port.ts +15 -0
  434. package/templates/be/skeleton/src/modules/platform/logging/correlation.service.spec.ts +23 -0
  435. package/templates/be/skeleton/src/modules/platform/logging/correlation.service.ts +28 -0
  436. package/templates/be/skeleton/src/modules/platform/logging/index.ts +14 -1
  437. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +39 -6
  438. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +15 -3
  439. package/templates/be/skeleton/src/modules/platform/logging/logging.decorators.ts +17 -2
  440. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +13 -1
  441. package/templates/be/skeleton/src/modules/platform/logging/logging.module.ts +13 -3
  442. package/templates/be/skeleton/src/modules/platform/logging/nest-logger.service.spec.ts +69 -0
  443. package/templates/be/skeleton/src/modules/platform/logging/nest-logger.service.ts +48 -0
  444. package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
  445. package/templates/be/skeleton/src/modules/platform/primitives/record.policy.spec.ts +12 -0
  446. package/templates/be/skeleton/src/modules/platform/primitives/record.policy.ts +3 -0
  447. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +6 -1
  448. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +4 -0
  449. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
  450. package/templates/be/skeleton-lite/src/modules/platform/logging/index.ts +14 -1
  451. package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +12 -0
  452. package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
  453. package/templates/be/test-world/world/test-apps.options.ts.tpl +2 -0
  454. package/templates/be/test-world/world/test-world.config.ts.tpl +1 -0
  455. package/upgrade/check.mjs +12 -135
  456. package/upgrade/file-state.mjs +1 -16
  457. package/upgrade/index.mjs +34 -361
  458. package/runtime/scripts/lib/check-code-loosening.mjs +0 -128
  459. package/runtime/scripts/lib/gate-loosening.mjs +0 -292
  460. package/templates/app/upgrade-full/.starcistacks/application-stacks.yaml +0 -89
  461. package/templates/app/upgrade-full/.starcistacks/dev/README.md +0 -16
  462. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/api.yaml +0 -13
  463. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/compose.yaml +0 -3
  464. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/postgres.yaml +0 -12
  465. package/templates/app/upgrade-full/.starcistacks/dev/infra/metadata.json +0 -7
  466. package/templates/be/skeleton-lite/apps/api/src/main.ts +0 -26
  467. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +0 -12
  468. package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +0 -7
  469. package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +0 -5
  470. package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +0 -9
  471. package/templates/be/upgrade-full/apps/cli/src/main.ts +0 -29
  472. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +0 -7
  473. package/templates/be/upgrade-full/src/features/cli/migrate/subs/run.cli.ts +0 -23
@@ -1,9 +1,24 @@
1
1
  import { Injectable } from "@nestjs/common"
2
+ import { isRecord } from "@modules/platform/primitives"
3
+ import { InjectLogger, isCorrelationId } from "@modules/platform/logging"
4
+ import type { Logger } from "@modules/platform/logging"
5
+ import { InjectIds } from "@modules/platform/ids"
6
+ import type { Ids } from "@modules/platform/ids"
2
7
  import { InjectQueueFactory, InjectQueueOptions } from "./queue.decorators"
3
- import type { QueueHandler, QueueSchedulerDefinition } from "./queue.contracts"
8
+ import { BULLMQ_JOB_SCHEMA } from "./queue.contracts"
9
+ import type { QueueDelivery, QueueHandler, QueueSchedulerDefinition } from "./queue.contracts"
10
+ import { QueueError, QueueErrorCode } from "./errors/queue.error"
11
+ import { QueueLogEvent } from "./queue.log-events"
4
12
  import type { QueueOptions } from "./queue.options"
5
13
  import { ATTEMPTS, BACKOFF_MS, KEEP_COMPLETED_SECONDS, jobNameOf } from "./queue.policy"
6
- import type { BullmqConnection, BullmqQueue, BullmqWorker, QueueFactory, QueueTransport } from "./queue-transport.port"
14
+ import type {
15
+ BullmqConnection,
16
+ BullmqJob,
17
+ BullmqQueue,
18
+ BullmqWorker,
19
+ QueueFactory,
20
+ QueueTransport,
21
+ } from "./queue-transport.port"
7
22
 
8
23
  @Injectable()
9
24
  /** The QueueTransport port over a Redis the app names in its options; the BullMQ objects come from the injected factory the module composes. */
@@ -14,16 +29,23 @@ export class BullmqQueueTransportClient implements QueueTransport {
14
29
  constructor(
15
30
  @InjectQueueOptions() private readonly options: QueueOptions,
16
31
  @InjectQueueFactory() private readonly factory: QueueFactory,
32
+ @InjectIds() private readonly ids: Ids,
33
+ @InjectLogger() private readonly logger: Logger,
17
34
  ) {}
18
35
 
19
36
  /** Adds the job with the outbox row id as its BullMQ id, so adding it twice leaves one job. */
20
- async add(queue: string, jobId: string, payload: object): Promise<void> {
21
- await this.queueOf(queue).add(jobNameOf(queue), payload, {
22
- jobId,
23
- attempts: ATTEMPTS,
24
- backoff: { type: "exponential", delay: BACKOFF_MS },
25
- removeOnComplete: { age: KEEP_COMPLETED_SECONDS },
26
- })
37
+ async add(queue: string, jobId: string, payload: object, correlationId: string): Promise<void> {
38
+ if (!isCorrelationId(correlationId)) throw new QueueError({ code: QueueErrorCode.CorrelationInvalid })
39
+ await this.queueOf(queue).add(
40
+ jobNameOf(queue),
41
+ { schema: BULLMQ_JOB_SCHEMA, kind: "enqueued", payload, correlationId },
42
+ {
43
+ jobId,
44
+ attempts: ATTEMPTS,
45
+ backoff: { type: "exponential", delay: BACKOFF_MS },
46
+ removeOnComplete: { age: KEEP_COMPLETED_SECONDS },
47
+ },
48
+ )
27
49
  }
28
50
 
29
51
  /** Creates or updates the scheduler; BullMQ keeps one per id however many replicas call this. */
@@ -31,25 +53,25 @@ export class BullmqQueueTransportClient implements QueueTransport {
31
53
  await this.queueOf(scheduler.queue).upsertJobScheduler(
32
54
  scheduler.id,
33
55
  { every: scheduler.everyMs },
34
- { name: jobNameOf(scheduler.queue), data: scheduler.payload ?? {}, opts: { attempts: ATTEMPTS } },
56
+ {
57
+ name: jobNameOf(scheduler.queue),
58
+ data: { schema: BULLMQ_JOB_SCHEMA, kind: "scheduled", payload: scheduler.payload ?? {} },
59
+ opts: { attempts: ATTEMPTS },
60
+ },
35
61
  )
36
62
  }
37
63
 
38
64
  /** Starts a worker of the queue. */
39
65
  work(handler: QueueHandler, concurrency: number): Promise<void> {
40
- this.workers.push(
41
- this.factory.worker(
42
- handler.queue,
43
- (job) =>
44
- handler.handle({
45
- id: job.id ?? "",
46
- queue: handler.queue,
47
- payload: job.data,
48
- attempt: job.attemptsMade + 1,
49
- }),
50
- { ...this.connection(), concurrency },
51
- ),
66
+ const worker = this.factory.worker(
67
+ handler.queue,
68
+ async (job) => {
69
+ await handler.handle(this.deliveryOf(handler.queue, job))
70
+ },
71
+ { ...this.connection(), concurrency },
52
72
  )
73
+ worker.on("error", (cause) => this.logger.error(QueueLogEvent.WorkerFailed, cause, { queue: handler.queue }))
74
+ this.workers.push(worker)
53
75
  return Promise.resolve()
54
76
  }
55
77
 
@@ -64,6 +86,39 @@ export class BullmqQueueTransportClient implements QueueTransport {
64
86
  await Promise.all([...this.queues.values()].map((queue) => queue.close()))
65
87
  }
66
88
 
89
+ /** Reads current envelopes and stored raw payloads without changing the identity of a retried job. */
90
+ private deliveryOf(queue: string, job: BullmqJob): QueueDelivery {
91
+ const data = job.data
92
+ if (!isRecord(data)) throw new QueueError({ code: QueueErrorCode.MalformedMessage })
93
+ let payload: object = data
94
+ let correlationId = job.id
95
+ const schema = data.schema
96
+ const schemaPrefix = BULLMQ_JOB_SCHEMA.slice(0, BULLMQ_JOB_SCHEMA.lastIndexOf("@") + 1)
97
+ if (typeof schema === "string" && schema.startsWith(schemaPrefix)) {
98
+ if (
99
+ schema !== BULLMQ_JOB_SCHEMA ||
100
+ (data.kind !== "enqueued" && data.kind !== "scheduled") ||
101
+ !isRecord(data.payload)
102
+ ) {
103
+ throw new QueueError({ code: QueueErrorCode.MalformedMessage })
104
+ }
105
+ payload = data.payload
106
+ if (data.kind === "enqueued" || "correlationId" in data) {
107
+ if (!isCorrelationId(data.correlationId)) {
108
+ throw new QueueError({ code: QueueErrorCode.MalformedMessage })
109
+ }
110
+ correlationId = data.correlationId
111
+ }
112
+ }
113
+ return {
114
+ id: job.id ?? "",
115
+ queue,
116
+ correlationId: correlationId ?? this.ids.next(),
117
+ payload,
118
+ attempt: job.attemptsMade + 1,
119
+ }
120
+ }
121
+
67
122
  private connection(): BullmqConnection {
68
123
  return {
69
124
  prefix: this.options.prefix,
@@ -80,6 +135,7 @@ export class BullmqQueueTransportClient implements QueueTransport {
80
135
  const known = this.queues.get(name)
81
136
  if (known !== undefined) return known
82
137
  const created = this.factory.queue(name, this.connection())
138
+ created.on("error", (cause) => this.logger.error(QueueLogEvent.QueueFailed, cause, { queue: name }))
83
139
  this.queues.set(name, created)
84
140
  return created
85
141
  }
@@ -0,0 +1,19 @@
1
+ import { DomainError } from "@modules/platform/errors"
2
+ import type { ErrorKind } from "@modules/platform/errors"
3
+
4
+ /** Codes of the queue capability. */
5
+ export enum QueueErrorCode {
6
+ /** Stored job data does not satisfy the delivery envelope contract. */
7
+ MalformedMessage = "QUEUE_MALFORMED_MESSAGE",
8
+ /** A producer supplied an identity that is unsafe or unbounded. */
9
+ CorrelationInvalid = "QUEUE_CORRELATION_INVALID",
10
+ }
11
+
12
+ /** How each queue code travels. */
13
+ export const QUEUE_ERROR_KINDS: Record<QueueErrorCode, ErrorKind> = {
14
+ [QueueErrorCode.MalformedMessage]: "internal",
15
+ [QueueErrorCode.CorrelationInvalid]: "invalid",
16
+ }
17
+
18
+ /** The queue capability's failures, retained by the broker for inspection and retry. */
19
+ export class QueueError extends DomainError<QueueErrorCode> {}
@@ -0,0 +1,13 @@
1
+ import type { MessageBundle } from "@modules/platform/i18n"
2
+
3
+ /** Display text of queue failures in Vietnamese and English. */
4
+ export const QUEUE_MESSAGES: MessageBundle = {
5
+ vi: {
6
+ "errors.QUEUE_MALFORMED_MESSAGE": "Th\u00f4ng \u0111i\u1ec7p h\u00e0ng \u0111\u1ee3i \u0111\u00e3 l\u01b0u kh\u00f4ng h\u1ee3p l\u1ec7.",
7
+ "errors.QUEUE_CORRELATION_INVALID": "M\u00e3 t\u01b0\u01a1ng quan c\u1ee7a th\u00f4ng \u0111i\u1ec7p h\u00e0ng \u0111\u1ee3i kh\u00f4ng h\u1ee3p l\u1ec7.",
8
+ },
9
+ en: {
10
+ "errors.QUEUE_MALFORMED_MESSAGE": "The stored queue message is malformed.",
11
+ "errors.QUEUE_CORRELATION_INVALID": "The queue message correlation id is invalid.",
12
+ },
13
+ }
@@ -10,6 +10,7 @@ export class CreateQueueOutbox@@epochMs13@@ implements MigrationInterface {
10
10
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
11
11
  queue varchar(200) NOT NULL,
12
12
  payload jsonb NOT NULL,
13
+ correlation_id varchar(128) NOT NULL DEFAULT pg_catalog.gen_random_uuid()::text,
13
14
  created_at timestamptz NOT NULL,
14
15
  sent_at timestamptz
15
16
  )`)
@@ -3,6 +3,10 @@ import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"
3
3
  @Entity("queue_outbox")
4
4
  /** One job waiting to start: written in the transaction of the change that needs it, handed to BullMQ by the relay. */
5
5
  export class QueueOutboxEntity {
6
+ /** The upstream correlation id persisted with the job. */
7
+ @Column({ name: "correlation_id", type: "varchar", length: 128, default: () => "pg_catalog.gen_random_uuid()::text" })
8
+ correlationId!: string
9
+
6
10
  /** The row id; it becomes the BullMQ job id. */
7
11
  @PrimaryGeneratedColumn("uuid", { name: "id" })
8
12
  id!: string
@@ -1,5 +1,7 @@
1
1
  /** One row the relay reads from the queue outbox. */
2
2
  export interface QueueRow {
3
+ /** The correlation id recorded by the producer's transaction. */
4
+ readonly correlationId: string
3
5
  /** The row id: it is the BullMQ job id, so a repeated relay pass adds nothing. */
4
6
  readonly id: string
5
7
  /** The queue the job goes to. */
@@ -1,11 +1,11 @@
1
1
  import { sql } from "@modules/platform/database"
2
2
 
3
- /** Writes one job in the outbox ($1 queue, $2 payload, $3 instant). */
4
- export const INSERT_QUEUE_ROW = sql`INSERT INTO queue_outbox (queue, payload, created_at)
5
- VALUES ($1, $2, $3)`
3
+ /** Writes one job in the outbox ($1 queue, $2 payload, $3 instant, $4 correlation id). */
4
+ export const INSERT_QUEUE_ROW = sql`INSERT INTO queue_outbox (queue, payload, created_at, correlation_id)
5
+ VALUES ($1, $2, $3, $4)`
6
6
 
7
7
  /** Locks and reads the oldest rows that wait ($1 batch size); a second relay skips the rows this one holds. */
8
- export const SELECT_WAITING_ROWS = sql`SELECT id, queue, payload FROM queue_outbox
8
+ export const SELECT_WAITING_ROWS = sql`SELECT id, queue, payload, correlation_id AS "correlationId" FROM queue_outbox
9
9
  WHERE sent_at IS NULL ORDER BY created_at, id LIMIT $1 FOR UPDATE SKIP LOCKED`
10
10
 
11
11
  /** Marks the rows as handed to BullMQ ($1 ids, $2 instant). */
@@ -3,6 +3,7 @@ import { FakeClock, fakeTransaction, mockEntityManager } from "@starci/jest-pres
3
3
  import { CLOCK } from "@modules/platform/clock"
4
4
  import { QueueOutboxService } from "./queue-outbox.service"
5
5
  import { INSERT_QUEUE_ROW } from "./persistence/queue.sql"
6
+ import { QueueErrorCode } from "./errors/queue.error"
6
7
 
7
8
  const AT = "2026-02-03T04:05:06.000Z"
8
9
 
@@ -19,12 +20,13 @@ describe("QueueOutboxService", () => {
19
20
  const { outbox } = await build()
20
21
  const tx = fakeTransaction(mockEntityManager({ query: [INSERT_QUEUE_ROW, []] }))
21
22
 
22
- await tx.em.transaction((manager) => outbox.write(manager, "mail", { id: "m-1" }))
23
+ await tx.em.transaction((manager) => outbox.write(manager, "mail", { id: "m-1" }, "request-1"))
23
24
 
24
25
  expect(tx.em.query).toHaveBeenCalledWith(INSERT_QUEUE_ROW, [
25
26
  "mail",
26
27
  JSON.stringify({ id: "m-1" }),
27
28
  new Date(AT),
29
+ "request-1",
28
30
  ])
29
31
  expect(tx.commits).toBe(1)
30
32
  })
@@ -36,7 +38,7 @@ describe("QueueOutboxService", () => {
36
38
 
37
39
  await expect(
38
40
  tx.em.transaction(async (manager) => {
39
- await outbox.write(manager, "mail", { id: "m-1" })
41
+ await outbox.write(manager, "mail", { id: "m-1" }, "request-1")
40
42
  return Promise.reject(failure)
41
43
  }),
42
44
  ).rejects.toBe(failure)
@@ -44,5 +46,15 @@ describe("QueueOutboxService", () => {
44
46
  expect(tx.rollbacks).toBe(1)
45
47
  expect(tx.committedWrites).toEqual([])
46
48
  })
49
+
50
+ it("rejects an unsafe correlation id before writing any SQL", async () => {
51
+ const { outbox } = await build()
52
+ const tx = fakeTransaction(mockEntityManager())
53
+ await expect(
54
+ tx.em.transaction((manager) => outbox.write(manager, "mail", {}, "bad\nid")),
55
+ ).rejects.toMatchObject({ code: QueueErrorCode.CorrelationInvalid })
56
+ expect(tx.em.query).not.toHaveBeenCalled()
57
+ expect(tx.rollbacks).toBe(1)
58
+ })
47
59
  })
48
60
  })
@@ -2,7 +2,9 @@ import { Injectable } from "@nestjs/common"
2
2
  import type { EntityManager } from "typeorm"
3
3
  import { InjectClock } from "@modules/platform/clock"
4
4
  import type { Clock } from "@modules/platform/clock"
5
+ import { isCorrelationId } from "@modules/platform/logging"
5
6
  import type { QueueOutbox } from "./queue.port"
7
+ import { QueueError, QueueErrorCode } from "./errors/queue.error"
6
8
  import { INSERT_QUEUE_ROW } from "./persistence/queue.sql"
7
9
 
8
10
  @Injectable()
@@ -11,7 +13,8 @@ export class QueueOutboxService implements QueueOutbox {
11
13
  constructor(@InjectClock() private readonly clock: Clock) {}
12
14
 
13
15
  /** Writes the job in the outbox of the transaction `tx`; the relay adds it to BullMQ after the commit. */
14
- async write(tx: EntityManager, queue: string, payload: object): Promise<void> {
15
- await tx.query(INSERT_QUEUE_ROW, [queue, JSON.stringify(payload), this.clock.now()])
16
+ async write(tx: EntityManager, queue: string, payload: object, correlationId: string): Promise<void> {
17
+ if (!isCorrelationId(correlationId)) throw new QueueError({ code: QueueErrorCode.CorrelationInvalid })
18
+ await tx.query(INSERT_QUEUE_ROW, [queue, JSON.stringify(payload), this.clock.now(), correlationId])
16
19
  }
17
20
  }
@@ -25,7 +25,7 @@ const options: QueueOptions = {
25
25
  schedulers: [],
26
26
  }
27
27
 
28
- const row = (id: string) => ({ id, queue: "mail", payload: { id: `m-${id}` } })
28
+ const row = (id: string) => ({ id, queue: "mail", payload: { id: `m-${id}` }, correlationId: `request-${id}` })
29
29
 
30
30
  const build = async (manager: MockEntityManager) => {
31
31
  const transport = mock<QueueTransport>()
@@ -43,87 +43,85 @@ const build = async (manager: MockEntityManager) => {
43
43
  return { relay: moduleRef.get(QueueRelayService), transport, logger }
44
44
  }
45
45
 
46
- describe("QueueRelayService", () => {
47
- describe("relay", () => {
48
- it("adds the waiting rows to BullMQ with the row id as the job id and marks them sent in the same transaction", async () => {
49
- const tx = fakeTransaction(
50
- mockEntityManager({
51
- query: [
52
- [SELECT_WAITING_ROWS, [row("1"), row("2")]],
53
- [MARK_ROWS_SENT, []],
54
- ],
55
- }),
56
- )
57
- const { relay, transport } = await build(tx.em)
58
-
59
- await expect(relay.relay()).resolves.toBe(2)
60
-
61
- expect(tx.em.query).toHaveBeenNthCalledWith(1, SELECT_WAITING_ROWS, [2])
62
- expect(transport.add).toHaveBeenNthCalledWith(1, "mail", "1", { id: "m-1" })
63
- expect(transport.add).toHaveBeenNthCalledWith(2, "mail", "2", { id: "m-2" })
64
- expect(tx.em.query).toHaveBeenNthCalledWith(2, MARK_ROWS_SENT, [["1", "2"], new Date(AT)])
65
- expect(tx.commits).toBe(1)
66
- })
46
+ describe("QueueRelayService relay", () => {
47
+ it("adds the waiting rows to BullMQ with the row id as the job id and marks them sent in the same transaction", async () => {
48
+ const tx = fakeTransaction(
49
+ mockEntityManager({
50
+ query: [
51
+ [SELECT_WAITING_ROWS, [row("1"), row("2")]],
52
+ [MARK_ROWS_SENT, []],
53
+ ],
54
+ }),
55
+ )
56
+ const { relay, transport } = await build(tx.em)
57
+
58
+ await expect(relay.relay()).resolves.toBe(2)
59
+
60
+ expect(tx.em.query).toHaveBeenNthCalledWith(1, SELECT_WAITING_ROWS, [2])
61
+ expect(transport.add).toHaveBeenNthCalledWith(1, "mail", "1", { id: "m-1" }, "request-1")
62
+ expect(transport.add).toHaveBeenNthCalledWith(2, "mail", "2", { id: "m-2" }, "request-2")
63
+ expect(tx.em.query).toHaveBeenNthCalledWith(2, MARK_ROWS_SENT, [["1", "2"], new Date(AT)])
64
+ expect(tx.commits).toBe(1)
65
+ })
67
66
 
68
- it("adds nothing and marks nothing when no row waits", async () => {
69
- const tx = fakeTransaction(mockEntityManager({ query: [SELECT_WAITING_ROWS, []] }))
70
- const { relay, transport } = await build(tx.em)
67
+ it("adds nothing and marks nothing when no row waits", async () => {
68
+ const tx = fakeTransaction(mockEntityManager({ query: [SELECT_WAITING_ROWS, []] }))
69
+ const { relay, transport } = await build(tx.em)
71
70
 
72
- await expect(relay.relay()).resolves.toBe(0)
71
+ await expect(relay.relay()).resolves.toBe(0)
73
72
 
74
- expect(transport.add).not.toHaveBeenCalled()
75
- expect(tx.em.query).toHaveBeenCalledTimes(1)
76
- })
73
+ expect(transport.add).not.toHaveBeenCalled()
74
+ expect(tx.em.query).toHaveBeenCalledTimes(1)
75
+ })
77
76
 
78
- it("leaves the rows unmarked when BullMQ refuses, so the next pass adds them again", async () => {
79
- const tx = fakeTransaction(mockEntityManager({ query: [SELECT_WAITING_ROWS, [row("1")]] }))
80
- const { relay, transport } = await build(tx.em)
81
- const failure = new Error("redis down")
82
- transport.add.mockRejectedValue(failure)
77
+ it("leaves the rows unmarked when BullMQ refuses, so the next pass adds them again", async () => {
78
+ const tx = fakeTransaction(mockEntityManager({ query: [SELECT_WAITING_ROWS, [row("1")]] }))
79
+ const { relay, transport } = await build(tx.em)
80
+ const failure = new Error("redis down")
81
+ transport.add.mockRejectedValue(failure)
83
82
 
84
- await expect(relay.relay()).rejects.toBe(failure)
83
+ await expect(relay.relay()).rejects.toBe(failure)
85
84
 
86
- expect(tx.rollbacks).toBe(1)
87
- expect(tx.em.query).toHaveBeenCalledTimes(1)
88
- })
85
+ expect(tx.rollbacks).toBe(1)
86
+ expect(tx.em.query).toHaveBeenCalledTimes(1)
89
87
  })
88
+ })
90
89
 
91
- describe("the loop", () => {
92
- it("passes again at once after a pass that added rows, pauses on an empty outbox, logs a failed pass and stops on shutdown", async () => {
93
- const tx = fakeTransaction(
94
- mockEntityManager({
95
- query: [
96
- [SELECT_WAITING_ROWS, [row("1")]],
97
- [MARK_ROWS_SENT, []],
98
- [SELECT_WAITING_ROWS, []],
99
- ],
100
- }),
101
- )
102
- const { relay, transport, logger } = await build(tx.em)
103
- const failure = new Error("outbox unreadable")
104
- let waits = 0
105
- let shutdown: Promise<void> = Promise.resolve()
106
- let reachedSecondWait: () => void = () => undefined
107
- const secondWait = new Promise<void>((resolve) => {
108
- reachedSecondWait = resolve
109
- })
110
- transport.wait.mockImplementation(() => {
111
- waits += 1
112
- if (waits === 1) tx.em.query.mockRejectedValueOnce(failure)
113
- else {
114
- shutdown = relay.onApplicationShutdown()
115
- reachedSecondWait()
116
- }
117
- return Promise.resolve()
118
- })
119
-
120
- relay.onApplicationBootstrap()
121
- await secondWait
122
- await shutdown
123
-
124
- expect(transport.add).toHaveBeenCalledTimes(1)
125
- expect(transport.wait).toHaveBeenCalledWith(100)
126
- expect(logger.error).toHaveBeenCalledWith(OutboxLogEvent.RelayFailed, failure, { outbox: "queue" })
90
+ describe("QueueRelayService the loop", () => {
91
+ it("passes again at once after a pass that added rows, pauses on an empty outbox, logs a failed pass and stops on shutdown", async () => {
92
+ const tx = fakeTransaction(
93
+ mockEntityManager({
94
+ query: [
95
+ [SELECT_WAITING_ROWS, [row("1")]],
96
+ [MARK_ROWS_SENT, []],
97
+ [SELECT_WAITING_ROWS, []],
98
+ ],
99
+ }),
100
+ )
101
+ const { relay, transport, logger } = await build(tx.em)
102
+ const failure = new Error("outbox unreadable")
103
+ let waits = 0
104
+ let shutdown: Promise<void> = Promise.resolve()
105
+ let reachedSecondWait: () => void = () => undefined
106
+ const secondWait = new Promise<void>((resolve) => {
107
+ reachedSecondWait = resolve
108
+ })
109
+ transport.wait.mockImplementation(() => {
110
+ waits += 1
111
+ if (waits === 1) tx.em.query.mockRejectedValueOnce(failure)
112
+ else {
113
+ shutdown = relay.onApplicationShutdown()
114
+ reachedSecondWait()
115
+ }
116
+ return Promise.resolve()
127
117
  })
118
+
119
+ relay.onApplicationBootstrap()
120
+ await secondWait
121
+ await shutdown
122
+
123
+ expect(transport.add).toHaveBeenCalledTimes(1)
124
+ expect(transport.wait).toHaveBeenCalledWith(100)
125
+ expect(logger.error).toHaveBeenCalledWith(OutboxLogEvent.RelayFailed, failure, { outbox: "queue" })
128
126
  })
129
127
  })
@@ -36,7 +36,7 @@ export class QueueRelayService extends OutboxRelayPolicy<QueueRow> {
36
36
  }
37
37
 
38
38
  protected deliver(rows: ReadonlyArray<QueueRow>): Promise<void> {
39
- return eachInOrder(rows, (row) => this.transport.add(row.queue, row.id, row.payload))
39
+ return eachInOrder(rows, (row) => this.transport.add(row.queue, row.id, row.payload, row.correlationId))
40
40
  }
41
41
 
42
42
  protected async mark(tx: EntityManager, rows: ReadonlyArray<QueueRow>): Promise<void> {
@@ -1,9 +1,9 @@
1
- import type { QueueHandler, QueueSchedulerDefinition } from "./queue.contracts"
1
+ import type { BULLMQ_JOB_SCHEMA, QueueHandler, QueueSchedulerDefinition } from "./queue.contracts"
2
2
 
3
3
  /** BullMQ as the queue capability sees it: the one port the BullMQ client implements. */
4
4
  export interface QueueTransport {
5
5
  /** Adds the job to its queue with `jobId`; adding an id BullMQ already holds changes nothing, so a repeated relay pass is harmless. */
6
- add(queue: string, jobId: string, payload: object): Promise<void>
6
+ add(queue: string, jobId: string, payload: object, correlationId: string): Promise<void>
7
7
  /** Creates or updates a job scheduler by its id. */
8
8
  upsertScheduler(scheduler: QueueSchedulerDefinition): Promise<void>
9
9
  /** Starts a worker of the queue the handler names that runs it for every job, `concurrency` at a time. */
@@ -77,15 +77,21 @@ export interface BullmqJobTemplate {
77
77
  /** The job name, which is the queue name. */
78
78
  readonly name: string
79
79
  /** The payload of every tick. */
80
- readonly data: object
80
+ readonly data: BullmqJobData
81
81
  /** The options of every tick. */
82
82
  readonly opts: BullmqSchedulerJobOptions
83
83
  }
84
84
 
85
+ /** A BullMQ driver whose infrastructure failures must use the application logger. */
86
+ export interface BullmqErrorSource {
87
+ /** Subscribes before asynchronous driver errors can fall back to the driver's raw console. */
88
+ on(event: "error", listener: (cause: Error) => void): unknown
89
+ }
90
+
85
91
  /** A queue as the transport uses it. */
86
- export interface BullmqQueue {
92
+ export interface BullmqQueue extends BullmqErrorSource {
87
93
  /** Adds the job; an id BullMQ already holds makes the add a no-op. */
88
- add(name: string, payload: object, options: BullmqJobOptions): Promise<unknown>
94
+ add(name: string, payload: BullmqJobData, options: BullmqJobOptions): Promise<unknown>
89
95
  /** Creates or updates the scheduler by its id. */
90
96
  upsertJobScheduler(id: string, repeat: { readonly every: number }, template: BullmqJobTemplate): Promise<unknown>
91
97
  /** Closes the queue. */
@@ -96,14 +102,23 @@ export interface BullmqQueue {
96
102
  export interface BullmqJob {
97
103
  /** The job id; a tick the scheduler fires can have none. */
98
104
  readonly id?: string
99
- /** The payload the producer wrote. */
100
- readonly data: object
105
+ /** Stored data is untrusted: queued jobs can also carry the raw payload written before envelopes. */
106
+ readonly data: unknown
101
107
  /** How many deliveries were started before this one. */
102
108
  readonly attemptsMade: number
103
109
  }
104
110
 
111
+ /** The durable job data: an enqueued job propagates its id; a scheduler starts a new root per tick. */
112
+ export type BullmqJobData = {
113
+ /** Distinguishes infrastructure envelopes from arbitrary raw payload fields. */
114
+ readonly schema: typeof BULLMQ_JOB_SCHEMA
115
+ } & (
116
+ | { readonly kind: "enqueued"; readonly correlationId: string; readonly payload: object }
117
+ | { readonly kind: "scheduled"; readonly payload: object }
118
+ )
119
+
105
120
  /** A worker as the transport uses it. */
106
- export interface BullmqWorker {
121
+ export interface BullmqWorker extends BullmqErrorSource {
107
122
  /** Stops the worker; `force` drops the jobs it is still running. */
108
123
  close(force?: boolean): Promise<void>
109
124
  }
@@ -1,7 +1,7 @@
1
1
  import { Test } from "@nestjs/testing"
2
2
  import { mock } from "@starci/jest-preset"
3
- import { LOGGER } from "@modules/platform/logging"
4
- import type { Logger } from "@modules/platform/logging"
3
+ import { CORRELATION_RUNNER, LOGGER } from "@modules/platform/logging"
4
+ import type { CorrelationRunner, Logger } from "@modules/platform/logging"
5
5
  import type { QueueHandler } from "./queue.contracts"
6
6
  import { QUEUE_OPTIONS, QUEUE_TRANSPORT } from "./queue.decorators"
7
7
  import { QueueLogEvent } from "./queue.log-events"
@@ -27,15 +27,18 @@ const build = async () => {
27
27
  const transport = mock<QueueTransport>()
28
28
  transport.upsertScheduler.mockResolvedValue(undefined)
29
29
  const logger = mock<Logger>()
30
+ const correlation = mock<CorrelationRunner>()
31
+ correlation.run.mockImplementation((_id, work) => work())
30
32
  const moduleRef = await Test.createTestingModule({
31
33
  providers: [
32
34
  QueueWorkerService,
33
35
  { provide: QUEUE_OPTIONS, useValue: options },
34
36
  { provide: QUEUE_TRANSPORT, useValue: transport },
35
37
  { provide: LOGGER, useValue: logger },
38
+ { provide: CORRELATION_RUNNER, useValue: correlation },
36
39
  ],
37
40
  }).compile()
38
- return { workers: moduleRef.get(QueueWorkerService), transport, logger }
41
+ return { workers: moduleRef.get(QueueWorkerService), transport, logger, correlation }
39
42
  }
40
43
 
41
44
  describe("QueueWorkerService", () => {
@@ -49,22 +52,40 @@ describe("QueueWorkerService", () => {
49
52
 
50
53
  await workers.onApplicationBootstrap()
51
54
 
52
- expect(transport.work).toHaveBeenCalledWith(mailHandler, 3)
53
- expect(transport.work).toHaveBeenCalledWith(sweepHandler, 3)
55
+ expect(transport.work).toHaveBeenCalledWith(expect.objectContaining({ queue: "mail" }), 3)
56
+ expect(transport.work).toHaveBeenCalledWith(expect.objectContaining({ queue: "sweep" }), 3)
54
57
  expect(transport.upsertScheduler).toHaveBeenCalledWith(options.schedulers[0])
55
58
  })
56
59
 
57
60
  it("keeps the last handler registered for a queue", async () => {
58
61
  const { workers, transport } = await build()
59
- const first = handlerOf("mail")
60
- const second = handlerOf("mail")
62
+ const first = mock<QueueHandler>({ queue: "mail" })
63
+ const second = mock<QueueHandler>({ queue: "mail" })
64
+ second.handle.mockResolvedValue(undefined)
61
65
  workers.add(first)
62
66
  workers.add(second)
63
67
 
64
68
  await workers.onApplicationBootstrap()
65
69
 
66
70
  expect(transport.work).toHaveBeenCalledTimes(1)
67
- expect(transport.work).toHaveBeenCalledWith(second, 3)
71
+ const delivery = { id: "job-1", queue: "mail", payload: {}, attempt: 1, correlationId: "request-1" }
72
+ await transport.work.mock.calls[0]?.[0].handle(delivery)
73
+ expect(second.handle).toHaveBeenCalledWith(delivery)
74
+ expect(first.handle).not.toHaveBeenCalled()
75
+ })
76
+
77
+ it("runs each delivery within its explicitly carried logging context", async () => {
78
+ const { workers, transport, correlation } = await build()
79
+ const handler = mock<QueueHandler>({ queue: "mail" })
80
+ handler.handle.mockResolvedValue(undefined)
81
+ workers.add(handler)
82
+ await workers.onApplicationBootstrap()
83
+ const delivery = { id: "job-1", queue: "mail", payload: {}, attempt: 2, correlationId: "request-1" }
84
+
85
+ await transport.work.mock.calls[0]?.[0].handle(delivery)
86
+
87
+ expect(correlation.run).toHaveBeenCalledWith("request-1", expect.any(Function))
88
+ expect(handler.handle).toHaveBeenCalledWith(delivery)
68
89
  })
69
90
 
70
91
  it("logs a scheduler that cannot be registered and still starts the app", async () => {
@@ -1,7 +1,7 @@
1
1
  import { Injectable } from "@nestjs/common"
2
2
  import type { OnApplicationBootstrap, OnApplicationShutdown } from "@nestjs/common"
3
- import { InjectLogger } from "@modules/platform/logging"
4
- import type { Logger } from "@modules/platform/logging"
3
+ import { InjectCorrelationRunner, InjectLogger } from "@modules/platform/logging"
4
+ import type { CorrelationRunner, Logger } from "@modules/platform/logging"
5
5
  import { eachInOrder } from "@modules/platform/primitives"
6
6
  import type { QueueHandler } from "./queue.contracts"
7
7
  import { InjectQueueOptions, InjectQueueTransport } from "./queue.decorators"
@@ -22,11 +22,15 @@ export class QueueWorkerService implements QueueWorkerRegistry, OnApplicationBoo
22
22
  @InjectQueueOptions() private readonly options: QueueOptions,
23
23
  @InjectQueueTransport() private readonly transport: QueueTransport,
24
24
  @InjectLogger() private readonly logger: Logger,
25
+ @InjectCorrelationRunner() private readonly correlation: CorrelationRunner,
25
26
  ) {}
26
27
 
27
28
  /** Registers the handler under the queue it names; a second handler for the same queue replaces the first. */
28
29
  add(handler: QueueHandler): void {
29
- this.handlers.set(handler.queue, handler)
30
+ this.handlers.set(handler.queue, {
31
+ queue: handler.queue,
32
+ handle: (delivery) => this.correlation.run(delivery.correlationId, () => handler.handle(delivery)),
33
+ })
30
34
  }
31
35
 
32
36
  /** Starts one worker per registered queue, then registers the schedulers of the options. */