@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
@@ -31,27 +31,41 @@ const sendResponse = async (
31
31
  }
32
32
 
33
33
  describe("localizeError", () => {
34
- it("returns an error without a string code unchanged", () => {
35
- const errors = mock<ErrorsService>()
36
- const error: GraphQLFormattedError = { message: "syntax error", extensions: { kind: "invalid" } }
34
+ it.each([undefined, {}, { code: 400 }, { code: null }, { code: false }])(
35
+ "returns an error without a string code unchanged (%p)",
36
+ (extensions) => {
37
+ const errors = mock<ErrorsService>()
38
+ const error: GraphQLFormattedError = { message: "syntax error", extensions }
37
39
 
38
- expect(localizeError(errors, "en", error)).toBe(error)
39
- expect(errors.text).not.toHaveBeenCalled()
40
- })
40
+ expect(localizeError(errors, "en", error)).toBe(error)
41
+ expect(errors.text).not.toHaveBeenCalled()
42
+ },
43
+ )
41
44
 
42
45
  it("replaces a coded error's message with the catalog text and keeps only string and number params", () => {
43
46
  const errors = mock<ErrorsService>()
44
47
  errors.text.mockReturnValue("The item was not found")
45
48
  const error: GraphQLFormattedError = {
46
49
  message: "ITEM_NOT_FOUND",
47
- extensions: { code: "ITEM_NOT_FOUND", params: { id: "i-1", retries: 2, transient: false } },
50
+ locations: [{ line: 1, column: 2 }],
51
+ path: ["items", 0],
52
+ extensions: {
53
+ code: "ITEM_NOT_FOUND",
54
+ params: { id: "i-1", retries: 2, transient: false, missing: null, nested: { id: "i-2" } },
55
+ kind: "missing",
56
+ },
48
57
  }
49
58
 
50
- expect(localizeError(errors, "en", error)).toEqual({ ...error, message: "The item was not found" })
59
+ const localized = localizeError(errors, "en", error)
60
+
61
+ expect(localized).toEqual({ ...error, message: "The item was not found" })
62
+ expect(localized).not.toBe(error)
63
+ expect(localized.extensions).toBe(error.extensions)
64
+ expect(error.message).toBe("ITEM_NOT_FOUND")
51
65
  expect(errors.text).toHaveBeenCalledWith("ITEM_NOT_FOUND", { id: "i-1", retries: 2 }, "en")
52
66
  })
53
67
 
54
- it.each([undefined, null, ["i-1"], "i-1"])("interpolates no params when extensions.params is %p", (params) => {
68
+ it.each([undefined, null, ["i-1"], "i-1", 42, false])("interpolates no params when extensions.params is %p", (params) => {
55
69
  const errors = mock<ErrorsService>()
56
70
  errors.text.mockReturnValue("The item was not found")
57
71
  const error: GraphQLFormattedError = {
@@ -65,40 +79,54 @@ describe("localizeError", () => {
65
79
  })
66
80
 
67
81
  describe("localizeErrorsPlugin", () => {
68
- it("localizes a coded error in the language the x-locale header declares", async () => {
82
+ it("forwards the Accept-Language preferences and localizes every coded error in the resolved locale", async () => {
69
83
  const errors = mock<ErrorsService>()
70
84
  const requestLocale = mock<RequestLocale>()
71
85
  requestLocale.of.mockReturnValue("vi")
72
- errors.text.mockReturnValue("localized: not found")
86
+ errors.text.mockReturnValueOnce("localized: not found").mockReturnValueOnce("localized: denied")
73
87
  const coded: GraphQLFormattedError = { message: "ITEM_NOT_FOUND", extensions: { code: "ITEM_NOT_FOUND" } }
74
- const response = singleResponse([coded])
88
+ const uncoded: GraphQLFormattedError = { message: "syntax error" }
89
+ const denied: GraphQLFormattedError = {
90
+ message: "ACCESS_DENIED",
91
+ extensions: { code: "ACCESS_DENIED", params: { id: "i-1" } },
92
+ }
93
+ const response = singleResponse([coded, uncoded, denied])
75
94
 
76
- const context = requestContext(response, { "x-locale": "vi", "accept-language": "en" })
95
+ const context = requestContext(response, { "accept-language": "vi-VN, en;q=0.8" })
77
96
 
78
97
  await sendResponse(errors, requestLocale, context)
79
98
 
80
- expect(requestLocale.of).toHaveBeenCalledWith("vi")
99
+ expect(requestLocale.of).toHaveBeenCalledTimes(1)
100
+ expect(requestLocale.of).toHaveBeenCalledWith("vi-VN, en;q=0.8")
101
+ expect(errors.text).toHaveBeenNthCalledWith(1, "ITEM_NOT_FOUND", {}, "vi")
102
+ expect(errors.text).toHaveBeenNthCalledWith(2, "ACCESS_DENIED", { id: "i-1" }, "vi")
103
+ expect(errors.text).toHaveBeenCalledTimes(2)
81
104
  expect(response.body.kind === "single" && response.body.singleResult.errors).toEqual([
82
105
  { ...coded, message: "localized: not found" },
106
+ uncoded,
107
+ { ...denied, message: "localized: denied" },
83
108
  ])
109
+ expect(response.body.kind === "single" && response.body.singleResult.errors?.[1]).toBe(uncoded)
84
110
  })
85
111
 
86
- it.each([{}, { "x-locale": "" }])(
87
- "falls back to Accept-Language when the x-locale header is absent or empty (%p)",
88
- async (declared) => {
112
+ it.each([undefined, ""])(
113
+ "delegates an absent or empty Accept-Language header to the locale default (%p)",
114
+ async (language) => {
89
115
  const errors = mock<ErrorsService>()
90
116
  const requestLocale = mock<RequestLocale>()
91
- requestLocale.of.mockReturnValue("en")
92
- errors.text.mockReturnValue("The item was not found")
117
+ requestLocale.of.mockReturnValue("vi")
118
+ errors.text.mockReturnValue("localized: not found")
93
119
  const coded: GraphQLFormattedError = { message: "ITEM_NOT_FOUND", extensions: { code: "ITEM_NOT_FOUND" } }
94
120
 
95
- await sendResponse(
96
- errors,
97
- requestLocale,
98
- requestContext(singleResponse([coded]), { ...declared, "accept-language": "en" }),
99
- )
121
+ const response = singleResponse([coded])
122
+
123
+ await sendResponse(errors, requestLocale, requestContext(response, { "accept-language": language }))
100
124
 
101
- expect(requestLocale.of).toHaveBeenCalledWith("en")
125
+ expect(requestLocale.of).toHaveBeenCalledWith(language)
126
+ expect(errors.text).toHaveBeenCalledWith("ITEM_NOT_FOUND", {}, "vi")
127
+ expect(response.body.kind === "single" && response.body.singleResult.errors).toEqual([
128
+ { ...coded, message: "localized: not found" },
129
+ ])
102
130
  },
103
131
  )
104
132
 
@@ -106,8 +134,34 @@ describe("localizeErrorsPlugin", () => {
106
134
  const errors = mock<ErrorsService>()
107
135
  const requestLocale = mock<RequestLocale>()
108
136
 
109
- await sendResponse(errors, requestLocale, requestContext(singleResponse(), { "accept-language": "en" }))
137
+ const response = singleResponse()
138
+ const body = response.body
110
139
 
140
+ await sendResponse(errors, requestLocale, requestContext(response, { "accept-language": "en" }))
141
+
142
+ expect(response.body).toBe(body)
143
+ expect(response.body.kind === "single" && response.body.singleResult.errors).toBeUndefined()
144
+ expect(requestLocale.of).not.toHaveBeenCalled()
145
+ expect(errors.text).not.toHaveBeenCalled()
146
+ })
147
+
148
+ it("leaves an incremental response alone", async () => {
149
+ const errors = mock<ErrorsService>()
150
+ const requestLocale = mock<RequestLocale>()
151
+ const coded: GraphQLFormattedError = { message: "ITEM_NOT_FOUND", extensions: { code: "ITEM_NOT_FOUND" } }
152
+ const response = mock<GraphQLResponse>({
153
+ body: {
154
+ kind: "incremental",
155
+ initialResult: { errors: [coded], hasNext: true },
156
+ subsequentResults: mock<AsyncGenerator>(),
157
+ },
158
+ })
159
+ const body = response.body
160
+
161
+ await sendResponse(errors, requestLocale, requestContext(response, { "accept-language": "en" }))
162
+
163
+ expect(response.body).toBe(body)
164
+ expect(response.body.kind === "incremental" && response.body.initialResult.errors).toEqual([coded])
111
165
  expect(requestLocale.of).not.toHaveBeenCalled()
112
166
  expect(errors.text).not.toHaveBeenCalled()
113
167
  })
@@ -14,11 +14,8 @@ const paramsOf = (value: unknown): ErrorParams => {
14
14
  return params
15
15
  }
16
16
 
17
- /** The language a request asks for: the `x-locale` header the front end's locale link writes, else Accept-Language. */
18
- const requestedLocale = (context: GraphqlContext): string | undefined => {
19
- const declared = context.req.headers["x-locale"]
20
- return typeof declared === "string" && declared !== "" ? declared : context.req.headers["accept-language"]
21
- }
17
+ /** The language a request asks for: Node normalizes the Accept-Language header name to lowercase. */
18
+ const requestedLocale = (context: GraphqlContext): string | undefined => context.req.headers["accept-language"]
22
19
 
23
20
  /** Replaces the message of an error carrying a code with the catalog text of that code in `locale`; other errors pass unchanged. */
24
21
  export const localizeError = (
@@ -0,0 +1,21 @@
1
+ import type { EnvSource } from "@modules/platform/config"
2
+ import { parseServerConfig } from "@modules/platform/config"
3
+ import { parsePrimaryDatabaseConfig } from "@modules/platform/database"
4
+ import { parseHttpSecurityConfig } from "@modules/platform/http-security"
5
+
6
+ /** The {{app}} environment values required before composition. */
7
+ export interface {{appPascal}}Env {
8
+ /** The HTTP listener. */
9
+ readonly server: ReturnType<typeof parseServerConfig>
10
+ /** The origin allowlist and rate limits. */
11
+ readonly httpSecurity: ReturnType<typeof parseHttpSecurityConfig>
12
+ /** The primary database connection. */
13
+ readonly database: ReturnType<typeof parsePrimaryDatabaseConfig>
14
+ }
15
+
16
+ /** Reads the {{app}} environment; missing or invalid values fail boot, and secrets use `_FILE` first. */
17
+ export const read{{appPascal}}Env = (env: EnvSource): {{appPascal}}Env => ({
18
+ server: parseServerConfig(env),
19
+ httpSecurity: parseHttpSecurityConfig(env),
20
+ database: parsePrimaryDatabaseConfig(env),
21
+ })
@@ -2,7 +2,7 @@ import type { HttpSecurityOptions } from "@modules/platform/http-security"
2
2
  import type { ServerOptions } from "@modules/platform/config"
3
3
  import type { DatabaseConnectionConfig } from "@modules/platform/database"
4
4
 
5
- /** Everything the {{app}} app needs from its environment, parsed once by `main.ts` and handed to `AppModule.register`. */
5
+ /** Everything the {{app}} app needs from its environment, parsed by `{{app}}.env.ts` and handed to `AppModule.register`. */
6
6
  export interface {{appPascal}}Options {
7
7
  /** The HTTP listener. */
8
8
  readonly server: ServerOptions
@@ -1,65 +1,35 @@
1
1
  import { Module } from "@nestjs/common"
2
2
  import type { DynamicModule } from "@nestjs/common"
3
3
  import { APP_FILTER, APP_GUARD } from "@nestjs/core"
4
- import { SystemHealthHttpModule } from "@features/api/system-health"
5
- import { AuthGuard, IDENTITY_ERROR_KINDS, IDENTITY_MESSAGES, IdentityModule } from "@modules/domain/identity"
6
- import { LivenessModule } from "@modules/domain/liveness"
7
- import { NOTE_ERROR_KINDS, NoteModule, noteEntities, noteMigrations } from "@modules/domain/note"
8
- import { ClockModule } from "@modules/platform/clock"
9
- import { CONFIG_ERROR_KINDS } from "@modules/platform/config"
10
- import { CqrsModule } from "@modules/platform/cqrs"
11
- import { DATABASE_ERROR_KINDS, DatabaseModule } from "@modules/platform/database"
12
- import { ERRORS_MESSAGES, ErrorsFilter, ErrorsModule } from "@modules/platform/errors"
13
- import {
14
- HTTP_SECURITY_ERROR_KINDS,
15
- HTTP_SECURITY_MESSAGES,
16
- HttpSecurityModule,
17
- OriginGuard,
18
- RateLimitGuard,
19
- } from "@modules/platform/http-security"
20
- import { I18nModule } from "@modules/platform/i18n"
21
- import { LoggingModule } from "@modules/platform/logging"
4
+ import { AuthGuard, CredentialGuard } from "@modules/domain/identity"
5
+ import { ErrorsFilter } from "@modules/platform/errors"
6
+ import { OriginGuard, RateLimitGuard } from "@modules/platform/http-security"
7
+ import { platformModules } from "./compose/platform"
8
+ import { domainModules } from "./compose/domain"
9
+ import { systemModules } from "./compose/system"
10
+ import { apiModules } from "./compose/api"
11
+ import { webhooksModules } from "./compose/webhooks"
22
12
  import type { {{appPascal}}Options } from "./{{app}}.options"
23
13
 
24
14
  @Module({})
25
- /** Composition root of the {{app}} app: every capability is registered once, app-wide, and the three guards run in a fixed order. */
15
+ /** Composition root of the {{app}} app: it spreads the composition groups of `compose/`, and the four guards run in a fixed order. */
26
16
  export class AppModule {
27
17
  /** Builds the module from options parsed once at boot; nothing here reads the environment. */
28
18
  static register(options: {{appPascal}}Options): DynamicModule {
29
19
  return {
30
20
  module: AppModule,
31
21
  imports: [
32
- ClockModule.register({ isGlobal: true }),
33
- LoggingModule.register({ isGlobal: true }),
34
- I18nModule.register({
35
- isGlobal: true,
36
- bundles: [ERRORS_MESSAGES, HTTP_SECURITY_MESSAGES, IDENTITY_MESSAGES],
37
- }),
38
- ErrorsModule.register({
39
- isGlobal: true,
40
- kinds: [
41
- CONFIG_ERROR_KINDS,
42
- DATABASE_ERROR_KINDS,
43
- HTTP_SECURITY_ERROR_KINDS,
44
- IDENTITY_ERROR_KINDS,
45
- NOTE_ERROR_KINDS,
46
- ],
47
- }),
48
- CqrsModule.register({ isGlobal: true }),
49
- HttpSecurityModule.register({ isGlobal: true, ...options.httpSecurity }),
50
- DatabaseModule.register({
51
- isGlobal: true,
52
- connections: [{ ...options.database, entities: noteEntities, migrations: noteMigrations }],
53
- }),
54
- IdentityModule.register({ isGlobal: true }),
55
- LivenessModule.register({ isGlobal: true }),
56
- NoteModule.register({ isGlobal: true }),
57
- SystemHealthHttpModule,
22
+ ...platformModules(options),
23
+ ...domainModules(),
24
+ ...systemModules(),
25
+ ...apiModules(),
26
+ ...webhooksModules(),
58
27
  ],
59
28
  providers: [
60
29
  { provide: APP_FILTER, useClass: ErrorsFilter },
61
30
  { provide: APP_GUARD, useClass: RateLimitGuard },
62
31
  { provide: APP_GUARD, useClass: OriginGuard },
32
+ { provide: APP_GUARD, useClass: CredentialGuard },
63
33
  { provide: APP_GUARD, useClass: AuthGuard },
64
34
  ],
65
35
  }
@@ -0,0 +1,4 @@
1
+ import type { DynamicModule, Type } from "@nestjs/common"
2
+
3
+ /** The API transports of the app; `starci app add api` registers each generated door here. */
4
+ export const apiModules = (): Array<Type<unknown> | DynamicModule> => []
@@ -0,0 +1,11 @@
1
+ import type { DynamicModule, Type } from "@nestjs/common"
2
+ import { IdentityModule } from "@modules/domain/identity"
3
+ import { LivenessModule } from "@modules/domain/liveness"
4
+ import { NoteModule } from "@modules/domain/note"
5
+
6
+ /** The domain capabilities of the app; transport scaffolding moves the skeleton registrations here. */
7
+ export const domainModules = (): Array<Type<unknown> | DynamicModule> => [
8
+ IdentityModule.register({ isGlobal: true }),
9
+ LivenessModule.register({ isGlobal: true }),
10
+ NoteModule.register({ isGlobal: true }),
11
+ ]
@@ -0,0 +1,44 @@
1
+ import type { DynamicModule } from "@nestjs/common"
2
+ import { IDENTITY_ERROR_KINDS, IDENTITY_MESSAGES } from "@modules/domain/identity"
3
+ import { NOTE_ERROR_KINDS, noteEntities, noteMigrations } from "@modules/domain/note"
4
+ import { ClockModule } from "@modules/platform/clock"
5
+ import { CONFIG_ERROR_KINDS } from "@modules/platform/config"
6
+ import { CqrsModule } from "@modules/platform/cqrs"
7
+ import { DATABASE_ERROR_KINDS, DatabaseModule } from "@modules/platform/database"
8
+ import { ERRORS_MESSAGES, ErrorsModule } from "@modules/platform/errors"
9
+ import { HTTP_SECURITY_ERROR_KINDS, HTTP_SECURITY_MESSAGES, HttpSecurityModule } from "@modules/platform/http-security"
10
+ import { I18nModule } from "@modules/platform/i18n"
11
+ import { LoggingModule } from "@modules/platform/logging"
12
+ import { RetryModule } from "@modules/platform/retry"
13
+ import type { {{appPascal}}Options } from "../{{app}}.options"
14
+
15
+ /**
16
+ * The platform area of the {{app}} app: every platform capability, registered once and app-wide. A composition
17
+ * group holds one area's module list and nothing else (no logic, no environment read); `app.module.ts` spreads it
18
+ * into its imports.
19
+ */
20
+ export const platformModules = (options: {{appPascal}}Options): Array<DynamicModule> => [
21
+ ClockModule.register({ isGlobal: true }),
22
+ RetryModule.register({ isGlobal: true }),
23
+ LoggingModule.register({ isGlobal: true }),
24
+ I18nModule.register({
25
+ isGlobal: true,
26
+ bundles: [ERRORS_MESSAGES, HTTP_SECURITY_MESSAGES, IDENTITY_MESSAGES],
27
+ }),
28
+ ErrorsModule.register({
29
+ isGlobal: true,
30
+ kinds: [
31
+ CONFIG_ERROR_KINDS,
32
+ DATABASE_ERROR_KINDS,
33
+ HTTP_SECURITY_ERROR_KINDS,
34
+ IDENTITY_ERROR_KINDS,
35
+ NOTE_ERROR_KINDS,
36
+ ],
37
+ }),
38
+ CqrsModule.register({ isGlobal: true }),
39
+ HttpSecurityModule.register({ isGlobal: true, ...options.httpSecurity }),
40
+ DatabaseModule.register({
41
+ isGlobal: true,
42
+ connections: [{ ...options.database, entities: noteEntities, migrations: noteMigrations }],
43
+ }),
44
+ ]
@@ -0,0 +1,5 @@
1
+ import type { DynamicModule, Type } from "@nestjs/common"
2
+ import { SystemHealthHttpModule } from "@features/api/system-health"
3
+
4
+ /** The system probes of the app, registered independently of product API doors. */
5
+ export const systemModules = (): Array<Type<unknown> | DynamicModule> => [SystemHealthHttpModule.register({})]
@@ -0,0 +1,4 @@
1
+ import type { DynamicModule, Type } from "@nestjs/common"
2
+
3
+ /** The signed webhook transports of the app; `starci app add webhook` registers each generated door here. */
4
+ export const webhooksModules = (): Array<Type<unknown> | DynamicModule> => []
@@ -1,18 +1,26 @@
1
1
  import { NestFactory } from "@nestjs/core"
2
2
  import { SystemClockService } from "@modules/platform/clock"
3
- import { EnvSource, parseServerConfig } from "@modules/platform/config"
4
- import { parsePrimaryDatabaseConfig } from "@modules/platform/database"
5
- import { parseHttpSecurityConfig } from "@modules/platform/http-security"
3
+ import { EnvSource } from "@modules/platform/config"
4
+ import { applyHttpSecurity } from "@modules/platform/http-security"
6
5
  import { createJsonLogger, LoggingLogEvent } from "@modules/platform/logging"
7
6
  import { AppModule } from "./app.module"
8
- import type { {{appPascal}}Options } from "./{{app}}.options"
7
+ import { read{{appPascal}}Env } from "./{{app}}.env"
9
8
 
10
9
  /** Reads the environment once, composes the app from the parsed options and serves until a shutdown signal. */
11
10
  const bootstrap = async (): Promise<void> => {
12
11
  const env = EnvSource.fromProcess()
13
- const options: {{appPascal}}Options = {
14
- server: parseServerConfig(env),
15
- httpSecurity: parseHttpSecurityConfig(env),
16
- database: parsePrimaryDatabaseConfig(env),
17
- }
18
- {{> be/common/main-listen.ts.partial}}
12
+ const options = read{{appPascal}}Env(env)
13
+ const app = await NestFactory.create(AppModule.register(options), { rawBody: true })
14
+ applyHttpSecurity(app, options.httpSecurity, { trustProxy: 1 })
15
+ app.enableShutdownHooks()
16
+ await app.listen(options.server.port)
17
+ createJsonLogger(new SystemClockService()).info(LoggingLogEvent.ServerStarted, {
18
+ service: "{{app}}",
19
+ port: options.server.port,
20
+ })
21
+ }
22
+
23
+ bootstrap().catch((error: unknown) => {
24
+ createJsonLogger(new SystemClockService()).error(LoggingLogEvent.StartupFailed, error, { service: "{{app}}" })
25
+ process.exitCode = 1
26
+ })
@@ -1,9 +1,7 @@
1
1
  import { Module } from "@nestjs/common"
2
2
  import type { DynamicModule } from "@nestjs/common"
3
- import { CliModule } from "@features/cli"
4
- import { ClockModule } from "@modules/platform/clock"
5
- import { DatabaseModule } from "@modules/platform/database"
6
- import { LoggingModule } from "@modules/platform/logging"
3
+ import { platformModules } from "./compose/platform"
4
+ import { cliModules } from "./compose/cli"
7
5
  import type { CliAppOptions } from "./cli.options"
8
6
 
9
7
  @Module({})
@@ -16,15 +14,7 @@ export class AppModule {
16
14
  static register(options: CliAppOptions): DynamicModule {
17
15
  return {
18
16
  module: AppModule,
19
- imports: [
20
- ClockModule.register({ isGlobal: true }),
21
- LoggingModule.register({ isGlobal: true }),
22
- DatabaseModule.register({
23
- isGlobal: true,
24
- connections: options.connections,
25
- }),
26
- CliModule,
27
- ],
17
+ imports: [...platformModules(options), ...cliModules()],
28
18
  }
29
19
  }
30
20
  }
@@ -0,0 +1,13 @@
1
+ import type { EnvSource, Secret } from "@modules/platform/config"
2
+ import { parsePrimaryDatabaseConfig } from "@modules/platform/database"
3
+
4
+ /** The cli's primary database environment values, before entities and migrations are bound. */
5
+ export interface CliEnv {
6
+ /** The logical connection name declared in `hfs.json`. */
7
+ readonly name: string
8
+ /** The primary database connection URL, read through the secret source. */
9
+ readonly url: Secret
10
+ }
11
+
12
+ /** Reads the required cli connection before composition; its secret uses `_FILE` first and has no default. */
13
+ export const readCliEnv = (env: EnvSource): CliEnv => parsePrimaryDatabaseConfig(env)
@@ -1,22 +1,12 @@
1
- import { noteEntities, noteMigrations } from "@modules/domain/note"
2
- import type { EnvSource } from "@modules/platform/config"
3
- import { parsePrimaryDatabaseConfig } from "@modules/platform/database"
4
- import type { DatabaseConnectionOptions } from "@modules/platform/database"
1
+ import type { DatabaseConnectionConfig } from "@modules/platform/database"
5
2
 
6
- /** Everything the cli app needs from its environment: every connection with the entities and migrations of its owners. */
3
+ /** The parsed database configuration handed to the cli composition root. */
7
4
  export interface CliAppOptions {
8
- /** The connections the migrate and seed commands run on, in the order they run them. */
9
- readonly connections: ReadonlyArray<DatabaseConnectionOptions>
5
+ /** The primary connection before its owners' entities and migrations are bound. */
6
+ readonly database: DatabaseConnectionConfig
10
7
  }
11
8
 
12
- /** The primary database: the notes. */
13
- export const primaryConnectionOf = (env: EnvSource): DatabaseConnectionOptions => ({
14
- ...parsePrimaryDatabaseConfig(env),
15
- entities: noteEntities,
16
- migrations: noteMigrations,
17
- })
18
-
19
- /** Parses the cli options from the environment: one entry per connection, each built by its own function above. */
20
- export const parseCliAppOptions = (env: EnvSource): CliAppOptions => ({
21
- connections: [primaryConnectionOf(env)],
9
+ /** Passes the parsed primary connection to composition without adding registrations. */
10
+ export const createCliAppOptions = (primary: DatabaseConnectionConfig): CliAppOptions => ({
11
+ database: primary,
22
12
  })
@@ -0,0 +1,5 @@
1
+ import type { DynamicModule, Type } from "@nestjs/common"
2
+ import { CliModule } from "@features/cli"
3
+
4
+ /** The command groups of the CLI app. */
5
+ export const cliModules = (): Array<Type<unknown> | DynamicModule> => [CliModule.register({})]
@@ -0,0 +1,24 @@
1
+ import type { DynamicModule } from "@nestjs/common"
2
+ import { noteEntities, noteMigrations } from "@modules/domain/note"
3
+ import { ClockModule } from "@modules/platform/clock"
4
+ import { DatabaseModule } from "@modules/platform/database"
5
+ import type { DatabaseConnectionConfig, DatabaseConnectionOptions } from "@modules/platform/database"
6
+ import { LoggingModule } from "@modules/platform/logging"
7
+ import type { CliAppOptions } from "../cli.options"
8
+
9
+ /** Binds the primary connection to its owners' entities and migrations during composition. */
10
+ const primaryConnectionOf = (primary: DatabaseConnectionConfig): DatabaseConnectionOptions => ({
11
+ ...primary,
12
+ entities: noteEntities,
13
+ migrations: noteMigrations,
14
+ })
15
+
16
+ /** The clock, logger and declared database connections shared by CLI commands. */
17
+ export const platformModules = (options: CliAppOptions): Array<DynamicModule> => [
18
+ ClockModule.register({ isGlobal: true }),
19
+ LoggingModule.register({ isGlobal: true }),
20
+ DatabaseModule.register({
21
+ isGlobal: true,
22
+ connections: [primaryConnectionOf(options.database)],
23
+ }),
24
+ ]
@@ -4,12 +4,13 @@ import { SystemClockService } from "@modules/platform/clock"
4
4
  import { EnvSource } from "@modules/platform/config"
5
5
  import { createJsonLogger, LoggingLogEvent } from "@modules/platform/logging"
6
6
  import { AppModule } from "./app.module"
7
- import { parseCliAppOptions } from "./cli.options"
7
+ import { readCliEnv } from "./cli.env"
8
+ import { createCliAppOptions } from "./cli.options"
8
9
 
9
10
  /** The command line of the back end: reads the environment once and runs the command its arguments name (`cli migrate run`). */
10
11
  async function bootstrap(): Promise<void> {
11
12
  const logger = createJsonLogger(new SystemClockService())
12
- await CommandFactory.run(AppModule.register(parseCliAppOptions(EnvSource.fromProcess())), {
13
+ await CommandFactory.run(AppModule.register(createCliAppOptions(readCliEnv(EnvSource.fromProcess()))), {
13
14
  logger: false,
14
15
  errorHandler: (error: Error) => {
15
16
  logger.error(LoggingLogEvent.StartupFailed, error, { service: "cli" })
@@ -0,0 +1,2 @@
1
+ /** Checking required-dependency readiness takes no input. */
2
+ export type CheckReadinessRequest = Readonly<Record<string, never>>
@@ -0,0 +1,22 @@
1
+ import { QueryHandler } from "@nestjs/cqrs"
2
+ import { ICQRSHandler } from "@modules/platform/cqrs"
3
+ import { InjectReadiness } from "@modules/platform/http-security"
4
+ import type { ReadinessReport, ReadinessService } from "@modules/platform/http-security"
5
+ import { InjectLogger } from "@modules/platform/logging"
6
+ import type { Logger } from "@modules/platform/logging"
7
+ import { CheckReadinessQuery } from "./check-readiness.query"
8
+
9
+ @QueryHandler(CheckReadinessQuery)
10
+ /** Answers readiness only after the bounded required-dependency probe succeeds. */
11
+ export class CheckReadinessHandler extends ICQRSHandler<CheckReadinessQuery, ReadinessReport> {
12
+ constructor(
13
+ @InjectLogger() logger: Logger,
14
+ @InjectReadiness() private readonly readiness: ReadinessService,
15
+ ) {
16
+ super(logger)
17
+ }
18
+
19
+ protected override process(): Promise<ReadinessReport> {
20
+ return this.readiness.probeRequired()
21
+ }
22
+ }
@@ -0,0 +1,11 @@
1
+ import { Query } from "@nestjs/cqrs"
2
+ import type { PublicExecuteParams } from "@modules/platform/cqrs"
3
+ import type { ReadinessReport } from "@modules/platform/http-security"
4
+ import type { CheckReadinessRequest } from "./check-readiness.contracts"
5
+
6
+ /** Asks whether the required primary database can serve requests. */
7
+ export class CheckReadinessQuery extends Query<ReadinessReport> {
8
+ constructor(readonly params: PublicExecuteParams<CheckReadinessRequest>) {
9
+ super()
10
+ }
11
+ }
@@ -0,0 +1,6 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { SystemHealthOptions } from "./system-health.options"
3
+
4
+ /** The configurable-module base of the system-health module. */
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ new ConfigurableModuleBuilder<SystemHealthOptions>().setClassMethodName("register").build()
@@ -1,6 +1,8 @@
1
1
  import { Module } from "@nestjs/common"
2
2
  import { CheckLivenessHandler } from "./application/check-liveness.handler"
3
+ import { CheckReadinessHandler } from "./application/check-readiness.handler"
4
+ import { ConfigurableModuleClass } from "./system-health.module-definition"
3
5
 
4
- @Module({ providers: [CheckLivenessHandler] })
5
- /** Application module of the health feature: the handler of the liveness probe; readiness comes with the design that declares dependencies. */
6
- export class SystemHealthModule {}
6
+ @Module({ providers: [CheckLivenessHandler, CheckReadinessHandler] })
7
+ /** Application handlers of the independent liveness and required-dependency readiness probes. */
8
+ export class SystemHealthModule extends ConfigurableModuleClass {}
@@ -0,0 +1,2 @@
1
+ /** The system-health module takes no options yet. */
2
+ export type SystemHealthOptions = Record<string, never>
@@ -0,0 +1,19 @@
1
+ import { Controller, Get } from "@nestjs/common"
2
+ import type { QueryBus } from "@nestjs/cqrs"
3
+ import { Public, PublicReason } from "@modules/domain/identity"
4
+ import { InjectQueryBus } from "@modules/platform/cqrs"
5
+ import type { ReadinessReport } from "@modules/platform/http-security"
6
+ import { CheckReadinessQuery } from "../../application/check-readiness.query"
7
+
8
+ @Controller("health")
9
+ /** Readiness door: reports the bounded required-dependency probe, independently of process liveness. */
10
+ export class ReadyController {
11
+ constructor(@InjectQueryBus() private readonly queryBus: QueryBus) {}
12
+
13
+ /** Answers 503 while the required primary database cannot serve the app. */
14
+ @Get("ready")
15
+ @Public({ reason: PublicReason.Health })
16
+ ready(): Promise<ReadinessReport> {
17
+ return this.queryBus.execute(new CheckReadinessQuery({ request: {} }))
18
+ }
19
+ }
@@ -0,0 +1,6 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { SystemHealthHttpOptions } from "./system-health-http.options"
3
+
4
+ /** The configurable-module base of the system-health-http module. */
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ new ConfigurableModuleBuilder<SystemHealthHttpOptions>().setClassMethodName("register").build()