@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
@@ -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 = (
@@ -1,17 +1,18 @@
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, IdentityModule } from "@modules/domain/identity"
6
- import { LivenessModule } from "@modules/domain/liveness"
7
- import { NoteModule } from "@modules/domain/note"
4
+ import { AuthGuard, CredentialGuard } from "@modules/domain/identity"
8
5
  import { ErrorsFilter } from "@modules/platform/errors"
9
6
  import { OriginGuard, RateLimitGuard } from "@modules/platform/http-security"
10
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"
11
12
  import type { {{appPascal}}Options } from "./{{app}}.options"
12
13
 
13
14
  @Module({})
14
- /** Composition root of the {{app}} app: it spreads the composition groups of `compose/`, 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. */
15
16
  export class AppModule {
16
17
  /** Builds the module from options parsed once at boot; nothing here reads the environment. */
17
18
  static register(options: {{appPascal}}Options): DynamicModule {
@@ -19,15 +20,16 @@ export class AppModule {
19
20
  module: AppModule,
20
21
  imports: [
21
22
  ...platformModules(options),
22
- IdentityModule.register({ isGlobal: true }),
23
- LivenessModule.register({ isGlobal: true }),
24
- NoteModule.register({ isGlobal: true }),
25
- SystemHealthHttpModule,
23
+ ...domainModules(),
24
+ ...systemModules(),
25
+ ...apiModules(),
26
+ ...webhooksModules(),
26
27
  ],
27
28
  providers: [
28
29
  { provide: APP_FILTER, useClass: ErrorsFilter },
29
30
  { provide: APP_GUARD, useClass: RateLimitGuard },
30
31
  { provide: APP_GUARD, useClass: OriginGuard },
32
+ { provide: APP_GUARD, useClass: CredentialGuard },
31
33
  { provide: APP_GUARD, useClass: AuthGuard },
32
34
  ],
33
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
+ ]
@@ -9,6 +9,7 @@ import { ERRORS_MESSAGES, ErrorsModule } from "@modules/platform/errors"
9
9
  import { HTTP_SECURITY_ERROR_KINDS, HTTP_SECURITY_MESSAGES, HttpSecurityModule } from "@modules/platform/http-security"
10
10
  import { I18nModule } from "@modules/platform/i18n"
11
11
  import { LoggingModule } from "@modules/platform/logging"
12
+ import { RetryModule } from "@modules/platform/retry"
12
13
  import type { {{appPascal}}Options } from "../{{app}}.options"
13
14
 
14
15
  /**
@@ -18,6 +19,7 @@ import type { {{appPascal}}Options } from "../{{app}}.options"
18
19
  */
19
20
  export const platformModules = (options: {{appPascal}}Options): Array<DynamicModule> => [
20
21
  ClockModule.register({ isGlobal: true }),
22
+ RetryModule.register({ isGlobal: true }),
21
23
  LoggingModule.register({ isGlobal: true }),
22
24
  I18nModule.register({
23
25
  isGlobal: true,
@@ -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,6 +1,7 @@
1
1
  import { NestFactory } from "@nestjs/core"
2
2
  import { SystemClockService } from "@modules/platform/clock"
3
3
  import { EnvSource } from "@modules/platform/config"
4
+ import { applyHttpSecurity } from "@modules/platform/http-security"
4
5
  import { createJsonLogger, LoggingLogEvent } from "@modules/platform/logging"
5
6
  import { AppModule } from "./app.module"
6
7
  import { read{{appPascal}}Env } from "./{{app}}.env"
@@ -9,4 +10,17 @@ import { read{{appPascal}}Env } from "./{{app}}.env"
9
10
  const bootstrap = async (): Promise<void> => {
10
11
  const env = EnvSource.fromProcess()
11
12
  const options = read{{appPascal}}Env(env)
12
- {{> be/common/main-listen.ts.partial}}
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
  }
@@ -1,20 +1,12 @@
1
- import { noteEntities, noteMigrations } from "@modules/domain/note"
2
- import type { DatabaseConnectionConfig, DatabaseConnectionOptions } from "@modules/platform/database"
1
+ import type { DatabaseConnectionConfig } from "@modules/platform/database"
3
2
 
4
- /** 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. */
5
4
  export interface CliAppOptions {
6
- /** The connections the migrate and seed commands run on, in the order they run them. */
7
- readonly connections: ReadonlyArray<DatabaseConnectionOptions>
5
+ /** The primary connection before its owners' entities and migrations are bound. */
6
+ readonly database: DatabaseConnectionConfig
8
7
  }
9
8
 
10
- /** The primary database: the notes. */
11
- export const primaryConnectionOf = (primary: DatabaseConnectionConfig): DatabaseConnectionOptions => ({
12
- ...primary,
13
- entities: noteEntities,
14
- migrations: noteMigrations,
15
- })
16
-
17
- /** Binds the parsed cli configuration to each connection's entities and migrations. */
9
+ /** Passes the parsed primary connection to composition without adding registrations. */
18
10
  export const createCliAppOptions = (primary: DatabaseConnectionConfig): CliAppOptions => ({
19
- connections: [primaryConnectionOf(primary)],
11
+ database: primary,
20
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
+ ]
@@ -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()
@@ -1,7 +1,12 @@
1
1
  import { Module } from "@nestjs/common"
2
2
  import { SystemHealthModule } from "../../system-health.module"
3
3
  import { LiveController } from "./live.controller"
4
+ import { ReadyController } from "./ready.controller"
5
+ import { ConfigurableModuleClass } from "./system-health-http.module-definition"
4
6
 
5
- @Module({ imports: [SystemHealthModule], controllers: [LiveController] })
6
- /** The HTTP transport of the health feature: the liveness door. */
7
- export class SystemHealthHttpModule {}
7
+ @Module({
8
+ imports: [SystemHealthModule.register({})],
9
+ controllers: [LiveController, ReadyController],
10
+ })
11
+ /** The HTTP transport of the health feature: independent liveness and required-dependency readiness doors. */
12
+ export class SystemHealthHttpModule extends ConfigurableModuleClass {}
@@ -0,0 +1,2 @@
1
+ /** The system-health-http module takes no options yet. */
2
+ export type SystemHealthHttpOptions = Record<string, never>
@@ -0,0 +1,6 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { CliOptions } from "./cli.options"
3
+
4
+ /** The configurable-module base of the cli module. */
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ new ConfigurableModuleBuilder<CliOptions>().setClassMethodName("register").build()
@@ -1,7 +1,8 @@
1
1
  import { Module } from "@nestjs/common"
2
+ import { ConfigurableModuleClass } from "./cli.module-definition"
2
3
  import { MigrateModule } from "./migrate/migrate.module"
3
4
  import { SeedModule } from "./seed/seed.module"
4
5
 
5
- @Module({ imports: [MigrateModule, SeedModule] })
6
+ @Module({ imports: [MigrateModule.register({}), SeedModule.register({})] })
6
7
  /** The cli feature root: every command group of the back end, compiled only into apps/cli. */
7
- export class CliModule {}
8
+ export class CliModule extends ConfigurableModuleClass {}
@@ -0,0 +1,2 @@
1
+ /** The cli module takes no options yet. */
2
+ export type CliOptions = Record<string, never>
@@ -0,0 +1,6 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { MigrateOptions } from "./migrate.options"
3
+
4
+ /** The configurable-module base of the migrate module. */
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ new ConfigurableModuleBuilder<MigrateOptions>().setClassMethodName("register").build()
@@ -1,7 +1,8 @@
1
1
  import { Module } from "@nestjs/common"
2
2
  import { MigrateCli } from "./migrate.cli"
3
+ import { ConfigurableModuleClass } from "./migrate.module-definition"
3
4
  import { RunCli } from "./subs/run.cli"
4
5
 
5
6
  @Module({ providers: [MigrateCli, RunCli] })
6
7
  /** The migrate group: its command and its sub-commands; the connections come from the database capability. */
7
- export class MigrateModule {}
8
+ export class MigrateModule extends ConfigurableModuleClass {}
@@ -0,0 +1,2 @@
1
+ /** The migrate module takes no options yet. */
2
+ export type MigrateOptions = Record<string, never>
@@ -0,0 +1,6 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { SeedOptions } from "./seed.options"
3
+
4
+ /** The configurable-module base of the seed module. */
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ new ConfigurableModuleBuilder<SeedOptions>().setClassMethodName("register").build()
@@ -1,7 +1,8 @@
1
1
  import { Module } from "@nestjs/common"
2
2
  import { SeedCli } from "./seed.cli"
3
+ import { ConfigurableModuleClass } from "./seed.module-definition"
3
4
  import { RunSeedsCli } from "./subs/run.cli"
4
5
 
5
6
  @Module({ providers: [SeedCli, RunSeedsCli] })
6
7
  /** The seed group: its command and its sub-commands; the connections and the seed file reader come from the database capability. */
7
- export class SeedModule {}
8
+ export class SeedModule extends ConfigurableModuleClass {}
@@ -0,0 +1,2 @@
1
+ /** The seed module takes no options yet. */
2
+ export type SeedOptions = Record<string, never>
@@ -3,17 +3,17 @@ import { Test } from "@nestjs/testing"
3
3
  import { SeedRunnerService } from "@modules/platform/database"
4
4
  import { RunSeedsCli } from "./run.cli"
5
5
 
6
- const build = async (seeds: SeedRunnerService) => {
6
+ const build = async () => {
7
+ const seeds = mock<SeedRunnerService>()
7
8
  const moduleRef = await Test.createTestingModule({
8
9
  providers: [RunSeedsCli, { provide: SeedRunnerService, useValue: seeds }],
9
10
  }).compile()
10
- return moduleRef.get(RunSeedsCli)
11
+ return { command: moduleRef.get(RunSeedsCli), seeds }
11
12
  }
12
13
 
13
14
  describe("RunSeedsCli", () => {
14
15
  it("delegates the seed run of the named environment to the seed runner once", async () => {
15
- const seeds = mock<SeedRunnerService>()
16
- const command = await build(seeds)
16
+ const { command, seeds } = await build()
17
17
 
18
18
  await command.run([], { env: "staging" })
19
19
 
@@ -22,8 +22,7 @@ describe("RunSeedsCli", () => {
22
22
  })
23
23
 
24
24
  it("delegates with no environment when none is named, and takes --env as given", async () => {
25
- const seeds = mock<SeedRunnerService>()
26
- const command = await build(seeds)
25
+ const { command, seeds } = await build()
27
26
 
28
27
  await command.run([])
29
28