@starci/hfs 1.0.0 → 2.0.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 (246) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +116 -12
  3. package/bin/hfs.mjs +119 -15
  4. package/emit/compiler.mjs +35 -0
  5. package/emit/contracts.mjs +97 -0
  6. package/emit/operations-worker.mjs +24 -0
  7. package/emit/operations.mjs +126 -0
  8. package/emit/schema-worker.mjs +117 -0
  9. package/emit/static-graph.mjs +670 -0
  10. package/emit/type-schema.mjs +145 -0
  11. package/package.json +10 -2
  12. package/report/sonar.mjs +180 -0
  13. package/runtime/engine/admission.mjs +284 -0
  14. package/runtime/engine/digest.mjs +10 -0
  15. package/runtime/engine/ledger-db.mjs +1245 -0
  16. package/runtime/engine/machine-db.mjs +1484 -0
  17. package/runtime/engine/migrations/machine/0001-init.sql +887 -0
  18. package/runtime/engine/migrations/runtime/0001-init.sql +1072 -0
  19. package/runtime/engine/migrations/runtime/0003-usage-unavailable.sql +13 -0
  20. package/runtime/engine/migrations/runtime/0004-attempt-why.sql +43 -0
  21. package/runtime/engine/plain-object.mjs +5 -0
  22. package/runtime/knowledge/hfs/canon-pins.yaml +16 -58
  23. package/runtime/knowledge/hfs/slots.yaml +409 -140
  24. package/runtime/knowledge/patterns/fe/folder.yaml +309 -0
  25. package/runtime/knowledge/sonar-gate.yaml +85 -0
  26. package/runtime/modules/kernel/failure-codes.yaml +1480 -16
  27. package/runtime/scripts/checks/architecture/backend.mjs +350 -0
  28. package/runtime/scripts/checks/architecture/background-unowned.mjs +107 -0
  29. package/runtime/scripts/checks/architecture/client-reaches-server.mjs +94 -0
  30. package/runtime/scripts/checks/architecture/clones.mjs +200 -0
  31. package/runtime/scripts/checks/architecture/config-unread.mjs +35 -0
  32. package/runtime/scripts/checks/architecture/config.mjs +310 -0
  33. package/runtime/scripts/checks/architecture/connection-map.mjs +208 -0
  34. package/runtime/scripts/checks/architecture/constructor-deps.mjs +100 -0
  35. package/runtime/scripts/checks/architecture/contract-fixture-guard.mjs +128 -0
  36. package/runtime/scripts/checks/architecture/contracts.mjs +792 -0
  37. package/runtime/scripts/checks/architecture/cross-app-duplicate.mjs +73 -0
  38. package/runtime/scripts/checks/architecture/dead-exports.mjs +265 -0
  39. package/runtime/scripts/checks/architecture/default-deny.mjs +129 -0
  40. package/runtime/scripts/checks/architecture/doc-language.mjs +39 -0
  41. package/runtime/scripts/checks/architecture/entrypoint.mjs +57 -0
  42. package/runtime/scripts/checks/architecture/error-codes.mjs +45 -0
  43. package/runtime/scripts/checks/architecture/error-masked.mjs +60 -0
  44. package/runtime/scripts/checks/architecture/fe-slot-allows.mjs +38 -0
  45. package/runtime/scripts/checks/architecture/feature-shape.mjs +49 -0
  46. package/runtime/scripts/checks/architecture/framework-pinned.mjs +83 -0
  47. package/runtime/scripts/checks/architecture/frontend.mjs +995 -0
  48. package/runtime/scripts/checks/architecture/hfs-graph.mjs +61 -0
  49. package/runtime/scripts/checks/architecture/hfs.mjs +521 -0
  50. package/runtime/scripts/checks/architecture/hooks-are-hooks.mjs +88 -0
  51. package/runtime/scripts/checks/architecture/i18n-keys.mjs +146 -0
  52. package/runtime/scripts/checks/architecture/index.mjs +316 -0
  53. package/runtime/scripts/checks/architecture/injection-token-exported.mjs +62 -0
  54. package/runtime/scripts/checks/architecture/machine-ast.mjs +138 -0
  55. package/runtime/scripts/checks/architecture/module-per-transport.mjs +130 -0
  56. package/runtime/scripts/checks/architecture/next-data.mjs +775 -0
  57. package/runtime/scripts/checks/architecture/owners.mjs +89 -0
  58. package/runtime/scripts/checks/architecture/package-shape.mjs +63 -0
  59. package/runtime/scripts/checks/architecture/reachability.mjs +233 -0
  60. package/runtime/scripts/checks/architecture/register-once.mjs +141 -0
  61. package/runtime/scripts/checks/architecture/registration.mjs +319 -0
  62. package/runtime/scripts/checks/architecture/required-files.mjs +172 -0
  63. package/runtime/scripts/checks/architecture/route-files-thin.mjs +97 -0
  64. package/runtime/scripts/checks/architecture/schema-owner.mjs +261 -0
  65. package/runtime/scripts/checks/architecture/size-growth.mjs +73 -0
  66. package/runtime/scripts/checks/architecture/source-names.mjs +607 -0
  67. package/runtime/scripts/checks/architecture/sql-owner.mjs +142 -0
  68. package/runtime/scripts/checks/architecture/sql-tokens.mjs +327 -0
  69. package/runtime/scripts/checks/architecture/symbols.mjs +193 -0
  70. package/runtime/scripts/checks/architecture/test-world-files.mjs +163 -0
  71. package/runtime/scripts/checks/architecture/tiers.mjs +130 -0
  72. package/runtime/scripts/checks/architecture/transport-owner.mjs +112 -0
  73. package/runtime/scripts/checks/architecture/typescript.mjs +500 -0
  74. package/runtime/scripts/checks/architecture/unit-spec-providers.mjs +122 -0
  75. package/runtime/scripts/checks/architecture.mjs +41 -0
  76. package/runtime/scripts/checks/common.mjs +37 -0
  77. package/runtime/scripts/checks/typescript-programs.mjs +82 -0
  78. package/runtime/scripts/lib/artifact-hold.mjs +89 -0
  79. package/runtime/scripts/lib/artifact-store.mjs +103 -0
  80. package/runtime/scripts/lib/fs-kind.mjs +10 -0
  81. package/runtime/scripts/lib/git.mjs +53 -0
  82. package/runtime/scripts/lib/hfs-allows.mjs +57 -0
  83. package/runtime/scripts/lib/hfs-check.mjs +254 -28
  84. package/runtime/scripts/lib/hfs-rules/contract.mjs +126 -0
  85. package/runtime/scripts/lib/hfs-rules/deps.mjs +63 -0
  86. package/runtime/scripts/lib/hfs-rules/fe-no-tests.mjs +47 -0
  87. package/runtime/scripts/lib/hfs-rules/frontend.mjs +124 -0
  88. package/runtime/scripts/lib/hfs-rules/lint-suppression.mjs +34 -0
  89. package/runtime/scripts/lib/hfs-rules/pipeline.mjs +51 -0
  90. package/runtime/scripts/lib/hfs-rules/proof-commands.mjs +64 -0
  91. package/runtime/scripts/lib/hfs-rules/read.mjs +28 -0
  92. package/runtime/scripts/lib/hfs-rules/repo-local-checks.mjs +32 -0
  93. package/runtime/scripts/lib/hfs-rules/secrets.mjs +54 -0
  94. package/runtime/scripts/lib/hfs-rules/spec-placement.mjs +31 -0
  95. package/runtime/scripts/lib/hfs-rules/stacks.mjs +54 -0
  96. package/runtime/scripts/lib/hfs-rules/test-topology.mjs +31 -0
  97. package/runtime/scripts/lib/hfs-slots.mjs +95 -41
  98. package/runtime/scripts/lib/hfs-tree.mjs +80 -0
  99. package/runtime/scripts/lib/hfs-view.mjs +68 -0
  100. package/runtime/scripts/lib/json.mjs +22 -0
  101. package/runtime/scripts/lib/language.mjs +107 -0
  102. package/runtime/scripts/lib/path-key.mjs +2 -0
  103. package/runtime/scripts/lib/redact.mjs +148 -0
  104. package/runtime/scripts/lib/repo-identity.mjs +50 -0
  105. package/runtime/scripts/lib/safe-remove.mjs +179 -0
  106. package/runtime/scripts/lib/secret-patterns.mjs +44 -0
  107. package/runtime/scripts/lib/sleep-sync.mjs +17 -0
  108. package/runtime/scripts/lib/stack-declaration.mjs +52 -0
  109. package/runtime/scripts/lib/stack-services.mjs +217 -0
  110. package/runtime/scripts/lib/test-secrets.mjs +120 -0
  111. package/scaffold/service.mjs +333 -0
  112. package/sync/format.mjs +46 -0
  113. package/sync/hygiene.mjs +56 -24
  114. package/sync/index.mjs +126 -41
  115. package/sync/managed.mjs +170 -0
  116. package/sync/skeleton.mjs +32 -10
  117. package/sync/sonar-key.mjs +13 -0
  118. package/sync/ts-strict.mjs +48 -0
  119. package/templates/{common → be/ci-workflows/github/workflows}/ci.yml +13 -13
  120. package/templates/be/{e2e.yml → ci-workflows/github/workflows/e2e.yml} +1 -0
  121. package/templates/be/hooks/husky/pre-commit +13 -0
  122. package/templates/be/hooks/husky/pre-push +7 -0
  123. package/templates/be/package-scripts/package.json +21 -0
  124. package/templates/be/{sonar-project.properties → quality-config/sonar-project.properties} +2 -3
  125. package/templates/be/skeleton/apps/__app__/src/app.module.ts +14 -4
  126. package/templates/be/skeleton/apps/__app__/src/main.ts +9 -6
  127. package/templates/be/skeleton/scripts/.gitkeep +0 -0
  128. package/templates/be/skeleton/src/features/system-health/application/check-liveness.contracts.ts +2 -0
  129. package/templates/be/skeleton/src/features/system-health/application/check-liveness.handler.ts +22 -0
  130. package/templates/be/skeleton/src/features/system-health/application/check-liveness.query.ts +11 -0
  131. package/templates/be/skeleton/src/features/system-health/index.ts +1 -1
  132. package/templates/be/skeleton/src/features/system-health/system-health.module.ts +3 -3
  133. package/templates/be/skeleton/src/features/system-health/transport/http/live.controller.ts +8 -9
  134. package/templates/be/skeleton/src/features/system-health/transport/http/system-health-http.module.ts +3 -6
  135. package/templates/be/skeleton/src/modules/domain/liveness/index.ts +3 -0
  136. package/templates/be/skeleton/src/modules/domain/liveness/liveness.contracts.ts +9 -0
  137. package/templates/be/skeleton/src/modules/domain/liveness/liveness.module-definition.ts +7 -0
  138. package/templates/be/skeleton/src/modules/domain/liveness/liveness.module.ts +14 -0
  139. package/templates/be/skeleton/src/modules/domain/liveness/liveness.options.ts +2 -0
  140. package/templates/be/skeleton/src/modules/domain/liveness/liveness.service.spec.ts +40 -0
  141. package/templates/be/skeleton/src/modules/domain/liveness/liveness.service.ts +26 -0
  142. package/templates/be/skeleton/src/modules/platform/clock/clock.decorators.ts +9 -0
  143. package/templates/be/skeleton/src/modules/platform/clock/clock.module-definition.ts +7 -0
  144. package/templates/be/skeleton/src/modules/platform/clock/clock.module.ts +19 -0
  145. package/templates/be/skeleton/src/modules/platform/clock/clock.options.ts +2 -0
  146. package/templates/be/skeleton/src/modules/platform/clock/clock.port.ts +5 -0
  147. package/templates/be/skeleton/src/modules/platform/clock/index.ts +4 -0
  148. package/templates/be/skeleton/src/modules/platform/clock/system-clock.service.spec.ts +20 -0
  149. package/templates/be/skeleton/src/modules/platform/clock/system-clock.service.ts +9 -0
  150. package/templates/be/skeleton/src/modules/platform/composition/composition.decorators.ts +8 -0
  151. package/templates/be/skeleton/src/modules/platform/composition/index.ts +2 -0
  152. package/templates/be/skeleton/src/modules/platform/config/config.decorators.ts +9 -0
  153. package/templates/be/skeleton/src/modules/platform/config/index.ts +2 -3
  154. package/templates/be/skeleton/src/modules/platform/config/server.config.ts +1 -1
  155. package/templates/be/skeleton/src/modules/platform/config/server.options.ts +0 -3
  156. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +5 -0
  157. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.decorators.ts +9 -0
  158. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.handler.ts +27 -0
  159. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.log-events.ts +5 -0
  160. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module-definition.ts +7 -0
  161. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module.ts +18 -0
  162. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.options.ts +2 -0
  163. package/templates/be/skeleton/src/modules/platform/cqrs/index.ts +4 -0
  164. package/templates/be/skeleton/src/modules/platform/errors/error.filter.ts +8 -12
  165. package/templates/be/skeleton/src/modules/platform/errors/errors.log-events.ts +5 -0
  166. package/templates/be/skeleton/src/modules/platform/errors/index.ts +1 -1
  167. package/templates/be/skeleton/src/modules/platform/logging/index.ts +4 -4
  168. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +99 -0
  169. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +38 -0
  170. package/templates/be/skeleton/src/modules/platform/logging/logging.decorators.ts +9 -0
  171. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +7 -0
  172. package/templates/be/skeleton/src/modules/platform/logging/logging.module-definition.ts +7 -0
  173. package/templates/be/skeleton/src/modules/platform/logging/logging.module.ts +23 -10
  174. package/templates/be/skeleton/src/modules/platform/logging/logging.options.ts +2 -0
  175. package/templates/be/skeleton/src/modules/platform/logging/logging.port.ts +15 -0
  176. package/templates/be/tool-config/eslint.config.mjs +3 -0
  177. package/templates/be/tool-config/jest.config.js +1 -0
  178. package/templates/be/tool-config/prettierignore +8 -0
  179. package/templates/be/tool-config/prettierrc +1 -0
  180. package/templates/be/tool-config/src/tests/tsconfig.json +5 -0
  181. package/templates/be/tool-config/tsconfig.build.json +5 -0
  182. package/templates/be/tool-config/tsconfig.json +11 -0
  183. package/templates/common/gitignore.base +1 -1
  184. package/templates/fe/ci-workflows/github/workflows/ci.yml +54 -0
  185. package/templates/fe/hooks/husky/pre-commit +16 -0
  186. package/templates/fe/hooks/husky/pre-push +6 -0
  187. package/templates/fe/package-scripts/package.json +17 -0
  188. package/templates/fe/parts/api-client.ts +44 -0
  189. package/templates/fe/parts/api-outcome.ts +7 -0
  190. package/templates/fe/quality-config/sonar-project.properties +8 -0
  191. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/layout.tsx +1 -1
  192. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/not-found.tsx +1 -1
  193. package/templates/fe/skeleton/apps/__app__/src/app/global-error.tsx +1 -1
  194. package/templates/fe/skeleton/scripts/.gitkeep +0 -0
  195. package/templates/fe/skeleton-app/apps/__app__/src/modules/api/client.ts +1 -0
  196. package/templates/fe/skeleton-app/apps/__app__/src/modules/api/index.ts +3 -0
  197. package/templates/fe/skeleton-app/apps/__app__/src/modules/api/outcome.ts +1 -0
  198. package/templates/fe/skeleton-app/apps/__app__/src/modules/i18n/index.ts +4 -0
  199. package/templates/fe/skeleton-shared/apps/__app__/next.config.ts +12 -0
  200. package/templates/fe/skeleton-shared/apps/__app__/src/modules/api/index.ts +2 -0
  201. package/templates/fe/skeleton-shared/apps/__app__/src/modules/i18n/index.ts +9 -0
  202. package/templates/fe/skeleton-shared/apps/__app__/src/modules/i18n/request.ts +5 -0
  203. package/templates/fe/skeleton-shared/apps/__app__/src/proxy.ts +5 -0
  204. package/templates/fe/skeleton-shared/packages/__family__-api/package.json +12 -0
  205. package/templates/fe/skeleton-shared/packages/__family__-api/src/client.ts +1 -0
  206. package/templates/fe/skeleton-shared/packages/__family__-api/src/index.ts +3 -0
  207. package/templates/fe/skeleton-shared/packages/__family__-api/src/outcome.ts +1 -0
  208. package/templates/fe/skeleton-shared/packages/__family__-api/tsconfig.json +5 -0
  209. package/templates/fe/skeleton-shared/packages/__family__-i18n/package.json +18 -0
  210. package/templates/fe/skeleton-shared/packages/__family__-i18n/src/app.ts +19 -0
  211. package/templates/fe/skeleton-shared/packages/__family__-i18n/src/index.ts +2 -0
  212. package/templates/fe/skeleton-shared/packages/__family__-i18n/src/proxy.ts +12 -0
  213. package/templates/fe/skeleton-shared/packages/__family__-i18n/src/request.ts +15 -0
  214. package/templates/fe/skeleton-shared/packages/__family__-i18n/tsconfig.json +5 -0
  215. package/templates/fe/tool-config/eslint.config.mjs +3 -0
  216. package/templates/fe/tool-config/prettierignore +10 -0
  217. package/templates/fe/tool-config/prettierrc +1 -0
  218. package/templates/fe/tool-config/stylelint.config.mjs +3 -0
  219. package/templates/fe/tool-config/tsconfig.json +4 -0
  220. package/templates/be/pre-commit +0 -8
  221. package/templates/be/skeleton/apps/__app__/src/__app__.composition.spec.ts +0 -31
  222. package/templates/be/skeleton/src/features/system-health/transport/http/live.controller.spec.ts +0 -13
  223. package/templates/be/skeleton/src/modules/platform/config/env-source.spec.ts +0 -36
  224. package/templates/be/skeleton/src/modules/platform/config/server.config.spec.ts +0 -19
  225. package/templates/be/skeleton/src/modules/platform/errors/domain-error.spec.ts +0 -15
  226. package/templates/be/skeleton/src/modules/platform/errors/error.filter.spec.ts +0 -38
  227. package/templates/be/skeleton/src/modules/platform/logging/json-logger.spec.ts +0 -33
  228. package/templates/be/skeleton/src/modules/platform/logging/json-logger.ts +0 -35
  229. package/templates/be/skeleton/src/modules/platform/logging/log-id.ts +0 -9
  230. package/templates/be/skeleton/src/modules/platform/logging/logger.port.ts +0 -19
  231. package/templates/common/codecov.yml +0 -13
  232. package/templates/common/pre-push +0 -5
  233. package/templates/fe/e2e.yml +0 -22
  234. package/templates/fe/pre-commit +0 -7
  235. package/templates/fe/skeleton/apps/__app__/src/app/health/live/route.spec.ts +0 -10
  236. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/messages.spec.ts +0 -27
  237. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/routing.spec.ts +0 -10
  238. package/templates/fe/sonar-project.properties +0 -11
  239. /package/templates/be/skeleton/src/modules/platform/config/{env-source.ts → env-source.config.ts} +0 -0
  240. /package/templates/be/skeleton/src/modules/platform/errors/{domain-error.ts → domain.error.ts} +0 -0
  241. /package/templates/fe/{skeleton → skeleton-app}/apps/__app__/next.config.ts +0 -0
  242. /package/templates/fe/{skeleton → skeleton-app}/apps/__app__/src/modules/i18n/config.ts +0 -0
  243. /package/templates/fe/{skeleton → skeleton-app}/apps/__app__/src/modules/i18n/navigation.ts +0 -0
  244. /package/templates/fe/{skeleton → skeleton-app}/apps/__app__/src/modules/i18n/request.ts +0 -0
  245. /package/templates/fe/{skeleton → skeleton-app}/apps/__app__/src/modules/i18n/routing.ts +0 -0
  246. /package/templates/fe/{skeleton → skeleton-app}/apps/__app__/src/proxy.ts +0 -0
@@ -0,0 +1,13 @@
1
+ {{header}}
2
+ # Commit gate: the secrets guard and work hygiene of the staged files, types, lint and format of the staged files, the unit specs the staged files touch. Never integration, e2e or contract.
3
+ npx hfs work-hygiene
4
+ npm run typecheck
5
+ sources=$(git diff --cached --name-only --diff-filter=ACMR -- '*.ts' '*.mts' '*.cts' '*.js' '*.mjs' '*.cjs' || true)
6
+ if [ -n "$sources" ]; then
7
+ npx eslint $sources
8
+ npx prettier --check $sources
9
+ fi
10
+ specs=$(git diff --cached --name-only --diff-filter=ACMR -- '*.ts' | grep -v -E '(^|/)src/tests/(world|integration|e2e|contract)/|\.(integration|e2e|contract)-spec\.ts$' || true)
11
+ if [ -n "$specs" ]; then
12
+ npm run test:affected -- --findRelatedTests $specs
13
+ fi
@@ -0,0 +1,7 @@
1
+ {{header}}
2
+ # Push gate: types, lint, format, the architecture check on the owners this push touches, unit specs affected since main. Never integration, e2e or contract.
3
+ npm run typecheck
4
+ npm run lint:check
5
+ npm run format:check
6
+ npm run hfs:check -- --fast
7
+ npm run test:affected -- --changedSince=origin/main
@@ -0,0 +1,21 @@
1
+ {
2
+ "scripts": {
3
+ "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
4
+ {{appScripts}}
5
+ "typecheck": "tsc -p tsconfig.json",
6
+ "typecheck:tests": "tsc -p src/tests/tsconfig.json",
7
+ "lint": "eslint --fix .",
8
+ "lint:check": "eslint .",
9
+ "lint:report": "eslint . --format json --output-file reports/eslint.json",
10
+ "format": "prettier --write .",
11
+ "format:check": "prettier --check .",
12
+ "test": "jest --selectProjects unit --coverage",
13
+ "test:affected": "jest --selectProjects unit --passWithNoTests",
14
+ "test:integration": "npm run typecheck:tests && jest --selectProjects integration",
15
+ "test:e2e": "npm run typecheck:tests && jest --selectProjects e2e",
16
+ "test:contract": "npm run typecheck:tests && jest --selectProjects contract",
17
+ "contract:emit": "hfs emit-contracts",
18
+ "hfs:check": "hfs check",
19
+ "hfs:report": "hfs check --sonar reports/hfs.sonar.json"
20
+ }
21
+ }
@@ -1,10 +1,9 @@
1
+ {{header}}
1
2
  sonar.projectKey={{sonarKey}}
2
- sonar.host.url=https://sonar.starci.org
3
3
  sonar.sourceEncoding=UTF-8
4
4
  sonar.sources=apps,src
5
5
  sonar.tests=apps,src
6
6
  sonar.exclusions={{sonarExclusions}}
7
7
  sonar.test.inclusions=**/*.spec.ts
8
- sonar.coverage.exclusions={{sonarCoverageExclusions}}
9
- sonar.javascript.lcov.reportPaths=coverage/lcov.info
8
+ sonar.externalIssuesReportPaths=reports/hfs.sonar.json,reports/eslint.sonar.json
10
9
  sonar.nodejs.maxspace=8192
@@ -1,19 +1,29 @@
1
- import { DynamicModule, Module } from "@nestjs/common"
1
+ import { Module } from "@nestjs/common"
2
+ import type { DynamicModule } from "@nestjs/common"
2
3
  import { APP_FILTER } from "@nestjs/core"
3
- import { SystemHealthModule } from "@features/system-health"
4
+ import { SystemHealthHttpModule } from "@features/system-health"
5
+ import { LivenessModule } from "@modules/domain/liveness"
6
+ import { ClockModule } from "@modules/platform/clock"
4
7
  import { SERVER_OPTIONS } from "@modules/platform/config"
8
+ import { CqrsModule } from "@modules/platform/cqrs"
5
9
  import { ErrorFilter } from "@modules/platform/errors"
6
10
  import { LoggingModule } from "@modules/platform/logging"
7
11
  import type { {{appPascal}}Options } from "./{{app}}.options"
8
12
 
9
- /** Composition root of the {{app}} app: the features it runs, the platform it needs and its one error filter. */
10
13
  @Module({})
14
+ /** Composition root of the {{app}} app: every capability is registered once, app-wide, the features it runs and its one error filter. */
11
15
  export class AppModule {
12
16
  /** Builds the module from options parsed once at boot; nothing here reads the environment. */
13
17
  static register(options: {{appPascal}}Options): DynamicModule {
14
18
  return {
15
19
  module: AppModule,
16
- imports: [LoggingModule, SystemHealthModule],
20
+ imports: [
21
+ ClockModule.register({ isGlobal: true }),
22
+ LoggingModule.register({ isGlobal: true }),
23
+ CqrsModule.register({ isGlobal: true }),
24
+ LivenessModule.register({ isGlobal: true }),
25
+ SystemHealthHttpModule,
26
+ ],
17
27
  providers: [
18
28
  { provide: SERVER_OPTIONS, useValue: options.server },
19
29
  { provide: APP_FILTER, useClass: ErrorFilter },
@@ -1,20 +1,23 @@
1
1
  import { NestFactory } from "@nestjs/core"
2
+ import { SystemClockService } from "@modules/platform/clock"
2
3
  import { EnvSource, parseServerConfig } from "@modules/platform/config"
3
- import { createJsonLogger, LogId } from "@modules/platform/logging"
4
+ import { createJsonLogger, LoggingLogEvent } from "@modules/platform/logging"
4
5
  import { AppModule } from "./app.module"
5
6
  import type { {{appPascal}}Options } from "./{{app}}.options"
6
7
 
7
8
  /** Reads the environment once, composes the app from the parsed options and serves until a shutdown signal. */
8
9
  const bootstrap = async (): Promise<void> => {
9
- const env = EnvSource.fromProcess()
10
- const options: {{appPascal}}Options = { server: parseServerConfig(env) }
10
+ const options: {{appPascal}}Options = { server: parseServerConfig(EnvSource.fromProcess()) }
11
11
  const app = await NestFactory.create(AppModule.register(options))
12
12
  app.enableShutdownHooks()
13
13
  await app.listen(options.server.port)
14
+ createJsonLogger(new SystemClockService()).info(LoggingLogEvent.ServerStarted, {
15
+ service: "{{app}}",
16
+ port: options.server.port,
17
+ })
14
18
  }
15
19
 
16
- const logger = createJsonLogger()
17
- bootstrap().catch((cause: unknown) => {
18
- logger.error(LogId.StartupFailed, { failure: cause instanceof Error ? { name: cause.name, message: cause.message } : { name: typeof cause } })
20
+ bootstrap().catch((error: unknown) => {
21
+ createJsonLogger(new SystemClockService()).error(LoggingLogEvent.StartupFailed, error, { service: "{{app}}" })
19
22
  process.exitCode = 1
20
23
  })
File without changes
@@ -0,0 +1,2 @@
1
+ /** Checking liveness takes no input. */
2
+ export type CheckLivenessRequest = Readonly<Record<string, never>>
@@ -0,0 +1,22 @@
1
+ import { QueryHandler } from "@nestjs/cqrs"
2
+ import { LivenessService } from "@modules/domain/liveness"
3
+ import type { LivenessReport } from "@modules/domain/liveness"
4
+ import { ICQRSHandler } from "@modules/platform/cqrs"
5
+ import { InjectLogger } from "@modules/platform/logging"
6
+ import type { Logger } from "@modules/platform/logging"
7
+ import { CheckLivenessQuery } from "./check-liveness.query"
8
+
9
+ @QueryHandler(CheckLivenessQuery)
10
+ /** Answers the liveness probe from the liveness service: no dependency is touched, so an outage never restarts the process. */
11
+ export class CheckLivenessHandler extends ICQRSHandler<CheckLivenessQuery, LivenessReport> {
12
+ constructor(
13
+ @InjectLogger() logger: Logger,
14
+ private readonly liveness: LivenessService,
15
+ ) {
16
+ super(logger)
17
+ }
18
+
19
+ protected override process(): Promise<LivenessReport> {
20
+ return this.liveness.check()
21
+ }
22
+ }
@@ -0,0 +1,11 @@
1
+ import { Query } from "@nestjs/cqrs"
2
+ import type { LivenessReport } from "@modules/domain/liveness"
3
+ import type { PublicExecuteParams } from "@modules/platform/cqrs"
4
+ import type { CheckLivenessRequest } from "./check-liveness.contracts"
5
+
6
+ /** Asks whether this process is alive. */
7
+ export class CheckLivenessQuery extends Query<LivenessReport> {
8
+ constructor(readonly params: PublicExecuteParams<CheckLivenessRequest>) {
9
+ super()
10
+ }
11
+ }
@@ -1 +1 @@
1
- export { SystemHealthModule } from "./system-health.module"
1
+ export { SystemHealthHttpModule } from "./transport/http/system-health-http.module"
@@ -1,6 +1,6 @@
1
1
  import { Module } from "@nestjs/common"
2
- import { SystemHealthHttpModule } from "./transport/http/system-health-http.module"
2
+ import { CheckLivenessHandler } from "./application/check-liveness.handler"
3
3
 
4
- /** Application module of the health feature: process-local liveness now, readiness with the design that declares dependencies. */
5
- @Module({ imports: [SystemHealthHttpModule] })
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
6
  export class SystemHealthModule {}
@@ -1,18 +1,17 @@
1
1
  import { Controller, Get } from "@nestjs/common"
2
- import { HealthCheck, HealthCheckResult, HealthCheckService } from "@nestjs/terminus"
2
+ import type { QueryBus } from "@nestjs/cqrs"
3
+ import type { LivenessReport } from "@modules/domain/liveness"
4
+ import { InjectQueryBus } from "@modules/platform/cqrs"
5
+ import { CheckLivenessQuery } from "../../application/check-liveness.query"
3
6
 
4
- /** Liveness probe: answers `{ status, info, error, details }` from process-local state and never touches a dependency. */
5
7
  @Controller("health")
8
+ /** Liveness probe: answers the liveness report from process-local state and never touches a dependency. */
6
9
  export class LiveController {
7
- constructor(
8
- /** Terminus executor; it runs no indicator, so a dependency outage never restarts the process. */
9
- private readonly health: HealthCheckService,
10
- ) {}
10
+ constructor(@InjectQueryBus() private readonly queryBus: QueryBus) {}
11
11
 
12
12
  /** Reports the process alive while its event loop still answers requests. */
13
13
  @Get("live")
14
- @HealthCheck()
15
- live(): Promise<HealthCheckResult> {
16
- return this.health.check([])
14
+ live(): Promise<LivenessReport> {
15
+ return this.queryBus.execute(new CheckLivenessQuery({ request: {} }))
17
16
  }
18
17
  }
@@ -1,10 +1,7 @@
1
1
  import { Module } from "@nestjs/common"
2
- import { TerminusModule } from "@nestjs/terminus"
2
+ import { SystemHealthModule } from "../../system-health.module"
3
3
  import { LiveController } from "./live.controller"
4
4
 
5
- /** The HTTP transport of the health feature. */
6
- @Module({
7
- imports: [TerminusModule],
8
- controllers: [LiveController],
9
- })
5
+ @Module({ imports: [SystemHealthModule], controllers: [LiveController] })
6
+ /** The HTTP transport of the health feature: the liveness door. */
10
7
  export class SystemHealthHttpModule {}
@@ -0,0 +1,3 @@
1
+ export type { LivenessReport } from "./liveness.contracts"
2
+ export { LivenessModule } from "./liveness.module"
3
+ export { LivenessService } from "./liveness.service"
@@ -0,0 +1,9 @@
1
+ /** What a liveness probe answers. */
2
+ export interface LivenessReport {
3
+ /** Always ok: the probe answers only while the process does. */
4
+ readonly status: "ok"
5
+ /** When this process started, as an ISO 8601 instant. */
6
+ readonly startedAt: string
7
+ /** Whole seconds the process has been up. */
8
+ readonly uptimeSeconds: number
9
+ }
@@ -0,0 +1,7 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { LivenessOptions } from "./liveness.options"
3
+
4
+ /** The configurable-module base of the liveness capability; `isGlobal` is decided by the app root. */
5
+ export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<LivenessOptions>()
6
+ .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
7
+ .build()
@@ -0,0 +1,14 @@
1
+ import { Module } from "@nestjs/common"
2
+ import type { DynamicModule } from "@nestjs/common"
3
+ import { LivenessService } from "./liveness.service"
4
+ import { ConfigurableModuleClass, OPTIONS_TYPE } from "./liveness.module-definition"
5
+
6
+ @Module({})
7
+ /** The liveness capability: the process reports itself alive from the Clock, with no dependency to probe. */
8
+ export class LivenessModule extends ConfigurableModuleClass {
9
+ /** Registers the capability once per app. */
10
+ static register(options: typeof OPTIONS_TYPE): DynamicModule {
11
+ const base = super.register(options)
12
+ return { ...base, providers: [...(base.providers ?? []), LivenessService], exports: [LivenessService] }
13
+ }
14
+ }
@@ -0,0 +1,2 @@
1
+ /** The liveness capability takes no options; the empty record keeps `isGlobal` assignable through the configurable module. */
2
+ export type LivenessOptions = Record<never, never>
@@ -0,0 +1,40 @@
1
+ import { Test } from "@nestjs/testing"
2
+ import { FakeClock } from "@starci/jest-preset"
3
+ import { CLOCK } from "@modules/platform/clock"
4
+ import { LivenessService } from "./liveness.service"
5
+
6
+ const START = "2026-01-01T00:00:00.000Z"
7
+
8
+ const build = async () => {
9
+ const clock = new FakeClock(START)
10
+ const moduleRef = await Test.createTestingModule({
11
+ providers: [LivenessService, { provide: CLOCK, useValue: clock }],
12
+ }).compile()
13
+ return { service: moduleRef.get(LivenessService), clock }
14
+ }
15
+
16
+ describe("LivenessService", () => {
17
+ describe("check", () => {
18
+ it("answers ok with the start instant and no uptime right after the start", async () => {
19
+ const { service } = await build()
20
+
21
+ await expect(service.check()).resolves.toEqual({ status: "ok", startedAt: START, uptimeSeconds: 0 })
22
+ })
23
+
24
+ it("counts whole seconds of uptime from the clock and keeps the start instant", async () => {
25
+ const { service, clock } = await build()
26
+
27
+ clock.advance(90_999)
28
+
29
+ await expect(service.check()).resolves.toEqual({ status: "ok", startedAt: START, uptimeSeconds: 90 })
30
+ })
31
+
32
+ it("rounds a partial second down and stays at zero under one second", async () => {
33
+ const { service, clock } = await build()
34
+
35
+ clock.advance(999)
36
+
37
+ await expect(service.check()).resolves.toMatchObject({ uptimeSeconds: 0 })
38
+ })
39
+ })
40
+ })
@@ -0,0 +1,26 @@
1
+ import { Injectable } from "@nestjs/common"
2
+ import { InjectClock } from "@modules/platform/clock"
3
+ import type { Clock } from "@modules/platform/clock"
4
+ import type { LivenessReport } from "./liveness.contracts"
5
+
6
+ const MS_PER_SECOND = 1000
7
+
8
+ @Injectable()
9
+ /** The liveness of this process: it answers ok with when it started and how long it has been up, read from the Clock. */
10
+ export class LivenessService {
11
+ private readonly startedAt: Date
12
+
13
+ constructor(@InjectClock() private readonly clock: Clock) {
14
+ this.startedAt = clock.now()
15
+ }
16
+
17
+ /** The report of one probe; it reads no dependency, so an outage elsewhere never restarts the process. */
18
+ check(): Promise<LivenessReport> {
19
+ const uptime = this.clock.now().getTime() - this.startedAt.getTime()
20
+ return Promise.resolve({
21
+ status: "ok",
22
+ startedAt: this.startedAt.toISOString(),
23
+ uptimeSeconds: Math.floor(uptime / MS_PER_SECOND),
24
+ })
25
+ }
26
+ }
@@ -0,0 +1,9 @@
1
+ import { injector } from "@modules/platform/composition"
2
+ import type { TypedParameterDecorator } from "@modules/platform/composition"
3
+ import type { Clock } from "./clock.port"
4
+
5
+ /** Token of the Clock port. */
6
+ export const CLOCK: unique symbol = Symbol("platform.clock")
7
+
8
+ /** Injects the Clock port. Parameter type: Clock. */
9
+ export const InjectClock = (): TypedParameterDecorator<Clock> => injector<Clock>(CLOCK)
@@ -0,0 +1,7 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { ClockOptions } from "./clock.options"
3
+
4
+ /** The configurable-module base of the clock capability; `isGlobal` is decided by the app root. */
5
+ export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<ClockOptions>()
6
+ .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
7
+ .build()
@@ -0,0 +1,19 @@
1
+ import { Module } from "@nestjs/common"
2
+ import type { DynamicModule } from "@nestjs/common"
3
+ import { CLOCK } from "./clock.decorators"
4
+ import { ConfigurableModuleClass, OPTIONS_TYPE } from "./clock.module-definition"
5
+ import { SystemClockService } from "./system-clock.service"
6
+
7
+ @Module({})
8
+ /** Provides the Clock port backed by the system clock. */
9
+ export class ClockModule extends ConfigurableModuleClass {
10
+ /** Registers the capability once per app. */
11
+ static register(options: typeof OPTIONS_TYPE): DynamicModule {
12
+ const base = super.register(options)
13
+ return {
14
+ ...base,
15
+ providers: [...(base.providers ?? []), { provide: CLOCK, useClass: SystemClockService }],
16
+ exports: [CLOCK],
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,2 @@
1
+ /** The clock capability takes no options; the empty record keeps `isGlobal` assignable through the configurable module. */
2
+ export type ClockOptions = Record<never, never>
@@ -0,0 +1,5 @@
1
+ /** The clock port: business code asks it for the time instead of reading the ambient clock, so a spec can drive time. */
2
+ export interface Clock {
3
+ /** The current instant. */
4
+ now(): Date
5
+ }
@@ -0,0 +1,4 @@
1
+ export type { Clock } from "./clock.port"
2
+ export { CLOCK, InjectClock } from "./clock.decorators"
3
+ export { ClockModule } from "./clock.module"
4
+ export { SystemClockService } from "./system-clock.service"
@@ -0,0 +1,20 @@
1
+ import { Test } from "@nestjs/testing"
2
+ import { SystemClockService } from "./system-clock.service"
3
+
4
+ const build = async () => {
5
+ const moduleRef = await Test.createTestingModule({ providers: [SystemClockService] }).compile()
6
+ return { service: moduleRef.get(SystemClockService) }
7
+ }
8
+
9
+ describe("SystemClockService", () => {
10
+ describe("now", () => {
11
+ it("answers the current instant of the host, a valid date", async () => {
12
+ const { service } = await build()
13
+
14
+ const instant = service.now()
15
+
16
+ expect(instant).toBeInstanceOf(Date)
17
+ expect(Number.isNaN(instant.getTime())).toBe(false)
18
+ })
19
+ })
20
+ })
@@ -0,0 +1,9 @@
1
+ import type { Clock } from "./clock.port"
2
+
3
+ /** The production clock: the one place that reads the ambient system time. It has no dependencies, so the DI container builds it without a decorator and main.ts builds it directly before the container exists. */
4
+ export class SystemClockService implements Clock {
5
+ /** The current instant of the host clock. */
6
+ now(): Date {
7
+ return new Date()
8
+ }
9
+ }
@@ -0,0 +1,8 @@
1
+ import { Inject } from "@nestjs/common"
2
+ import type { InjectionToken } from "@nestjs/common"
3
+
4
+ /** A parameter decorator that remembers the type `T` it injects, so a lint rule can compare it with the parameter annotation. */
5
+ export type TypedParameterDecorator<T> = ParameterDecorator & { readonly __injects?: T }
6
+
7
+ /** Builds the parameter decorator that injects `token`, typed with the value it injects. Every `Inject<Thing>()` is built with it. */
8
+ export const injector = <T>(token: InjectionToken): TypedParameterDecorator<T> => Inject(token)
@@ -0,0 +1,2 @@
1
+ export { injector } from "./composition.decorators"
2
+ export type { TypedParameterDecorator } from "./composition.decorators"
@@ -0,0 +1,9 @@
1
+ import { injector } from "@modules/platform/composition"
2
+ import type { TypedParameterDecorator } from "@modules/platform/composition"
3
+ import type { ServerOptions } from "./server.options"
4
+
5
+ /** Token under which the app module provides its {@link ServerOptions}. */
6
+ export const SERVER_OPTIONS: unique symbol = Symbol("platform.config.server-options")
7
+
8
+ /** Injects the server options of the app. Parameter type: ServerOptions. */
9
+ export const InjectServerOptions = (): TypedParameterDecorator<ServerOptions> => injector<ServerOptions>(SERVER_OPTIONS)
@@ -1,5 +1,4 @@
1
- export { EnvSource } from "./env-source"
2
- export { ConfigError } from "./errors/config.error"
1
+ export { SERVER_OPTIONS } from "./config.decorators"
2
+ export { EnvSource } from "./env-source.config"
3
3
  export { parseServerConfig } from "./server.config"
4
- export { SERVER_OPTIONS } from "./server.options"
5
4
  export type { ServerOptions } from "./server.options"
@@ -1,4 +1,4 @@
1
- import type { EnvSource } from "./env-source"
1
+ import type { EnvSource } from "./env-source.config"
2
2
  import { ConfigError } from "./errors/config.error"
3
3
  import type { ServerOptions } from "./server.options"
4
4
 
@@ -3,6 +3,3 @@ export interface ServerOptions {
3
3
  /** TCP port the HTTP listener binds. */
4
4
  readonly port: number
5
5
  }
6
-
7
- /** Injection token under which the app module provides its {@link ServerOptions}. */
8
- export const SERVER_OPTIONS = "SERVER_OPTIONS"
@@ -0,0 +1,5 @@
1
+ /** The params of a public operation (no caller is identified): the request as the transport mapped it. */
2
+ export interface PublicExecuteParams<T> {
3
+ /** The request mapped by the transport. */
4
+ readonly request: T
5
+ }
@@ -0,0 +1,9 @@
1
+ import { CommandBus, QueryBus } from "@nestjs/cqrs"
2
+ import { injector } from "@modules/platform/composition"
3
+ import type { TypedParameterDecorator } from "@modules/platform/composition"
4
+
5
+ /** Injects the command bus. Parameter type: CommandBus. */
6
+ export const InjectCommandBus = (): TypedParameterDecorator<CommandBus> => injector<CommandBus>(CommandBus)
7
+
8
+ /** Injects the query bus. Parameter type: QueryBus. */
9
+ export const InjectQueryBus = (): TypedParameterDecorator<QueryBus> => injector<QueryBus>(QueryBus)
@@ -0,0 +1,27 @@
1
+ import { InjectLogger } from "@modules/platform/logging"
2
+ import type { Logger } from "@modules/platform/logging"
3
+ import { CqrsLogEvent } from "./cqrs.log-events"
4
+
5
+ /**
6
+ * The template every command and query handler extends. `execute` is what `@nestjs/cqrs` calls: it delegates to the
7
+ * handler `process` and, when that throws, logs `OperationFailed` with the operation name and rethrows. A handler
8
+ * implements `process` and never overrides `execute`. It does not `implements ICommandHandler`: since `@nestjs/cqrs` 11
9
+ * that type is conditional on the message result type, which a class cannot implement; `@CommandHandler` and
10
+ * `@QueryHandler` check the shape where they are applied.
11
+ */
12
+ export abstract class ICQRSHandler<TMessage, TResult> {
13
+ constructor(@InjectLogger() private readonly logger: Logger) {}
14
+
15
+ /** The bus entry point. */
16
+ async execute(message: TMessage): Promise<TResult> {
17
+ try {
18
+ return await this.process(message)
19
+ } catch (error) {
20
+ this.logger.error(CqrsLogEvent.OperationFailed, error, { operation: this.constructor.name })
21
+ throw error
22
+ }
23
+ }
24
+
25
+ /** The one call of the handler: it hands the mapped message to one method of one service and returns the answer. */
26
+ protected abstract process(message: TMessage): Promise<TResult>
27
+ }
@@ -0,0 +1,5 @@
1
+ /** Log events of the cqrs capability. */
2
+ export enum CqrsLogEvent {
3
+ /** A handler threw; the operation name rides in the fields and the failure is the cause. */
4
+ OperationFailed = "cqrs.operation.failed",
5
+ }
@@ -0,0 +1,7 @@
1
+ import { ConfigurableModuleBuilder } from "@nestjs/common"
2
+ import type { CqrsOptions } from "./cqrs.options"
3
+
4
+ /** The configurable-module base of the cqrs capability; `isGlobal` is decided by the app root. */
5
+ export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<CqrsOptions>()
6
+ .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
7
+ .build()
@@ -0,0 +1,18 @@
1
+ import { Module } from "@nestjs/common"
2
+ import type { DynamicModule } from "@nestjs/common"
3
+ import { CqrsModule as NestCqrsModule } from "@nestjs/cqrs"
4
+ import { ConfigurableModuleClass, OPTIONS_TYPE } from "./cqrs.module-definition"
5
+
6
+ @Module({})
7
+ /** Provides the command and query buses; the handlers of every feature register themselves with them. */
8
+ export class CqrsModule extends ConfigurableModuleClass {
9
+ /** Registers the capability once per app. */
10
+ static register(options: typeof OPTIONS_TYPE): DynamicModule {
11
+ const base = super.register(options)
12
+ return {
13
+ ...base,
14
+ imports: [...(base.imports ?? []), NestCqrsModule.forRoot()],
15
+ exports: [NestCqrsModule],
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,2 @@
1
+ /** The cqrs capability takes no options; the empty record keeps `isGlobal` assignable through the configurable module. */
2
+ export type CqrsOptions = Record<never, never>
@@ -0,0 +1,4 @@
1
+ export type { PublicExecuteParams } from "./cqrs.contracts"
2
+ export { InjectQueryBus } from "./cqrs.decorators"
3
+ export { ICQRSHandler } from "./cqrs.handler"
4
+ export { CqrsModule } from "./cqrs.module"
@@ -1,23 +1,19 @@
1
- import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from "@nestjs/common"
1
+ import { Catch, HttpException, HttpStatus } from "@nestjs/common"
2
+ import type { ArgumentsHost, ExceptionFilter } from "@nestjs/common"
2
3
  import type { Response } from "express"
3
- import { Logger, LogId } from "@modules/platform/logging"
4
- import { DomainError } from "./domain-error"
4
+ import { InjectLogger } from "@modules/platform/logging"
5
+ import type { Logger } from "@modules/platform/logging"
6
+ import { ErrorsLogEvent } from "./errors.log-events"
5
7
 
6
- /** What a log line says about a failure: its name and code, never the stack or the request. */
7
- const describeFailure = (exception: unknown): Readonly<Record<string, unknown>> =>
8
- exception instanceof DomainError
9
- ? { name: exception.name, code: exception.code }
10
- : { name: exception instanceof Error ? exception.name : typeof exception }
11
-
12
- /** The one filter of an app: logs every failure once and answers with a status and a code that reveal nothing else. */
13
8
  @Catch()
9
+ /** The one filter of an app: logs every failure once and answers with a status and a code that reveal nothing else. */
14
10
  export class ErrorFilter implements ExceptionFilter {
15
- constructor(private readonly logger: Logger) {}
11
+ constructor(@InjectLogger() private readonly logger: Logger) {}
16
12
 
17
13
  /** Answers the request; an unknown failure is a 500 whose body names no detail. */
18
14
  catch(exception: unknown, host: ArgumentsHost): void {
19
15
  const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR
20
- this.logger.error(LogId.RequestFailed, { status, failure: describeFailure(exception) })
16
+ this.logger.error(ErrorsLogEvent.RequestFailed, exception, { status })
21
17
  const code = status === HttpStatus.INTERNAL_SERVER_ERROR ? "internal_error" : `http_${status}`
22
18
  host.switchToHttp().getResponse<Response>().status(status).json({ code })
23
19
  }
@@ -0,0 +1,5 @@
1
+ /** Log events of the errors capability. */
2
+ export enum ErrorsLogEvent {
3
+ /** A request ended in a failure the app's filter answered. */
4
+ RequestFailed = "http.request_failed",
5
+ }
@@ -1,2 +1,2 @@
1
- export { DomainError } from "./domain-error"
1
+ export { DomainError } from "./domain.error"
2
2
  export { ErrorFilter } from "./error.filter"