@starci/hfs 5.0.7 → 5.1.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 (204) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +7 -8
  3. package/cache/fake-session.mjs +1 -1
  4. package/cache/hfs-family.mjs +22 -9
  5. package/cache/import-graph.d.mts +32 -4
  6. package/cache/import-graph.mjs +88 -11
  7. package/cache/lint-family.d.mts +10 -3
  8. package/cache/lint-family.mjs +65 -37
  9. package/cache/records.d.mts +7 -0
  10. package/cache/records.mjs +17 -1
  11. package/cache/run.d.mts +1 -1
  12. package/cache/session.mjs +6 -2
  13. package/cache/side-key.d.mts +2 -0
  14. package/cache/side-key.mjs +5 -1
  15. package/cache/structure-memo.d.mts +34 -0
  16. package/cache/structure-memo.mjs +150 -0
  17. package/cache/types.d.mts +6 -2
  18. package/cache/wiring-family.mjs +1 -1
  19. package/codemod/directive-first.d.mts +48 -0
  20. package/codemod/directive-first.mjs +136 -0
  21. package/codemod/explicit-surface-edit.d.mts +14 -0
  22. package/codemod/explicit-surface-edit.mjs +57 -0
  23. package/codemod/explicit-surface-names.d.mts +30 -0
  24. package/codemod/explicit-surface-names.mjs +135 -0
  25. package/codemod/explicit-surface-render.d.mts +29 -0
  26. package/codemod/explicit-surface-render.mjs +197 -0
  27. package/codemod/explicit-surface.d.mts +50 -0
  28. package/codemod/explicit-surface.mjs +374 -0
  29. package/codemod/main.d.mts +7 -5
  30. package/codemod/main.mjs +37 -16
  31. package/codemod/small-renames.mjs +22 -2
  32. package/codemod/source-tree.d.mts +8 -0
  33. package/codemod/source-tree.mjs +23 -0
  34. package/lint/eslint-list.cjs +55 -0
  35. package/lint/run.d.mts +35 -61
  36. package/lint/run.mjs +91 -288
  37. package/package.json +1 -1
  38. package/release/lint-report.d.mts +1 -1
  39. package/release/lint-report.mjs +3 -3
  40. package/report/sonar.d.mts +1 -1
  41. package/report/sonar.mjs +3 -3
  42. package/report/stylelint-findings.d.mts +23 -0
  43. package/report/stylelint-findings.mjs +9 -0
  44. package/runtime/knowledge/hfs/canon-pins.yaml +8 -8
  45. package/runtime/knowledge/hfs/ci-standard.yaml +2 -2
  46. package/runtime/knowledge/hfs/rules.yaml +56 -5
  47. package/runtime/knowledge/hfs/slots.yaml +1 -1
  48. package/runtime/knowledge/sonar-gate.yaml +2 -2
  49. package/runtime/modules/kernel/failure-codes.yaml +4 -4
  50. package/runtime/scripts/api/node/run-node.d.mts +5 -0
  51. package/runtime/scripts/hfs/architecture/test-world-files.d.mts +4 -3
  52. package/runtime/scripts/hfs/architecture/test-world-files.mjs +26 -7
  53. package/runtime/scripts/hfs/check.d.mts +16 -1
  54. package/runtime/scripts/hfs/check.mjs +18 -7
  55. package/runtime/scripts/hfs/explicit-surface-rule.d.mts +58 -0
  56. package/runtime/scripts/hfs/explicit-surface-rule.mjs +59 -0
  57. package/runtime/scripts/hfs/gate-threshold.mjs +1 -1
  58. package/runtime/scripts/hfs/rule-memo.d.mts +30 -0
  59. package/runtime/scripts/hfs/rule-memo.mjs +24 -0
  60. package/runtime/scripts/hfs/rules/a15-findings.d.mts +6 -0
  61. package/runtime/scripts/hfs/rules/a15-findings.mjs +11 -2
  62. package/runtime/scripts/hfs/rules/ci-standard.mjs +2 -2
  63. package/runtime/scripts/hfs/rules/monorepo.d.mts +1 -3
  64. package/runtime/scripts/hfs/rules/monorepo.mjs +9 -11
  65. package/runtime/scripts/hfs/rules/pipeline.mjs +13 -9
  66. package/runtime/scripts/hfs/rules/read.d.mts +5 -0
  67. package/runtime/scripts/hfs/rules/read.mjs +13 -0
  68. package/runtime/scripts/hfs/rules/secrets.d.mts +2 -0
  69. package/runtime/scripts/hfs/rules/secrets.mjs +4 -1
  70. package/runtime/scripts/hfs/rules/stacks-release.mjs +35 -21
  71. package/runtime/scripts/lib/declaration-surface.d.mts +35 -0
  72. package/runtime/scripts/lib/declaration-surface.mjs +112 -0
  73. package/runtime/scripts/lib/worktree-exclude.mjs +1 -1
  74. package/scaffold/app.mjs +3 -3
  75. package/src/check-cache-structure-support.d.mts +52 -0
  76. package/src/main.d.mts +3 -2
  77. package/src/main.mjs +123 -109
  78. package/sync/ci-commands.d.mts +2 -1
  79. package/sync/ci-commands.mjs +19 -20
  80. package/sync/index.mjs +2 -2
  81. package/templates/app/package-scripts/core.json +2 -2
  82. package/templates/app/task-graph/turbo.json +0 -3
  83. package/templates/be/inbox/delivery-recorded.event.ts.tpl +2 -2
  84. package/templates/be/inbox/entities.index.ts.tpl +1 -1
  85. package/templates/be/inbox/module-definition.ts.tpl +8 -2
  86. package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +10 -2
  87. package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +8 -2
  88. package/templates/be/patterns/api/feature.module-definition.ts.tpl +9 -3
  89. package/templates/be/patterns/api/graphql.module-definition.ts.tpl +9 -3
  90. package/templates/be/patterns/cli/group.module-definition.ts.tpl +9 -3
  91. package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +8 -3
  92. package/templates/be/patterns/event-bus/platform/persistence/connection.ts.tpl +3 -2
  93. package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +4 -3
  94. package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +8 -3
  95. package/templates/be/patterns/idempotency/platform/inbox/persistence/connection.ts.tpl +3 -2
  96. package/templates/be/patterns/idempotency/platform/inbox/persistence/inbox.sql.ts.tpl +7 -6
  97. package/templates/be/patterns/jobs/job.module-definition.ts.tpl +9 -3
  98. package/templates/be/patterns/jobs/job.processor.ts.tpl +1 -1
  99. package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +9 -3
  100. package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +8 -3
  101. package/templates/be/patterns/jobs/platform/persistence.connection.ts.tpl +3 -2
  102. package/templates/be/patterns/jobs/platform/persistence.jobs.sql.ts.tpl +5 -4
  103. package/templates/be/patterns/projections/persistence.connection.ts.tpl +3 -2
  104. package/templates/be/patterns/projections/projection.module-definition.ts.tpl +8 -3
  105. package/templates/be/patterns/queues/platform/persistence.connection.ts.tpl +3 -2
  106. package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +4 -3
  107. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +8 -3
  108. package/templates/be/patterns/queues/queue.module-definition.ts.tpl +9 -3
  109. package/templates/be/patterns/reactors/consumer.ts.tpl +1 -1
  110. package/templates/be/patterns/reactors/message.module-definition.ts.tpl +9 -3
  111. package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +9 -3
  112. package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +9 -3
  113. package/templates/be/patterns/realtime/channel.sse.module-definition.ts.tpl +9 -3
  114. package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +9 -3
  115. package/templates/be/patterns/saga/saga.done.consumer.ts.tpl +1 -1
  116. package/templates/be/patterns/saga/saga.failed.consumer.ts.tpl +1 -1
  117. package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +9 -3
  118. package/templates/be/patterns/saga/saga.module-definition.ts.tpl +9 -3
  119. package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +9 -3
  120. package/templates/be/platform/graphql/graphql.module-definition.ts +8 -3
  121. package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +9 -3
  122. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +9 -3
  123. package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +9 -3
  124. package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +9 -3
  125. package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +9 -3
  126. package/templates/be/skeleton/src/modules/domain/identity/identity.module-definition.ts +8 -3
  127. package/templates/be/skeleton/src/modules/domain/liveness/liveness.module-definition.ts +8 -3
  128. package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +8 -3
  129. package/templates/be/skeleton/src/modules/domain/note/persistence/connection.ts +3 -2
  130. package/templates/be/skeleton/src/modules/domain/note/persistence/note.sql.ts +2 -1
  131. package/templates/be/skeleton/src/modules/platform/clock/clock.module-definition.ts +8 -3
  132. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module-definition.ts +8 -3
  133. package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +8 -3
  134. package/templates/be/skeleton/src/modules/platform/database/index.ts +1 -0
  135. package/templates/be/skeleton/src/modules/platform/errors/errors.module-definition.ts +10 -4
  136. package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +8 -4
  137. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts +10 -4
  138. package/templates/be/skeleton/src/modules/platform/http-security/persistence/connection.ts +3 -2
  139. package/templates/be/skeleton/src/modules/platform/http-security/persistence/rate-limit.sql.ts +3 -2
  140. package/templates/be/skeleton/src/modules/platform/i18n/i18n.module-definition.ts +10 -4
  141. package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +8 -3
  142. package/templates/be/skeleton/src/modules/platform/logging/logging.module-definition.ts +8 -3
  143. package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +8 -3
  144. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +1 -1
  145. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +5 -1
  146. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +5 -1
  147. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +1 -1
  148. package/templates/be/skeleton-lite/src/modules/platform/http-security/persistence/entities/index.ts +1 -1
  149. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +7 -1
  150. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +7 -1
  151. package/templates/be/table/api/feature.module-definition.ts.tpl +9 -3
  152. package/templates/be/table/api/graphql.module-definition.ts.tpl +9 -3
  153. package/templates/be/table/entities.index.ts.tpl +1 -1
  154. package/templates/be/table/module-definition.ts.tpl +8 -3
  155. package/templates/be/test-world/fixtures/migrations.sql.ts.tpl +1 -0
  156. package/templates/be/test-world/world/test-world.config.ts.tpl +8 -1
  157. package/templates/fe/common/app-layout.tsx.partial +1 -1
  158. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +2 -1
  159. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignIn.ts +11 -3
  160. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +8 -2
  161. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +2 -1
  162. package/templates/fe/skeleton/apps/__app__/src/modules/toast/hooks.ts +1 -1
  163. package/templates/fe/skeleton/apps/__app__/src/modules/toast/toast.tsx +4 -1
  164. package/templates/fe/skeleton/apps/__app__/src/redux/hooks.ts +3 -2
  165. package/templates/fe/skeleton/apps/__app__/src/redux/index.ts +1 -0
  166. package/templates/fe/skeleton/apps/__app__/src/redux/slices/session.ts +12 -4
  167. package/templates/fe/skeleton/apps/__app__/src/redux/store.ts +8 -3
  168. package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +4 -3
  169. package/templates/fe/skeleton/apps/app/src/features/layouts/AppLayout/index.tsx +1 -1
  170. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +2 -1
  171. package/templates/fe/skeleton/apps/app/src/features/pages/AppLoadingPage/index.tsx +2 -1
  172. package/templates/fe/skeleton/apps/app/src/features/pages/AppNotFoundPage/index.tsx +2 -1
  173. package/templates/fe/skeleton/apps/app/src/modules/i18n/index.ts +2 -1
  174. package/templates/fe/skeleton/apps/app/src/modules/i18n/request.ts +4 -1
  175. package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +4 -3
  176. package/templates/fe/skeleton/apps/landing/src/features/layouts/LandingLayout/index.tsx +2 -2
  177. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingLoadingPage/index.tsx +2 -1
  178. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingNotFoundPage/index.tsx +2 -1
  179. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/index.tsx +2 -1
  180. package/templates/fe/skeleton/apps/landing/src/modules/i18n/index.ts +2 -1
  181. package/templates/fe/skeleton/apps/landing/src/modules/i18n/request.ts +4 -1
  182. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +19 -1
  183. package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +1 -1
  184. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +5 -1
  185. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +4 -3
  186. package/templates/fe/skeleton-lite/apps/web/src/features/layouts/AppLayout/index.tsx +2 -2
  187. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +2 -1
  188. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppLoadingPage/index.tsx +2 -1
  189. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppNotFoundPage/index.tsx +2 -1
  190. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +3 -1
  191. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +5 -1
  192. package/templates/fe/table/graphql/useMutateSwr.ts.tpl +2 -1
  193. package/templates/fe/table/graphql/useQuerySwr.ts.tpl +2 -1
  194. package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
  195. package/templates/fe/transport/graphql/clients/clients/no-auth.ts +1 -1
  196. package/templates/fe/transport/graphql/clients/links/attach-access-token.ts +1 -1
  197. package/templates/fe/transport/graphql/clients/links/error.ts +1 -1
  198. package/templates/fe/transport/graphql/clients/links/http.ts +1 -1
  199. package/templates/fe/transport/graphql/clients/links/locale.ts +1 -1
  200. package/templates/fe/transport/graphql/clients/links/refresh-token.ts +1 -1
  201. package/templates/fe/transport/graphql/clients/links/retry.ts +1 -1
  202. package/templates/fe/transport/graphql/clients/links/timeout.ts +1 -1
  203. package/templates/fe/units/page/index.tsx.tpl +3 -2
  204. package/templates/fe/units/slot/SlotView.tsx.tpl +1 -1
@@ -179,7 +179,7 @@ function ciJobSettings(declaration, source) {
179
179
  };
180
180
  }
181
181
  /** An omitted quality declaration preserves hosted Sonar, required OIDC Codecov, and the high production dependency audit. */
182
- export function ciWorkflowCommands(source, lcovReport = 'be/coverage/lcov.info', quality = undefined, { nodeMajor = '22', app } = {}) {
182
+ export function ciWorkflowCommands(source, lcovReport = 'be/coverage/lcov.info', quality = undefined, { nodeMajor = '22', app, packages = false } = {}) {
183
183
  const configuredSonar = quality === undefined ? { token: 'SONAR_TOKEN', hostVar: 'SONAR_HOST_URL' } : quality.sonar;
184
184
  const sonar = configuredSonar?.wiring === 'not-used' || configuredSonar?.mode === 'disabled' ? null : configuredSonar;
185
185
  const codecov = quality?.codecov ?? { enabled: true, auth: 'oidc' };
@@ -195,8 +195,6 @@ export function ciWorkflowCommands(source, lcovReport = 'be/coverage/lcov.info',
195
195
  shardCoverageReport: lcovReport.split('/').slice(0, -1).concat('coverage-final.json').join('/'),
196
196
  ciBuildEnv: build.env,
197
197
  ciBuildSteps: build.steps,
198
- ciLint: exampleCommand(source, 'npm run lint'),
199
- ciLintSonar: exampleCommand(source, 'npm run lint -- --out reports/lint.sonar.json'),
200
198
  // The coverage judgement alone over the merged report: the fast and lint jobs already ran every other rule of the app check.
201
199
  ciCoverageProof: exampleCommand(source, 'npx starci app coverage verify ' + lcovReport),
202
200
  // An app with an entity list also checks its entities against the schema its migrations build, in the build job (a runner with Docker).
@@ -205,23 +203,24 @@ export function ciWorkflowCommands(source, lcovReport = 'be/coverage/lcov.info',
205
203
  sonarTokenSecret: sonar?.token ?? 'SONAR_TOKEN',
206
204
  sonarHostVar: sonar?.hostVar ?? 'SONAR_HOST_URL',
207
205
  // One lint job per side (the lint matrix): with Sonar each side writes its own report, uploaded as sonar-lint-<side> and merged by the sonar job.
208
- ciLintSteps: (sonar ? [
209
- ' - name: lint with Sonar import',
210
- ` run: ${exampleCommand(source, 'npm run lint -- --side ' + side + ' --out reports/lint.' + side + '.sonar.json')}`,
211
- ' env:',
212
- ` NODE_OPTIONS: ${settings.nodeOptions}`,
213
- ` - uses: ${UPLOAD_ARTIFACT}`,
214
- // Inside an if: expression the matrix value is an argument of format(), never a nested ${{ }} (actionlint: a nested expression makes the condition always true).
215
- " if: always() && hashFiles(format('reports/lint.{0}.sonar.json', matrix.side)) != ''",
216
- ' with:',
217
- ` name: sonar-lint-${side}`,
218
- ` path: reports/lint.${side}.sonar.json`,
219
- ] : [
220
- ' - name: lint',
221
- ` run: ${exampleCommand(source, 'npm run lint -- --side ' + side)}`,
222
- ' env:',
223
- ` NODE_OPTIONS: ${settings.nodeOptions}`,
224
- ]).join('\n'),
206
+ // The typed lint reads the generated code and the types the fe packages declare (their dist), so both are built first, the way the root lint script does.
207
+ ciLintSteps: [' - name: build the lint inputs', ` run: ${exampleCommand(source, packages ? 'npm run codegen --silent && npx --no-install turbo run build --filter=./fe/packages/*' : 'npm run codegen --silent')}`, ...(sonar ? [
208
+ ' - name: lint with Sonar import',
209
+ ` run: ${exampleCommand(source, 'npx --no-install starci app check --gate lint --side ' + side + ' --out reports/lint.' + side + '.sonar.json')}`,
210
+ ' env:',
211
+ ` NODE_OPTIONS: ${settings.nodeOptions}`,
212
+ ` - uses: ${UPLOAD_ARTIFACT}`,
213
+ // Inside an if: expression the matrix value is an argument of format(), never a nested ${{ }} (actionlint: a nested expression makes the condition always true).
214
+ " if: always() && hashFiles(format('reports/lint.{0}.sonar.json', matrix.side)) != ''",
215
+ ' with:',
216
+ ` name: sonar-lint-${side}`,
217
+ ` path: reports/lint.${side}.sonar.json`,
218
+ ] : [
219
+ ' - name: lint',
220
+ ` run: ${exampleCommand(source, 'npx --no-install starci app check --gate lint --side ' + side)}`,
221
+ ' env:',
222
+ ` NODE_OPTIONS: ${settings.nodeOptions}`,
223
+ ])].join('\n'),
225
224
  codecovSteps: codecovSteps(lcovReport, codecov),
226
225
  verdictSonarNeed: sonar ? ', sonar' : '',
227
226
  verdictSonarCheck: sonar ? `\n if [[ "$FULL" == true && "$CHECKS" == true ]]; then expect sonar success; else expect sonar idle; fi` : '',
package/sync/index.mjs CHANGED
@@ -404,7 +404,7 @@ export function variables(app, scope, presets, sonarKey, manifest = loadSlotMani
404
404
  buildFe: 'npm run codegen --silent && turbo run build --filter=./fe/apps/*',
405
405
  // The be side with its tsc; every fe workspace with its own typecheck script through turbo (the packages are built first: ^build).
406
406
  typecheck: 'npm run codegen --silent && tsc -p be/tsconfig.json && turbo run typecheck',
407
- // The typed lint reads the types the fe packages declare (their dist, turbo.json: lint dependsOn ^build), so the packages are built first.
407
+ // The typed lint reads the types the fe packages declare (their dist), so the packages are built first.
408
408
  lintPrefix: packages ? 'npm run codegen --silent && turbo run build --filter=./fe/packages/* && ' : 'npm run codegen --silent && ',
409
409
  nodeMajor: String(NODE_MAJOR),
410
410
  // The deploy server's age identity the restore workflow writes (stack/server-key.mjs owns the path).
@@ -418,7 +418,7 @@ export function variables(app, scope, presets, sonarKey, manifest = loadSlotMani
418
418
  sonarSources: ['be/apps', 'be/src', 'fe/apps', ...(packages ? ['fe/packages'] : [])].join(','),
419
419
  lcovReport: LCOV_REPORT,
420
420
  cpdExclusions: SONAR_CPD_EXCLUSIONS.join(','),
421
- ...ciWorkflowCommands(source, LCOV_REPORT, source.qualityDeclared === false ? undefined : source.quality, { app }),
421
+ ...ciWorkflowCommands(source, LCOV_REPORT, source.qualityDeclared === false ? undefined : source.quality, { app, packages }),
422
422
  // The coverage scope is the same in both editions: lite measures exactly what full measures (ruling:R212/case-1, amended by ruling:R212/case-4).
423
423
  jestCoverage: scope === 'be' ? jestCoverageSource(jestCoverage(manifest, providers)) : '',
424
424
  coverageExclusions: scope === APP_SCOPE ? sonarCoverageExclusions(manifest, providers).join(',') : '',
@@ -3,7 +3,7 @@
3
3
  "codegen": "node scripts/codegen.mjs",
4
4
  "codegen:graphql": "node scripts/codegen.mjs --graphql",
5
5
  "typecheck": "{{typecheck}}",
6
- "lint": "{{lintPrefix}}{{appBinary}} lint",
7
- "lint:fix": "{{lintPrefix}}{{appBinary}} lint --fix",
6
+ "lint": "{{lintPrefix}}{{appBinary}} check --gate lint",
7
+ "lint:fix": "{{lintPrefix}}{{appBinary}} check --gate lint --fix",
8
8
  "format": "prettier --write .",
9
9
  "format:check": "prettier --check .",
@@ -13,9 +13,6 @@
13
13
  "cache": false,
14
14
  "persistent": true
15
15
  },
16
- "lint": {
17
- "dependsOn": ["^build"]
18
- },
19
16
  "typecheck": {
20
17
  "dependsOn": ["^build"]
21
18
  }
@@ -10,10 +10,10 @@ export interface @@Provider@@DeliveryRecordedPayload {
10
10
 
11
11
  /** Version 1 of `@@provider@@.delivery-recorded`, published once by the provider inbox intake. */
12
12
  export class @@Provider@@DeliveryRecordedEvent extends BaseEvent {
13
- static readonly eventName = "@@provider@@.delivery-recorded"
13
+ static readonly eventName: string = "@@provider@@.delivery-recorded"
14
14
  static readonly version = 1
15
15
 
16
- readonly eventName = @@Provider@@DeliveryRecordedEvent.eventName
16
+ readonly eventName: string = @@Provider@@DeliveryRecordedEvent.eventName
17
17
 
18
18
  private constructor(
19
19
  readonly eventId: string,
@@ -1,4 +1,4 @@
1
1
  import { @@Provider@@InboxEntity } from "./@@provider@@-inbox.entity"
2
2
 
3
3
  /** The entities of the @@provider@@-inbox capability, named in PRIMARY_ENTITIES of platform/database/entities.ts. */
4
- export const @@providerCamel@@InboxEntities = [@@Provider@@InboxEntity]
4
+ export const @@providerCamel@@InboxEntities: ReadonlyArray<typeof @@Provider@@InboxEntity> = [@@Provider@@InboxEntity]
@@ -1,6 +1,12 @@
1
- import { ConfigurableModuleBuilder } from "@nestjs/common"
1
+ import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
2
2
  import type { @@Provider@@InboxOptions } from "./@@provider@@-inbox.options"
3
3
 
4
- export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<@@Provider@@InboxOptions>()
4
+ const built = new ConfigurableModuleBuilder<@@Provider@@InboxOptions>()
5
5
  .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
6
6
  .build()
7
+ type Host = ConfigurableModuleHost<@@Provider@@InboxOptions, "register", "create", { isGlobal: boolean }>
8
+
9
+ /** The configurable-module class the module extends. */
10
+ export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
11
+ /** The options of a synchronous registration, for the register signature. */
12
+ export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
@@ -1,6 +1,14 @@
1
- import { ConfigurableModuleBuilder } from "@nestjs/common"
1
+ import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
2
2
  import type { EventBusOptions } from "./event-bus.options"
3
3
 
4
- export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } = new ConfigurableModuleBuilder<EventBusOptions>()
4
+ const built = new ConfigurableModuleBuilder<EventBusOptions>()
5
5
  .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
6
6
  .build()
7
+ type Host = ConfigurableModuleHost<EventBusOptions, "register", "create", { isGlobal: boolean }>
8
+
9
+ /** The configurable-module class the module extends. */
10
+ export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
11
+ /** The injection token of the registered options. */
12
+ export const MODULE_OPTIONS_TOKEN: Host["MODULE_OPTIONS_TOKEN"] = built.MODULE_OPTIONS_TOKEN
13
+ /** The options of a synchronous registration, for the register signature. */
14
+ export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
@@ -1,6 +1,12 @@
1
- import { ConfigurableModuleBuilder } from "@nestjs/common"
1
+ import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
2
2
  import type { InboxOptions } from "./inbox.options"
3
3
 
4
- export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<InboxOptions>()
4
+ const built = new ConfigurableModuleBuilder<InboxOptions>()
5
5
  .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
6
6
  .build()
7
+ type Host = ConfigurableModuleHost<InboxOptions, "register", "create", { isGlobal: boolean }>
8
+
9
+ /** The configurable-module class the module extends. */
10
+ export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
11
+ /** The options of a synchronous registration, for the register signature. */
12
+ export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
@@ -1,6 +1,12 @@
1
- import { ConfigurableModuleBuilder } from "@nestjs/common"
1
+ import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
2
2
  import type { @@Feature@@ApiOptions } from "./@@feature@@.options"
3
3
 
4
+ const built = new ConfigurableModuleBuilder<@@Feature@@ApiOptions>().setClassMethodName("register").build()
5
+ type Host = ConfigurableModuleHost<@@Feature@@ApiOptions, "register", "create", {}>
6
+
4
7
  /** The configurable-module base of the @@feature@@ module. */
5
- export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
- new ConfigurableModuleBuilder<@@Feature@@ApiOptions>().setClassMethodName("register").build()
8
+ export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
9
+ /** The injection token of the registered options. */
10
+ export const MODULE_OPTIONS_TOKEN: Host["MODULE_OPTIONS_TOKEN"] = built.MODULE_OPTIONS_TOKEN
11
+ /** The options of a synchronous registration, for the register signature. */
12
+ export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
@@ -1,6 +1,12 @@
1
- import { ConfigurableModuleBuilder } from "@nestjs/common"
1
+ import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
2
2
  import type { @@Feature@@GraphqlOptions } from "./@@feature@@-graphql.options"
3
3
 
4
+ const built = new ConfigurableModuleBuilder<@@Feature@@GraphqlOptions>().setClassMethodName("register").build()
5
+ type Host = ConfigurableModuleHost<@@Feature@@GraphqlOptions, "register", "create", {}>
6
+
4
7
  /** The configurable-module base of the @@feature@@-graphql module. */
5
- export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
- new ConfigurableModuleBuilder<@@Feature@@GraphqlOptions>().setClassMethodName("register").build()
8
+ export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
9
+ /** The injection token of the registered options. */
10
+ export const MODULE_OPTIONS_TOKEN: Host["MODULE_OPTIONS_TOKEN"] = built.MODULE_OPTIONS_TOKEN
11
+ /** The options of a synchronous registration, for the register signature. */
12
+ export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
@@ -1,6 +1,12 @@
1
- import { ConfigurableModuleBuilder } from "@nestjs/common"
1
+ import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
2
2
  import type { @@Group@@Options } from "./@@group@@.options"
3
3
 
4
+ const built = new ConfigurableModuleBuilder<@@Group@@Options>().setClassMethodName("register").build()
5
+ type Host = ConfigurableModuleHost<@@Group@@Options, "register", "create", {}>
6
+
4
7
  /** The configurable-module base of the @@group@@ module. */
5
- export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
- new ConfigurableModuleBuilder<@@Group@@Options>().setClassMethodName("register").build()
8
+ export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
9
+ /** The injection token of the registered options. */
10
+ export const MODULE_OPTIONS_TOKEN: Host["MODULE_OPTIONS_TOKEN"] = built.MODULE_OPTIONS_TOKEN
11
+ /** The options of a synchronous registration, for the register signature. */
12
+ export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
@@ -1,10 +1,15 @@
1
- import { ConfigurableModuleBuilder } from "@nestjs/common"
1
+ import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
2
2
  import { EVENT_BUS_OPTIONS } from "./event-bus.decorators"
3
3
  import type { EventBusOptions } from "./event-bus.options"
4
4
 
5
- /** The configurable-module base of the event bus; `isGlobal` is decided by the app root. */
6
- export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<EventBusOptions>({
5
+ const built = new ConfigurableModuleBuilder<EventBusOptions>({
7
6
  optionsInjectionToken: EVENT_BUS_OPTIONS,
8
7
  })
9
8
  .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
10
9
  .build()
10
+ type Host = ConfigurableModuleHost<EventBusOptions, "register", "create", { isGlobal: boolean }>
11
+
12
+ /** The configurable-module base of the event bus; `isGlobal` is decided by the app root. */
13
+ export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
14
+ /** The options of a synchronous registration, for the register signature. */
15
+ export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
@@ -1,8 +1,9 @@
1
+ import type { DatabaseConnectionOptions } from "@modules/platform/database"
1
2
  import { EventOutboxEntity } from "./entities/event-outbox.entity"
2
3
  import { CreateEventOutbox@@epochMs13@@ } from "./migrations/@@epochMs13@@-create-event-outbox"
3
4
 
4
5
  /** The entities of the event bus, for every connection that publishes events. */
5
- export const eventBusEntities = [EventOutboxEntity]
6
+ export const eventBusEntities: DatabaseConnectionOptions["entities"] = [EventOutboxEntity]
6
7
 
7
8
  /** The migrations of the event bus, in the order they run. */
8
- export const eventBusMigrations = [CreateEventOutbox@@epochMs13@@]
9
+ export const eventBusMigrations: DatabaseConnectionOptions["migrations"] = [CreateEventOutbox@@epochMs13@@]
@@ -1,12 +1,13 @@
1
1
  import { sql } from "@modules/platform/database"
2
+ import type { SqlText } from "@modules/platform/database"
2
3
 
3
4
  /** Writes one event in the outbox ($1 event id, $2 event name, $3 topic, $4 key, $5 envelope, $6 instant, $7 correlation id). */
4
- export const INSERT_OUTBOX_ROW = sql`INSERT INTO event_outbox (event_id, event_name, topic, message_key, envelope, created_at, correlation_id)
5
+ export const INSERT_OUTBOX_ROW: SqlText = sql`INSERT INTO event_outbox (event_id, event_name, topic, message_key, envelope, created_at, correlation_id)
5
6
  VALUES ($1, $2, $3, $4, $5, $6, $7)`
6
7
 
7
8
  /** Locks and reads the oldest rows that wait ($1 batch size); a second relay skips the rows this one holds. */
8
- export const SELECT_WAITING_ROWS = sql`SELECT id, topic, message_key, envelope, correlation_id FROM event_outbox
9
+ export const SELECT_WAITING_ROWS: SqlText = sql`SELECT id, topic, message_key, envelope, correlation_id FROM event_outbox
9
10
  WHERE sent_at IS NULL ORDER BY id LIMIT $1 FOR UPDATE SKIP LOCKED`
10
11
 
11
12
  /** Marks the rows as handed to the broker ($1 ids, $2 instant). */
12
- export const MARK_ROWS_SENT = sql`UPDATE event_outbox SET sent_at = $2 WHERE id = ANY($1::bigint[])`
13
+ export const MARK_ROWS_SENT: SqlText = sql`UPDATE event_outbox SET sent_at = $2 WHERE id = ANY($1::bigint[])`
@@ -1,7 +1,12 @@
1
- import { ConfigurableModuleBuilder } from "@nestjs/common"
1
+ import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
2
2
  import type { InboxOptions } from "./inbox.options"
3
3
 
4
- /** The configurable-module base of the inbox capability; `isGlobal` is decided by the app root. */
5
- export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<InboxOptions>()
4
+ const built = new ConfigurableModuleBuilder<InboxOptions>()
6
5
  .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
7
6
  .build()
7
+ type Host = ConfigurableModuleHost<InboxOptions, "register", "create", { isGlobal: boolean }>
8
+
9
+ /** The configurable-module base of the inbox capability; `isGlobal` is decided by the app root. */
10
+ export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
11
+ /** The options of a synchronous registration, for the register signature. */
12
+ export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
@@ -1,9 +1,10 @@
1
+ import type { DatabaseConnectionOptions } from "@modules/platform/database"
1
2
  import { InboxClaimEntity } from "./entities/inbox-claim.entity"
2
3
  import { CreateInboxClaims1789800005000 } from "./migrations/1789800005000-create-inbox-claims"
3
4
  import { ClientOperationOutcomes1792800000000 } from "./migrations/1792800000000-client-operation-outcomes"
4
5
 
5
6
  /** The entities of the inbox capability, for the connection that holds them. */
6
- export const inboxEntities = [InboxClaimEntity]
7
+ export const inboxEntities: DatabaseConnectionOptions["entities"] = [InboxClaimEntity]
7
8
 
8
9
  /** The migrations of the inbox capability, in the order they run. */
9
- export const inboxMigrations = [CreateInboxClaims1789800005000, ClientOperationOutcomes1792800000000]
10
+ export const inboxMigrations: DatabaseConnectionOptions["migrations"] = [CreateInboxClaims1789800005000, ClientOperationOutcomes1792800000000]
@@ -1,20 +1,21 @@
1
1
  import { sql } from "@modules/platform/database"
2
+ import type { SqlText } from "@modules/platform/database"
2
3
 
3
4
  /** Claims the event ($1 source, $2 event id, $3 instant); answers one row when this call won and none when it was claimed before. */
4
- export const CLAIM_EVENT = sql`INSERT INTO inbox_claims (source, event_id, claimed_at) VALUES ($1, $2, $3)
5
+ export const CLAIM_EVENT: SqlText = sql`INSERT INTO inbox_claims (source, event_id, claimed_at) VALUES ($1, $2, $3)
5
6
  ON CONFLICT (source, event_id) DO NOTHING RETURNING event_id`
6
7
 
7
8
  /** Gives a delivery claim back; client outcomes can expire only through the retention API. */
8
- export const RELEASE_EVENT = sql`DELETE FROM inbox_claims WHERE source = $1 AND event_id = $2 AND request_fingerprint IS NULL`
9
+ export const RELEASE_EVENT: SqlText = sql`DELETE FROM inbox_claims WHERE source = $1 AND event_id = $2 AND request_fingerprint IS NULL`
9
10
 
10
11
  /** A conflicting insert locks the existing row and returns its committed fingerprint and outcome after the winner commits. */
11
- export const CLAIM_CLIENT = sql`INSERT INTO inbox_claims (source, event_id, request_fingerprint, claimed_at)
12
+ export const CLAIM_CLIENT: SqlText = sql`INSERT INTO inbox_claims (source, event_id, request_fingerprint, claimed_at)
12
13
  VALUES ($1, $2, $3, $4)
13
14
  ON CONFLICT (source, event_id) DO UPDATE SET outcome = COALESCE(inbox_claims.outcome, '{}'::jsonb)
14
15
  RETURNING request_fingerprint, outcome, (outcome IS NOT NULL AND (jsonb_typeof(outcome) <> 'object' OR NOT (outcome ? 'value'))) AS incomplete`
15
16
 
16
17
  /** The JSON envelope distinguishes a completed null result from an uncompleted claim in this transaction. */
17
- export const SAVE_CLIENT_OUTCOME = sql`WITH saved AS (
18
+ export const SAVE_CLIENT_OUTCOME: SqlText = sql`WITH saved AS (
18
19
  UPDATE inbox_claims SET outcome = $3::jsonb
19
20
  WHERE source = $1 AND event_id = $2
20
21
  RETURNING outcome
@@ -22,7 +23,7 @@ export const SAVE_CLIENT_OUTCOME = sql`WITH saved AS (
22
23
  SELECT outcome FROM saved LIMIT 1`
23
24
 
24
25
  /** Retention never deletes delivery rows or an incomplete claim; its bounded batch skips rows held by active retries. */
25
- export const SWEEP_CLIENTS = sql`WITH expired AS (
26
+ export const SWEEP_CLIENTS: SqlText = sql`WITH expired AS (
26
27
  SELECT source, event_id FROM inbox_claims
27
28
  WHERE source LIKE 'client:%' AND request_fingerprint IS NOT NULL AND outcome IS NOT NULL AND jsonb_typeof(outcome) = 'object' AND outcome ? 'value' AND claimed_at < $1
28
29
  ORDER BY claimed_at, source, event_id LIMIT $2 FOR UPDATE SKIP LOCKED
@@ -34,6 +35,6 @@ RETURNING claims.event_id
34
35
  SELECT count(*)::int AS deleted_count FROM deleted`
35
36
 
36
37
  /** Imports an already completed legacy operation without replacing a key another migration has already transferred. */
37
- export const BACKFILL_CLIENT_OUTCOME = sql`INSERT INTO inbox_claims (source, event_id, request_fingerprint, claimed_at, outcome)
38
+ export const BACKFILL_CLIENT_OUTCOME: SqlText = sql`INSERT INTO inbox_claims (source, event_id, request_fingerprint, claimed_at, outcome)
38
39
  VALUES ($1, $2, $3, $4, $5::jsonb)
39
40
  ON CONFLICT (source, event_id) DO NOTHING`
@@ -1,6 +1,12 @@
1
- import { ConfigurableModuleBuilder } from "@nestjs/common"
1
+ import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
2
2
  import type { @@Job@@Options } from "./@@job@@.options"
3
3
 
4
+ const built = new ConfigurableModuleBuilder<@@Job@@Options>().setClassMethodName("register").build()
5
+ type Host = ConfigurableModuleHost<@@Job@@Options, "register", "create", {}>
6
+
4
7
  /** The configurable-module base of the @@job@@ module. */
5
- export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
- new ConfigurableModuleBuilder<@@Job@@Options>().setClassMethodName("register").build()
8
+ export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
9
+ /** The injection token of the registered options. */
10
+ export const MODULE_OPTIONS_TOKEN: Host["MODULE_OPTIONS_TOKEN"] = built.MODULE_OPTIONS_TOKEN
11
+ /** The options of a synchronous registration, for the register signature. */
12
+ export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
@@ -7,7 +7,7 @@ import { @@Step@@Step } from "../../steps/@@step@@.step"
7
7
  @Injectable()
8
8
  /** The processor of the @@job@@ job: the runner claims and settles, `process` runs the steps in order. */
9
9
  export class @@Job@@Processor extends FencedProcessor {
10
- readonly queue = @@jobUpper@@_QUEUE
10
+ readonly queue: string = @@jobUpper@@_QUEUE
11
11
 
12
12
  constructor(private readonly @@stepCamel@@Step: @@Step@@Step) {
13
13
  super()
@@ -1,6 +1,12 @@
1
- import { ConfigurableModuleBuilder } from "@nestjs/common"
1
+ import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
2
2
  import type { @@Job@@QueueOptions } from "./@@job@@-queue.options"
3
3
 
4
+ const built = new ConfigurableModuleBuilder<@@Job@@QueueOptions>().setClassMethodName("register").build()
5
+ type Host = ConfigurableModuleHost<@@Job@@QueueOptions, "register", "create", {}>
6
+
4
7
  /** The configurable-module base of the @@job@@-queue module. */
5
- export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
- new ConfigurableModuleBuilder<@@Job@@QueueOptions>().setClassMethodName("register").build()
8
+ export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
9
+ /** The injection token of the registered options. */
10
+ export const MODULE_OPTIONS_TOKEN: Host["MODULE_OPTIONS_TOKEN"] = built.MODULE_OPTIONS_TOKEN
11
+ /** The options of a synchronous registration, for the register signature. */
12
+ export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
@@ -1,10 +1,15 @@
1
- import { ConfigurableModuleBuilder } from "@nestjs/common"
1
+ import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
2
2
  import { JOBS_OPTIONS } from "./jobs.decorators"
3
3
  import type { JobsOptions } from "./jobs.options"
4
4
 
5
- /** The configurable-module base of the jobs; `isGlobal` is decided by the app root. */
6
- export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<JobsOptions>({
5
+ const built = new ConfigurableModuleBuilder<JobsOptions>({
7
6
  optionsInjectionToken: JOBS_OPTIONS,
8
7
  })
9
8
  .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
10
9
  .build()
10
+ type Host = ConfigurableModuleHost<JobsOptions, "register", "create", { isGlobal: boolean }>
11
+
12
+ /** The configurable-module base of the jobs; `isGlobal` is decided by the app root. */
13
+ export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
14
+ /** The options of a synchronous registration, for the register signature. */
15
+ export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
@@ -1,8 +1,9 @@
1
+ import type { DatabaseConnectionOptions } from "@modules/platform/database"
1
2
  import { JobEntity } from "./entities/job.entity"
2
3
  import { CreateJobs@@epochMs13@@ } from "./migrations/@@epochMs13@@-create-jobs"
3
4
 
4
5
  /** The entities of the jobs, for every connection that runs jobs. */
5
- export const jobsEntities = [JobEntity]
6
+ export const jobsEntities: DatabaseConnectionOptions["entities"] = [JobEntity]
6
7
 
7
8
  /** The migrations of the jobs, in the order they run. */
8
- export const jobsMigrations = [CreateJobs@@epochMs13@@]
9
+ export const jobsMigrations: DatabaseConnectionOptions["migrations"] = [CreateJobs@@epochMs13@@]
@@ -1,11 +1,12 @@
1
1
  import { sql } from "@modules/platform/database"
2
+ import type { SqlText } from "@modules/platform/database"
2
3
 
3
4
  /**
4
5
  * Claims a delivery in one statement ($1 kind, $2 job key, $3 worker, $4 lease end, $5 payload, $6 instant). A new key inserts the row
5
6
  * with token 1; an existing key is claimed again only when the job failed or its lease expired, and then the token is bumped by the
6
7
  * store. A done job and a live claim match nothing, so no row comes back.
7
8
  */
8
- export const CLAIM_JOB = sql`INSERT INTO jobs (kind, job_key, status, fencing_token, claimed_by, lease_expires_at, payload, created_at, updated_at)
9
+ export const CLAIM_JOB: SqlText = sql`INSERT INTO jobs (kind, job_key, status, fencing_token, claimed_by, lease_expires_at, payload, created_at, updated_at)
9
10
  VALUES ($1, $2, 'running', 1, $3, $4, $5::jsonb, $6, $6)
10
11
  ON CONFLICT (job_key) DO UPDATE
11
12
  SET fencing_token = jobs.fencing_token + 1, claimed_by = $3, lease_expires_at = $4, status = 'running', error = NULL, updated_at = $6
@@ -15,19 +16,19 @@ export const CLAIM_JOB = sql`INSERT INTO jobs (kind, job_key, status, fencing_to
15
16
  /** The guarded writes wrap their UPDATE in a CTE: TypeORM returns a bare `UPDATE ... RETURNING` as `[rows, count]`, a SELECT as rows. */
16
17
 
17
18
  /** Records a step ($1 job id, $2 expected token, $3 step, $4 instant); no row comes back when the token is stale. */
18
- export const ADVANCE_JOB = sql`WITH changed AS (UPDATE jobs SET current_step = $3, updated_at = $4
19
+ export const ADVANCE_JOB: SqlText = sql`WITH changed AS (UPDATE jobs SET current_step = $3, updated_at = $4
19
20
  WHERE id = $1 AND fencing_token = $2 AND status = 'running'
20
21
  RETURNING id)
21
22
  SELECT id FROM changed`
22
23
 
23
24
  /** Completes the job ($1 job id, $2 expected token, $3 instant). */
24
- export const COMPLETE_JOB = sql`WITH changed AS (UPDATE jobs SET status = 'done', lease_expires_at = NULL, updated_at = $3
25
+ export const COMPLETE_JOB: SqlText = sql`WITH changed AS (UPDATE jobs SET status = 'done', lease_expires_at = NULL, updated_at = $3
25
26
  WHERE id = $1 AND fencing_token = $2 AND status = 'running'
26
27
  RETURNING id)
27
28
  SELECT id FROM changed`
28
29
 
29
30
  /** Fails the job ($1 job id, $2 expected token, $3 reason, $4 instant). */
30
- export const FAIL_JOB = sql`WITH changed AS (UPDATE jobs SET status = 'failed', error = $3, lease_expires_at = NULL, updated_at = $4
31
+ export const FAIL_JOB: SqlText = sql`WITH changed AS (UPDATE jobs SET status = 'failed', error = $3, lease_expires_at = NULL, updated_at = $4
31
32
  WHERE id = $1 AND fencing_token = $2 AND status = 'running'
32
33
  RETURNING id)
33
34
  SELECT id FROM changed`
@@ -1,8 +1,9 @@
1
+ import type { DatabaseConnectionOptions } from "@modules/platform/database"
1
2
  import { @@Name@@ProjectionEntity } from "../@@name@@.projection-entity"
2
3
  import { Create@@Name@@Projection@@epochMs13@@ } from "./migrations/@@epochMs13@@-create-@@name@@"
3
4
 
4
5
  /** The entities of the @@name@@ read model, for the connection that holds them. */
5
- export const @@nameCamel@@Entities = [@@Name@@ProjectionEntity]
6
+ export const @@nameCamel@@Entities: DatabaseConnectionOptions["entities"] = [@@Name@@ProjectionEntity]
6
7
 
7
8
  /** The migrations of the @@name@@ read model, in the order they run. */
8
- export const @@nameCamel@@Migrations = [Create@@Name@@Projection@@epochMs13@@]
9
+ export const @@nameCamel@@Migrations: DatabaseConnectionOptions["migrations"] = [Create@@Name@@Projection@@epochMs13@@]
@@ -1,7 +1,12 @@
1
- import { ConfigurableModuleBuilder } from "@nestjs/common"
1
+ import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
2
2
  import type { @@Name@@Options } from "./@@name@@.options"
3
3
 
4
- /** The configurable-module base of the @@name@@ projection; `isGlobal` is decided by the app root. */
5
- export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<@@Name@@Options>()
4
+ const built = new ConfigurableModuleBuilder<@@Name@@Options>()
6
5
  .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
7
6
  .build()
7
+ type Host = ConfigurableModuleHost<@@Name@@Options, "register", "create", { isGlobal: boolean }>
8
+
9
+ /** The configurable-module base of the @@name@@ projection; `isGlobal` is decided by the app root. */
10
+ export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
11
+ /** The options of a synchronous registration, for the register signature. */
12
+ export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
@@ -1,8 +1,9 @@
1
+ import type { DatabaseConnectionOptions } from "@modules/platform/database"
1
2
  import { QueueOutboxEntity } from "./entities/queue-outbox.entity"
2
3
  import { CreateQueueOutbox@@epochMs13@@ } from "./migrations/@@epochMs13@@-create-queue-outbox"
3
4
 
4
5
  /** The entities of the queues, for every connection that enqueues jobs. */
5
- export const queueEntities = [QueueOutboxEntity]
6
+ export const queueEntities: DatabaseConnectionOptions["entities"] = [QueueOutboxEntity]
6
7
 
7
8
  /** The migrations of the queues, in the order they run. */
8
- export const queueMigrations = [CreateQueueOutbox@@epochMs13@@]
9
+ export const queueMigrations: DatabaseConnectionOptions["migrations"] = [CreateQueueOutbox@@epochMs13@@]
@@ -1,12 +1,13 @@
1
1
  import { sql } from "@modules/platform/database"
2
+ import type { SqlText } from "@modules/platform/database"
2
3
 
3
4
  /** Writes one job in the outbox ($1 queue, $2 payload, $3 instant, $4 correlation id). */
4
- export const INSERT_QUEUE_ROW = sql`INSERT INTO queue_outbox (queue, payload, created_at, correlation_id)
5
+ export const INSERT_QUEUE_ROW: SqlText = sql`INSERT INTO queue_outbox (queue, payload, created_at, correlation_id)
5
6
  VALUES ($1, $2, $3, $4)`
6
7
 
7
8
  /** Locks and reads the oldest rows that wait ($1 batch size); a second relay skips the rows this one holds. */
8
- export const SELECT_WAITING_ROWS = sql`SELECT id, queue, payload, correlation_id AS "correlationId" FROM queue_outbox
9
+ export const SELECT_WAITING_ROWS: SqlText = sql`SELECT id, queue, payload, correlation_id AS "correlationId" FROM queue_outbox
9
10
  WHERE sent_at IS NULL ORDER BY created_at, id LIMIT $1 FOR UPDATE SKIP LOCKED`
10
11
 
11
12
  /** Marks the rows as handed to BullMQ ($1 ids, $2 instant). */
12
- export const MARK_ROWS_SENT = sql`UPDATE queue_outbox SET sent_at = $2 WHERE id = ANY($1::uuid[])`
13
+ export const MARK_ROWS_SENT: SqlText = sql`UPDATE queue_outbox SET sent_at = $2 WHERE id = ANY($1::uuid[])`
@@ -1,10 +1,15 @@
1
- import { ConfigurableModuleBuilder } from "@nestjs/common"
1
+ import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
2
2
  import { QUEUE_OPTIONS } from "./queue.decorators"
3
3
  import type { QueueRegistrationOptions } from "./queue.options"
4
4
 
5
- /** The configurable-module base of the queues; `isGlobal` is decided by the app root. */
6
- export const { ConfigurableModuleClass, OPTIONS_TYPE } = new ConfigurableModuleBuilder<QueueRegistrationOptions>({
5
+ const built = new ConfigurableModuleBuilder<QueueRegistrationOptions>({
7
6
  optionsInjectionToken: QUEUE_OPTIONS,
8
7
  })
9
8
  .setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
10
9
  .build()
10
+ type Host = ConfigurableModuleHost<QueueRegistrationOptions, "register", "create", { isGlobal: boolean }>
11
+
12
+ /** The configurable-module base of the queues; `isGlobal` is decided by the app root. */
13
+ export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
14
+ /** The options of a synchronous registration, for the register signature. */
15
+ export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
@@ -1,6 +1,12 @@
1
- import { ConfigurableModuleBuilder } from "@nestjs/common"
1
+ import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
2
2
  import type { @@Queue@@Options } from "./@@queue@@.options"
3
3
 
4
+ const built = new ConfigurableModuleBuilder<@@Queue@@Options>().setClassMethodName("register").build()
5
+ type Host = ConfigurableModuleHost<@@Queue@@Options, "register", "create", {}>
6
+
4
7
  /** The configurable-module base of the @@queue@@ module. */
5
- export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
- new ConfigurableModuleBuilder<@@Queue@@Options>().setClassMethodName("register").build()
8
+ export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
9
+ /** The injection token of the registered options. */
10
+ export const MODULE_OPTIONS_TOKEN: Host["MODULE_OPTIONS_TOKEN"] = built.MODULE_OPTIONS_TOKEN
11
+ /** The options of a synchronous registration, for the register signature. */
12
+ export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
@@ -8,7 +8,7 @@ import { @@Action@@Command } from "../../application/@@action@@.command"
8
8
  @Injectable()
9
9
  /** The consumer door of the @@event@@ event in the @@reactor@@ reactor: it dispatches one command and forwards the event id. */
10
10
  export class @@Event@@Consumer implements EventConsumer<@@Event@@Event> {
11
- readonly event = @@Event@@Event
11
+ readonly event: typeof @@Event@@Event = @@Event@@Event
12
12
 
13
13
  constructor(@InjectCommandBus() private readonly commandBus: CommandBus) {}
14
14
 
@@ -1,6 +1,12 @@
1
- import { ConfigurableModuleBuilder } from "@nestjs/common"
1
+ import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
2
2
  import type { @@Reactor@@MessageOptions } from "./@@reactor@@-message.options"
3
3
 
4
+ const built = new ConfigurableModuleBuilder<@@Reactor@@MessageOptions>().setClassMethodName("register").build()
5
+ type Host = ConfigurableModuleHost<@@Reactor@@MessageOptions, "register", "create", {}>
6
+
4
7
  /** The configurable-module base of the @@reactor@@-message module. */
5
- export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
- new ConfigurableModuleBuilder<@@Reactor@@MessageOptions>().setClassMethodName("register").build()
8
+ export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
9
+ /** The injection token of the registered options. */
10
+ export const MODULE_OPTIONS_TOKEN: Host["MODULE_OPTIONS_TOKEN"] = built.MODULE_OPTIONS_TOKEN
11
+ /** The options of a synchronous registration, for the register signature. */
12
+ export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE