@starci/hfs 4.2.17 → 4.4.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 (564) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +7 -0
  3. package/db/migrate.mjs +361 -0
  4. package/db/post-cutover.mjs +136 -0
  5. package/emit/db-types.mjs +10 -1
  6. package/lint/run.mjs +1 -1
  7. package/package.json +4 -1
  8. package/release/gate.mjs +298 -0
  9. package/release/index.mjs +103 -0
  10. package/release/push.mjs +110 -0
  11. package/runtime/knowledge/hfs/canon-pins.yaml +11 -11
  12. package/runtime/knowledge/hfs/fe-stack.yaml +22 -1
  13. package/runtime/knowledge/hfs/rules.yaml +3437 -160
  14. package/runtime/knowledge/hfs/slots.yaml +430 -92
  15. package/runtime/knowledge/patterns/be/api.yaml +92 -2
  16. package/runtime/knowledge/patterns/be/cli.yaml +37 -9
  17. package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
  18. package/runtime/knowledge/patterns/be/jobs.yaml +20 -2
  19. package/runtime/knowledge/patterns/be/projections.yaml +4 -2
  20. package/runtime/knowledge/patterns/be/queues.yaml +11 -3
  21. package/runtime/knowledge/patterns/be/reactors.yaml +20 -1
  22. package/runtime/knowledge/patterns/be/realtime.yaml +25 -3
  23. package/runtime/knowledge/patterns/be/saga.yaml +20 -3
  24. package/runtime/knowledge/patterns/be/webhooks.yaml +210 -36
  25. package/runtime/knowledge/patterns/fe/folder.yaml +4 -0
  26. package/runtime/knowledge/sonar-gate.yaml +1 -1
  27. package/runtime/modules/kernel/failure-codes.yaml +3128 -47
  28. package/runtime/modules/models/runtimes.yaml +1 -1
  29. package/runtime/modules/schemas/application-stacks.schema.yaml +663 -0
  30. package/runtime/packages/cli/src/catalog.generated.mjs +14104 -0
  31. package/runtime/packages/cli/src/validate-args.mjs +213 -0
  32. package/runtime/scripts/api/git/add.mjs +5 -0
  33. package/runtime/scripts/api/git/cat-file.mjs +5 -0
  34. package/runtime/scripts/api/git/init.mjs +7 -0
  35. package/runtime/scripts/api/git/lib.mjs +3 -0
  36. package/runtime/scripts/api/git/tag.mjs +5 -0
  37. package/runtime/scripts/api/node/lib.mjs +20 -0
  38. package/runtime/scripts/api/node/run-node.mjs +6 -0
  39. package/runtime/scripts/api/sops/lib.mjs +41 -0
  40. package/runtime/scripts/api/sops/resolve-sops.mjs +7 -0
  41. package/runtime/scripts/api/sops/seal.mjs +1 -1
  42. package/runtime/scripts/api/sops/selected-age-recipient.mjs +7 -0
  43. package/runtime/scripts/api/supabase/database-dump.mjs +5 -0
  44. package/runtime/scripts/api/supabase/database-query.mjs +5 -0
  45. package/runtime/scripts/api/supabase/lib.mjs +44 -0
  46. package/runtime/scripts/api/supabase/migration-repair.mjs +5 -0
  47. package/runtime/scripts/hfs/app-wiring.mjs +201 -0
  48. package/runtime/scripts/hfs/architecture/backend-app-files.mjs +103 -0
  49. package/runtime/scripts/hfs/architecture/backend.mjs +22 -10
  50. package/runtime/scripts/hfs/architecture/background-unowned.mjs +157 -41
  51. package/runtime/scripts/hfs/architecture/canon-clones.mjs +240 -0
  52. package/runtime/scripts/hfs/architecture/canon-jsx-clones.mjs +143 -0
  53. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +6 -3
  54. package/runtime/scripts/hfs/architecture/clones.mjs +17 -8
  55. package/runtime/scripts/hfs/architecture/connection-map.mjs +14 -12
  56. package/runtime/scripts/hfs/architecture/context-map.mjs +25 -14
  57. package/runtime/scripts/hfs/architecture/context-owner.mjs +1 -2
  58. package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +149 -0
  59. package/runtime/scripts/hfs/architecture/dead-exports.mjs +23 -50
  60. package/runtime/scripts/hfs/architecture/default-deny.mjs +8 -6
  61. package/runtime/scripts/hfs/architecture/frontend-block-render.mjs +104 -0
  62. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +30 -11
  63. package/runtime/scripts/hfs/architecture/frontend-world.mjs +1 -1
  64. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +153 -0
  65. package/runtime/scripts/hfs/architecture/hfs.mjs +3 -3
  66. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +16 -3
  67. package/runtime/scripts/hfs/architecture/machine-ast.mjs +17 -0
  68. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +49 -25
  69. package/runtime/scripts/hfs/architecture/next-data-inference.mjs +176 -0
  70. package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +26 -0
  71. package/runtime/scripts/hfs/architecture/next-data.mjs +31 -23
  72. package/runtime/scripts/hfs/architecture/reachability.mjs +14 -0
  73. package/runtime/scripts/hfs/architecture/register-once.mjs +15 -7
  74. package/runtime/scripts/hfs/architecture/required-files.mjs +2 -1
  75. package/runtime/scripts/hfs/architecture/source-names.mjs +48 -3
  76. package/runtime/scripts/hfs/architecture/staged-consumers.mjs +37 -0
  77. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +214 -17
  78. package/runtime/scripts/hfs/architecture/supabase-be.mjs +1 -6
  79. package/runtime/scripts/hfs/architecture/supabase.mjs +59 -21
  80. package/runtime/scripts/hfs/architecture/surface.mjs +2 -1
  81. package/runtime/scripts/hfs/architecture/tiers.mjs +11 -4
  82. package/runtime/scripts/hfs/architecture/typescript.mjs +1 -8
  83. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +9 -13
  84. package/runtime/scripts/hfs/check-results.mjs +28 -0
  85. package/runtime/scripts/hfs/check.mjs +63 -72
  86. package/runtime/scripts/hfs/coverage-from.mjs +70 -0
  87. package/runtime/scripts/hfs/db-baseline-verb.mjs +276 -0
  88. package/runtime/scripts/hfs/db-plan-verb.mjs +254 -0
  89. package/runtime/scripts/hfs/db-restamp-verb.mjs +60 -0
  90. package/runtime/scripts/hfs/declaration-shape.mjs +39 -3
  91. package/runtime/scripts/hfs/doc-commands.mjs +367 -0
  92. package/runtime/scripts/hfs/edition-slots.mjs +2 -2
  93. package/runtime/scripts/hfs/example-secrets.mjs +158 -0
  94. package/runtime/scripts/hfs/manifest-shape.mjs +91 -6
  95. package/runtime/scripts/hfs/path-findings.mjs +32 -2
  96. package/runtime/scripts/hfs/refusal-codes.mjs +6 -0
  97. package/runtime/scripts/hfs/rule-catalog.mjs +156 -11
  98. package/runtime/scripts/hfs/rule-params-shape.mjs +22 -10
  99. package/runtime/scripts/hfs/rules/a15-findings.mjs +88 -0
  100. package/runtime/scripts/hfs/rules/app-env-required.mjs +20 -0
  101. package/runtime/scripts/hfs/rules/app-root.mjs +2 -2
  102. package/runtime/scripts/hfs/rules/assets.mjs +79 -0
  103. package/runtime/scripts/hfs/rules/boot-proof.mjs +16 -0
  104. package/runtime/scripts/hfs/rules/canon-copy-drift.mjs +59 -0
  105. package/runtime/scripts/hfs/rules/contract.mjs +66 -5
  106. package/runtime/scripts/hfs/rules/coverage-below.mjs +230 -0
  107. package/runtime/scripts/hfs/rules/database-access.mjs +343 -0
  108. package/runtime/scripts/hfs/rules/database-baseline.mjs +142 -0
  109. package/runtime/scripts/hfs/rules/database-config.mjs +250 -2
  110. package/runtime/scripts/hfs/rules/database-constants.mjs +9 -0
  111. package/runtime/scripts/hfs/rules/database-migrations.mjs +317 -37
  112. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +119 -9
  113. package/runtime/scripts/hfs/rules/database-queue-sources.mjs +176 -0
  114. package/runtime/scripts/hfs/rules/database-queues.mjs +361 -0
  115. package/runtime/scripts/hfs/rules/database-sql.mjs +91 -9
  116. package/runtime/scripts/hfs/rules/database.mjs +11 -2
  117. package/runtime/scripts/hfs/rules/deploy-scan.mjs +192 -0
  118. package/runtime/scripts/hfs/rules/deploy-workflow.mjs +219 -0
  119. package/runtime/scripts/hfs/rules/doc-fresh.mjs +286 -0
  120. package/runtime/scripts/hfs/rules/docker-health.mjs +249 -0
  121. package/runtime/scripts/hfs/rules/docker.mjs +24 -7
  122. package/runtime/scripts/hfs/rules/edition.mjs +11 -10
  123. package/runtime/scripts/hfs/rules/env-parity.mjs +373 -0
  124. package/runtime/scripts/hfs/rules/env-value-shape.mjs +113 -0
  125. package/runtime/scripts/hfs/rules/event-bus.mjs +3 -2
  126. package/runtime/scripts/hfs/rules/event-tables.mjs +100 -0
  127. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +115 -15
  128. package/runtime/scripts/hfs/rules/fe-stack.mjs +57 -0
  129. package/runtime/scripts/hfs/rules/feature-files.mjs +28 -0
  130. package/runtime/scripts/hfs/rules/frontend-tree.mjs +54 -35
  131. package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -2
  132. package/runtime/scripts/hfs/rules/lexicon.mjs +62 -0
  133. package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +89 -0
  134. package/runtime/scripts/hfs/rules/monorepo.mjs +14 -1
  135. package/runtime/scripts/hfs/rules/pipeline.mjs +8 -1
  136. package/runtime/scripts/hfs/rules/proof-commands.mjs +2 -0
  137. package/runtime/scripts/hfs/rules/release-provenance.mjs +211 -0
  138. package/runtime/scripts/hfs/rules/saga-delivery.mjs +66 -0
  139. package/runtime/scripts/hfs/rules/saga.mjs +8 -16
  140. package/runtime/scripts/hfs/rules/script-hygiene.mjs +151 -0
  141. package/runtime/scripts/hfs/rules/secret-custody-only.mjs +65 -0
  142. package/runtime/scripts/hfs/rules/secrets.mjs +26 -0
  143. package/runtime/scripts/hfs/rules/services.mjs +4 -4
  144. package/runtime/scripts/hfs/rules/stack-tooling.mjs +93 -0
  145. package/runtime/scripts/hfs/rules/stacks-deploy.mjs +76 -0
  146. package/runtime/scripts/hfs/rules/stacks-ingress.mjs +87 -0
  147. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +111 -0
  148. package/runtime/scripts/hfs/rules/stacks-release.mjs +366 -0
  149. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +168 -0
  150. package/runtime/scripts/hfs/rules/stacks-swarm.mjs +417 -0
  151. package/runtime/scripts/hfs/rules/stacks.mjs +13 -8
  152. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +1 -1
  153. package/runtime/scripts/hfs/rules/supply-chain.mjs +167 -0
  154. package/runtime/scripts/hfs/rules/unused-symbol-flow.mjs +101 -0
  155. package/runtime/scripts/hfs/rules/unused-symbol-ledger.mjs +77 -0
  156. package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +165 -0
  157. package/runtime/scripts/hfs/rules/unused-symbol-values.mjs +103 -0
  158. package/runtime/scripts/hfs/rules/unused-symbol.mjs +320 -0
  159. package/runtime/scripts/hfs/runtime-rules/source-ast.mjs +185 -0
  160. package/runtime/scripts/hfs/runtime-rules/workflow-source.mjs +19 -0
  161. package/runtime/scripts/hfs/sealed-members.mjs +101 -0
  162. package/runtime/scripts/hfs/secret-bootstrap-crypto.mjs +200 -0
  163. package/runtime/scripts/hfs/secret-document.mjs +97 -0
  164. package/runtime/scripts/hfs/secret-edit.mjs +170 -0
  165. package/runtime/scripts/hfs/secret-errors.mjs +20 -0
  166. package/runtime/scripts/hfs/secret-files.mjs +124 -0
  167. package/runtime/scripts/hfs/secret-init.mjs +300 -0
  168. package/runtime/scripts/hfs/secret-output.mjs +100 -0
  169. package/runtime/scripts/hfs/secret-sops.mjs +130 -0
  170. package/runtime/scripts/hfs/secret-store.mjs +163 -0
  171. package/runtime/scripts/hfs/secret.mjs +162 -193
  172. package/runtime/scripts/hfs/slot-imports.mjs +18 -3
  173. package/runtime/scripts/hfs/slot-required.mjs +13 -0
  174. package/runtime/scripts/hfs/slot-semantic-problems.mjs +2 -1
  175. package/runtime/scripts/hfs/slots.mjs +1 -0
  176. package/runtime/scripts/hfs/stack-config-kit.mjs +34 -0
  177. package/runtime/scripts/hfs/stack-deploy-config.mjs +313 -0
  178. package/runtime/scripts/hfs/stack-ingress-config.mjs +237 -0
  179. package/runtime/scripts/hfs/stacks-gate.mjs +414 -0
  180. package/runtime/scripts/hfs/starcistacks-runtime.mjs +61 -0
  181. package/runtime/scripts/hfs/tree-findings.mjs +22 -0
  182. package/runtime/scripts/lib/command-tokens.mjs +126 -0
  183. package/runtime/scripts/lib/compose-values.mjs +11 -0
  184. package/runtime/scripts/lib/deployment-findings.mjs +7 -0
  185. package/runtime/scripts/lib/json-clone.mjs +61 -0
  186. package/runtime/scripts/lib/list.mjs +4 -0
  187. package/runtime/scripts/lib/roots.mjs +59 -0
  188. package/runtime/scripts/lib/runtime-example.mjs +13 -0
  189. package/runtime/scripts/lib/secret-patterns.mjs +60 -5
  190. package/runtime/scripts/lib/sql-ast.mjs +32 -0
  191. package/runtime/scripts/lib/stack-services.mjs +1 -1
  192. package/runtime/scripts/lib/swarm-stack.mjs +126 -0
  193. package/runtime/scripts/lib/trim-end.mjs +8 -0
  194. package/runtime/scripts/lib/ts-analysis.mjs +23 -0
  195. package/runtime/scripts/lib/ts-ast.mjs +27 -2
  196. package/runtime/scripts/lib/workflow-events.mjs +8 -0
  197. package/scaffold/add-app.mjs +77 -59
  198. package/scaffold/add-cli-lite.mjs +24 -17
  199. package/scaffold/add-compose.mjs +79 -0
  200. package/scaffold/add-database-lite.mjs +14 -0
  201. package/scaffold/add-fe-overlay.mjs +122 -0
  202. package/scaffold/add-fe-page.mjs +269 -0
  203. package/scaffold/add-inbox-lite.mjs +87 -124
  204. package/scaffold/add-plan.mjs +183 -0
  205. package/scaffold/add-table.mjs +132 -70
  206. package/scaffold/add.mjs +186 -204
  207. package/scaffold/app.mjs +128 -43
  208. package/scaffold/edition-gate.mjs +3 -3
  209. package/scaffold/image.mjs +2 -2
  210. package/scaffold/lite-exports.mjs +2 -1
  211. package/scaffold/service.mjs +2 -2
  212. package/src/main.mjs +238 -119
  213. package/stack/age-install.mjs +37 -0
  214. package/stack/backup.mjs +100 -0
  215. package/stack/build-args.mjs +42 -0
  216. package/stack/build.mjs +173 -0
  217. package/stack/cli.mjs +130 -0
  218. package/stack/config.mjs +57 -0
  219. package/stack/context.mjs +57 -0
  220. package/stack/converge.mjs +107 -0
  221. package/stack/deploy.mjs +301 -0
  222. package/stack/digest.mjs +16 -0
  223. package/stack/docker-transport.mjs +53 -0
  224. package/stack/edge-alias.mjs +118 -0
  225. package/stack/errors.mjs +21 -0
  226. package/stack/image-scan.mjs +152 -0
  227. package/stack/ingress-create.mjs +114 -0
  228. package/stack/ingress-publish.mjs +273 -0
  229. package/stack/ingress.mjs +706 -0
  230. package/stack/jobs.mjs +51 -0
  231. package/stack/live-restore.mjs +177 -0
  232. package/stack/prepare.mjs +266 -0
  233. package/stack/private-file.mjs +22 -0
  234. package/stack/provision.mjs +90 -0
  235. package/stack/prune.mjs +46 -0
  236. package/stack/release-store.mjs +87 -0
  237. package/stack/remote.mjs +205 -0
  238. package/stack/render.mjs +149 -0
  239. package/stack/retire.mjs +47 -0
  240. package/stack/sealed.mjs +149 -0
  241. package/stack/task.mjs +42 -0
  242. package/stack/verify.mjs +99 -0
  243. package/sync/ci-commands.mjs +13 -0
  244. package/sync/index.mjs +127 -64
  245. package/sync/stack-images.mjs +127 -0
  246. package/templates/app/ci-workflows/github/workflows/ci.yml +5 -1
  247. package/templates/app/ci-workflows/github/workflows/images.yml +15 -3
  248. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +148 -0
  249. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +7 -3
  250. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +38 -6
  251. package/templates/app/ci-workflows-lite/github/workflows/images.yml +14 -2
  252. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +148 -0
  253. package/templates/app/codegen-graphql.mjs.partial +13 -2
  254. package/templates/app/codegen-lite/scripts/codegen.mjs +6 -1
  255. package/templates/app/codegen-prelude.mjs.partial +8 -0
  256. package/templates/app/gitignore +4 -0
  257. package/templates/app/gitignore-lite +4 -0
  258. package/templates/app/hooks/husky/pre-commit +7 -12
  259. package/templates/app/package-scripts/package.json +1 -0
  260. package/templates/app/package-scripts/supabase.json +7 -0
  261. package/templates/app/package-scripts-lite/package.json +1 -7
  262. package/templates/app/skeleton/.editorconfig +1 -0
  263. package/templates/app/skeleton/.gitattributes +7 -1
  264. package/templates/app/skeleton/.github/dependabot.yml +17 -0
  265. package/templates/app/skeleton/.nvmrc +1 -1
  266. package/templates/app/skeleton/.sops.yaml +1 -1
  267. package/templates/app/skeleton/.starcistacks/application-stacks.yaml +134 -1
  268. package/templates/app/skeleton/.starcistacks/dev/README.md +16 -0
  269. package/templates/app/skeleton/.starcistacks/dev/environment.json +5 -0
  270. package/templates/app/skeleton/.starcistacks/dev/infra/compose/stack.yaml +71 -0
  271. package/templates/app/skeleton/.starcistacks/dev/runtime/env/KEYS.md +14 -0
  272. package/templates/app/skeleton/.starcistacks/production/README.md +37 -0
  273. package/templates/app/skeleton/.starcistacks/production/environment.json +5 -0
  274. package/templates/app/skeleton/.starcistacks/production/infra/swarm/Caddyfile +15 -0
  275. package/templates/app/skeleton/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  276. package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  277. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  278. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +9 -0
  279. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  280. package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +142 -0
  281. package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +14 -0
  282. package/templates/app/skeleton/README.md +6 -6
  283. package/templates/app/skeleton/scripts/codegen.mjs +1 -2
  284. package/templates/app/skeleton-lite/.editorconfig +16 -0
  285. package/templates/app/skeleton-lite/.gitattributes +8 -0
  286. package/templates/app/skeleton-lite/.github/dependabot.yml +17 -0
  287. package/templates/app/skeleton-lite/.nvmrc +1 -0
  288. package/templates/app/skeleton-lite/.sops.yaml +3 -2
  289. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +193 -0
  290. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +20 -0
  291. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +2 -1
  292. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +16 -0
  293. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +11 -0
  294. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +7 -0
  295. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +6 -0
  296. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +8 -0
  297. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +6 -0
  298. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +120 -0
  299. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -0
  300. package/templates/app/skeleton-lite/.starcistacks/production/README.md +38 -0
  301. package/templates/app/skeleton-lite/.starcistacks/production/environment.json +6 -0
  302. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/Caddyfile +11 -0
  303. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  304. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  305. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  306. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/images.lock.yaml +8 -0
  307. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  308. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +121 -0
  309. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +21 -0
  310. package/templates/app/skeleton-lite/.starciwork/brand/index.yaml +12 -0
  311. package/templates/app/skeleton-lite/.starciwork/workspace.yaml +9 -0
  312. package/templates/app/skeleton-lite/README.md +8 -4
  313. package/templates/app/skeleton-lite/supabase/config.toml +8 -1
  314. package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +15 -6
  315. package/templates/app/starciwork.gitignore-lite +6 -0
  316. package/templates/app/upgrade-full/.starcistacks/dev/README.md +13 -7
  317. package/templates/be/image/worker/Dockerfile +1 -1
  318. package/templates/be/inbox/database-client.spec.ts.tpl +22 -28
  319. package/templates/be/inbox/database-client.ts.tpl +14 -28
  320. package/templates/be/inbox/delivery-recorded.event.ts.tpl +36 -0
  321. package/templates/be/inbox/event-index.ts.tpl +1 -0
  322. package/templates/be/inbox/events.json.tpl +10 -0
  323. package/templates/be/inbox/migration.sql.tpl +1 -0
  324. package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +42 -0
  325. package/templates/be/inbox/platform/event-bus/event-bus.decorators.ts.tpl +9 -0
  326. package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +6 -0
  327. package/templates/be/inbox/platform/event-bus/event-bus.module.ts.tpl +19 -0
  328. package/templates/be/inbox/platform/event-bus/event-bus.options.ts.tpl +2 -0
  329. package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +16 -0
  330. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +45 -0
  331. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +15 -0
  332. package/templates/be/inbox/platform/event-bus/index.ts.tpl +5 -0
  333. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +5 -0
  334. package/templates/be/inbox/platform/inbox/inbox.decorators.ts.tpl +9 -0
  335. package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +6 -0
  336. package/templates/be/inbox/platform/inbox/inbox.module.ts.tpl +19 -0
  337. package/templates/be/inbox/platform/inbox/inbox.options.ts.tpl +2 -0
  338. package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +10 -0
  339. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +41 -0
  340. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +19 -0
  341. package/templates/be/inbox/platform/inbox/index.ts.tpl +3 -0
  342. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +7 -0
  343. package/templates/be/inbox/platform/migration.sql.tpl +48 -0
  344. package/templates/be/inbox/service.spec.ts.tpl +49 -20
  345. package/templates/be/inbox/service.ts.tpl +27 -16
  346. package/templates/be/inbox/sql.ts.tpl +2 -2
  347. package/templates/be/inbox/webhook-door.spec.ts.tpl +3 -3
  348. package/templates/be/inbox/webhook-door.ts.tpl +4 -3
  349. package/templates/be/patterns/api/feature.module-definition.ts.tpl +6 -0
  350. package/templates/be/patterns/api/feature.module.ts.tpl +2 -1
  351. package/templates/be/patterns/api/feature.options.ts.tpl +2 -0
  352. package/templates/be/patterns/api/graphql.module-definition.ts.tpl +6 -0
  353. package/templates/be/patterns/api/graphql.module.ts.tpl +3 -2
  354. package/templates/be/patterns/api/graphql.options.ts.tpl +2 -0
  355. package/templates/be/patterns/cli/group.module-definition.ts.tpl +6 -0
  356. package/templates/be/patterns/cli/group.module.ts.tpl +2 -1
  357. package/templates/be/patterns/cli/group.options.ts.tpl +2 -0
  358. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +0 -3
  359. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +0 -2
  360. package/templates/be/patterns/jobs/job.module-definition.ts.tpl +6 -0
  361. package/templates/be/patterns/jobs/job.module.ts.tpl +2 -1
  362. package/templates/be/patterns/jobs/job.options.ts.tpl +2 -0
  363. package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +6 -0
  364. package/templates/be/patterns/jobs/job.queue.module.ts.tpl +6 -3
  365. package/templates/be/patterns/jobs/job.queue.options.ts.tpl +2 -0
  366. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +3 -1
  367. package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +25 -9
  368. package/templates/be/patterns/queues/platform/queue.config.ts.tpl +1 -1
  369. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +1 -1
  370. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +2 -2
  371. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +11 -0
  372. package/templates/be/patterns/queues/platform/queue.options.ts.tpl +10 -0
  373. package/templates/be/patterns/queues/queue.module-definition.ts.tpl +6 -0
  374. package/templates/be/patterns/queues/queue.module.ts.tpl +2 -1
  375. package/templates/be/patterns/queues/queue.options.ts.tpl +2 -0
  376. package/templates/be/patterns/reactors/message.module-definition.ts.tpl +6 -0
  377. package/templates/be/patterns/reactors/message.module.ts.tpl +6 -3
  378. package/templates/be/patterns/reactors/message.options.ts.tpl +2 -0
  379. package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +6 -0
  380. package/templates/be/patterns/reactors/reactor.module.ts.tpl +2 -1
  381. package/templates/be/patterns/reactors/reactor.options.ts.tpl +2 -0
  382. package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +188 -0
  383. package/templates/be/patterns/realtime/channel.gateway.ts.tpl +22 -0
  384. package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +6 -0
  385. package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +2 -1
  386. package/templates/be/patterns/realtime/channel.graphql.options.ts.tpl +2 -0
  387. package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +6 -0
  388. package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +110 -0
  389. package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +7 -0
  390. package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +6 -0
  391. package/templates/be/patterns/saga/saga.message.module.ts.tpl +6 -3
  392. package/templates/be/patterns/saga/saga.message.options.ts.tpl +2 -0
  393. package/templates/be/patterns/saga/saga.module-definition.ts.tpl +6 -0
  394. package/templates/be/patterns/saga/saga.module.ts.tpl +2 -1
  395. package/templates/be/patterns/saga/saga.options.ts.tpl +2 -0
  396. package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +3 -3
  397. package/templates/be/patterns/webhooks/webhook.door.ts.tpl +3 -3
  398. package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +6 -0
  399. package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +2 -1
  400. package/templates/be/patterns/webhooks/webhook.http.options.ts.tpl +2 -0
  401. package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +80 -26
  402. package/templates/be/platform/graphql/localize-errors.mapper.ts +2 -5
  403. package/templates/be/skeleton/apps/__app__/src/__app__.env.ts +21 -0
  404. package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +1 -1
  405. package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -45
  406. package/templates/be/skeleton/apps/__app__/src/compose/api.ts +4 -0
  407. package/templates/be/skeleton/apps/__app__/src/compose/domain.ts +11 -0
  408. package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +44 -0
  409. package/templates/be/skeleton/apps/__app__/src/compose/system.ts +5 -0
  410. package/templates/be/skeleton/apps/__app__/src/compose/webhooks.ts +4 -0
  411. package/templates/be/skeleton/apps/__app__/src/main.ts +18 -10
  412. package/templates/be/skeleton/apps/cli/src/app.module.ts +3 -13
  413. package/templates/be/skeleton/apps/cli/src/cli.env.ts +13 -0
  414. package/templates/be/skeleton/apps/cli/src/cli.options.ts +7 -17
  415. package/templates/be/skeleton/apps/cli/src/compose/cli.ts +5 -0
  416. package/templates/be/skeleton/apps/cli/src/compose/platform.ts +24 -0
  417. package/templates/be/skeleton/apps/cli/src/main.ts +3 -2
  418. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.contracts.ts +2 -0
  419. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.handler.ts +22 -0
  420. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.query.ts +11 -0
  421. package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +6 -0
  422. package/templates/be/skeleton/src/features/api/system-health/system-health.module.ts +5 -3
  423. package/templates/be/skeleton/src/features/api/system-health/system-health.options.ts +2 -0
  424. package/templates/be/skeleton/src/features/api/system-health/transport/http/ready.controller.ts +19 -0
  425. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +6 -0
  426. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module.ts +8 -3
  427. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.options.ts +2 -0
  428. package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +6 -0
  429. package/templates/be/skeleton/src/features/cli/cli.module.ts +3 -2
  430. package/templates/be/skeleton/src/features/cli/cli.options.ts +2 -0
  431. package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +6 -0
  432. package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +2 -1
  433. package/templates/be/skeleton/src/features/cli/migrate/migrate.options.ts +2 -0
  434. package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +6 -0
  435. package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +2 -1
  436. package/templates/be/skeleton/src/features/cli/seed/seed.options.ts +2 -0
  437. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +5 -6
  438. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +56 -4
  439. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.ts +36 -5
  440. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +39 -8
  441. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.ts +14 -8
  442. package/templates/be/skeleton/src/modules/domain/identity/credential.contracts.ts +26 -0
  443. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.spec.ts +119 -0
  444. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.ts +43 -0
  445. package/templates/be/skeleton/src/modules/domain/identity/identity.contracts.ts +16 -0
  446. package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -1
  447. package/templates/be/skeleton/src/modules/domain/identity/identity.module.ts +22 -4
  448. package/templates/be/skeleton/src/modules/domain/identity/identity.options.ts +8 -2
  449. package/templates/be/skeleton/src/modules/domain/identity/index.ts +1 -0
  450. package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +6 -6
  451. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +11 -4
  452. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.spec.ts +32 -0
  453. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.ts +14 -0
  454. package/templates/be/skeleton/src/modules/platform/database/database.module.ts +7 -1
  455. package/templates/be/skeleton/src/modules/platform/database/index.ts +2 -0
  456. package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +8 -0
  457. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +25 -6
  458. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +16 -6
  459. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.spec.ts +130 -0
  460. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.ts +58 -0
  461. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +3 -0
  462. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -4
  463. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +16 -10
  464. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +3 -1
  465. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +2 -0
  466. package/templates/be/skeleton/src/modules/platform/http-security/index.ts +5 -0
  467. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +12 -5
  468. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +13 -8
  469. package/templates/be/skeleton/src/modules/platform/http-security/readiness.contracts.ts +5 -0
  470. package/templates/be/skeleton/src/modules/platform/http-security/readiness.decorators.ts +7 -0
  471. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.spec.ts +86 -0
  472. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.ts +39 -0
  473. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +139 -131
  474. package/templates/be/skeleton/src/modules/platform/i18n/index.ts +1 -1
  475. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +91 -67
  476. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +5 -4
  477. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +0 -2
  478. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +20 -6
  479. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +16 -4
  480. package/templates/be/skeleton/src/modules/platform/retry/index.ts +3 -0
  481. package/templates/be/skeleton/src/modules/platform/retry/retry.decorators.ts +9 -0
  482. package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +7 -0
  483. package/templates/be/skeleton/src/modules/platform/retry/retry.module.ts +19 -0
  484. package/templates/be/skeleton/src/modules/platform/retry/retry.options.ts +2 -0
  485. package/templates/be/skeleton/src/modules/platform/retry/retry.port.ts +11 -0
  486. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.spec.ts +36 -0
  487. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.ts +10 -0
  488. package/templates/be/skeleton/src/tests/contract/http-security/http-security.contract-spec.ts +54 -0
  489. package/templates/be/skeleton-lite/apps/api/src/api.env.ts +25 -0
  490. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +1 -1
  491. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +18 -58
  492. package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +4 -0
  493. package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +10 -0
  494. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +16 -0
  495. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +47 -0
  496. package/templates/be/skeleton-lite/apps/api/src/compose/system.ts +5 -0
  497. package/templates/be/skeleton-lite/apps/api/src/compose/webhooks.ts +4 -0
  498. package/templates/be/skeleton-lite/apps/api/src/main.ts +5 -13
  499. package/templates/be/skeleton-lite/apps/cli/src/cli.env.ts +17 -0
  500. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +12 -0
  501. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.spec.ts +71 -5
  502. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +29 -2
  503. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +68 -15
  504. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +14 -9
  505. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +9 -2
  506. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +16 -1
  507. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +19 -4
  508. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +5 -1
  509. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -1
  510. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -3
  511. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +17 -5
  512. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +24 -13
  513. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +5 -0
  514. package/templates/be/skeleton-lite/src/modules/platform/database/errors/database.error.ts +16 -0
  515. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +3 -0
  516. package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +5 -2
  517. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +4 -1
  518. package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +1 -1
  519. package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +7 -0
  520. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +29 -0
  521. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +16 -0
  522. package/templates/be/table/api/feature.module-definition.ts.tpl +6 -0
  523. package/templates/be/table/api/feature.module.ts.tpl +2 -1
  524. package/templates/be/table/api/feature.options.ts.tpl +2 -0
  525. package/templates/be/table/api/graphql.module-definition.ts.tpl +6 -0
  526. package/templates/be/table/api/graphql.module.ts.tpl +3 -2
  527. package/templates/be/table/api/graphql.options.ts.tpl +2 -0
  528. package/templates/be/table/migration.sql.tpl +1 -0
  529. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +4 -6
  530. package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +7 -0
  531. package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +5 -0
  532. package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +9 -0
  533. package/templates/be/upgrade-full/apps/cli/src/main.ts +3 -0
  534. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +3 -2
  535. package/templates/fe/auth/supabase-client.ts.tpl +34 -0
  536. package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +11 -0
  537. package/templates/fe/skeleton/apps/__app__/src/components/blocks/SignInForm/index.tsx +6 -1
  538. package/templates/fe/skeleton/apps/__app__/src/components/leaves/Icon/index.tsx +4 -0
  539. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/component.tsx +31 -0
  540. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/index.tsx +24 -0
  541. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +6 -1
  542. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +3 -4
  543. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/index.ts +1 -0
  544. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +15 -0
  545. package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +18 -0
  546. package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +19 -0
  547. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +7 -1
  548. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/en.json +57 -0
  549. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +6 -0
  550. package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/en.json +31 -0
  551. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +1 -1
  552. package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +1 -0
  553. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +1 -34
  554. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +7 -1
  555. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +57 -0
  556. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +30 -24
  557. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +1 -1
  558. package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
  559. package/templates/fe/transport/graphql/clients/links/locale.ts +2 -2
  560. package/upgrade/check.mjs +3 -1
  561. package/upgrade/file-state.mjs +68 -0
  562. package/upgrade/index.mjs +58 -94
  563. package/runtime/scripts/api/git/log.mjs +0 -5
  564. package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +0 -55
@@ -1,46 +1,75 @@
1
- import { fakeTransaction, mock, mockEntityManager } from "@starci/jest-preset"
1
+ import { fakeInbox, fakeTransaction, mockEntityManager, recordingEventBus } from "@starci/jest-preset"
2
2
  import { Test } from "@nestjs/testing"
3
- import { @@service@@ } from "@@serviceModule@@"
4
3
  import { PRIMARY_ENTITY_MANAGER } from "@modules/platform/database"
5
- import { COMPLETE_@@providerUpper@@_INBOX, INSERT_@@providerUpper@@_INBOX } from "./persistence/@@provider@@-inbox.sql"
4
+ import { @@Provider@@DeliveryRecordedEvent } from "@modules/events/@@provider@@"
5
+ import { EVENT_BUS } from "@modules/platform/event-bus"
6
+ import { INBOX } from "@modules/platform/inbox"
7
+ import { INSERT_@@providerUpper@@_INBOX } from "./persistence/@@provider@@-inbox.sql"
6
8
  import { @@Provider@@InboxService } from "./@@provider@@-inbox.service"
7
9
 
8
10
  const DELIVERY = { id: "delivery-1" }
9
11
 
10
- const build = async (claimed: ReadonlyArray<{ readonly id: string }>) => {
11
- const entityManager = mockEntityManager({
12
- query: [[INSERT_@@providerUpper@@_INBOX, claimed], [COMPLETE_@@providerUpper@@_INBOX, claimed]],
13
- })
12
+ const build = async (fresh: boolean, recorded: ReadonlyArray<{ readonly id: string }> = [{ id: "inbox-1" }]) => {
13
+ const entityManager = mockEntityManager({ query: [INSERT_@@providerUpper@@_INBOX, recorded] })
14
14
  const transaction = fakeTransaction(entityManager)
15
- const deliveries = mock<@@service@@>()
15
+ const claims = fakeInbox()
16
+ if (!fresh) claims.seen("webhook.@@provider@@", "delivery-1")
17
+ const events = recordingEventBus<@@Provider@@DeliveryRecordedEvent>()
16
18
  const moduleRef = await Test.createTestingModule({
17
19
  providers: [
18
20
  @@Provider@@InboxService,
19
21
  { provide: PRIMARY_ENTITY_MANAGER, useValue: entityManager },
20
- { provide: @@service@@, useValue: deliveries },
22
+ { provide: INBOX, useValue: claims },
23
+ { provide: EVENT_BUS, useValue: events },
21
24
  ],
22
25
  }).compile()
23
- return { inbox: moduleRef.get(@@Provider@@InboxService), deliveries, transaction }
26
+ return { inbox: moduleRef.get(@@Provider@@InboxService), claims, events, entityManager, transaction }
24
27
  }
25
28
 
26
29
  describe("@@Provider@@InboxService", () => {
27
- describe("accept@@Provider@@Delivery", () => {
28
- it("claims a new @@provider@@ delivery and runs the domain intake inside the claiming transaction", async () => {
29
- const { inbox, deliveries, transaction } = await build([{ id: "inbox-1" }])
30
+ describe("record@@Provider@@Delivery", () => {
31
+ it("claims, records and publishes a typed event in the same transaction", async () => {
32
+ const { inbox, claims, events, entityManager, transaction } = await build(true)
33
+
34
+ await inbox.record@@Provider@@Delivery({ eventId: "delivery-1", delivery: DELIVERY })
35
+
36
+ expect(entityManager.query).toHaveBeenCalledTimes(1)
37
+ expect(entityManager.query).toHaveBeenCalledWith(INSERT_@@providerUpper@@_INBOX, ["@@provider@@", "delivery-1", JSON.stringify(DELIVERY)])
38
+ expect(claims.claims).toEqual([{ source: "webhook.@@provider@@", eventId: "delivery-1" }])
39
+ expect(events.events).toEqual([@@Provider@@DeliveryRecordedEvent.create({ receiptId: "inbox-1" })])
40
+ expect(events.allInTransaction).toBe(true)
41
+ expect(events.entries[0]?.tx).not.toBe(entityManager)
42
+ expect(transaction.commits).toBe(1)
43
+ })
44
+
45
+ it("acknowledges a duplicate @@provider@@ delivery without further work", async () => {
46
+ const { inbox, events, entityManager, transaction } = await build(false)
47
+
48
+ await inbox.record@@Provider@@Delivery({ eventId: "delivery-1", delivery: DELIVERY })
49
+
50
+ expect(entityManager.query).not.toHaveBeenCalled()
51
+ expect(events.events).toEqual([])
52
+ expect(transaction.commits).toBe(1)
53
+ })
54
+
55
+ it("does not publish for an already recorded receipt", async () => {
56
+ const { inbox, events, transaction } = await build(true, [])
30
57
 
31
- await inbox.accept@@Provider@@Delivery("delivery-1", DELIVERY)
58
+ await inbox.record@@Provider@@Delivery({ eventId: "delivery-1", delivery: DELIVERY })
32
59
 
33
- expect(deliveries.accept@@Provider@@Delivery).toHaveBeenCalledTimes(1)
34
- expect(deliveries.accept@@Provider@@Delivery).toHaveBeenCalledWith(DELIVERY, expect.anything())
60
+ expect(events.events).toEqual([])
35
61
  expect(transaction.commits).toBe(1)
36
62
  })
37
63
 
38
- it("acknowledges a duplicate @@provider@@ delivery without running the domain intake again", async () => {
39
- const { inbox, deliveries } = await build([])
64
+ it("rolls back the receipt when publishing fails", async () => {
65
+ const { inbox, events, transaction } = await build(true)
66
+ const failure = new Error("outbox unavailable")
67
+ events.failNext("publish", failure)
40
68
 
41
- await inbox.accept@@Provider@@Delivery("delivery-1", DELIVERY)
69
+ await expect(inbox.record@@Provider@@Delivery({ eventId: "delivery-1", delivery: DELIVERY })).rejects.toBe(failure)
42
70
 
43
- expect(deliveries.accept@@Provider@@Delivery).not.toHaveBeenCalled()
71
+ expect(transaction.rollbacks).toBe(1)
72
+ expect(transaction.commits).toBe(0)
44
73
  })
45
74
  })
46
75
  })
@@ -1,33 +1,44 @@
1
1
  import { Injectable } from "@nestjs/common"
2
2
  import type { EntityManager } from "typeorm"
3
- import { @@service@@ } from "@@serviceModule@@"
4
- import { acceptWebhookDelivery, InjectPrimaryEntityManager } from "@modules/platform/database"
5
- import { COMPLETE_@@providerUpper@@_INBOX, INSERT_@@providerUpper@@_INBOX } from "./persistence/@@provider@@-inbox.sql"
3
+ import { InjectPrimaryEntityManager, recordWebhookDelivery } from "@modules/platform/database"
4
+ import { @@Provider@@DeliveryRecordedEvent } from "@modules/events/@@provider@@"
5
+ import { InjectEventBus } from "@modules/platform/event-bus"
6
+ import type { EventBus } from "@modules/platform/event-bus"
7
+ import { InjectInbox } from "@modules/platform/inbox"
8
+ import type { Inbox } from "@modules/platform/inbox"
9
+ import { INSERT_@@providerUpper@@_INBOX } from "./persistence/@@provider@@-inbox.sql"
6
10
 
7
11
  interface @@Provider@@Delivery {
8
12
  readonly id: string
9
13
  }
10
14
 
11
- type @@Provider@@DeliveryId = string | undefined
15
+ interface @@Provider@@DeliveryRequest {
16
+ readonly eventId: string | undefined
17
+ readonly delivery: @@Provider@@Delivery
18
+ }
12
19
 
13
20
  @Injectable()
14
- /** Claims each @@provider@@ delivery once, then runs the domain intake and completion marker in the same transaction. */
21
+ /** Records each @@provider@@ delivery once. Pending rows are consumed by a separate reactor or job after commit. */
15
22
  export class @@Provider@@InboxService {
16
23
  constructor(
17
24
  @InjectPrimaryEntityManager() private readonly entityManager: EntityManager,
18
- private readonly deliveries: @@service@@,
25
+ @InjectInbox() private readonly inbox: Inbox,
26
+ @InjectEventBus() private readonly eventBus: EventBus,
19
27
  ) {}
20
28
 
21
- /** A duplicate is acknowledged without invoking the domain intake a second time. */
22
- accept@@Provider@@Delivery(deliveryId: @@Provider@@DeliveryId, delivery: @@Provider@@Delivery): Promise<void> {
23
- return acceptWebhookDelivery({
24
- entityManager: this.entityManager,
25
- insert: INSERT_@@providerUpper@@_INBOX,
26
- complete: COMPLETE_@@providerUpper@@_INBOX,
27
- provider: "@@provider@@",
28
- deliveryId,
29
- payload: delivery,
30
- process: (manager) => this.deliveries.accept@@Provider@@Delivery(delivery, manager),
29
+ /** A duplicate is acknowledged without creating another pending row; this intake never dispatches a command. */
30
+ record@@Provider@@Delivery(request: @@Provider@@DeliveryRequest): Promise<void> {
31
+ return this.entityManager.transaction(async (manager) => {
32
+ if (!(await this.inbox.claim("webhook.@@provider@@", request.eventId, manager))) return
33
+ const receiptId = await recordWebhookDelivery({
34
+ manager,
35
+ insert: INSERT_@@providerUpper@@_INBOX,
36
+ provider: "@@provider@@",
37
+ deliveryId: request.eventId,
38
+ payload: request.delivery,
39
+ })
40
+ if (receiptId === undefined) return
41
+ await this.eventBus.publish(@@Provider@@DeliveryRecordedEvent.create({ receiptId }), manager)
31
42
  })
32
43
  }
33
44
  }
@@ -1,12 +1,12 @@
1
1
  import { sql } from "@modules/platform/database"
2
2
 
3
- /** Claims one @@provider@@ delivery and returns its durable inbox identity only to the winning transaction. */
3
+ /** Records one pending @@provider@@ delivery and returns its durable inbox identity only to the winning transaction. */
4
4
  export const INSERT_@@providerUpper@@_INBOX = sql`INSERT INTO public.@@table@@ (provider, delivery_id, payload)
5
5
  VALUES ($1, $2, $3::jsonb)
6
6
  ON CONFLICT (provider, delivery_id) DO NOTHING
7
7
  RETURNING id`
8
8
 
9
- /** Marks the claimed delivery complete before the same transaction commits. */
9
+ /** The reactor or job marks a recorded delivery complete in the transaction that applies its effect. Never called by the intake. */
10
10
  export const COMPLETE_@@providerUpper@@_INBOX = sql`WITH completed AS (
11
11
  UPDATE public.@@table@@
12
12
  SET processed_at = now()
@@ -57,8 +57,8 @@ describe("@@Provider@@Webhook", () => {
57
57
 
58
58
  await door.receive(REQUEST, sign(RAW_BODY, SIGNED_AT), SIGNED_AT, DELIVERY_ID, DELIVERY)
59
59
 
60
- expect(deliveries.accept@@Provider@@Delivery).toHaveBeenCalledTimes(1)
61
- expect(deliveries.accept@@Provider@@Delivery).toHaveBeenCalledWith(DELIVERY_ID, DELIVERY)
60
+ expect(deliveries.record@@Provider@@Delivery).toHaveBeenCalledTimes(1)
61
+ expect(deliveries.record@@Provider@@Delivery).toHaveBeenCalledWith({ eventId: DELIVERY_ID, delivery: DELIVERY })
62
62
  })
63
63
 
64
64
  it("never reaches the inbox when the signature is not the provider's", async () => {
@@ -69,7 +69,7 @@ describe("@@Provider@@Webhook", () => {
69
69
  door.receive(REQUEST, sign(Buffer.from("other body"), SIGNED_AT), SIGNED_AT, DELIVERY_ID, DELIVERY),
70
70
  ).rejects.toMatchObject({ code: "HTTP_SECURITY_WEBHOOK_SIGNATURE_INVALID" })
71
71
 
72
- expect(deliveries.accept@@Provider@@Delivery).not.toHaveBeenCalled()
72
+ expect(deliveries.record@@Provider@@Delivery).not.toHaveBeenCalled()
73
73
  })
74
74
  })
75
75
  })
@@ -10,7 +10,8 @@ import { @@Event@@Request } from "./dto/@@event@@.request"
10
10
  @Controller("webhooks/@@provider@@")
11
11
  /**
12
12
  * POST /webhooks/@@provider@@, the signed intake door of the @@provider@@ notifier. The signature and replay window are proven
13
- * first; the verified delivery goes to one domain intake that claims its inbox row and processes it in one transaction.
13
+ * first; the verified delivery goes to one domain intake that records a receipt and typed outbox event in its transaction. A separate
14
+ * reactor or job consumes the committed row and dispatches the command.
14
15
  */
15
16
  export class @@Provider@@Webhook {
16
17
  constructor(
@@ -18,7 +19,7 @@ export class @@Provider@@Webhook {
18
19
  private readonly deliveries: @@Provider@@InboxService,
19
20
  ) {}
20
21
 
21
- /** Acknowledges with 204 and no body: a duplicate receives the same acknowledgement without being processed again. */
22
+ /** Acknowledges with 204 and no body: a duplicate receives the same acknowledgement without another inbox row. */
22
23
  @Post()
23
24
  @HttpCode(HttpStatus.NO_CONTENT)
24
25
  @Public({ reason: PublicReason.SignedWebhook })
@@ -31,6 +32,6 @@ export class @@Provider@@Webhook {
31
32
  @Body() delivery: @@Event@@Request,
32
33
  ): Promise<void> {
33
34
  this.signature.verify({ provider: "@@provider@@", rawBody: request.rawBody, signature, timestamp })
34
- await this.deliveries.accept@@Provider@@Delivery(deliveryId, delivery)
35
+ await this.deliveries.record@@Provider@@Delivery({ eventId: deliveryId, delivery })
35
36
  }
36
37
  }
@@ -0,0 +1,6 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { @@Feature@@ApiOptions } from "./@@feature@@.options"
3
+
4
+ /** The configurable-module base of the @@feature@@ module. */
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ new ConfigurableModuleBuilder<@@Feature@@ApiOptions>().setClassMethodName("register").build()
@@ -1,6 +1,7 @@
1
1
  import { Module } from "@nestjs/common"
2
+ import { ConfigurableModuleClass } from "./@@feature@@.module-definition"
2
3
  import { @@Action@@Handler } from "./application/@@action@@.handler"
3
4
 
4
5
  @Module({ providers: [@@Action@@Handler] })
5
6
  /** The @@feature@@ feature: the handlers of its application. */
6
- export class @@Feature@@ApiModule {}
7
+ export class @@Feature@@ApiModule extends ConfigurableModuleClass {}
@@ -0,0 +1,2 @@
1
+ /** The @@feature@@ module takes no options yet. */
2
+ export type @@Feature@@ApiOptions = Record<string, never>
@@ -0,0 +1,6 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { @@Feature@@GraphqlOptions } from "./@@feature@@-graphql.options"
3
+
4
+ /** The configurable-module base of the @@feature@@-graphql module. */
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ new ConfigurableModuleBuilder<@@Feature@@GraphqlOptions>().setClassMethodName("register").build()
@@ -1,7 +1,8 @@
1
1
  import { Module } from "@nestjs/common"
2
2
  import { @@Feature@@ApiModule } from "../../@@feature@@.module"
3
3
  import { @@Action@@Resolver } from "./@@action@@.resolver"
4
+ import { ConfigurableModuleClass } from "./@@feature@@-graphql.module-definition"
4
5
 
5
- @Module({ imports: [@@Feature@@ApiModule], providers: [@@Action@@Resolver] })
6
+ @Module({ imports: [@@Feature@@ApiModule.register({})], providers: [@@Action@@Resolver] })
6
7
  /** The GraphQL transport of the @@feature@@ feature. */
7
- export class @@Feature@@GraphqlModule {}
8
+ export class @@Feature@@GraphqlModule extends ConfigurableModuleClass {}
@@ -0,0 +1,2 @@
1
+ /** The @@feature@@-graphql module takes no options yet. */
2
+ export type @@Feature@@GraphqlOptions = Record<string, never>
@@ -0,0 +1,6 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { @@Group@@Options } from "./@@group@@.options"
3
+
4
+ /** The configurable-module base of the @@group@@ module. */
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ new ConfigurableModuleBuilder<@@Group@@Options>().setClassMethodName("register").build()
@@ -1,7 +1,8 @@
1
1
  import { Module } from "@nestjs/common"
2
2
  import { @@Group@@Cli } from "./@@group@@.cli"
3
+ import { ConfigurableModuleClass } from "./@@group@@.module-definition"
3
4
  import { RunCli } from "./subs/run.cli"
4
5
 
5
6
  @Module({ providers: [@@Group@@Cli, RunCli] })
6
7
  /** The @@group@@ group: its command and its sub-commands; the work is the runner service the sub-command delegates to. */
7
- export class @@Group@@Module {}
8
+ export class @@Group@@Module extends ConfigurableModuleClass {}
@@ -0,0 +1,2 @@
1
+ /** The @@group@@ module takes no options yet. */
2
+ export type @@Group@@Options = Record<string, never>
@@ -3,8 +3,6 @@ import type { ErrorKind } from "@modules/platform/errors"
3
3
 
4
4
  /** Codes of the event-bus capability. */
5
5
  export enum EventBusErrorCode {
6
- /** A delivered envelope does not have the shape its consumer's event class reads. */
7
- EnvelopeInvalid = "EVENT_BUS_ENVELOPE_INVALID",
8
6
  /** The broker did not answer a call within its deadline, or refused it. */
9
7
  BrokerUnavailable = "EVENT_BUS_BROKER_UNAVAILABLE",
10
8
  /** A dead letter id does not name a message the dead-letter topic holds. */
@@ -13,7 +11,6 @@ export enum EventBusErrorCode {
13
11
 
14
12
  /** How each event-bus code travels. */
15
13
  export const EVENT_BUS_ERROR_KINDS: Record<EventBusErrorCode, ErrorKind> = {
16
- [EventBusErrorCode.EnvelopeInvalid]: "internal",
17
14
  [EventBusErrorCode.BrokerUnavailable]: "unavailable",
18
15
  [EventBusErrorCode.DeadLetterUnknown]: "not-found",
19
16
  }
@@ -3,12 +3,10 @@ import type { MessageBundle } from "@modules/platform/i18n"
3
3
  /** Display text of the event-bus codes, Vietnamese and English. */
4
4
  export const EVENT_BUS_MESSAGES: MessageBundle = {
5
5
  vi: {
6
- "errors.EVENT_BUS_ENVELOPE_INVALID": "Sự kiện {{event}} nhận được có nội dung không đúng dạng.",
7
6
  "errors.EVENT_BUS_BROKER_UNAVAILABLE": "Kênh sự kiện {{operation}} không phản hồi kịp.",
8
7
  "errors.EVENT_BUS_DEAD_LETTER_UNKNOWN": "Không có sự kiện lỗi {{id}} để đưa lại.",
9
8
  },
10
9
  en: {
11
- "errors.EVENT_BUS_ENVELOPE_INVALID": "The received {{event}} event has a payload of the wrong shape.",
12
10
  "errors.EVENT_BUS_BROKER_UNAVAILABLE": "The event broker did not answer {{operation}} in time.",
13
11
  "errors.EVENT_BUS_DEAD_LETTER_UNKNOWN": "There is no dead letter {{id}} to requeue.",
14
12
  },
@@ -0,0 +1,6 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { @@Job@@Options } from "./@@job@@.options"
3
+
4
+ /** The configurable-module base of the @@job@@ module. */
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ new ConfigurableModuleBuilder<@@Job@@Options>().setClassMethodName("register").build()
@@ -1,6 +1,7 @@
1
1
  import { Module } from "@nestjs/common"
2
+ import { ConfigurableModuleClass } from "./@@job@@.module-definition"
2
3
  import { @@Step@@Step } from "./steps/@@step@@.step"
3
4
 
4
5
  @Module({ providers: [@@Step@@Step], exports: [@@Step@@Step] })
5
6
  /** The application of the @@job@@ job: its steps; the queue transport module imports it. */
6
- export class @@Job@@Module {}
7
+ export class @@Job@@Module extends ConfigurableModuleClass {}
@@ -0,0 +1,2 @@
1
+ /** The @@job@@ module takes no options yet. */
2
+ export type @@Job@@Options = Record<string, never>
@@ -0,0 +1,6 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { @@Job@@QueueOptions } from "./@@job@@-queue.options"
3
+
4
+ /** The configurable-module base of the @@job@@-queue module. */
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ new ConfigurableModuleBuilder<@@Job@@QueueOptions>().setClassMethodName("register").build()
@@ -3,15 +3,18 @@ import type { OnModuleInit } from "@nestjs/common"
3
3
  import { InjectJobProcessorRegistry } from "@modules/platform/jobs"
4
4
  import type { JobProcessorRegistry } from "@modules/platform/jobs"
5
5
  import { @@Job@@Module } from "../../@@job@@.module"
6
+ import { ConfigurableModuleClass } from "./@@job@@-queue.module-definition"
6
7
  import { @@Job@@Processor } from "./@@job@@.processor"
7
8
 
8
- @Module({ imports: [@@Job@@Module], providers: [@@Job@@Processor] })
9
+ @Module({ imports: [@@Job@@Module.register({})], providers: [@@Job@@Processor] })
9
10
  /** The queue transport of the @@job@@ job: its processor, which a worker or api app composes and which subscribes to its queue when the module starts. */
10
- export class @@Job@@QueueModule implements OnModuleInit {
11
+ export class @@Job@@QueueModule extends ConfigurableModuleClass implements OnModuleInit {
11
12
  constructor(
12
13
  @InjectJobProcessorRegistry() private readonly registry: JobProcessorRegistry,
13
14
  private readonly @@jobCamel@@Processor: @@Job@@Processor,
14
- ) {}
15
+ ) {
16
+ super()
17
+ }
15
18
 
16
19
  /** Hands the processor to the registry. */
17
20
  onModuleInit(): void {
@@ -0,0 +1,2 @@
1
+ /** The @@job@@-queue module takes no options yet. */
2
+ export type @@Job@@QueueOptions = Record<string, never>
@@ -1,3 +1,4 @@
1
+ import { Test } from "@nestjs/testing"
1
2
  import type { ClaimedJob } from "./jobs.contracts"
2
3
  import { FencedProcessor } from "./fenced.processor"
3
4
 
@@ -21,7 +22,8 @@ class MailProcessor extends FencedProcessor {
21
22
 
22
23
  describe("FencedProcessor", () => {
23
24
  it("lets a processor declare its queue and process a claimed job", async () => {
24
- const processor = new MailProcessor()
25
+ const moduleRef = await Test.createTestingModule({ providers: [MailProcessor] }).compile()
26
+ const processor = moduleRef.get(MailProcessor)
25
27
 
26
28
  await processor.process(claimedJob)
27
29
 
@@ -1,6 +1,10 @@
1
+ import { Test } from "@nestjs/testing"
1
2
  import { fakeTransaction, mock, mockEntityManager } from "@starci/jest-preset"
2
3
  import type { MockEntityManager } from "@starci/jest-preset"
3
4
  import type { EntityManager } from "typeorm"
5
+ import { injector } from "@modules/platform/composition"
6
+ import type { TypedParameterDecorator } from "@modules/platform/composition"
7
+ import { InjectLogger, LOGGER } from "@modules/platform/logging"
4
8
  import type { Logger } from "@modules/platform/logging"
5
9
  import { OutboxLogEvent } from "./outbox.log-events"
6
10
  import { OutboxRelayPolicy } from "./outbox-relay.policy"
@@ -9,25 +13,37 @@ interface Row {
9
13
  readonly id: string
10
14
  }
11
15
 
16
+ const OUTBOX_MANAGERS: unique symbol = Symbol("spec.outbox.managers")
17
+
18
+ /** Injects the entity managers of the test relay. Parameter type: ReadonlyArray<EntityManager>. */
19
+ const InjectOutboxManagers = (): TypedParameterDecorator<ReadonlyArray<EntityManager>> =>
20
+ injector<ReadonlyArray<EntityManager>>(OUTBOX_MANAGERS)
21
+
12
22
  class ProbeRelay extends OutboxRelayPolicy<Row> {
13
23
  readonly select = jest.fn<Promise<Array<Row>>, [EntityManager]>()
14
24
  readonly deliver = jest.fn<Promise<void>, [ReadonlyArray<Row>]>()
15
25
  readonly mark = jest.fn<Promise<void>, [EntityManager, ReadonlyArray<Row>]>()
16
26
  readonly idle = jest.fn<Promise<void>, []>()
27
+ protected readonly outbox = "probe"
17
28
 
18
29
  constructor(
19
- protected readonly managers: ReadonlyArray<EntityManager>,
20
- protected readonly logger: Logger,
21
- protected readonly outbox = "probe",
30
+ @InjectOutboxManagers() protected readonly managers: ReadonlyArray<EntityManager>,
31
+ @InjectLogger() protected readonly logger: Logger,
22
32
  ) {
23
33
  super()
24
34
  }
25
35
  }
26
36
 
27
- const build = (...managers: Array<MockEntityManager>) => {
37
+ const build = async (...managers: Array<MockEntityManager>) => {
28
38
  const logger = mock<Logger>()
29
- const relay = new ProbeRelay(managers, logger)
30
- return { relay, logger }
39
+ const moduleRef = await Test.createTestingModule({
40
+ providers: [
41
+ ProbeRelay,
42
+ { provide: OUTBOX_MANAGERS, useValue: [...managers] },
43
+ { provide: LOGGER, useValue: logger },
44
+ ],
45
+ }).compile()
46
+ return { relay: moduleRef.get(ProbeRelay), logger }
31
47
  }
32
48
 
33
49
  describe("OutboxRelayPolicy", () => {
@@ -35,7 +51,7 @@ describe("OutboxRelayPolicy", () => {
35
51
  it("delivers and marks every connection's waiting rows in its own transaction", async () => {
36
52
  const first = fakeTransaction(mockEntityManager())
37
53
  const second = fakeTransaction(mockEntityManager())
38
- const { relay } = build(first.em, second.em)
54
+ const { relay } = await build(first.em, second.em)
39
55
  const rows = [{ id: "row-1" }, { id: "row-2" }]
40
56
  relay.select.mockResolvedValueOnce(rows).mockResolvedValueOnce([])
41
57
  relay.deliver.mockResolvedValue(undefined)
@@ -52,7 +68,7 @@ describe("OutboxRelayPolicy", () => {
52
68
 
53
69
  it("leaves the rows unmarked when delivery fails", async () => {
54
70
  const transaction = fakeTransaction(mockEntityManager())
55
- const { relay } = build(transaction.em)
71
+ const { relay } = await build(transaction.em)
56
72
  const failure = new Error("transport down")
57
73
  relay.select.mockResolvedValue([{ id: "row-1" }])
58
74
  relay.deliver.mockRejectedValue(failure)
@@ -66,7 +82,7 @@ describe("OutboxRelayPolicy", () => {
66
82
 
67
83
  describe("the loop", () => {
68
84
  it("repeats immediately after work, idles after an empty or failed pass, logs the failure and stops on shutdown", async () => {
69
- const { relay, logger } = build()
85
+ const { relay, logger } = await build()
70
86
  const failure = new Error("outbox unreadable")
71
87
  jest.spyOn(relay, "relay").mockResolvedValueOnce(1).mockResolvedValueOnce(0).mockRejectedValueOnce(failure)
72
88
  let waits = 0
@@ -2,7 +2,7 @@ import type { EnvSource } from "@modules/platform/config"
2
2
  import type { QueueOptions } from "./queue.options"
3
3
 
4
4
  /** What the app's `main.ts` reads for the queues; the connections and the schedulers are the app root's choice. */
5
- export type QueueConfig = Omit<QueueOptions, "connections" | "schedulers">
5
+ export type QueueConfig = Omit<QueueOptions, "connections" | "schedulers" | "outboxOnly">
6
6
 
7
7
  /** Reads the queue options: the Redis address is required, the rest are tunables with literal defaults. */
8
8
  export const parseQueueConfig = (env: EnvSource): QueueConfig => ({
@@ -1,7 +1,7 @@
1
1
  export { parseQueueConfig } from "./queue.config"
2
2
  export type { QueueConfig } from "./queue.config"
3
3
  export type { QueueDelivery, QueueSchedulerDefinition } from "./queue.contracts"
4
- export { InjectQueueOutbox, InjectQueueWorkerRegistry, QUEUE_WORKER_REGISTRY } from "./queue.decorators"
4
+ export { InjectQueueOutbox, InjectQueueWorkerRegistry, QUEUE_OUTBOX, QUEUE_WORKER_REGISTRY } from "./queue.decorators"
5
5
  export { QueueModule } from "./queue.module"
6
6
  export type { QueueOutbox, QueueWorkerRegistry } from "./queue.port"
7
7
  export { queueEntities, queueMigrations } from "./persistence/connection"
@@ -1,9 +1,9 @@
1
1
  import { ConfigurableModuleBuilder } from "@nestjs/common"
2
2
  import { QUEUE_OPTIONS } from "./queue.decorators"
3
- import type { QueueOptions } from "./queue.options"
3
+ import type { QueueRegistrationOptions } from "./queue.options"
4
4
 
5
5
  /** The configurable-module base of the queues; `isGlobal` is decided by the app root. */
6
- export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<QueueOptions>({
6
+ export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<QueueRegistrationOptions>({
7
7
  optionsInjectionToken: QUEUE_OPTIONS,
8
8
  })
9
9
  .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
@@ -28,6 +28,17 @@ export class QueueModule extends ConfigurableModuleClass {
28
28
  /** Registers the capability once per app that enqueues or processes jobs. */
29
29
  static register(options: typeof OPTIONS_TYPE): DynamicModule {
30
30
  const base = super.register(options)
31
+ if (options.outboxOnly === true) {
32
+ return {
33
+ ...base,
34
+ providers: [
35
+ ...(base.providers ?? []),
36
+ QueueOutboxService,
37
+ { provide: QUEUE_OUTBOX, useExisting: QueueOutboxService },
38
+ ],
39
+ exports: [QUEUE_OUTBOX],
40
+ }
41
+ }
31
42
  return {
32
43
  ...base,
33
44
  providers: [
@@ -3,6 +3,8 @@ import type { QueueSchedulerDefinition } from "./queue.contracts"
3
3
 
4
4
  /** What the queues of one app need: where Redis is, how the relay and the workers run, which connections hold an outbox, and the schedulers it registers. */
5
5
  export interface QueueOptions {
6
+ /** The runtime registration owns the relay, workers and schedulers. */
7
+ readonly outboxOnly?: false
6
8
  /** The Redis host BullMQ talks to. */
7
9
  readonly redisHost: string
8
10
  /** The Redis port. */
@@ -22,3 +24,11 @@ export interface QueueOptions {
22
24
  /** The job schedulers the app registers at boot; BullMQ upserts them by id, so a pool of replicas fires each one once per interval. */
23
25
  readonly schedulers: ReadonlyArray<QueueSchedulerDefinition>
24
26
  }
27
+
28
+ /** A one-off producer writes an outbox row and exits; the running app owns delivery. */
29
+ export interface QueueOutboxOnlyOptions {
30
+ readonly outboxOnly: true
31
+ }
32
+
33
+ /** Registers either the running queue capability or only its transactional writer. */
34
+ export type QueueRegistrationOptions = QueueOptions | QueueOutboxOnlyOptions
@@ -0,0 +1,6 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { @@Queue@@Options } from "./@@queue@@.options"
3
+
4
+ /** The configurable-module base of the @@queue@@ module. */
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ new ConfigurableModuleBuilder<@@Queue@@Options>().setClassMethodName("register").build()
@@ -1,6 +1,7 @@
1
1
  import { Module } from "@nestjs/common"
2
+ import { ConfigurableModuleClass } from "./@@queue@@.module-definition"
2
3
  import { @@Queue@@Queue } from "./@@queue@@.queue"
3
4
 
4
5
  @Module({ providers: [@@Queue@@Queue], exports: [@@Queue@@Queue] })
5
6
  /** The @@queue@@ queue: its typed producer, imported by the domain module whose service enqueues. */
6
- export class @@Queue@@Module {}
7
+ export class @@Queue@@Module extends ConfigurableModuleClass {}
@@ -0,0 +1,2 @@
1
+ /** The @@queue@@ module takes no options yet. */
2
+ export type @@Queue@@Options = Record<string, never>
@@ -0,0 +1,6 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { @@Reactor@@MessageOptions } from "./@@reactor@@-message.options"
3
+
4
+ /** The configurable-module base of the @@reactor@@-message module. */
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ new ConfigurableModuleBuilder<@@Reactor@@MessageOptions>().setClassMethodName("register").build()
@@ -4,14 +4,17 @@ import { InjectEventConsumerRegistry } from "@modules/platform/event-bus"
4
4
  import type { EventConsumerRegistry } from "@modules/platform/event-bus"
5
5
  import { @@Reactor@@Module } from "../../@@reactor@@.module"
6
6
  import { @@Event@@Consumer } from "./@@event@@.consumer"
7
+ import { ConfigurableModuleClass } from "./@@reactor@@-message.module-definition"
7
8
 
8
- @Module({ imports: [@@Reactor@@Module], providers: [@@Event@@Consumer] })
9
+ @Module({ imports: [@@Reactor@@Module.register({})], providers: [@@Event@@Consumer] })
9
10
  /** The message transport of the @@reactor@@ reactor: registers its consumers with the event bus; a worker or api app composes it. */
10
- export class @@Reactor@@MessageModule implements OnModuleInit {
11
+ export class @@Reactor@@MessageModule extends ConfigurableModuleClass implements OnModuleInit {
11
12
  constructor(
12
13
  @InjectEventConsumerRegistry() private readonly registry: EventConsumerRegistry,
13
14
  private readonly @@eventCamel@@Consumer: @@Event@@Consumer,
14
- ) {}
15
+ ) {
16
+ super()
17
+ }
15
18
 
16
19
  /** Hands every consumer to the registry. */
17
20
  onModuleInit(): void {
@@ -0,0 +1,2 @@
1
+ /** The @@reactor@@-message module takes no options yet. */
2
+ export type @@Reactor@@MessageOptions = Record<string, never>
@@ -0,0 +1,6 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { @@Reactor@@Options } from "./@@reactor@@.options"
3
+
4
+ /** The configurable-module base of the @@reactor@@ module. */
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ new ConfigurableModuleBuilder<@@Reactor@@Options>().setClassMethodName("register").build()