@starci/hfs 4.3.0 → 4.4.1

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 (538) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -1
  3. package/db/migrate.mjs +361 -0
  4. package/db/post-cutover.mjs +136 -0
  5. package/emit/contracts.mjs +2 -2
  6. package/emit/db-types.mjs +12 -3
  7. package/lint/run.mjs +3 -3
  8. package/package.json +4 -1
  9. package/release/gate.mjs +298 -0
  10. package/release/index.mjs +103 -0
  11. package/release/push.mjs +110 -0
  12. package/report/sonar.mjs +1 -1
  13. package/runtime/config.example.yaml +7 -6
  14. package/runtime/engine/config.mjs +10 -18
  15. package/runtime/engine/removed-vocabulary.mjs +0 -2
  16. package/runtime/knowledge/hfs/canon-pins.yaml +11 -11
  17. package/runtime/knowledge/hfs/fe-stack.yaml +22 -1
  18. package/runtime/knowledge/hfs/rules.yaml +2855 -163
  19. package/runtime/knowledge/hfs/slots.yaml +333 -62
  20. package/runtime/knowledge/patterns/be/api.yaml +32 -5
  21. package/runtime/knowledge/patterns/be/cli.yaml +14 -4
  22. package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
  23. package/runtime/knowledge/patterns/be/jobs.yaml +20 -2
  24. package/runtime/knowledge/patterns/be/projections.yaml +4 -2
  25. package/runtime/knowledge/patterns/be/queues.yaml +11 -3
  26. package/runtime/knowledge/patterns/be/reactors.yaml +20 -1
  27. package/runtime/knowledge/patterns/be/realtime.yaml +25 -3
  28. package/runtime/knowledge/patterns/be/saga.yaml +20 -3
  29. package/runtime/knowledge/patterns/be/webhooks.yaml +23 -4
  30. package/runtime/knowledge/patterns/fe/folder.yaml +1 -0
  31. package/runtime/knowledge/sonar-gate.yaml +1 -1
  32. package/runtime/modules/kernel/failure-codes.yaml +3091 -43
  33. package/runtime/modules/kernel/removed-vocabulary.yaml +30 -0
  34. package/runtime/modules/models/runtimes.yaml +5 -7
  35. package/runtime/modules/schemas/application-stacks.schema.yaml +663 -0
  36. package/runtime/packages/cli/src/catalog.generated.mjs +13769 -0
  37. package/runtime/packages/cli/src/validate-args.mjs +213 -0
  38. package/runtime/scripts/api/git/add.mjs +5 -0
  39. package/runtime/scripts/api/git/init.mjs +7 -0
  40. package/runtime/scripts/api/git/lib.mjs +4 -1
  41. package/runtime/scripts/api/git/tag.mjs +5 -0
  42. package/runtime/scripts/api/node/lib.mjs +20 -0
  43. package/runtime/scripts/api/node/run-node.mjs +6 -0
  44. package/runtime/scripts/api/sops/lib.mjs +42 -1
  45. package/runtime/scripts/api/sops/selected-age-recipient.mjs +7 -0
  46. package/runtime/scripts/api/supabase/database-dump.mjs +5 -0
  47. package/runtime/scripts/api/supabase/database-query.mjs +5 -0
  48. package/runtime/scripts/api/supabase/lib.mjs +44 -0
  49. package/runtime/scripts/api/supabase/migration-repair.mjs +5 -0
  50. package/runtime/scripts/hfs/app-wiring.mjs +201 -0
  51. package/runtime/scripts/hfs/architecture/backend-app-files.mjs +1 -8
  52. package/runtime/scripts/hfs/architecture/backend.mjs +2 -7
  53. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +6 -3
  54. package/runtime/scripts/hfs/architecture/config.mjs +1 -1
  55. package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +149 -0
  56. package/runtime/scripts/hfs/architecture/dead-exports.mjs +9 -41
  57. package/runtime/scripts/hfs/architecture/default-deny.mjs +8 -6
  58. package/runtime/scripts/hfs/architecture/frontend-world.mjs +1 -1
  59. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +153 -0
  60. package/runtime/scripts/hfs/architecture/hfs.mjs +3 -3
  61. package/runtime/scripts/hfs/architecture/index.mjs +1 -1
  62. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +28 -13
  63. package/runtime/scripts/hfs/architecture/next-data-inference.mjs +176 -0
  64. package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +26 -0
  65. package/runtime/scripts/hfs/architecture/next-data.mjs +31 -23
  66. package/runtime/scripts/hfs/architecture/reachability.mjs +14 -0
  67. package/runtime/scripts/hfs/architecture/register-once.mjs +2 -2
  68. package/runtime/scripts/hfs/architecture/source-names.mjs +48 -3
  69. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +211 -11
  70. package/runtime/scripts/hfs/architecture/supabase-be.mjs +1 -1
  71. package/runtime/scripts/hfs/architecture/supabase.mjs +59 -21
  72. package/runtime/scripts/hfs/architecture/surface.mjs +2 -1
  73. package/runtime/scripts/hfs/architecture/tiers.mjs +4 -4
  74. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +8 -2
  75. package/runtime/scripts/hfs/architecture.mjs +1 -1
  76. package/runtime/scripts/hfs/check-results.mjs +28 -0
  77. package/runtime/scripts/hfs/check.mjs +46 -47
  78. package/runtime/scripts/hfs/coverage-from.mjs +70 -0
  79. package/runtime/scripts/hfs/coverage-scope.mjs +1 -1
  80. package/runtime/scripts/hfs/db-baseline-verb.mjs +276 -0
  81. package/runtime/scripts/hfs/db-plan-verb.mjs +254 -0
  82. package/runtime/scripts/hfs/db-restamp-verb.mjs +60 -0
  83. package/runtime/scripts/hfs/declaration-shape.mjs +39 -3
  84. package/runtime/scripts/hfs/doc-commands.mjs +367 -0
  85. package/runtime/scripts/hfs/edition-slots.mjs +2 -2
  86. package/runtime/scripts/hfs/example-secrets.mjs +158 -0
  87. package/runtime/scripts/hfs/manifest-shape.mjs +69 -9
  88. package/runtime/scripts/hfs/param-names.mjs +1 -1
  89. package/runtime/scripts/hfs/refusal-codes.mjs +6 -0
  90. package/runtime/scripts/hfs/rule-catalog.mjs +158 -11
  91. package/runtime/scripts/hfs/rule-params-shape.mjs +22 -10
  92. package/runtime/scripts/hfs/rules/a15-findings.mjs +88 -0
  93. package/runtime/scripts/hfs/rules/app-env-required.mjs +20 -0
  94. package/runtime/scripts/hfs/rules/app-root.mjs +2 -2
  95. package/runtime/scripts/hfs/rules/assets.mjs +79 -0
  96. package/runtime/scripts/hfs/rules/boot-proof.mjs +16 -0
  97. package/runtime/scripts/hfs/rules/canon-copy-drift.mjs +59 -0
  98. package/runtime/scripts/hfs/rules/cli.mjs +1 -1
  99. package/runtime/scripts/hfs/rules/contract-compat.mjs +1 -1
  100. package/runtime/scripts/hfs/rules/contract.mjs +67 -6
  101. package/runtime/scripts/hfs/rules/coverage-below.mjs +230 -0
  102. package/runtime/scripts/hfs/rules/database-access.mjs +343 -0
  103. package/runtime/scripts/hfs/rules/database-baseline.mjs +142 -0
  104. package/runtime/scripts/hfs/rules/database-config.mjs +250 -2
  105. package/runtime/scripts/hfs/rules/database-constants.mjs +9 -0
  106. package/runtime/scripts/hfs/rules/database-migrations.mjs +286 -7
  107. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +119 -9
  108. package/runtime/scripts/hfs/rules/database-queue-sources.mjs +176 -0
  109. package/runtime/scripts/hfs/rules/database-queues.mjs +361 -0
  110. package/runtime/scripts/hfs/rules/database-sql.mjs +91 -9
  111. package/runtime/scripts/hfs/rules/database.mjs +9 -0
  112. package/runtime/scripts/hfs/rules/deploy-scan.mjs +192 -0
  113. package/runtime/scripts/hfs/rules/deploy-workflow.mjs +219 -0
  114. package/runtime/scripts/hfs/rules/doc-fresh.mjs +286 -0
  115. package/runtime/scripts/hfs/rules/docker-health.mjs +249 -0
  116. package/runtime/scripts/hfs/rules/docker.mjs +24 -7
  117. package/runtime/scripts/hfs/rules/edition.mjs +9 -8
  118. package/runtime/scripts/hfs/rules/env-parity.mjs +373 -0
  119. package/runtime/scripts/hfs/rules/env-value-shape.mjs +113 -0
  120. package/runtime/scripts/hfs/rules/event-bus.mjs +3 -2
  121. package/runtime/scripts/hfs/rules/event-tables.mjs +100 -0
  122. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +115 -15
  123. package/runtime/scripts/hfs/rules/fe-stack.mjs +57 -0
  124. package/runtime/scripts/hfs/rules/feature-files.mjs +28 -0
  125. package/runtime/scripts/hfs/rules/frontend-tree.mjs +54 -35
  126. package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -2
  127. package/runtime/scripts/hfs/rules/lexicon.mjs +62 -0
  128. package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +89 -0
  129. package/runtime/scripts/hfs/rules/monorepo.mjs +14 -1
  130. package/runtime/scripts/hfs/rules/pipeline.mjs +8 -1
  131. package/runtime/scripts/hfs/rules/release-provenance.mjs +211 -0
  132. package/runtime/scripts/hfs/rules/saga-delivery.mjs +66 -0
  133. package/runtime/scripts/hfs/rules/saga.mjs +8 -16
  134. package/runtime/scripts/hfs/rules/script-hygiene.mjs +151 -0
  135. package/runtime/scripts/hfs/rules/secret-custody-only.mjs +65 -0
  136. package/runtime/scripts/hfs/rules/secrets.mjs +13 -0
  137. package/runtime/scripts/hfs/rules/services.mjs +4 -4
  138. package/runtime/scripts/hfs/rules/stack-tooling.mjs +93 -0
  139. package/runtime/scripts/hfs/rules/stacks-deploy.mjs +76 -0
  140. package/runtime/scripts/hfs/rules/stacks-ingress.mjs +87 -0
  141. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +111 -0
  142. package/runtime/scripts/hfs/rules/stacks-release.mjs +366 -0
  143. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +41 -1
  144. package/runtime/scripts/hfs/rules/stacks-swarm.mjs +417 -0
  145. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +2 -2
  146. package/runtime/scripts/hfs/rules/supply-chain.mjs +167 -0
  147. package/runtime/scripts/hfs/rules/unused-symbol-flow.mjs +101 -0
  148. package/runtime/scripts/hfs/rules/unused-symbol-ledger.mjs +77 -0
  149. package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +165 -0
  150. package/runtime/scripts/hfs/rules/unused-symbol-values.mjs +103 -0
  151. package/runtime/scripts/hfs/rules/unused-symbol.mjs +320 -0
  152. package/runtime/scripts/hfs/runtime-rules/source-ast.mjs +185 -0
  153. package/runtime/scripts/hfs/runtime-rules/workflow-source.mjs +19 -0
  154. package/runtime/scripts/hfs/sealed-members.mjs +101 -0
  155. package/runtime/scripts/hfs/secret-bootstrap-crypto.mjs +200 -0
  156. package/runtime/scripts/hfs/secret-edit.mjs +170 -0
  157. package/runtime/scripts/hfs/secret-init.mjs +127 -19
  158. package/runtime/scripts/hfs/secret-sops.mjs +38 -6
  159. package/runtime/scripts/hfs/secret-store.mjs +56 -54
  160. package/runtime/scripts/hfs/secret.mjs +34 -5
  161. package/runtime/scripts/hfs/slot-imports.mjs +18 -5
  162. package/runtime/scripts/hfs/slots.mjs +3 -2
  163. package/runtime/scripts/hfs/stack-config-kit.mjs +34 -0
  164. package/runtime/scripts/hfs/stack-deploy-config.mjs +313 -0
  165. package/runtime/scripts/hfs/stack-ingress-config.mjs +237 -0
  166. package/runtime/scripts/hfs/stacks-gate.mjs +414 -0
  167. package/runtime/scripts/hfs/starcistacks-runtime.mjs +61 -0
  168. package/runtime/scripts/hfs/typescript-programs.mjs +0 -8
  169. package/runtime/scripts/lib/command-tokens.mjs +126 -0
  170. package/runtime/scripts/lib/compose-values.mjs +11 -0
  171. package/runtime/scripts/lib/deployment-findings.mjs +7 -0
  172. package/runtime/scripts/lib/env.mjs +2 -2
  173. package/runtime/scripts/lib/git.mjs +1 -1
  174. package/runtime/scripts/lib/i18n.mjs +0 -2
  175. package/runtime/scripts/lib/json-clone.mjs +61 -0
  176. package/runtime/scripts/lib/language.mjs +1 -1
  177. package/runtime/scripts/lib/list.mjs +4 -0
  178. package/runtime/scripts/lib/roots.mjs +2 -4
  179. package/runtime/scripts/lib/runtime-example.mjs +13 -0
  180. package/runtime/scripts/lib/secret-patterns.mjs +60 -5
  181. package/runtime/scripts/lib/sql-ast.mjs +32 -0
  182. package/runtime/scripts/lib/stack-services.mjs +1 -1
  183. package/runtime/scripts/lib/swarm-stack.mjs +5 -18
  184. package/runtime/scripts/lib/trim-end.mjs +8 -0
  185. package/runtime/scripts/lib/ts-analysis.mjs +5 -2
  186. package/runtime/scripts/lib/ts-ast.mjs +27 -2
  187. package/runtime/scripts/lib/workflow-events.mjs +8 -0
  188. package/scaffold/add-app.mjs +77 -59
  189. package/scaffold/add-cli-lite.mjs +14 -6
  190. package/scaffold/add-compose.mjs +79 -0
  191. package/scaffold/add-database-lite.mjs +14 -0
  192. package/scaffold/add-fe-overlay.mjs +1 -0
  193. package/scaffold/add-fe-page.mjs +1 -0
  194. package/scaffold/add-inbox-lite.mjs +74 -42
  195. package/scaffold/add-table.mjs +110 -87
  196. package/scaffold/add.mjs +159 -44
  197. package/scaffold/app.mjs +124 -39
  198. package/scaffold/edition-gate.mjs +3 -3
  199. package/scaffold/lite-exports.mjs +2 -1
  200. package/scaffold/service.mjs +2 -2
  201. package/scaffold/supabase-ports.mjs +1 -1
  202. package/src/main.mjs +229 -116
  203. package/stack/age-install.mjs +37 -0
  204. package/stack/backup.mjs +100 -0
  205. package/stack/build-args.mjs +42 -0
  206. package/stack/build.mjs +173 -0
  207. package/stack/cli.mjs +130 -0
  208. package/stack/config.mjs +57 -0
  209. package/stack/context.mjs +57 -0
  210. package/stack/converge.mjs +107 -0
  211. package/stack/deploy.mjs +301 -0
  212. package/stack/digest.mjs +16 -0
  213. package/stack/docker-transport.mjs +53 -0
  214. package/stack/edge-alias.mjs +118 -0
  215. package/stack/errors.mjs +21 -0
  216. package/stack/image-scan.mjs +152 -0
  217. package/stack/ingress-create.mjs +114 -0
  218. package/stack/ingress-publish.mjs +273 -0
  219. package/stack/ingress.mjs +706 -0
  220. package/stack/jobs.mjs +51 -0
  221. package/stack/live-restore.mjs +177 -0
  222. package/stack/prepare.mjs +266 -0
  223. package/stack/private-file.mjs +22 -0
  224. package/stack/provision.mjs +90 -0
  225. package/stack/prune.mjs +46 -0
  226. package/stack/release-store.mjs +87 -0
  227. package/stack/remote.mjs +205 -0
  228. package/stack/render.mjs +149 -0
  229. package/stack/retire.mjs +47 -0
  230. package/stack/sealed.mjs +149 -0
  231. package/stack/task.mjs +42 -0
  232. package/stack/verify.mjs +99 -0
  233. package/sync/ci-commands.mjs +13 -0
  234. package/sync/format.mjs +1 -1
  235. package/sync/hygiene.mjs +1 -1
  236. package/sync/index.mjs +110 -54
  237. package/sync/stack-images.mjs +127 -0
  238. package/templates/app/ci-workflows/github/workflows/ci.yml +5 -1
  239. package/templates/app/ci-workflows/github/workflows/images.yml +15 -3
  240. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +148 -0
  241. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +7 -3
  242. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +38 -6
  243. package/templates/app/ci-workflows-lite/github/workflows/images.yml +14 -2
  244. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +148 -0
  245. package/templates/app/codegen-graphql.mjs.partial +13 -2
  246. package/templates/app/codegen-lite/scripts/codegen.mjs +6 -1
  247. package/templates/app/codegen-prelude.mjs.partial +8 -0
  248. package/templates/app/gitignore +3 -0
  249. package/templates/app/gitignore-lite +3 -0
  250. package/templates/app/package-scripts/package.json +1 -0
  251. package/templates/app/package-scripts/supabase.json +7 -0
  252. package/templates/app/package-scripts-lite/package.json +1 -7
  253. package/templates/app/skeleton/.editorconfig +1 -0
  254. package/templates/app/skeleton/.gitattributes +7 -1
  255. package/templates/app/skeleton/.github/dependabot.yml +17 -0
  256. package/templates/app/skeleton/.nvmrc +1 -1
  257. package/templates/app/skeleton/.starcistacks/application-stacks.yaml +134 -1
  258. package/templates/app/skeleton/.starcistacks/dev/README.md +16 -0
  259. package/templates/app/skeleton/.starcistacks/dev/environment.json +5 -0
  260. package/templates/app/skeleton/.starcistacks/dev/infra/compose/stack.yaml +71 -0
  261. package/templates/app/skeleton/.starcistacks/dev/runtime/env/KEYS.md +14 -0
  262. package/templates/app/skeleton/.starcistacks/production/README.md +37 -0
  263. package/templates/app/skeleton/.starcistacks/production/environment.json +5 -0
  264. package/templates/app/skeleton/.starcistacks/production/infra/swarm/Caddyfile +15 -0
  265. package/templates/app/skeleton/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  266. package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  267. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  268. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +9 -0
  269. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  270. package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +142 -0
  271. package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +14 -0
  272. package/templates/app/skeleton/README.md +6 -6
  273. package/templates/app/skeleton/scripts/codegen.mjs +1 -2
  274. package/templates/app/skeleton-lite/.editorconfig +16 -0
  275. package/templates/app/skeleton-lite/.gitattributes +8 -0
  276. package/templates/app/skeleton-lite/.github/dependabot.yml +17 -0
  277. package/templates/app/skeleton-lite/.nvmrc +1 -0
  278. package/templates/app/skeleton-lite/.sops.yaml +3 -2
  279. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +178 -1
  280. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +20 -0
  281. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +2 -1
  282. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +16 -0
  283. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +11 -0
  284. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +7 -0
  285. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +6 -0
  286. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +8 -0
  287. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +6 -0
  288. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +120 -0
  289. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -0
  290. package/templates/app/skeleton-lite/.starcistacks/production/README.md +38 -0
  291. package/templates/app/skeleton-lite/.starcistacks/production/environment.json +6 -0
  292. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/Caddyfile +11 -0
  293. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  294. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  295. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  296. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/images.lock.yaml +8 -0
  297. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  298. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +121 -0
  299. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +21 -0
  300. package/templates/app/skeleton-lite/.starciwork/brand/index.yaml +12 -0
  301. package/templates/app/skeleton-lite/README.md +5 -3
  302. package/templates/app/skeleton-lite/supabase/config.toml +8 -1
  303. package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +15 -6
  304. package/templates/app/upgrade-full/.starcistacks/dev/README.md +13 -7
  305. package/templates/be/image/worker/Dockerfile +1 -1
  306. package/templates/be/inbox/database-client.spec.ts.tpl +17 -18
  307. package/templates/be/inbox/database-client.ts.tpl +12 -11
  308. package/templates/be/inbox/delivery-recorded.event.ts.tpl +36 -0
  309. package/templates/be/inbox/event-index.ts.tpl +1 -0
  310. package/templates/be/inbox/events.json.tpl +10 -0
  311. package/templates/be/inbox/migration.sql.tpl +1 -0
  312. package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +42 -0
  313. package/templates/be/inbox/platform/event-bus/event-bus.decorators.ts.tpl +9 -0
  314. package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +6 -0
  315. package/templates/be/inbox/platform/event-bus/event-bus.module.ts.tpl +19 -0
  316. package/templates/be/inbox/platform/event-bus/event-bus.options.ts.tpl +2 -0
  317. package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +16 -0
  318. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +45 -0
  319. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +15 -0
  320. package/templates/be/inbox/platform/event-bus/index.ts.tpl +5 -0
  321. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +5 -0
  322. package/templates/be/inbox/platform/inbox/inbox.decorators.ts.tpl +9 -0
  323. package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +6 -0
  324. package/templates/be/inbox/platform/inbox/inbox.module.ts.tpl +19 -0
  325. package/templates/be/inbox/platform/inbox/inbox.options.ts.tpl +2 -0
  326. package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +10 -0
  327. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +41 -0
  328. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +19 -0
  329. package/templates/be/inbox/platform/inbox/index.ts.tpl +3 -0
  330. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +7 -0
  331. package/templates/be/inbox/platform/migration.sql.tpl +48 -0
  332. package/templates/be/inbox/service.spec.ts.tpl +42 -9
  333. package/templates/be/inbox/service.ts.tpl +23 -8
  334. package/templates/be/inbox/webhook-door.spec.ts.tpl +1 -1
  335. package/templates/be/inbox/webhook-door.ts.tpl +2 -2
  336. package/templates/be/patterns/api/feature.module-definition.ts.tpl +6 -0
  337. package/templates/be/patterns/api/feature.module.ts.tpl +2 -1
  338. package/templates/be/patterns/api/feature.options.ts.tpl +2 -0
  339. package/templates/be/patterns/api/graphql.module-definition.ts.tpl +6 -0
  340. package/templates/be/patterns/api/graphql.module.ts.tpl +3 -2
  341. package/templates/be/patterns/api/graphql.options.ts.tpl +2 -0
  342. package/templates/be/patterns/cli/group.module-definition.ts.tpl +6 -0
  343. package/templates/be/patterns/cli/group.module.ts.tpl +2 -1
  344. package/templates/be/patterns/cli/group.options.ts.tpl +2 -0
  345. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +0 -3
  346. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +0 -2
  347. package/templates/be/patterns/jobs/job.module-definition.ts.tpl +6 -0
  348. package/templates/be/patterns/jobs/job.module.ts.tpl +2 -1
  349. package/templates/be/patterns/jobs/job.options.ts.tpl +2 -0
  350. package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +6 -0
  351. package/templates/be/patterns/jobs/job.queue.module.ts.tpl +6 -3
  352. package/templates/be/patterns/jobs/job.queue.options.ts.tpl +2 -0
  353. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +3 -1
  354. package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +25 -9
  355. package/templates/be/patterns/queues/platform/queue.config.ts.tpl +1 -1
  356. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +1 -1
  357. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +2 -2
  358. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +11 -0
  359. package/templates/be/patterns/queues/platform/queue.options.ts.tpl +10 -0
  360. package/templates/be/patterns/queues/queue.module-definition.ts.tpl +6 -0
  361. package/templates/be/patterns/queues/queue.module.ts.tpl +2 -1
  362. package/templates/be/patterns/queues/queue.options.ts.tpl +2 -0
  363. package/templates/be/patterns/reactors/message.module-definition.ts.tpl +6 -0
  364. package/templates/be/patterns/reactors/message.module.ts.tpl +6 -3
  365. package/templates/be/patterns/reactors/message.options.ts.tpl +2 -0
  366. package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +6 -0
  367. package/templates/be/patterns/reactors/reactor.module.ts.tpl +2 -1
  368. package/templates/be/patterns/reactors/reactor.options.ts.tpl +2 -0
  369. package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +188 -0
  370. package/templates/be/patterns/realtime/channel.gateway.ts.tpl +22 -0
  371. package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +6 -0
  372. package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +2 -1
  373. package/templates/be/patterns/realtime/channel.graphql.options.ts.tpl +2 -0
  374. package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +6 -0
  375. package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +110 -0
  376. package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +7 -0
  377. package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +6 -0
  378. package/templates/be/patterns/saga/saga.message.module.ts.tpl +6 -3
  379. package/templates/be/patterns/saga/saga.message.options.ts.tpl +2 -0
  380. package/templates/be/patterns/saga/saga.module-definition.ts.tpl +6 -0
  381. package/templates/be/patterns/saga/saga.module.ts.tpl +2 -1
  382. package/templates/be/patterns/saga/saga.options.ts.tpl +2 -0
  383. package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +6 -0
  384. package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +2 -1
  385. package/templates/be/patterns/webhooks/webhook.http.options.ts.tpl +2 -0
  386. package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +80 -26
  387. package/templates/be/platform/graphql/localize-errors.mapper.ts +2 -5
  388. package/templates/be/skeleton/apps/__app__/src/app.module.ts +11 -9
  389. package/templates/be/skeleton/apps/__app__/src/compose/api.ts +4 -0
  390. package/templates/be/skeleton/apps/__app__/src/compose/domain.ts +11 -0
  391. package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +2 -0
  392. package/templates/be/skeleton/apps/__app__/src/compose/system.ts +5 -0
  393. package/templates/be/skeleton/apps/__app__/src/compose/webhooks.ts +4 -0
  394. package/templates/be/skeleton/apps/__app__/src/main.ts +15 -1
  395. package/templates/be/skeleton/apps/cli/src/app.module.ts +3 -13
  396. package/templates/be/skeleton/apps/cli/src/cli.options.ts +6 -14
  397. package/templates/be/skeleton/apps/cli/src/compose/cli.ts +5 -0
  398. package/templates/be/skeleton/apps/cli/src/compose/platform.ts +24 -0
  399. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.contracts.ts +2 -0
  400. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.handler.ts +22 -0
  401. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.query.ts +11 -0
  402. package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +6 -0
  403. package/templates/be/skeleton/src/features/api/system-health/system-health.module.ts +5 -3
  404. package/templates/be/skeleton/src/features/api/system-health/system-health.options.ts +2 -0
  405. package/templates/be/skeleton/src/features/api/system-health/transport/http/ready.controller.ts +19 -0
  406. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +6 -0
  407. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module.ts +8 -3
  408. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.options.ts +2 -0
  409. package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +6 -0
  410. package/templates/be/skeleton/src/features/cli/cli.module.ts +3 -2
  411. package/templates/be/skeleton/src/features/cli/cli.options.ts +2 -0
  412. package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +6 -0
  413. package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +2 -1
  414. package/templates/be/skeleton/src/features/cli/migrate/migrate.options.ts +2 -0
  415. package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +6 -0
  416. package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +2 -1
  417. package/templates/be/skeleton/src/features/cli/seed/seed.options.ts +2 -0
  418. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +5 -6
  419. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +56 -4
  420. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.ts +36 -5
  421. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +39 -8
  422. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.ts +14 -8
  423. package/templates/be/skeleton/src/modules/domain/identity/credential.contracts.ts +26 -0
  424. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.spec.ts +119 -0
  425. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.ts +43 -0
  426. package/templates/be/skeleton/src/modules/domain/identity/identity.contracts.ts +16 -0
  427. package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -1
  428. package/templates/be/skeleton/src/modules/domain/identity/identity.module.ts +22 -4
  429. package/templates/be/skeleton/src/modules/domain/identity/identity.options.ts +8 -2
  430. package/templates/be/skeleton/src/modules/domain/identity/index.ts +1 -0
  431. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +11 -4
  432. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.spec.ts +32 -0
  433. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.ts +14 -0
  434. package/templates/be/skeleton/src/modules/platform/database/database.module.ts +7 -1
  435. package/templates/be/skeleton/src/modules/platform/database/index.ts +2 -0
  436. package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +8 -0
  437. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +25 -6
  438. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +16 -6
  439. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.spec.ts +130 -0
  440. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.ts +58 -0
  441. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +3 -0
  442. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -4
  443. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +16 -10
  444. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +3 -1
  445. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +2 -0
  446. package/templates/be/skeleton/src/modules/platform/http-security/index.ts +5 -0
  447. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +12 -5
  448. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +13 -8
  449. package/templates/be/skeleton/src/modules/platform/http-security/readiness.contracts.ts +5 -0
  450. package/templates/be/skeleton/src/modules/platform/http-security/readiness.decorators.ts +7 -0
  451. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.spec.ts +86 -0
  452. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.ts +39 -0
  453. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +139 -131
  454. package/templates/be/skeleton/src/modules/platform/i18n/index.ts +1 -1
  455. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +91 -67
  456. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +5 -4
  457. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +0 -2
  458. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +20 -6
  459. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +16 -4
  460. package/templates/be/skeleton/src/modules/platform/retry/index.ts +3 -0
  461. package/templates/be/skeleton/src/modules/platform/retry/retry.decorators.ts +9 -0
  462. package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +7 -0
  463. package/templates/be/skeleton/src/modules/platform/retry/retry.module.ts +19 -0
  464. package/templates/be/skeleton/src/modules/platform/retry/retry.options.ts +2 -0
  465. package/templates/be/skeleton/src/modules/platform/retry/retry.port.ts +11 -0
  466. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.spec.ts +36 -0
  467. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.ts +10 -0
  468. package/templates/be/skeleton/src/tests/contract/http-security/http-security.contract-spec.ts +54 -0
  469. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +14 -18
  470. package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +4 -0
  471. package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +10 -0
  472. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +16 -0
  473. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +2 -0
  474. package/templates/be/skeleton-lite/apps/api/src/compose/system.ts +5 -0
  475. package/templates/be/skeleton-lite/apps/api/src/compose/webhooks.ts +4 -0
  476. package/templates/be/skeleton-lite/apps/api/src/main.ts +2 -3
  477. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +12 -0
  478. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.spec.ts +71 -5
  479. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +29 -2
  480. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +68 -15
  481. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +14 -9
  482. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +9 -2
  483. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +16 -1
  484. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +19 -4
  485. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +5 -1
  486. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -1
  487. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -3
  488. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +17 -5
  489. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +24 -13
  490. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +5 -0
  491. package/templates/be/skeleton-lite/src/modules/platform/database/errors/database.error.ts +16 -0
  492. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +3 -0
  493. package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +5 -2
  494. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +4 -1
  495. package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +1 -1
  496. package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +7 -0
  497. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +29 -0
  498. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +16 -0
  499. package/templates/be/table/api/feature.module-definition.ts.tpl +6 -0
  500. package/templates/be/table/api/feature.module.ts.tpl +2 -1
  501. package/templates/be/table/api/feature.options.ts.tpl +2 -0
  502. package/templates/be/table/api/graphql.module-definition.ts.tpl +6 -0
  503. package/templates/be/table/api/graphql.module.ts.tpl +3 -2
  504. package/templates/be/table/api/graphql.options.ts.tpl +2 -0
  505. package/templates/be/table/migration.sql.tpl +1 -0
  506. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +4 -6
  507. package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +7 -0
  508. package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +5 -0
  509. package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +9 -0
  510. package/templates/be/upgrade-full/apps/cli/src/main.ts +3 -0
  511. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +3 -2
  512. package/templates/fe/auth/supabase-client.ts.tpl +34 -0
  513. package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +11 -0
  514. package/templates/fe/skeleton/apps/__app__/src/components/blocks/SignInForm/index.tsx +6 -1
  515. package/templates/fe/skeleton/apps/__app__/src/components/leaves/Icon/index.tsx +4 -0
  516. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/component.tsx +31 -0
  517. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/index.tsx +24 -0
  518. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +6 -1
  519. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +3 -4
  520. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/index.ts +1 -0
  521. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +15 -0
  522. package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +18 -0
  523. package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +19 -0
  524. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +7 -1
  525. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/en.json +57 -0
  526. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +6 -0
  527. package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/en.json +31 -0
  528. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +1 -1
  529. package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +1 -0
  530. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +1 -34
  531. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +7 -1
  532. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +57 -0
  533. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +30 -24
  534. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +1 -1
  535. package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
  536. package/templates/fe/transport/graphql/clients/links/locale.ts +2 -2
  537. package/upgrade/check.mjs +3 -1
  538. package/upgrade/index.mjs +54 -41
@@ -8,35 +8,47 @@ import { ok, refused } from "@modules/platform/primitives"
8
8
  import { SupabaseErrorCode } from "@modules/integrations/supabase"
9
9
  import type { VerifySupabaseAccessToken } from "@modules/integrations/supabase"
10
10
  import { AuthGuard } from "./auth.guard"
11
+ import type { CredentialMetadata, CredentialPrincipal } from "./credential.contracts"
11
12
  import { IdentityErrorCode } from "./errors/identity.error"
12
13
  import type { PublicMetadata } from "./identity.contracts"
13
14
  import { PublicReason } from "./identity.contracts"
14
- import { ACCESS_TOKEN_VERIFIER } from "./identity.decorators"
15
+ import { ACCESS_TOKEN_VERIFIER, CREDENTIAL_KEY, PUBLIC_KEY } from "./identity.decorators"
15
16
 
16
17
  const principal: Principal = { id: "user-1", role: "authenticated" }
18
+ const API_KEY: CredentialPrincipal = { kind: "api-key", subject: "key-1", workspaceId: "ws-1" }
19
+ const TOOL_TOKEN: CredentialPrincipal = { kind: "tool-token", subject: "tool-1", workspaceId: "ws-1" }
17
20
 
18
- const build = async (metadata: PublicMetadata | undefined, verify: VerifySupabaseAccessToken) => {
21
+ const build = async (metadata: PublicMetadata | undefined, credential?: CredentialMetadata) => {
22
+ const verifier = mock<{ verify: VerifySupabaseAccessToken }>()
19
23
  const reflector = mock<Reflector>()
20
- reflector.getAllAndOverride.mockReturnValue(metadata)
24
+ reflector.getAllAndOverride.mockImplementation((key: unknown) =>
25
+ key === CREDENTIAL_KEY ? credential : key === PUBLIC_KEY ? metadata : undefined,
26
+ )
21
27
  const moduleRef = await Test.createTestingModule({
22
28
  providers: [
23
29
  AuthGuard,
24
30
  { provide: Reflector, useValue: reflector },
25
- { provide: ACCESS_TOKEN_VERIFIER, useValue: verify },
31
+ { provide: ACCESS_TOKEN_VERIFIER, useFactory: () => verifier.verify },
26
32
  ],
27
33
  }).compile()
28
- return moduleRef.get(AuthGuard)
34
+ return { guard: moduleRef.get(AuthGuard), verify: verifier.verify }
29
35
  }
30
36
 
31
- const contextOf = (authorization: string | undefined) => {
37
+ const contextOf = (
38
+ authorization: string | undefined,
39
+ credentialPrincipal?: CredentialPrincipal,
40
+ sessionPrincipal?: Principal,
41
+ ) => {
32
42
  const request = mock<Request>()
33
43
  request.headers = authorization === undefined ? {} : { authorization }
34
- request.principal = undefined
44
+ request.principal = sessionPrincipal
45
+ request.credentialPrincipal = credentialPrincipal
35
46
  const http = mock<ReturnType<ExecutionContext["switchToHttp"]>>()
36
47
  http.getRequest.mockReturnValue(request)
37
48
  const context = mock<ExecutionContext>()
38
49
  context.getHandler.mockReturnValue(() => undefined)
39
50
  context.getClass.mockReturnValue(class Door {})
51
+ context.getType.mockReturnValue("http")
40
52
  context.switchToHttp.mockReturnValue(http)
41
53
  return { context, request }
42
54
  }
@@ -45,8 +57,7 @@ const unauthenticated = expect.objectContaining({ code: IdentityErrorCode.Unauth
45
57
 
46
58
  describe("AuthGuard", () => {
47
59
  it("lets a public door through without reading a token", async () => {
48
- const verify = jest.fn<ReturnType<VerifySupabaseAccessToken>, Parameters<VerifySupabaseAccessToken>>()
49
- const guard = await build({ reason: PublicReason.Health }, verify)
60
+ const { guard, verify } = await build({ reason: PublicReason.Health })
50
61
  const { context } = contextOf("Bearer token")
51
62
 
52
63
  await expect(guard.canActivate(context)).resolves.toBe(true)
@@ -55,9 +66,8 @@ describe("AuthGuard", () => {
55
66
  })
56
67
 
57
68
  it("admits a door that is not public for a verified bearer token and records the principal on the request", async () => {
58
- const verify = jest.fn<ReturnType<VerifySupabaseAccessToken>, Parameters<VerifySupabaseAccessToken>>()
69
+ const { guard, verify } = await build(undefined)
59
70
  verify.mockResolvedValue(ok(principal))
60
- const guard = await build(undefined, verify)
61
71
  const { context, request } = contextOf("Bearer signed-token")
62
72
 
63
73
  await expect(guard.canActivate(context)).resolves.toBe(true)
@@ -65,11 +75,55 @@ describe("AuthGuard", () => {
65
75
  expect(verify).toHaveBeenCalledWith("signed-token")
66
76
  expect(request.principal).toEqual(principal)
67
77
  })
78
+ })
79
+
80
+ describe("AuthGuard credential doors", () => {
81
+ it("admits a credential door with the required principal without verifying a session token", async () => {
82
+ const { guard, verify } = await build(undefined, { kind: "api-key" })
83
+ verify.mockResolvedValue(ok(principal))
84
+ const { context, request } = contextOf("Bearer signed-token", API_KEY)
85
+
86
+ await expect(guard.canActivate(context)).resolves.toBe(true)
87
+
88
+ expect(verify).not.toHaveBeenCalled()
89
+ expect(request.credentialPrincipal).toEqual(API_KEY)
90
+ expect(request.principal).toBeUndefined()
91
+ })
92
+
93
+ it.each([
94
+ { label: "missing", credentialPrincipal: undefined },
95
+ { label: "of another kind", credentialPrincipal: TOOL_TOKEN },
96
+ ])(
97
+ "refuses a credential door when its credential principal is $label, even with a valid session",
98
+ async ({ credentialPrincipal }) => {
99
+ const { guard, verify } = await build(undefined, { kind: "api-key" })
100
+ verify.mockResolvedValue(ok(principal))
101
+ const { context } = contextOf("Bearer signed-token", credentialPrincipal, principal)
102
+
103
+ await expect(guard.canActivate(context)).rejects.toThrow(unauthenticated)
104
+
105
+ expect(verify).not.toHaveBeenCalled()
106
+ },
107
+ )
108
+
109
+ it.each([
110
+ { label: "missing", credentialPrincipal: undefined },
111
+ { label: "of another kind", credentialPrincipal: TOOL_TOKEN },
112
+ ])(
113
+ "refuses a credential door with public metadata when its credential principal is $label",
114
+ async ({ credentialPrincipal }) => {
115
+ const { guard, verify } = await build({ reason: PublicReason.Health }, { kind: "api-key" })
116
+ const { context } = contextOf("Bearer signed-token", credentialPrincipal, principal)
117
+
118
+ await expect(guard.canActivate(context)).rejects.toThrow(unauthenticated)
119
+
120
+ expect(verify).not.toHaveBeenCalled()
121
+ },
122
+ )
68
123
 
69
124
  it("refuses a door that is not public when the token fails verification", async () => {
70
- const verify = jest.fn<ReturnType<VerifySupabaseAccessToken>, Parameters<VerifySupabaseAccessToken>>()
125
+ const { guard, verify } = await build(undefined)
71
126
  verify.mockResolvedValue(refused(SupabaseErrorCode.AccessTokenInvalid))
72
- const guard = await build(undefined, verify)
73
127
  const { context, request } = contextOf("bearer forged-token")
74
128
 
75
129
  await expect(guard.canActivate(context)).rejects.toThrow(unauthenticated)
@@ -83,8 +137,7 @@ describe("AuthGuard", () => {
83
137
  ["a scheme without a token", "Bearer"],
84
138
  ["a token followed by more text", "Bearer one two"],
85
139
  ])("refuses a door that is not public with %s, without calling the verifier", async (_label, authorization) => {
86
- const verify = jest.fn<ReturnType<VerifySupabaseAccessToken>, Parameters<VerifySupabaseAccessToken>>()
87
- const guard = await build(undefined, verify)
140
+ const { guard, verify } = await build(undefined)
88
141
  const { context } = contextOf(authorization)
89
142
 
90
143
  await expect(guard.canActivate(context)).rejects.toThrow(unauthenticated)
@@ -7,9 +7,10 @@ import type { Principal } from "@modules/platform/cqrs"
7
7
  import { requestOf } from "@modules/platform/http-security"
8
8
  import { unwrapOutcome } from "@modules/platform/primitives"
9
9
  import { admit } from "./admission.policy"
10
+ import type { CredentialMetadata } from "./credential.contracts"
10
11
  import { IdentityError } from "./errors/identity.error"
11
12
  import type { PublicMetadata } from "./identity.contracts"
12
- import { InjectAccessTokenVerifier, PUBLIC_KEY } from "./identity.decorators"
13
+ import { CREDENTIAL_KEY, InjectAccessTokenVerifier, PUBLIC_KEY } from "./identity.decorators"
13
14
 
14
15
  const bearerToken = (authorization: string | undefined): string | undefined => {
15
16
  const [scheme, token, extra] = authorization?.split(" ") ?? []
@@ -17,21 +18,25 @@ const bearerToken = (authorization: string | undefined): string | undefined => {
17
18
  }
18
19
 
19
20
  @Injectable()
20
- /** The third app guard and default-deny gate: non-public doors require a verified Supabase principal. */
21
+ /**
22
+ * The fourth app guard and default-deny gate: a credential door requires the matching principal from `CredentialGuard`,
23
+ * a public door states its reason, and every other door requires a verified Supabase principal. It never calls another guard.
24
+ */
21
25
  export class AuthGuard implements CanActivate {
22
26
  constructor(
23
27
  @InjectReflector() private readonly reflector: Reflector,
24
28
  @InjectAccessTokenVerifier() private readonly verifyAccessToken: VerifySupabaseAccessToken,
25
29
  ) {}
26
30
 
27
- /** Verifies the bearer token when the door is not public, then applies the admission policy. */
31
+ /** Verifies session tokens only for doors without public or credential metadata, then applies the admission policy. */
28
32
  async canActivate(context: ExecutionContext): Promise<boolean> {
29
- const metadata = this.reflector.getAllAndOverride<PublicMetadata | undefined>(PUBLIC_KEY, [
30
- context.getHandler(),
31
- context.getClass(),
32
- ])
33
+ const targets = [context.getHandler(), context.getClass()]
34
+ const required = this.reflector.getAllAndOverride<CredentialMetadata | undefined>(CREDENTIAL_KEY, targets)
35
+ const metadata = this.reflector.getAllAndOverride<PublicMetadata | undefined>(PUBLIC_KEY, targets)
36
+ const credential =
37
+ required === undefined ? undefined : { required, principal: requestOf(context).credentialPrincipal }
33
38
  let principal: Principal | undefined
34
- if (metadata === undefined) {
39
+ if (metadata === undefined && required === undefined) {
35
40
  const request = requestOf(context)
36
41
  const token = bearerToken(request.headers.authorization)
37
42
  if (token !== undefined) {
@@ -42,7 +47,7 @@ export class AuthGuard implements CanActivate {
42
47
  }
43
48
  }
44
49
  }
45
- unwrapOutcome(admit(metadata, principal), IdentityError)
50
+ unwrapOutcome(admit(metadata, principal, credential), IdentityError)
46
51
  return true
47
52
  }
48
53
  }
@@ -1,8 +1,11 @@
1
1
  import type { Principal } from "@modules/platform/cqrs"
2
+ import type { CredentialPrincipal } from "./credential.contracts"
2
3
 
3
4
  declare module "express-serve-static-core" {
4
- /** The request carries the principal established by the default-deny auth guard. */
5
+ /** The request carries the separate principals established by the identity guards. */
5
6
  interface Request {
7
+ /** The identity `CredentialGuard` authenticated on a door marked `@Credential`; never a session principal. */
8
+ credentialPrincipal?: CredentialPrincipal
6
9
  /** The authenticated Supabase caller, when the door is not public. */
7
10
  principal?: Principal
8
11
  }
@@ -12,8 +15,12 @@ declare module "express-serve-static-core" {
12
15
  export enum PublicReason {
13
16
  /** Liveness and readiness probes. */
14
17
  Health = "health",
18
+ /** The authentication protocol's anonymous handshake. */
19
+ AuthHandshake = "auth-handshake",
15
20
  /** A webhook whose signature is verified before its domain intake runs. */
16
21
  SignedWebhook = "signed-webhook",
22
+ /** A declared public catalog read. */
23
+ CatalogRead = "catalog-read",
17
24
  }
18
25
 
19
26
  /** The metadata `@Public` attaches to a door. */
@@ -23,4 +30,4 @@ export interface PublicMetadata {
23
30
  }
24
31
 
25
32
  /** What identity admission returns to the guard. */
26
- export type IdentityAdmission = Principal | PublicReason
33
+ export type IdentityAdmission = Principal | CredentialPrincipal | PublicReason
@@ -5,6 +5,7 @@ import type { TypedParameterDecorator } from "@modules/platform/composition"
5
5
  import type { VerifySupabaseAccessToken } from "@modules/integrations/supabase"
6
6
  import { requestOf } from "@modules/platform/http-security"
7
7
  import type { Principal } from "@modules/platform/cqrs"
8
+ import type { CredentialAuthenticator, CredentialMetadata } from "./credential.contracts"
8
9
  import { IdentityError, IdentityErrorCode } from "./errors/identity.error"
9
10
  import type { PublicMetadata } from "./identity.contracts"
10
11
 
@@ -18,9 +19,23 @@ export const InjectAccessTokenVerifier = (): TypedParameterDecorator<VerifySupab
18
19
  /** Metadata key of `@Public`. */
19
20
  export const PUBLIC_KEY = "domain.identity.public"
20
21
 
21
- /** Opens a door to anonymous callers, stating why; every other door needs a verified Supabase session. */
22
+ /** Opens a door to anonymous callers, stating why; every other door needs a credential or a verified Supabase session. */
22
23
  export const Public = (metadata: PublicMetadata): ReturnType<typeof SetMetadata> => SetMetadata(PUBLIC_KEY, metadata)
23
24
 
25
+ /** Metadata key of `@Credential`. */
26
+ export const CREDENTIAL_KEY: unique symbol = Symbol("domain.identity.credential")
27
+
28
+ /** Token of the identity-owned port that authenticates an API key, run token, tool token or link token. */
29
+ export const CREDENTIAL_AUTHENTICATOR: unique symbol = Symbol("domain.identity.credential-authenticator")
30
+
31
+ /** Injects the credential authenticator identity was registered with. Parameter type: CredentialAuthenticator. */
32
+ export const InjectCredentialAuthenticator = (): TypedParameterDecorator<CredentialAuthenticator> =>
33
+ injector<CredentialAuthenticator>(CREDENTIAL_AUTHENTICATOR)
34
+
35
+ /** Marks a door authenticated by its own credential; `CredentialGuard` authenticates it and `AuthGuard` admits it. */
36
+ export const Credential = (metadata: CredentialMetadata): ReturnType<typeof SetMetadata> =>
37
+ SetMetadata(CREDENTIAL_KEY, metadata)
38
+
24
39
  /** The authenticated Supabase caller established by the default-deny guard. */
25
40
  export const CurrentPrincipal = createParamDecorator((_data: unknown, context: ExecutionContext): Principal => {
26
41
  const principal = requestOf(context).principal
@@ -1,15 +1,28 @@
1
1
  import { Module } from "@nestjs/common"
2
- import type { DynamicModule } from "@nestjs/common"
2
+ import type { DynamicModule, Provider } from "@nestjs/common"
3
3
  import { AuthGuard } from "./auth.guard"
4
- import { ACCESS_TOKEN_VERIFIER } from "./identity.decorators"
4
+ import { CredentialGuard } from "./credential.guard"
5
+ import { IdentityError, IdentityErrorCode } from "./errors/identity.error"
6
+ import { ACCESS_TOKEN_VERIFIER, CREDENTIAL_AUTHENTICATOR } from "./identity.decorators"
5
7
  import { ConfigurableModuleClass, OPTIONS_TYPE } from "./identity.module-definition"
6
8
 
9
+ const refusingAuthenticator: Provider = {
10
+ provide: CREDENTIAL_AUTHENTICATOR,
11
+ useValue: {
12
+ authenticate: () => Promise.reject(new IdentityError({ code: IdentityErrorCode.Unauthenticated })),
13
+ },
14
+ }
15
+
7
16
  @Module({})
8
- /** The identity capability: its default-deny guard admits only public doors or a verified Supabase principal. */
17
+ /** The identity capability: the app registers its credential and default-deny guards after the throttler and origin guard. */
9
18
  export class IdentityModule extends ConfigurableModuleClass {
10
19
  /** Registers identity once per app with the verifier parsed at the composition root. */
11
20
  static register(options: typeof OPTIONS_TYPE): DynamicModule {
12
21
  const base = super.register(options)
22
+ const authenticator: Provider =
23
+ options.credentialAuthenticator === undefined
24
+ ? refusingAuthenticator
25
+ : { provide: CREDENTIAL_AUTHENTICATOR, useClass: options.credentialAuthenticator }
13
26
  return {
14
27
  ...base,
15
28
  providers: [
@@ -18,9 +31,11 @@ export class IdentityModule extends ConfigurableModuleClass {
18
31
  provide: ACCESS_TOKEN_VERIFIER,
19
32
  useValue: options.verifyAccessToken,
20
33
  },
34
+ authenticator,
35
+ CredentialGuard,
21
36
  AuthGuard,
22
37
  ],
23
- exports: [AuthGuard],
38
+ exports: [CredentialGuard, CREDENTIAL_AUTHENTICATOR, AuthGuard, ACCESS_TOKEN_VERIFIER],
24
39
  }
25
40
  }
26
41
  }
@@ -1,7 +1,11 @@
1
+ import type { Type } from "@nestjs/common"
1
2
  import type { VerifySupabaseAccessToken } from "@modules/integrations/supabase"
3
+ import type { CredentialAuthenticator } from "./credential.contracts"
2
4
 
3
- /** The verified identity source used by the default-deny guard. */
5
+ /** The identity capability's credential and session verification options. */
4
6
  export interface IdentityOptions {
7
+ /** The app-owned authenticator of alternate credentials; a credential door fails closed while it is absent. */
8
+ readonly credentialAuthenticator?: Type<CredentialAuthenticator>
5
9
  /** Cryptographically verifies a Supabase access token before it can become a principal. */
6
10
  readonly verifyAccessToken: VerifySupabaseAccessToken
7
11
  }
@@ -1,6 +1,7 @@
1
1
  export { AuthGuard } from "./auth.guard"
2
+ export { CredentialGuard } from "./credential.guard"
2
3
  export { IDENTITY_ERROR_KINDS } from "./errors/identity.error"
3
4
  export { IDENTITY_MESSAGES } from "./messages/identity.messages"
4
5
  export { PublicReason } from "./identity.contracts"
5
- export { Public } from "./identity.decorators"
6
+ export { ACCESS_TOKEN_VERIFIER, Public } from "./identity.decorators"
6
7
  export { IdentityModule } from "./identity.module"
@@ -5,14 +5,11 @@ import type { ErrorKind } from "@modules/platform/errors"
5
5
  export enum SupabaseErrorCode {
6
6
  /** The access token failed signature or required-claim verification. */
7
7
  AccessTokenInvalid = "SUPABASE_ACCESS_TOKEN_INVALID",
8
- /** A privileged Auth or Storage operation could not reach Supabase. */
9
- ProviderUnavailable = "SUPABASE_PROVIDER_UNAVAILABLE",
10
8
  }
11
9
 
12
10
  /** How each Supabase integration code travels. */
13
11
  export const SUPABASE_ERROR_KINDS: Record<SupabaseErrorCode, ErrorKind> = {
14
12
  [SupabaseErrorCode.AccessTokenInvalid]: "unauthenticated",
15
- [SupabaseErrorCode.ProviderUnavailable]: "unavailable",
16
13
  }
17
14
 
18
15
  /** The one error class of the Supabase integration. */
@@ -1,12 +1,24 @@
1
+ import { Test } from "@nestjs/testing"
1
2
  import { Secret } from "@modules/platform/config"
2
3
  import { createSupabaseAdminClient } from "./supabase.client"
4
+ import type { SupabaseAdminClient } from "./supabase.client"
5
+ import { SUPABASE_ADMIN_CLIENT } from "./supabase.decorators"
3
6
 
4
7
  describe("createSupabaseAdminClient", () => {
5
- it("exposes only the Auth administration and Storage surfaces of a session-less service client", () => {
6
- const client = createSupabaseAdminClient({
7
- url: "http://127.0.0.1:54321",
8
- serviceRoleKey: new Secret("service-role"),
9
- })
8
+ it("exposes only the Auth administration and Storage surfaces of a session-less service client", async () => {
9
+ const moduleRef = await Test.createTestingModule({
10
+ providers: [
11
+ {
12
+ provide: SUPABASE_ADMIN_CLIENT,
13
+ useFactory: () =>
14
+ createSupabaseAdminClient({
15
+ url: "http://127.0.0.1:54321",
16
+ serviceRoleKey: new Secret("service-role"),
17
+ }),
18
+ },
19
+ ],
20
+ }).compile()
21
+ const client = moduleRef.get<SupabaseAdminClient>(SUPABASE_ADMIN_CLIENT)
10
22
 
11
23
  expect(Object.keys(client).sort()).toEqual(["auth", "storage"])
12
24
  expect(typeof client.auth.createUser).toBe("function")
@@ -2,10 +2,12 @@ import { Module } from "@nestjs/common"
2
2
  import type { DynamicModule } from "@nestjs/common"
3
3
  import { TypeOrmModule, getDataSourceToken } from "@nestjs/typeorm"
4
4
  import type { DataSource } from "typeorm"
5
+ import { DatabaseProbeService } from "./database-probe.service"
5
6
  import { DATABASE_OPTIONS } from "./database.port"
6
7
  import { ConfigurableModuleClass, OPTIONS_TYPE } from "./database.module-definition"
8
+ import { DatabaseError, DatabaseErrorCode } from "./errors/database.error"
7
9
  import { PRIMARY_CONNECTION } from "./primary.connection"
8
- import { PRIMARY_ENTITY_MANAGER } from "./primary.decorators"
10
+ import { DATABASE_PROBE, PRIMARY_ENTITY_MANAGER } from "./primary.decorators"
9
11
 
10
12
  /** The token each declared connection provides its shared EntityManager under. */
11
13
  type EntityManagerToken = typeof PRIMARY_ENTITY_MANAGER
@@ -20,18 +22,23 @@ export class DatabaseModule extends ConfigurableModuleClass {
20
22
  /** Registers the capability once per app with the connections it opens. */
21
23
  static register(options: typeof OPTIONS_TYPE): DynamicModule {
22
24
  const base = super.register(options)
23
- const managers = options.connections.flatMap((connection) => {
25
+ const managers = options.connections.map((connection) => {
24
26
  const token = ENTITY_MANAGER_TOKENS.get(connection.name)
25
- return token === undefined
26
- ? []
27
- : [
28
- {
29
- provide: token,
30
- inject: [getDataSourceToken(connection.name)],
31
- useFactory: (source: DataSource) => source.manager,
32
- },
33
- ]
27
+ if (token === undefined) {
28
+ throw new DatabaseError({
29
+ code: DatabaseErrorCode.ConnectionUndeclared,
30
+ params: { name: connection.name },
31
+ })
32
+ }
33
+ return {
34
+ provide: token,
35
+ inject: [getDataSourceToken(connection.name)],
36
+ useFactory: (source: DataSource) => source.manager,
37
+ }
34
38
  })
39
+ const probes = options.connections.some((connection) => connection.name === PRIMARY_CONNECTION)
40
+ ? [{ provide: DATABASE_PROBE, useClass: DatabaseProbeService }]
41
+ : []
35
42
  return {
36
43
  ...base,
37
44
  imports: [
@@ -47,8 +54,12 @@ export class DatabaseModule extends ConfigurableModuleClass {
47
54
  }),
48
55
  ),
49
56
  ],
50
- providers: [...(base.providers ?? []), ...managers],
51
- exports: [DATABASE_OPTIONS, ...managers.map((manager) => manager.provide)],
57
+ providers: [...(base.providers ?? []), ...managers, ...probes],
58
+ exports: [
59
+ DATABASE_OPTIONS,
60
+ ...probes.map((probe) => probe.provide),
61
+ ...managers.map((manager) => manager.provide),
62
+ ],
52
63
  }
53
64
  }
54
65
  }
@@ -1,6 +1,11 @@
1
+ import { getDataSourceToken } from "@nestjs/typeorm"
1
2
  import { injector } from "@modules/platform/composition"
2
3
  import type { TypedParameterDecorator } from "@modules/platform/composition"
3
4
  import type { DatabaseOptions } from "./database.options"
5
+ import { PRIMARY_CONNECTION } from "./primary.connection"
6
+
7
+ /** The primary pool's override token for infrastructure fixtures; TypeORM tokens stay owned by this capability. */
8
+ export const PRIMARY_DATA_SOURCE = getDataSourceToken(PRIMARY_CONNECTION)
4
9
 
5
10
  /** Token of the options of the database capability. */
6
11
  export const DATABASE_OPTIONS: unique symbol = Symbol("platform.database.options")
@@ -0,0 +1,16 @@
1
+ import { DomainError } from "@modules/platform/errors"
2
+ import type { ErrorKind } from "@modules/platform/errors"
3
+
4
+ /** Codes of the database capability. */
5
+ export enum DatabaseErrorCode {
6
+ /** A configured connection has no declared EntityManager injection token. */
7
+ ConnectionUndeclared = "DATABASE_CONNECTION_UNDECLARED",
8
+ }
9
+
10
+ /** How each database code travels. */
11
+ export const DATABASE_ERROR_KINDS: Record<DatabaseErrorCode, ErrorKind> = {
12
+ [DatabaseErrorCode.ConnectionUndeclared]: "internal",
13
+ }
14
+
15
+ /** The one error class of the database capability. */
16
+ export class DatabaseError extends DomainError<DatabaseErrorCode> {}
@@ -1,4 +1,7 @@
1
1
  export { DatabaseModule } from "./database.module"
2
+ export { DATABASE_PROBE, InjectDatabaseProbe } from "./primary.decorators"
3
+ export { DatabaseProbeService } from "./database-probe.service"
2
4
  export type { DatabaseConnectionOptions } from "./database.options"
5
+ export { PRIMARY_DATA_SOURCE } from "./database.port"
3
6
  export { DATABASE_ERROR_KINDS } from "./errors/database.error"
4
7
  export { parsePrimaryDatabaseConfig } from "./primary.config"
@@ -1,7 +1,9 @@
1
1
  import { Module } from "@nestjs/common"
2
2
  import type { DynamicModule } from "@nestjs/common"
3
+ import { APP_PIPE } from "@nestjs/core"
3
4
  import { WEBHOOK_SIGNATURE } from "./http-security.decorators"
4
5
  import { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } from "./http-security.module-definition"
6
+ import { ReadinessService } from "./readiness.service"
5
7
  import { RequestValidationService } from "./request-validation.service"
6
8
  import { WebhookSignatureService } from "./webhook-signature.service"
7
9
 
@@ -15,11 +17,12 @@ export class HttpSecurityModule extends ConfigurableModuleClass {
15
17
  ...base,
16
18
  providers: [
17
19
  ...(base.providers ?? []),
18
- RequestValidationService,
20
+ { provide: APP_PIPE, useClass: RequestValidationService },
19
21
  WebhookSignatureService,
22
+ ReadinessService,
20
23
  { provide: WEBHOOK_SIGNATURE, useExisting: WebhookSignatureService },
21
24
  ],
22
- exports: [MODULE_OPTIONS_TOKEN, RequestValidationService, WebhookSignatureService, WEBHOOK_SIGNATURE],
25
+ exports: [MODULE_OPTIONS_TOKEN, WebhookSignatureService, WEBHOOK_SIGNATURE, ReadinessService],
23
26
  }
24
27
  }
25
28
  }
@@ -6,4 +6,7 @@ export type { HttpSecurityOptions } from "./http-security.options"
6
6
  export { HTTP_SECURITY_MESSAGES } from "./messages/http-security.messages"
7
7
  export { OriginGuard } from "./origin.guard"
8
8
  export { RateLimitGuard } from "./rate-limit.guard"
9
- export { RequestValidationService } from "./request-validation.service"
9
+ export { applyHttpSecurity } from "./edge-security.service"
10
+ export { ReadinessService } from "./readiness.service"
11
+ export { InjectReadiness } from "./readiness.decorators"
12
+ export type { ReadinessReport } from "./readiness.contracts"
@@ -1,4 +1,4 @@
1
1
  export type { Locale, MessageBundle } from "./i18n.contracts"
2
- export { InjectMessageCatalog, InjectRequestLocale, MESSAGE_CATALOG } from "./i18n.decorators"
2
+ export { InjectMessageCatalog, InjectRequestLocale, MESSAGE_CATALOG, REQUEST_LOCALE } from "./i18n.decorators"
3
3
  export { I18nModule } from "./i18n.module"
4
4
  export type { MessageCatalog, RequestLocale } from "./i18n.port"
@@ -0,0 +1,7 @@
1
+ /** Log events of the process lifecycle: the entry points log these. */
2
+ export enum LoggingLogEvent {
3
+ /** The HTTP listener is bound and the service answers requests. */
4
+ ServerStarted = "server.started",
5
+ /** The service failed before it could serve; the process exits non-zero. */
6
+ StartupFailed = "server.startup_failed",
7
+ }
@@ -0,0 +1,29 @@
1
+ import { Secret } from "@modules/platform/config"
2
+ import { PRIMARY_DATA_SOURCE } from "@modules/platform/database"
3
+ import { ACCESS_TOKEN_VERIFIER } from "@modules/domain/identity"
4
+ import { SUPABASE_ACCESS_TOKEN_VERIFIER, SUPABASE_ADMIN_CLIENT } from "@modules/integrations/supabase"
5
+
6
+ /** Compiles the real API root with public configuration and explicit infrastructure doubles. */
7
+ export const buildWiringProof = () => ({
8
+ options: {
9
+ server: { port: 3000 },
10
+ httpSecurity: {
11
+ allowedOrigins: ["https://fixture.example"],
12
+ rateLimit: { windowMs: 60000, defaultLimit: 100, strictLimit: 10 },
13
+ webhooks: {},
14
+ },
15
+ database: {
16
+ name: "primary",
17
+ provider: "supabase",
18
+ url: new Secret("postgresql://app_be@127.0.0.1:5432/postgres"),
19
+ schema: "public",
20
+ },
21
+ supabase: { url: "https://fixture.supabase.co", serviceRoleKey: new Secret("fixture-only") },
22
+ },
23
+ overrides: [
24
+ { token: PRIMARY_DATA_SOURCE, value: { manager: {} } },
25
+ { token: SUPABASE_ADMIN_CLIENT, value: { auth: {}, storage: {} } },
26
+ { token: SUPABASE_ACCESS_TOKEN_VERIFIER, value: () => ({ ok: false }) },
27
+ { token: ACCESS_TOKEN_VERIFIER, value: () => ({ ok: false }) },
28
+ ],
29
+ })
@@ -0,0 +1,16 @@
1
+ import { getDataSourceToken } from "@nestjs/typeorm"
2
+ import { Secret } from "@modules/platform/config"
3
+ import { PRIMARY_CONNECTION } from "@modules/platform/database"
4
+
5
+ /** Public fixture options and the database double for compiling the real CLI AppModule without network access. */
6
+ export const buildWiringProof = () => ({
7
+ options: {
8
+ connections: [{
9
+ name: PRIMARY_CONNECTION,
10
+ provider: "supabase",
11
+ url: new Secret("postgresql://app_be@127.0.0.1:5432/postgres"),
12
+ schema: "public",
13
+ }],
14
+ },
15
+ overrides: [{ token: getDataSourceToken(PRIMARY_CONNECTION), value: { manager: {} } }],
16
+ })
@@ -0,0 +1,6 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { {{Name}}ApiOptions } from "./{{name}}.options"
3
+
4
+ /** The configurable-module base of the {{name}} module. */
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ new ConfigurableModuleBuilder<{{Name}}ApiOptions>().setClassMethodName("register").build()
@@ -1,7 +1,8 @@
1
1
  import { Module } from "@nestjs/common"
2
2
  import { Create{{Singular}}Handler } from "./application/create-{{singular}}.handler"
3
3
  import { Get{{Singular}}Handler } from "./application/get-{{singular}}.handler"
4
+ import { ConfigurableModuleClass } from "./{{name}}.module-definition"
4
5
 
5
6
  @Module({ providers: [Get{{Singular}}Handler, Create{{Singular}}Handler] })
6
7
  /** The {{name}} feature: the handlers of its application. */
7
- export class {{Name}}ApiModule {}
8
+ export class {{Name}}ApiModule extends ConfigurableModuleClass {}
@@ -0,0 +1,2 @@
1
+ /** The {{name}} module takes no options yet. */
2
+ export type {{Name}}ApiOptions = Record<string, never>
@@ -0,0 +1,6 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { {{Name}}GraphqlOptions } from "./{{name}}-graphql.options"
3
+
4
+ /** The configurable-module base of the {{name}}-graphql module. */
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ new ConfigurableModuleBuilder<{{Name}}GraphqlOptions>().setClassMethodName("register").build()
@@ -2,7 +2,8 @@ import { Module } from "@nestjs/common"
2
2
  import { {{Name}}ApiModule } from "../../{{name}}.module"
3
3
  import { Create{{Singular}}Resolver } from "./create-{{singular}}.resolver"
4
4
  import { Get{{Singular}}Resolver } from "./get-{{singular}}.resolver"
5
+ import { ConfigurableModuleClass } from "./{{name}}-graphql.module-definition"
5
6
 
6
- @Module({ imports: [{{Name}}ApiModule], providers: [Get{{Singular}}Resolver, Create{{Singular}}Resolver] })
7
+ @Module({ imports: [{{Name}}ApiModule.register({})], providers: [Get{{Singular}}Resolver, Create{{Singular}}Resolver] })
7
8
  /** The GraphQL transport of the {{name}} feature. */
8
- export class {{Name}}GraphqlModule {}
9
+ export class {{Name}}GraphqlModule extends ConfigurableModuleClass {}
@@ -0,0 +1,2 @@
1
+ /** The {{name}}-graphql module takes no options yet. */
2
+ export type {{Name}}GraphqlOptions = Record<string, never>