@starci/hfs 4.0.8 → 4.0.9

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 (667) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +81 -39
  3. package/emit/contracts.mjs +56 -9
  4. package/emit/db-types.mjs +169 -0
  5. package/emit/events.mjs +41 -0
  6. package/emit/operations-worker.mjs +1 -1
  7. package/emit/operations.mjs +2 -2
  8. package/emit/schema-worker.mjs +1 -1
  9. package/lint/bound-sys.cjs +50 -0
  10. package/lint/run.mjs +53 -19
  11. package/package.json +14 -9
  12. package/report/sonar.mjs +8 -8
  13. package/runtime/engine/config.mjs +494 -0
  14. package/runtime/engine/invalid-config.mjs +15 -0
  15. package/runtime/engine/orca-config.mjs +27 -0
  16. package/runtime/knowledge/hfs/canon-pins.yaml +160 -29
  17. package/runtime/knowledge/hfs/peer-integrations.yaml +2 -2
  18. package/runtime/knowledge/hfs/rules.yaml +2059 -0
  19. package/runtime/knowledge/hfs/slots.yaml +912 -120
  20. package/runtime/knowledge/patterns/be/api.yaml +369 -0
  21. package/runtime/knowledge/patterns/be/cli.yaml +297 -0
  22. package/runtime/knowledge/patterns/be/domain.yaml +113 -0
  23. package/runtime/knowledge/patterns/be/event-bus.yaml +285 -0
  24. package/runtime/knowledge/patterns/be/jobs.yaml +261 -0
  25. package/runtime/knowledge/patterns/be/projections.yaml +178 -0
  26. package/runtime/knowledge/patterns/be/queues.yaml +236 -0
  27. package/runtime/knowledge/patterns/be/reactors.yaml +140 -0
  28. package/runtime/knowledge/patterns/be/realtime.yaml +222 -0
  29. package/runtime/knowledge/patterns/be/saga.yaml +248 -0
  30. package/runtime/knowledge/patterns/be/webhooks.yaml +221 -0
  31. package/runtime/knowledge/patterns/fe/folder.yaml +131 -25
  32. package/runtime/knowledge/sonar-gate.yaml +52 -18
  33. package/runtime/modules/kernel/failure-codes.yaml +571 -51
  34. package/runtime/modules/models/registry.yaml +591 -0
  35. package/runtime/modules/models/runtimes.yaml +701 -0
  36. package/runtime/scripts/api/fs/forbidden-root.mjs +51 -0
  37. package/runtime/scripts/api/fs/is-link-like.mjs +25 -0
  38. package/runtime/scripts/api/fs/lib.mjs +10 -2
  39. package/runtime/scripts/api/fs/safe-remove.mjs +12 -132
  40. package/runtime/scripts/api/git/config-get.mjs +10 -0
  41. package/runtime/scripts/api/git/diff.mjs +5 -0
  42. package/runtime/scripts/api/git/lib.mjs +16 -26
  43. package/runtime/scripts/api/git/log.mjs +5 -0
  44. package/runtime/scripts/api/git/ls-files.mjs +6 -0
  45. package/runtime/scripts/api/git/ls-tree.mjs +5 -0
  46. package/runtime/scripts/api/git/merge-base.mjs +9 -0
  47. package/runtime/scripts/api/git/remote.mjs +5 -0
  48. package/runtime/scripts/api/git/rev-parse-query.mjs +5 -0
  49. package/runtime/scripts/api/git/show.mjs +6 -0
  50. package/runtime/scripts/api/sops/decrypt.mjs +9 -5
  51. package/runtime/scripts/api/sops/lib.mjs +35 -0
  52. package/runtime/scripts/api/sops/seal.mjs +18 -0
  53. package/runtime/scripts/hfs/architecture/ast-walks.mjs +220 -0
  54. package/runtime/scripts/hfs/architecture/backend.mjs +16 -23
  55. package/runtime/scripts/hfs/architecture/background-unowned.mjs +16 -34
  56. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +3 -1
  57. package/runtime/scripts/hfs/architecture/clones.mjs +4 -4
  58. package/runtime/scripts/hfs/architecture/config.mjs +9 -6
  59. package/runtime/scripts/hfs/architecture/connection-map.mjs +20 -8
  60. package/runtime/scripts/hfs/architecture/context-coupling.mjs +88 -0
  61. package/runtime/scripts/hfs/architecture/context-map.mjs +237 -0
  62. package/runtime/scripts/hfs/architecture/context-owner.mjs +82 -0
  63. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +47 -0
  64. package/runtime/scripts/hfs/architecture/context-transaction.mjs +60 -0
  65. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +4 -10
  66. package/runtime/scripts/hfs/architecture/contracts.mjs +33 -162
  67. package/runtime/scripts/hfs/architecture/dead-exports.mjs +58 -29
  68. package/runtime/scripts/hfs/architecture/doc-language.mjs +1 -1
  69. package/runtime/scripts/hfs/architecture/entrypoint.mjs +2 -1
  70. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +1 -1
  71. package/runtime/scripts/hfs/architecture/feature-shape.mjs +5 -2
  72. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
  73. package/runtime/scripts/hfs/architecture/frontend.mjs +17 -43
  74. package/runtime/scripts/hfs/architecture/hfs.mjs +24 -39
  75. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +1 -1
  76. package/runtime/scripts/hfs/architecture/index.mjs +17 -4
  77. package/runtime/scripts/hfs/architecture/machine-ast.mjs +44 -11
  78. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +27 -0
  79. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +30 -6
  80. package/runtime/scripts/hfs/architecture/next-data.mjs +22 -63
  81. package/runtime/scripts/hfs/architecture/owners.mjs +35 -15
  82. package/runtime/scripts/hfs/architecture/register-once.mjs +2 -1
  83. package/runtime/scripts/hfs/architecture/registration.mjs +23 -81
  84. package/runtime/scripts/hfs/architecture/required-files.mjs +20 -12
  85. package/runtime/scripts/hfs/architecture/schema-owner.mjs +17 -114
  86. package/runtime/scripts/hfs/architecture/server-action.mjs +24 -0
  87. package/runtime/scripts/hfs/architecture/side-boundary.mjs +16 -0
  88. package/runtime/scripts/hfs/architecture/source-names.mjs +27 -147
  89. package/runtime/scripts/hfs/architecture/sql-owner.mjs +59 -18
  90. package/runtime/scripts/hfs/architecture/sql-returning.mjs +64 -0
  91. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +111 -0
  92. package/runtime/scripts/hfs/architecture/supabase-be.mjs +131 -0
  93. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +42 -0
  94. package/runtime/scripts/hfs/architecture/supabase.mjs +434 -0
  95. package/runtime/scripts/hfs/architecture/surface.mjs +9 -3
  96. package/runtime/scripts/hfs/architecture/test-world-files.mjs +9 -19
  97. package/runtime/scripts/hfs/architecture/tiers.mjs +47 -5
  98. package/runtime/scripts/hfs/architecture/transport-owner.mjs +3 -3
  99. package/runtime/scripts/hfs/architecture/typescript.mjs +12 -26
  100. package/runtime/scripts/hfs/architecture.mjs +4 -4
  101. package/runtime/scripts/hfs/check.mjs +75 -72
  102. package/runtime/scripts/hfs/coverage-scope.mjs +162 -0
  103. package/runtime/scripts/hfs/declaration-shape.mjs +62 -0
  104. package/runtime/scripts/hfs/declaration-slots.mjs +75 -0
  105. package/runtime/scripts/hfs/edition-slots.mjs +66 -0
  106. package/runtime/scripts/hfs/manifest-shape.mjs +109 -32
  107. package/runtime/scripts/hfs/param-names.mjs +18 -0
  108. package/runtime/scripts/hfs/path-findings.mjs +1 -1
  109. package/runtime/scripts/hfs/repo-identity.mjs +9 -6
  110. package/runtime/scripts/hfs/rule-params-shape.mjs +50 -0
  111. package/runtime/scripts/hfs/rules/app-root.mjs +51 -0
  112. package/runtime/scripts/hfs/rules/cli.mjs +37 -0
  113. package/runtime/scripts/hfs/rules/contract-compat.mjs +71 -0
  114. package/runtime/scripts/hfs/rules/contract.mjs +10 -9
  115. package/runtime/scripts/hfs/rules/database-config.mjs +102 -0
  116. package/runtime/scripts/hfs/rules/database-constants.mjs +13 -0
  117. package/runtime/scripts/hfs/rules/database-migrations.mjs +111 -0
  118. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +233 -0
  119. package/runtime/scripts/hfs/rules/database-sql.mjs +336 -0
  120. package/runtime/scripts/hfs/rules/database.mjs +68 -0
  121. package/runtime/scripts/hfs/rules/deps.mjs +1 -1
  122. package/runtime/scripts/hfs/rules/docker.mjs +259 -0
  123. package/runtime/scripts/hfs/rules/edition.mjs +111 -0
  124. package/runtime/scripts/hfs/rules/event-bus.mjs +153 -0
  125. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +1 -1
  126. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +6 -5
  127. package/runtime/scripts/hfs/rules/frontend-tree.mjs +9 -9
  128. package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -6
  129. package/runtime/scripts/hfs/rules/kinds.mjs +84 -0
  130. package/runtime/scripts/hfs/rules/lint-suppression.mjs +1 -1
  131. package/runtime/scripts/hfs/rules/monorepo.mjs +179 -0
  132. package/runtime/scripts/hfs/rules/peer-integrations.mjs +3 -3
  133. package/runtime/scripts/hfs/rules/pipeline.mjs +11 -21
  134. package/runtime/scripts/hfs/rules/proof-commands.mjs +2 -2
  135. package/runtime/scripts/hfs/rules/read.mjs +1 -1
  136. package/runtime/scripts/hfs/rules/repo-local-checks.mjs +2 -2
  137. package/runtime/scripts/hfs/rules/saga.mjs +170 -0
  138. package/runtime/scripts/hfs/rules/secrets.mjs +3 -3
  139. package/runtime/scripts/hfs/rules/services.mjs +220 -0
  140. package/runtime/scripts/hfs/rules/spec-placement.mjs +1 -1
  141. package/runtime/scripts/hfs/rules/stacks.mjs +3 -3
  142. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +89 -0
  143. package/runtime/scripts/hfs/rules/test-topology.mjs +2 -2
  144. package/runtime/scripts/hfs/secret.mjs +175 -0
  145. package/runtime/scripts/hfs/slots.mjs +78 -108
  146. package/runtime/scripts/hfs/sql/pg-parse.mjs +46 -0
  147. package/runtime/scripts/hfs/test-world-slot.mjs +6 -0
  148. package/runtime/scripts/hfs/tree.mjs +5 -4
  149. package/runtime/scripts/hfs/typescript-programs.mjs +0 -5
  150. package/runtime/scripts/hfs/view.mjs +3 -2
  151. package/runtime/scripts/lib/dockerfile.mjs +124 -0
  152. package/runtime/scripts/lib/env.mjs +10 -0
  153. package/runtime/scripts/lib/event-contract.mjs +103 -0
  154. package/runtime/scripts/lib/git.mjs +36 -0
  155. package/runtime/scripts/lib/graphql-contract.mjs +1 -1
  156. package/runtime/scripts/lib/i18n.mjs +86 -0
  157. package/runtime/scripts/lib/json.mjs +13 -3
  158. package/runtime/scripts/lib/language.mjs +24 -5
  159. package/runtime/scripts/lib/list.mjs +20 -0
  160. package/runtime/scripts/lib/package-at.mjs +37 -0
  161. package/runtime/scripts/lib/path-key.mjs +57 -2
  162. package/runtime/scripts/lib/read-text.mjs +34 -0
  163. package/runtime/scripts/lib/regex.mjs +4 -0
  164. package/runtime/scripts/lib/same-text.mjs +5 -0
  165. package/runtime/scripts/lib/secret-patterns.mjs +4 -3
  166. package/runtime/scripts/lib/sleep-sync.mjs +2 -1
  167. package/runtime/scripts/lib/sops-envelope.mjs +58 -0
  168. package/runtime/scripts/lib/stack-services.mjs +7 -31
  169. package/runtime/scripts/lib/ts-ast.mjs +35 -0
  170. package/runtime/scripts/lib/walk.mjs +4 -3
  171. package/scaffold/add-app.mjs +174 -0
  172. package/scaffold/add-cli-lite.mjs +257 -0
  173. package/scaffold/add-inbox-lite.mjs +314 -0
  174. package/scaffold/add-table.mjs +344 -0
  175. package/scaffold/add.mjs +299 -0
  176. package/scaffold/app.mjs +358 -69
  177. package/scaffold/edition-gate.mjs +25 -0
  178. package/scaffold/image.mjs +19 -0
  179. package/scaffold/lite-exports.mjs +186 -0
  180. package/scaffold/service.mjs +32 -26
  181. package/scaffold/supabase-ports.mjs +26 -0
  182. package/src/main.mjs +327 -0
  183. package/sync/cli.mjs +6 -9
  184. package/sync/format.mjs +1 -1
  185. package/sync/hygiene.mjs +10 -10
  186. package/sync/index.mjs +167 -104
  187. package/sync/managed.mjs +19 -11
  188. package/sync/sonar-key.mjs +5 -0
  189. package/templates/app/ci-workflows/github/workflows/ci.yml +14 -14
  190. package/templates/app/ci-workflows/github/workflows/images.yml +37 -0
  191. package/templates/app/ci-workflows/sonar-steps.yml +9 -0
  192. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +66 -0
  193. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +31 -0
  194. package/templates/app/ci-workflows-lite/github/workflows/images.yml +37 -0
  195. package/templates/app/codegen-lite/scripts/codegen.mjs +32 -0
  196. package/templates/app/docker-ignore/dockerignore +50 -0
  197. package/templates/app/gitignore-lite +35 -0
  198. package/templates/app/hooks/husky/pre-commit +3 -8
  199. package/templates/app/hooks/husky/pre-push +45 -6
  200. package/templates/app/package-scripts/core.json +8 -0
  201. package/templates/app/package-scripts/package.json +3 -10
  202. package/templates/app/package-scripts-lite/package.json +15 -0
  203. package/templates/app/quality-config/codecov.yml +15 -3
  204. package/templates/app/quality-config/sonar-base.properties +4 -0
  205. package/templates/app/quality-config/sonar-project.properties +1 -4
  206. package/templates/app/quality-config-lite/sonar-project.properties +5 -0
  207. package/templates/app/skeleton/.starcistacks/dev/seeds/primary-notes.sql +5 -0
  208. package/templates/app/skeleton/.starciwork/features/system-health/index.yaml +4 -0
  209. package/templates/app/skeleton/.starciwork/{features/index.yaml → index.yaml} +2 -2
  210. package/templates/app/skeleton/README.md +7 -5
  211. package/templates/app/skeleton-lite/.sops.yaml +3 -0
  212. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +5 -0
  213. package/templates/app/skeleton-lite/.starcistacks/dev/secrets/.gitkeep +1 -0
  214. package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +55 -0
  215. package/templates/app/skeleton-lite/README.md +40 -0
  216. package/templates/app/skeleton-lite/supabase/config.toml +41 -0
  217. package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +56 -0
  218. package/templates/app/skeleton-lite/supabase/seed.sql +0 -0
  219. package/templates/app/task-graph/turbo.json +23 -0
  220. package/templates/app/tool-config-lite/prettierignore +2 -0
  221. package/templates/app/tool-config-lite/prettierrc +1 -0
  222. package/templates/app/upgrade-full/.starcistacks/application-stacks.yaml +38 -0
  223. package/templates/app/upgrade-full/.starcistacks/dev/README.md +10 -0
  224. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/api.yaml +13 -0
  225. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/compose.yaml +3 -0
  226. package/templates/app/upgrade-full/.starcistacks/dev/infra/compose/postgres.yaml +12 -0
  227. package/templates/app/upgrade-full/.starcistacks/dev/infra/metadata.json +7 -0
  228. package/templates/be/common/main-listen.ts.partial +14 -0
  229. package/templates/be/common/sql-ident.ts.partial +7 -0
  230. package/templates/be/common/sql-tag.ts.partial +5 -0
  231. package/templates/be/image/api/Dockerfile +38 -0
  232. package/templates/be/image/cli/Dockerfile +38 -0
  233. package/templates/be/image/worker/Dockerfile +36 -0
  234. package/templates/be/inbox/database-client.ts.tpl +50 -0
  235. package/templates/be/inbox/index.ts.tpl +2 -0
  236. package/templates/be/inbox/migration.sql.tpl +29 -0
  237. package/templates/be/inbox/module-definition.ts.tpl +6 -0
  238. package/templates/be/inbox/module.ts.tpl +14 -0
  239. package/templates/be/inbox/options.ts.tpl +2 -0
  240. package/templates/be/inbox/service.ts.tpl +33 -0
  241. package/templates/be/inbox/sql.ts.tpl +16 -0
  242. package/templates/be/inbox/webhook-door.ts.tpl +36 -0
  243. package/templates/be/patterns/api/application.command.http.ts.tpl +10 -0
  244. package/templates/be/patterns/api/application.command.ts.tpl +10 -0
  245. package/templates/be/patterns/api/application.contracts.ts.tpl +11 -0
  246. package/templates/be/patterns/api/application.handler.http.ts.tpl +22 -0
  247. package/templates/be/patterns/api/application.handler.ts.tpl +22 -0
  248. package/templates/be/patterns/api/feature.index.http.ts.tpl +1 -0
  249. package/templates/be/patterns/api/feature.index.ts.tpl +1 -0
  250. package/templates/be/patterns/api/feature.module.ts.tpl +6 -0
  251. package/templates/be/patterns/api/graphql.input.ts.tpl +13 -0
  252. package/templates/be/patterns/api/graphql.mapper.ts.tpl +9 -0
  253. package/templates/be/patterns/api/graphql.module.ts.tpl +7 -0
  254. package/templates/be/patterns/api/graphql.resolver.ts.tpl +27 -0
  255. package/templates/be/patterns/api/graphql.type.ts.tpl +9 -0
  256. package/templates/be/patterns/api/http.controller.ts.tpl +27 -0
  257. package/templates/be/patterns/api/http.mapper.ts.tpl +9 -0
  258. package/templates/be/patterns/api/http.module.ts.tpl +7 -0
  259. package/templates/be/patterns/api/http.request.ts.tpl +10 -0
  260. package/templates/be/patterns/api/http.response.ts.tpl +5 -0
  261. package/templates/be/patterns/cli/group.cli.spec.ts.tpl +15 -0
  262. package/templates/be/patterns/cli/group.cli.ts.tpl +11 -0
  263. package/templates/be/patterns/cli/group.module.ts.tpl +7 -0
  264. package/templates/be/patterns/cli/sub.run.cli.spec.ts.tpl +17 -0
  265. package/templates/be/patterns/cli/sub.run.cli.ts.tpl +15 -0
  266. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +22 -0
  267. package/templates/be/patterns/event-bus/platform/event-bus.config.ts.tpl +19 -0
  268. package/templates/be/patterns/event-bus/platform/event-bus.contracts.ts.tpl +58 -0
  269. package/templates/be/patterns/event-bus/platform/event-bus.decorators.ts.tpl +46 -0
  270. package/templates/be/patterns/event-bus/platform/event-bus.log-events.ts.tpl +12 -0
  271. package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +10 -0
  272. package/templates/be/patterns/event-bus/platform/event-bus.module.ts.tpl +52 -0
  273. package/templates/be/patterns/event-bus/platform/event-bus.options.ts.tpl +19 -0
  274. package/templates/be/patterns/event-bus/platform/event-bus.port.ts.tpl +28 -0
  275. package/templates/be/patterns/event-bus/platform/event-bus.service.spec.ts.tpl +165 -0
  276. package/templates/be/patterns/event-bus/platform/event-bus.service.ts.tpl +93 -0
  277. package/templates/be/patterns/event-bus/platform/event-envelope.policy.spec.ts.tpl +90 -0
  278. package/templates/be/patterns/event-bus/platform/event-envelope.policy.ts.tpl +58 -0
  279. package/templates/be/patterns/event-bus/platform/event-relay.service.spec.ts.tpl +136 -0
  280. package/templates/be/patterns/event-bus/platform/event-relay.service.ts.tpl +56 -0
  281. package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +272 -0
  282. package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +128 -0
  283. package/templates/be/patterns/event-bus/platform/event-transport.port.ts.tpl +180 -0
  284. package/templates/be/patterns/event-bus/platform/event.policy.spec.ts.tpl +67 -0
  285. package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +38 -0
  286. package/templates/be/patterns/event-bus/platform/index.ts.tpl +11 -0
  287. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.spec.ts.tpl +348 -0
  288. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +241 -0
  289. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +15 -0
  290. package/templates/be/patterns/event-bus/platform/persistence/connection.ts.tpl +8 -0
  291. package/templates/be/patterns/event-bus/platform/persistence/entities/event-outbox.entity.ts.tpl +37 -0
  292. package/templates/be/patterns/event-bus/platform/persistence/event-bus.rows.ts.tpl +11 -0
  293. package/templates/be/patterns/event-bus/platform/persistence/event-bus.sql.ts.tpl +12 -0
  294. package/templates/be/patterns/event-bus/platform/persistence/migrations/1789800009000-create-event-outbox.ts.tpl +26 -0
  295. package/templates/be/patterns/jobs/job.index.ts.tpl +1 -0
  296. package/templates/be/patterns/jobs/job.module.ts.tpl +6 -0
  297. package/templates/be/patterns/jobs/job.processor.ts.tpl +20 -0
  298. package/templates/be/patterns/jobs/job.queue.module.ts.tpl +20 -0
  299. package/templates/be/patterns/jobs/job.step.ts.tpl +19 -0
  300. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +31 -0
  301. package/templates/be/patterns/jobs/platform/fenced.processor.ts.tpl +13 -0
  302. package/templates/be/patterns/jobs/platform/job-claim.service.spec.ts.tpl +150 -0
  303. package/templates/be/patterns/jobs/platform/job-claim.service.ts.tpl +100 -0
  304. package/templates/be/patterns/jobs/platform/job-runner.service.spec.ts.tpl +138 -0
  305. package/templates/be/patterns/jobs/platform/job-runner.service.ts.tpl +69 -0
  306. package/templates/be/patterns/jobs/platform/jobs.config.ts.tpl +11 -0
  307. package/templates/be/patterns/jobs/platform/jobs.contracts.ts.tpl +62 -0
  308. package/templates/be/patterns/jobs/platform/jobs.decorators.ts.tpl +31 -0
  309. package/templates/be/patterns/jobs/platform/jobs.error.ts.tpl +22 -0
  310. package/templates/be/patterns/jobs/platform/jobs.index.ts.tpl +11 -0
  311. package/templates/be/patterns/jobs/platform/jobs.log-events.ts.tpl +9 -0
  312. package/templates/be/patterns/jobs/platform/jobs.messages.ts.tpl +15 -0
  313. package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +10 -0
  314. package/templates/be/patterns/jobs/platform/jobs.module.ts.tpl +32 -0
  315. package/templates/be/patterns/jobs/platform/jobs.options.ts.tpl +11 -0
  316. package/templates/be/patterns/jobs/platform/jobs.port.ts.tpl +36 -0
  317. package/templates/be/patterns/jobs/platform/persistence.connection.ts.tpl +8 -0
  318. package/templates/be/patterns/jobs/platform/persistence.job.entity.ts.tpl +53 -0
  319. package/templates/be/patterns/jobs/platform/persistence.jobs.rows.ts.tpl +13 -0
  320. package/templates/be/patterns/jobs/platform/persistence.jobs.sql.ts.tpl +33 -0
  321. package/templates/be/patterns/jobs/platform/persistence.migration.ts.tpl +29 -0
  322. package/templates/be/patterns/outbox/platform/index.ts.tpl +2 -0
  323. package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +96 -0
  324. package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +78 -0
  325. package/templates/be/patterns/outbox/platform/outbox.log-events.ts.tpl +5 -0
  326. package/templates/be/patterns/projections/migration.ts.tpl +18 -0
  327. package/templates/be/patterns/projections/persistence.connection.ts.tpl +8 -0
  328. package/templates/be/patterns/projections/projection.entity.ts.tpl +9 -0
  329. package/templates/be/patterns/projections/projection.index.ts.tpl +3 -0
  330. package/templates/be/patterns/projections/projection.module-definition.ts.tpl +7 -0
  331. package/templates/be/patterns/projections/projection.module.ts.tpl +18 -0
  332. package/templates/be/patterns/projections/projection.options.ts.tpl +2 -0
  333. package/templates/be/patterns/projections/projection.projection.spec.ts.tpl +55 -0
  334. package/templates/be/patterns/projections/projection.projection.ts.tpl +21 -0
  335. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.spec.ts.tpl +142 -0
  336. package/templates/be/patterns/queues/platform/bullmq-queue-transport.client.ts.tpl +81 -0
  337. package/templates/be/patterns/queues/platform/persistence.connection.ts.tpl +8 -0
  338. package/templates/be/patterns/queues/platform/persistence.migration.ts.tpl +25 -0
  339. package/templates/be/patterns/queues/platform/persistence.queue-outbox.entity.ts.tpl +25 -0
  340. package/templates/be/patterns/queues/platform/persistence.queue.rows.ts.tpl +9 -0
  341. package/templates/be/patterns/queues/platform/persistence.queue.sql.ts.tpl +12 -0
  342. package/templates/be/patterns/queues/platform/queue-outbox.service.spec.ts.tpl +48 -0
  343. package/templates/be/patterns/queues/platform/queue-outbox.service.ts.tpl +17 -0
  344. package/templates/be/patterns/queues/platform/queue-relay.service.spec.ts.tpl +128 -0
  345. package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +48 -0
  346. package/templates/be/patterns/queues/platform/queue-transport.port.ts.tpl +115 -0
  347. package/templates/be/patterns/queues/platform/queue-worker.service.spec.ts.tpl +91 -0
  348. package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +45 -0
  349. package/templates/be/patterns/queues/platform/queue.config.ts.tpl +15 -0
  350. package/templates/be/patterns/queues/platform/queue.contracts.ts.tpl +31 -0
  351. package/templates/be/patterns/queues/platform/queue.decorators.ts.tpl +45 -0
  352. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +7 -0
  353. package/templates/be/patterns/queues/platform/queue.log-events.ts.tpl +5 -0
  354. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +10 -0
  355. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +62 -0
  356. package/templates/be/patterns/queues/platform/queue.options.ts.tpl +22 -0
  357. package/templates/be/patterns/queues/platform/queue.policy.spec.ts.tpl +19 -0
  358. package/templates/be/patterns/queues/platform/queue.policy.ts.tpl +11 -0
  359. package/templates/be/patterns/queues/platform/queue.port.ts.tpl +14 -0
  360. package/templates/be/patterns/queues/queue.index.ts.tpl +3 -0
  361. package/templates/be/patterns/queues/queue.module.ts.tpl +6 -0
  362. package/templates/be/patterns/queues/queue.queue.ts.tpl +23 -0
  363. package/templates/be/patterns/reactors/application.command.ts.tpl +10 -0
  364. package/templates/be/patterns/reactors/application.contracts.ts.tpl +12 -0
  365. package/templates/be/patterns/reactors/application.handler.ts.tpl +22 -0
  366. package/templates/be/patterns/reactors/consumer.ts.tpl +19 -0
  367. package/templates/be/patterns/reactors/message.module.ts.tpl +20 -0
  368. package/templates/be/patterns/reactors/reactor.index.ts.tpl +1 -0
  369. package/templates/be/patterns/reactors/reactor.module.ts.tpl +6 -0
  370. package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +6 -0
  371. package/templates/be/patterns/realtime/channel.index.ts.tpl +1 -0
  372. package/templates/be/patterns/realtime/channel.input.ts.tpl +12 -0
  373. package/templates/be/patterns/realtime/channel.subscription.spec.ts.tpl +51 -0
  374. package/templates/be/patterns/realtime/channel.subscription.ts.tpl +32 -0
  375. package/templates/be/patterns/realtime/channel.type.ts.tpl +9 -0
  376. package/templates/be/patterns/saga/saga.compensate.command.ts.tpl +11 -0
  377. package/templates/be/patterns/saga/saga.compensate.contracts.ts.tpl +7 -0
  378. package/templates/be/patterns/saga/saga.compensate.handler.ts.tpl +22 -0
  379. package/templates/be/patterns/saga/saga.compensation.ts.tpl +21 -0
  380. package/templates/be/patterns/saga/saga.complete.command.ts.tpl +11 -0
  381. package/templates/be/patterns/saga/saga.complete.contracts.ts.tpl +7 -0
  382. package/templates/be/patterns/saga/saga.complete.handler.ts.tpl +22 -0
  383. package/templates/be/patterns/saga/saga.done.consumer.ts.tpl +21 -0
  384. package/templates/be/patterns/saga/saga.failed.consumer.ts.tpl +21 -0
  385. package/templates/be/patterns/saga/saga.index.ts.tpl +1 -0
  386. package/templates/be/patterns/saga/saga.message.module.ts.tpl +23 -0
  387. package/templates/be/patterns/saga/saga.module.ts.tpl +20 -0
  388. package/templates/be/patterns/saga/saga.service.spec.ts.tpl +70 -0
  389. package/templates/be/patterns/saga/saga.service.ts.tpl +44 -0
  390. package/templates/be/patterns/saga/saga.state.ts.tpl +14 -0
  391. package/templates/be/patterns/saga/saga.step.ts.tpl +11 -0
  392. package/templates/be/patterns/saga/saga.undo.command.ts.tpl +10 -0
  393. package/templates/be/patterns/saga/saga.undo.contracts.ts.tpl +5 -0
  394. package/templates/be/patterns/saga/saga.undo.handler.ts.tpl +21 -0
  395. package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +72 -0
  396. package/templates/be/patterns/webhooks/webhook.door.ts.tpl +36 -0
  397. package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +6 -0
  398. package/templates/be/patterns/webhooks/webhook.index.ts.tpl +1 -0
  399. package/templates/be/patterns/webhooks/webhook.request.ts.tpl +9 -0
  400. package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +3 -0
  401. package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -2
  402. package/templates/be/skeleton/apps/__app__/src/main.ts +7 -15
  403. package/templates/be/skeleton/apps/cli/src/app.module.ts +30 -0
  404. package/templates/be/skeleton/apps/cli/src/cli.options.ts +22 -0
  405. package/templates/be/skeleton/apps/cli/src/main.ts +24 -0
  406. package/templates/be/skeleton/src/features/cli/cli.module.ts +7 -0
  407. package/templates/be/skeleton/src/features/cli/index.ts +1 -0
  408. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +17 -0
  409. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +15 -0
  410. package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +7 -0
  411. package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.spec.ts +17 -0
  412. package/templates/be/skeleton/src/features/cli/migrate/subs/run.cli.ts +18 -0
  413. package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +17 -0
  414. package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +15 -0
  415. package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +7 -0
  416. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +33 -0
  417. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.ts +34 -0
  418. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +13 -0
  419. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +40 -0
  420. package/templates/be/skeleton/src/modules/domain/identity/messages/identity.messages.ts +1 -1
  421. package/templates/be/skeleton/src/modules/domain/note/errors/note.error.ts +16 -0
  422. package/templates/be/skeleton/src/modules/domain/note/index.ts +3 -0
  423. package/templates/be/skeleton/src/modules/domain/note/note.contracts.ts +19 -0
  424. package/templates/be/skeleton/src/modules/domain/note/note.decorators.ts +9 -0
  425. package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +7 -0
  426. package/templates/be/skeleton/src/modules/domain/note/note.module.ts +19 -0
  427. package/templates/be/skeleton/src/modules/domain/note/note.options.ts +2 -0
  428. package/templates/be/skeleton/src/modules/domain/note/note.service.spec.ts +76 -0
  429. package/templates/be/skeleton/src/modules/domain/note/note.service.ts +37 -0
  430. package/templates/be/skeleton/src/modules/domain/note/persistence/connection.ts +8 -0
  431. package/templates/be/skeleton/src/modules/domain/note/persistence/entities/note.entity.ts +17 -0
  432. package/templates/be/skeleton/src/modules/domain/note/persistence/migrations/1790000000000-init-note.ts +22 -0
  433. package/templates/be/skeleton/src/modules/domain/note/persistence/note.rows.ts +17 -0
  434. package/templates/be/skeleton/src/modules/domain/note/persistence/note.sql.ts +4 -0
  435. package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +20 -0
  436. package/templates/be/skeleton/src/modules/platform/config/index.ts +1 -1
  437. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +18 -0
  438. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +41 -0
  439. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.ts +17 -0
  440. package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +13 -0
  441. package/templates/be/skeleton/src/modules/platform/database/database.module.ts +77 -0
  442. package/templates/be/skeleton/src/modules/platform/database/database.options.ts +30 -0
  443. package/templates/be/skeleton/src/modules/platform/database/database.port.ts +65 -0
  444. package/templates/be/skeleton/src/modules/platform/database/database.sql.ts +47 -0
  445. package/templates/be/skeleton/src/modules/platform/database/errors/database.error.ts +16 -0
  446. package/templates/be/skeleton/src/modules/platform/database/index.ts +9 -0
  447. package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +30 -0
  448. package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.spec.ts +84 -0
  449. package/templates/be/skeleton/src/modules/platform/database/migration-runner.service.ts +23 -0
  450. package/templates/be/skeleton/src/modules/platform/database/primary.config.ts +9 -0
  451. package/templates/be/skeleton/src/modules/platform/database/primary.connection.ts +2 -0
  452. package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +10 -0
  453. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +108 -0
  454. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +75 -0
  455. package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.spec.ts +88 -0
  456. package/templates/be/skeleton/src/modules/platform/database/seed-runner.service.ts +29 -0
  457. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +48 -0
  458. package/templates/be/skeleton/src/modules/platform/errors/messages/errors.messages.ts +1 -1
  459. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +12 -0
  460. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -0
  461. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +8 -1
  462. package/templates/be/skeleton/src/modules/platform/http-security/http-security.decorators.ts +8 -0
  463. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +14 -1
  464. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +12 -0
  465. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +10 -2
  466. package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.spec.ts +63 -0
  467. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +108 -0
  468. package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.spec.ts +39 -0
  469. package/templates/be/skeleton/src/modules/platform/http-security/request-validation.service.ts +25 -0
  470. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +172 -0
  471. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.ts +66 -0
  472. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +3 -1
  473. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +52 -0
  474. package/templates/be/skeleton-lite/apps/api/Dockerfile +37 -0
  475. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +16 -0
  476. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +81 -0
  477. package/templates/be/skeleton-lite/apps/api/src/main.ts +34 -0
  478. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +14 -0
  479. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +50 -0
  480. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +26 -0
  481. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +19 -0
  482. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +26 -0
  483. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +7 -0
  484. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +6 -0
  485. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +19 -0
  486. package/templates/be/skeleton-lite/src/modules/integrations/supabase/index.ts +10 -0
  487. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.ts +24 -0
  488. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.config.ts +8 -0
  489. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.decorators.ts +17 -0
  490. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.jwks.ts +32 -0
  491. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.options.ts +24 -0
  492. package/templates/be/skeleton-lite/src/modules/platform/cqrs/index.ts +4 -0
  493. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +54 -0
  494. package/templates/be/skeleton-lite/src/modules/platform/database/database.options.ts +23 -0
  495. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +10 -0
  496. package/templates/be/skeleton-lite/src/modules/platform/database/database.sql.ts +49 -0
  497. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +4 -0
  498. package/templates/be/skeleton-lite/src/modules/platform/database/migration-runner.service.ts +21 -0
  499. package/templates/be/skeleton-lite/src/modules/platform/database/primary.config.ts +11 -0
  500. package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +28 -0
  501. package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +25 -0
  502. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +9 -0
  503. package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +4 -0
  504. package/templates/be/skeleton-lite/src/modules/platform/logging/index.ts +5 -0
  505. package/templates/be/table/error.ts.tpl +16 -0
  506. package/templates/be/table/index.ts.tpl +2 -0
  507. package/templates/be/table/migration.sql.tpl +51 -0
  508. package/templates/be/table/module-definition.ts.tpl +7 -0
  509. package/templates/be/table/module.ts.tpl +14 -0
  510. package/templates/be/table/options.ts.tpl +2 -0
  511. package/templates/be/table/rows.ts.tpl +4 -0
  512. package/templates/be/table/service.ts.tpl +23 -0
  513. package/templates/be/table/sql.ts.tpl +4 -0
  514. package/templates/be/tool-config/jest.config.js +3 -1
  515. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +14 -0
  516. package/templates/be/upgrade-full/apps/cli/src/main.ts +26 -0
  517. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +6 -0
  518. package/templates/be/upgrade-full/src/features/cli/migrate/subs/run.cli.ts +23 -0
  519. package/templates/common/gitignore.base +1 -2
  520. package/templates/fe/app/package.json.tpl +1 -0
  521. package/templates/fe/app/tsconfig.json +12 -0
  522. package/templates/fe/common/app-layout.tsx.partial +8 -0
  523. package/templates/fe/common/site-shell.tsx.partial +9 -0
  524. package/templates/fe/image/next/Dockerfile +42 -0
  525. package/templates/fe/skeleton/apps/app/next.config.ts +23 -0
  526. package/templates/fe/skeleton/apps/app/postcss.config.mjs +7 -0
  527. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/error.tsx +2 -2
  528. package/templates/fe/skeleton/apps/app/src/app/[locale]/layout.tsx +22 -0
  529. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/loading.tsx +2 -2
  530. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/[locale]/not-found.tsx +2 -2
  531. package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +9 -0
  532. package/templates/fe/skeleton/apps/{__app__ → app}/src/app/global-error.tsx +2 -2
  533. package/templates/fe/skeleton/apps/app/src/app/globals.css +10 -0
  534. package/templates/fe/skeleton/apps/app/src/features/layouts/AppLayout/index.tsx +18 -0
  535. package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/component.tsx +19 -0
  536. package/templates/fe/skeleton/apps/app/src/features/pages/AppErrorPage/index.tsx +11 -0
  537. package/templates/fe/skeleton/apps/app/src/features/pages/AppGlobalErrorPage/index.tsx +16 -0
  538. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/component.tsx +10 -0
  539. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +15 -0
  540. package/templates/fe/skeleton/apps/app/src/features/pages/AppLoadingPage/index.tsx +8 -0
  541. package/templates/fe/skeleton/apps/app/src/features/pages/AppNotFoundPage/index.tsx +9 -0
  542. package/templates/fe/skeleton/apps/app/src/modules/i18n/index.ts +4 -0
  543. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +28 -0
  544. package/templates/fe/skeleton/apps/app/src/modules/i18n/request.ts +4 -0
  545. package/templates/fe/skeleton/apps/app/src/modules/routes/index.ts +4 -0
  546. package/templates/fe/skeleton/apps/app/src/proxy.ts +1 -0
  547. package/templates/fe/skeleton/apps/{__app__ → landing}/next.config.ts +4 -2
  548. package/templates/fe/skeleton/apps/landing/postcss.config.mjs +7 -0
  549. package/templates/fe/skeleton/apps/landing/src/app/[locale]/error.tsx +10 -0
  550. package/templates/fe/skeleton/apps/landing/src/app/[locale]/layout.tsx +22 -0
  551. package/templates/fe/skeleton/apps/landing/src/app/[locale]/loading.tsx +6 -0
  552. package/templates/fe/skeleton/apps/landing/src/app/[locale]/not-found.tsx +6 -0
  553. package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +9 -0
  554. package/templates/fe/skeleton/apps/landing/src/app/global-error.tsx +10 -0
  555. package/templates/fe/skeleton/apps/landing/src/app/globals.css +10 -0
  556. package/templates/fe/skeleton/apps/landing/src/features/layouts/LandingLayout/index.tsx +24 -0
  557. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/component.tsx +18 -0
  558. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingErrorPage/index.tsx +11 -0
  559. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingGlobalErrorPage/index.tsx +16 -0
  560. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingLoadingPage/index.tsx +8 -0
  561. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingNotFoundPage/index.tsx +9 -0
  562. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/component.tsx +24 -0
  563. package/templates/fe/skeleton/apps/landing/src/features/pages/LandingPage/index.tsx +16 -0
  564. package/templates/fe/skeleton/apps/landing/src/modules/config/index.ts +12 -0
  565. package/templates/fe/skeleton/apps/landing/src/modules/i18n/index.ts +7 -0
  566. package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/vi.json +31 -0
  567. package/templates/fe/skeleton/apps/landing/src/modules/i18n/request.ts +4 -0
  568. package/templates/fe/skeleton/apps/landing/src/modules/routes/index.ts +4 -0
  569. package/templates/fe/skeleton/apps/landing/src/proxy.ts +1 -0
  570. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +49 -0
  571. package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +24 -0
  572. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +20 -0
  573. package/templates/fe/skeleton/packages/__project__-ui/src/branches/Providers/index.tsx +23 -0
  574. package/templates/fe/skeleton/packages/__project__-ui/src/composites/ErrorNotice/index.tsx +14 -0
  575. package/templates/fe/skeleton/packages/__project__-ui/src/composites/GlobalErrorNotice/index.tsx +25 -0
  576. package/templates/fe/skeleton/packages/__project__-ui/src/composites/LoadingNotice/index.tsx +9 -0
  577. package/templates/fe/skeleton/packages/__project__-ui/src/composites/LocaleShell/index.tsx +21 -0
  578. package/templates/fe/skeleton/packages/__project__-ui/src/composites/NotFoundNotice/index.tsx +19 -0
  579. package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/classNames.ts +4 -0
  580. package/templates/fe/skeleton/packages/__project__-ui/src/composites/SiteShell/index.tsx +19 -0
  581. package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +6 -0
  582. package/templates/fe/skeleton-lite/apps/web/next.config.ts +22 -0
  583. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/error.tsx +10 -0
  584. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +37 -0
  585. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/loading.tsx +6 -0
  586. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/not-found.tsx +6 -0
  587. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +9 -0
  588. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/providers.tsx +26 -0
  589. package/templates/fe/skeleton-lite/apps/web/src/app/auth/callback/route.ts +24 -0
  590. package/templates/fe/skeleton-lite/apps/web/src/app/global-error.tsx +10 -0
  591. package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/src/app/globals.css +1 -1
  592. package/templates/fe/skeleton-lite/apps/web/src/app/health/live/route.ts +5 -0
  593. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/component.tsx +29 -0
  594. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +41 -0
  595. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/component.tsx +17 -0
  596. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignOutButton/index.tsx +38 -0
  597. package/templates/fe/skeleton-lite/apps/web/src/components/composites/ErrorNotice/index.tsx +13 -0
  598. package/templates/fe/skeleton-lite/apps/web/src/components/composites/GlobalErrorNotice/index.tsx +24 -0
  599. package/templates/fe/skeleton-lite/apps/web/src/components/composites/LoadingNotice/index.tsx +7 -0
  600. package/templates/fe/skeleton-lite/apps/web/src/components/composites/NotFoundNotice/index.tsx +18 -0
  601. package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/classNames.ts +4 -0
  602. package/templates/fe/skeleton-lite/apps/web/src/components/composites/SiteShell/index.tsx +13 -0
  603. package/templates/fe/skeleton-lite/apps/web/src/features/layouts/AppLayout/index.tsx +31 -0
  604. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/component.tsx +12 -0
  605. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppErrorPage/index.tsx +10 -0
  606. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppGlobalErrorPage/index.tsx +15 -0
  607. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/component.tsx +14 -0
  608. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +30 -0
  609. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppLoadingPage/index.tsx +8 -0
  610. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppNotFoundPage/index.tsx +9 -0
  611. package/templates/fe/skeleton-lite/apps/web/src/modules/brand/brand.css +20 -0
  612. package/templates/fe/skeleton-lite/apps/web/src/modules/brand/index.ts +2 -0
  613. package/templates/fe/skeleton-lite/apps/web/src/modules/config/index.ts +20 -0
  614. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/read-session.ts +25 -0
  615. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-in.ts +25 -0
  616. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +17 -0
  617. package/templates/fe/skeleton-lite/apps/web/src/modules/db/browser.ts +14 -0
  618. package/templates/fe/skeleton-lite/apps/web/src/modules/db/index.ts +3 -0
  619. package/templates/fe/skeleton-lite/apps/web/src/modules/db/outcome.ts +59 -0
  620. package/templates/fe/skeleton-lite/apps/web/src/modules/db/principal.ts +29 -0
  621. package/templates/fe/skeleton-lite/apps/web/src/modules/db/server.ts +70 -0
  622. package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +50 -0
  623. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/index.ts +8 -0
  624. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +48 -0
  625. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +16 -0
  626. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +14 -0
  627. package/templates/fe/skeleton-lite/apps/web/src/modules/routes/index.ts +7 -0
  628. package/templates/fe/skeleton-lite/apps/web/src/modules/routes/safe-next-path.ts +26 -0
  629. package/templates/fe/skeleton-lite/apps/web/src/proxy.ts +43 -0
  630. package/templates/fe/table/read.ts.tpl +16 -0
  631. package/templates/fe/table/write.ts.tpl +16 -0
  632. package/upgrade/check.mjs +137 -0
  633. package/upgrade/index.mjs +524 -0
  634. package/bin/hfs.mjs +0 -233
  635. package/runtime/scripts/api/fs/rmdir-link.mjs +0 -12
  636. package/runtime/scripts/lib/test-secrets.mjs +0 -116
  637. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/layout.tsx +0 -20
  638. package/templates/fe/skeleton/apps/__app__/src/app/[locale]/page.tsx +0 -9
  639. package/templates/fe/skeleton/apps/__app__/src/components/composites/FailureScreen/index.tsx +0 -28
  640. package/templates/fe/skeleton/apps/__app__/src/features/layouts/LocaleShell/index.tsx +0 -35
  641. package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/component.tsx +0 -19
  642. package/templates/fe/skeleton/apps/__app__/src/features/pages/ErrorPage/index.tsx +0 -11
  643. package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/component.tsx +0 -24
  644. package/templates/fe/skeleton/apps/__app__/src/features/pages/GlobalErrorPage/index.tsx +0 -14
  645. package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/component.tsx +0 -21
  646. package/templates/fe/skeleton/apps/__app__/src/features/pages/HomePage/index.tsx +0 -15
  647. package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/component.tsx +0 -23
  648. package/templates/fe/skeleton/apps/__app__/src/features/pages/LoadingPage/index.tsx +0 -8
  649. package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/component.tsx +0 -28
  650. package/templates/fe/skeleton/apps/__app__/src/features/pages/NotFoundPage/index.tsx +0 -8
  651. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/config.ts +0 -8
  652. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/index.ts +0 -2
  653. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/messages/vi.json +0 -25
  654. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/request.ts +0 -19
  655. package/templates/fe/skeleton/apps/__app__/src/modules/i18n/routing.ts +0 -9
  656. package/templates/fe/skeleton/apps/__app__/src/modules/routes/index.ts +0 -4
  657. package/templates/fe/skeleton/apps/__app__/src/proxy.ts +0 -10
  658. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.contracts.ts +0 -0
  659. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.handler.ts +0 -0
  660. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/application/check-liveness.query.ts +0 -0
  661. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/index.ts +0 -0
  662. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/system-health.module.ts +0 -0
  663. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/live.controller.ts +0 -0
  664. /package/templates/be/skeleton/src/features/{system-health → api/system-health}/transport/http/system-health-http.module.ts +0 -0
  665. /package/templates/fe/skeleton/apps/{__app__ → app}/src/app/health/live/route.ts +0 -0
  666. /package/templates/fe/skeleton/apps/{__app__ → app}/src/modules/config/index.ts +0 -0
  667. /package/templates/fe/{skeleton/apps/__app__ → skeleton-lite/apps/web}/postcss.config.mjs +0 -0
@@ -0,0 +1,220 @@
1
+ // ast-walks.mjs - AST walks the architecture checkers share (moved out of typescript.mjs, which keeps the program loading and module resolution).
2
+ import { canonical, isInside } from './config.mjs';
3
+ import { sourceLocation } from '../../lib/ts-ast.mjs';
4
+ import { UNPROVEN_FRAMEWORK, isUnshadowedCommonJsRequire, relativePath, unwrapExpression } from './typescript.mjs';
5
+
6
+ /** `expression` unwrapped by the caller's own predicate set (a predicate absent on an older ts reads as never). */
7
+ export function unwrapEach(ts, expression, predicates) {
8
+ while (expression && predicates.some((predicate) => predicate?.(expression))) expression = expression.expression;
9
+ return expression;
10
+ }
11
+
12
+ /** `value` with every TS alias hop resolved (a re-exported import reads as its target symbol). */
13
+ export function normalizedSymbolValue(ts, checker, value) {
14
+ let symbol = value ?? null;
15
+ const seen = new Set();
16
+ while (symbol && (symbol.flags & ts.SymbolFlags.Alias) && !seen.has(symbol)) {
17
+ seen.add(symbol);
18
+ const target = checker.getAliasedSymbol(symbol);
19
+ if (!target || target === symbol) break;
20
+ symbol = target;
21
+ }
22
+ return symbol;
23
+ }
24
+
25
+ /** The symbol `node` names, aliases resolved. */
26
+ export function normalizedSymbol(ts, checker, node) {
27
+ return normalizedSymbolValue(ts, checker, checker?.getSymbolAtLocation(node) ?? null);
28
+ }
29
+
30
+ /** The identifier or member-name node an expression selects through unwrapExpression, or null. */
31
+ export function selectedNode(ts, expression) {
32
+ expression = unwrapExpression(ts, expression);
33
+ if (ts.isIdentifier(expression)) return expression;
34
+ if (ts.isPropertyAccessExpression(expression)) return expression.name;
35
+ if (ts.isElementAccessExpression(expression) && ts.isStringLiteralLike(expression.argumentExpression)) return expression.argumentExpression;
36
+ return null;
37
+ }
38
+
39
+ /**
40
+ * The symbol `node` ultimately evaluates to: its own symbol, followed through a single `const` variable
41
+ * initializer (and so on). `unselected: 'null'` answers null when `node` selects no name at all.
42
+ */
43
+ export function valueSymbol(ts, checker, node, seen = new Set(), { unselected = 'node' } = {}) {
44
+ const selected = selectedNode(ts, node);
45
+ if (!selected && unselected === 'null') return null;
46
+ const symbol = normalizedSymbol(ts, checker, selected ?? node);
47
+ if (!symbol || seen.has(symbol)) return symbol;
48
+ seen.add(symbol);
49
+ const declarations = symbol.getDeclarations?.() ?? [];
50
+ if (declarations.length === 1 && ts.isVariableDeclaration(declarations[0]) && declarations[0].initializer
51
+ && (ts.getCombinedNodeFlags(declarations[0].parent) & ts.NodeFlags.Const)) {
52
+ return valueSymbol(ts, checker, declarations[0].initializer, seen, { unselected });
53
+ }
54
+ return symbol;
55
+ }
56
+
57
+ /**
58
+ * The expressions `declaration` returns: its body when it is an expression-bodied arrow/function, else every
59
+ * `return`'s argument without descending into nested functions. `functionLike` overrides which node kinds bound
60
+ * that descent (default the four callable declaration kinds; pass `ts.isFunctionLike` for the wider set).
61
+ */
62
+ export function returnedExpressions(ts, declaration, { functionLike = null } = {}) {
63
+ const boundary = functionLike ?? ((node) => ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)
64
+ || ts.isArrowFunction(node) || ts.isFunctionExpression(node));
65
+ if ((ts.isArrowFunction(declaration) || ts.isFunctionExpression(declaration)) && !ts.isBlock(declaration.body)) return [declaration.body];
66
+ const body = boundary(declaration) ? declaration.body : null;
67
+ if (!body || !ts.isBlock(body)) return [];
68
+ const returned = [];
69
+ const visit = (node) => {
70
+ if (node !== body && boundary(node)) return;
71
+ if (ts.isReturnStatement(node) && node.expression) returned.push(node.expression);
72
+ else ts.forEachChild(node, visit);
73
+ };
74
+ visit(body);
75
+ return returned;
76
+ }
77
+
78
+ /** A node's decorators across TS versions (ts.getDecorators, else the legacy `decorators` property). */
79
+ export function nodeDecorators(ts, node) {
80
+ return ts.canHaveDecorators(node) ? ts.getDecorators(node) ?? [] : node.decorators ?? [];
81
+ }
82
+
83
+ /** One finding row: rule id, root-relative path, node position and message, plus rule extras. */
84
+ export function violation(config, sourceFile, node, ruleId, message, extra = {}) {
85
+ return { ruleId, path: relativePath(config.root, sourceFile.fileName), ...sourceLocation(sourceFile, node), message, ...extra };
86
+ }
87
+
88
+ /** Whether `match` holds for `node` or a descendant (the walk stops at the first match). */
89
+ export function anyDescendant(ts, node, match) {
90
+ let found = false;
91
+ const visit = (current) => {
92
+ if (found) return;
93
+ if (match(current)) { found = true; return; }
94
+ ts.forEachChild(current, visit);
95
+ };
96
+ visit(node);
97
+ return found;
98
+ }
99
+
100
+ /**
101
+ * The framework kinds `expression` can produce: `directOf(ts, checker, {symbol, selected, targets})` maps a
102
+ * selected node's symbols to a kind (default `targets.get(symbol)`); `shorthandOf` (when set) also resolves
103
+ * shorthand-property declarations and descends property assignments; `newExpression` descends `new X()` callees.
104
+ */
105
+ export function tracedFrameworkKinds(ts, checker, expression, targets,
106
+ { seen = new Set(), depth = 0, directOf = null, shorthandOf = null, newExpression = false } = {}) {
107
+ if (!expression) return new Set();
108
+ if (depth > 10) return new Set([UNPROVEN_FRAMEWORK]);
109
+ expression = unwrapExpression(ts, expression);
110
+ const selected = selectedNode(ts, expression);
111
+ const symbol = selected ? normalizedSymbol(ts, checker, selected) : null;
112
+ const direct = symbol ? (directOf ?? ((ts2, c, { symbol: s, targets: t }) => t.get(s) ?? null))(ts, checker, { symbol, selected, targets }) : null;
113
+ if (direct) return new Set([direct]);
114
+ const opts = { depth: depth + 1, directOf, shorthandOf, newExpression };
115
+ if (ts.isArrowFunction(expression) || ts.isFunctionExpression(expression)) {
116
+ const kinds = new Set();
117
+ for (const returned of returnedExpressions(ts, expression)) {
118
+ for (const kind of tracedFrameworkKinds(ts, checker, returned, targets, { ...opts, seen })) kinds.add(kind);
119
+ }
120
+ return kinds;
121
+ }
122
+ if (ts.isCallExpression(expression)) {
123
+ const kinds = tracedFrameworkKinds(ts, checker, expression.expression, targets, { ...opts, seen });
124
+ if (kinds.size) return kinds;
125
+ }
126
+ if (newExpression && ts.isNewExpression(expression)) {
127
+ return tracedFrameworkKinds(ts, checker, expression.expression, targets, { ...opts, seen });
128
+ }
129
+ if (!symbol || seen.has(symbol)) return new Set();
130
+ const nextSeen = new Set(seen).add(symbol);
131
+ const kinds = new Set();
132
+ for (const declaration of symbol.getDeclarations?.() ?? []) {
133
+ if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
134
+ for (const kind of tracedFrameworkKinds(ts, checker, declaration.initializer, targets, { ...opts, seen: nextSeen })) kinds.add(kind);
135
+ }
136
+ if (shorthandOf && ts.isShorthandPropertyAssignment(declaration)) {
137
+ const target = normalizedSymbolValue(ts, checker, checker.getShorthandAssignmentValueSymbol?.(declaration));
138
+ const kind = shorthandOf(ts, checker, target, targets);
139
+ if (kind) kinds.add(kind);
140
+ }
141
+ if (shorthandOf && ts.isPropertyAssignment(declaration)) {
142
+ for (const kind of tracedFrameworkKinds(ts, checker, declaration.initializer, targets, { ...opts, seen: nextSeen })) kinds.add(kind);
143
+ }
144
+ for (const returned of returnedExpressions(ts, declaration)) {
145
+ for (const kind of tracedFrameworkKinds(ts, checker, returned, targets, { ...opts, seen: nextSeen })) kinds.add(kind);
146
+ }
147
+ }
148
+ return kinds;
149
+ }
150
+
151
+ /** 'unproven framework' | 'multiple framework' | the one kind | null, for a decorator whose expression constructs. */
152
+ export function constructedDecoratorKind(ts, checker, decorator, targets, traceOpts = {}) {
153
+ const callee = ts.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression;
154
+ const kinds = tracedFrameworkKinds(ts, checker, callee, targets, traceOpts);
155
+ if (kinds.has(UNPROVEN_FRAMEWORK)) return 'unproven framework';
156
+ return kinds.size === 1 ? [...kinds][0] : kinds.size ? 'multiple framework' : null;
157
+ }
158
+
159
+ /** The target a decorator resolves to through a `let`/`var` alias's single initializer, or null. */
160
+ export function mutableDecoratorKind(ts, checker, decorator, targets) {
161
+ const callee = ts.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression;
162
+ const selected = selectedNode(ts, callee);
163
+ const symbol = selected ? normalizedSymbol(ts, checker, selected) : null;
164
+ const declarations = symbol?.getDeclarations?.() ?? [];
165
+ if (declarations.length !== 1 || !ts.isVariableDeclaration(declarations[0]) || !declarations[0].initializer
166
+ || (ts.getCombinedNodeFlags(declarations[0].parent) & ts.NodeFlags.Const)) return null;
167
+ return targets.get(valueSymbol(ts, checker, declarations[0].initializer)) ?? null;
168
+ }
169
+
170
+ /** The callee expression of a decorator (the called expression of `@x(...)`, else the expression itself). */
171
+ export function decoratorCallee(ts, decorator) {
172
+ return ts.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression;
173
+ }
174
+
175
+ /**
176
+ * The module `statement` (import, export-from or import-equals) statically binds: its specifier text, the
177
+ * module's symbol and its exported symbols by name (aliases resolved); `symbol` is null when the module does
178
+ * not resolve. Null when the statement names no static specifier.
179
+ */
180
+ export function moduleExportsOf(ts, checker, statement) {
181
+ const moduleSpecifier = (ts.isImportDeclaration(statement) || ts.isExportDeclaration(statement))
182
+ && statement.moduleSpecifier && ts.isStringLiteralLike(statement.moduleSpecifier) ? statement.moduleSpecifier : null;
183
+ const importEquals = ts.isImportEqualsDeclaration(statement) && ts.isExternalModuleReference(statement.moduleReference)
184
+ && statement.moduleReference.expression && ts.isStringLiteralLike(statement.moduleReference.expression)
185
+ ? statement.moduleReference.expression : null;
186
+ const specifierNode = moduleSpecifier ?? importEquals;
187
+ const specifier = specifierNode?.text;
188
+ if (specifier == null) return null;
189
+ const symbol = checker.getSymbolAtLocation(specifierNode) ?? null;
190
+ const exports = new Map((symbol ? checker.getExportsOfModule(symbol) : []).map(item => [item.getName(), normalizedSymbolValue(ts, checker, item)]));
191
+ return { specifierNode, specifier, symbol, exports };
192
+ }
193
+
194
+ /**
195
+ * The program source files a framework-target scan of `checker` judges: every file in `localFiles`, plus (when
196
+ * `root` is given) declaration files inside the root but outside node_modules. Null when no program of the
197
+ * context owns the checker.
198
+ */
199
+ export function programSourcesOf(context, checker, localFiles, { root = null } = {}) {
200
+ const program = context.programs.find(candidate => candidate.getTypeChecker() === checker) ?? null;
201
+ if (!program) return null;
202
+ return program.getSourceFiles().filter(sourceFile => localFiles.has(canonical(sourceFile.fileName))
203
+ || (root && sourceFile.isDeclarationFile && isInside(root, sourceFile.fileName)
204
+ && !sourceFile.fileName.replaceAll('\\', '/').includes('/node_modules/')));
205
+ }
206
+
207
+ /**
208
+ * The CommonJS require() caveat every framework scan reports: each `require('<specifier>')` `accepted` pushes
209
+ * `${filePath} uses a CommonJS <label ?? specifier> <detail>` (the import-shape reasoning cannot prove that binding).
210
+ */
211
+ export function commonJsRequireReasons(ts, checker, sourceFile, accepted, reasons, filePath, detail, label = null) {
212
+ const visit = node => {
213
+ if (ts.isCallExpression(node) && isUnshadowedCommonJsRequire(ts, checker, node.expression)
214
+ && node.arguments.length === 1 && ts.isStringLiteralLike(node.arguments[0]) && accepted(node.arguments[0].text)) {
215
+ reasons.push(`${filePath} uses a CommonJS ${label ?? node.arguments[0].text} ${detail}`);
216
+ }
217
+ ts.forEachChild(node, visit);
218
+ };
219
+ visit(sourceFile);
220
+ }
@@ -1,7 +1,8 @@
1
1
  import path from 'node:path';
2
2
  import { isInside } from './config.mjs';
3
3
  import { slotAdmitsFile } from '../allows.mjs';
4
- import { reachableViolation, relativePath, sourceLocation } from './typescript.mjs';
4
+ import { reachableViolation, relativePath } from './typescript.mjs';
5
+ import { sourceLocation } from '../../lib/ts-ast.mjs';
5
6
 
6
7
  const FORBIDDEN_APP_ROLE = /(?:^|\.)(?:service|provider|providers|resolver|controller|handler|repository|entity|use-case|command|query|listener|consumer|processor)\.[cm]?[jt]sx?$/i;
7
8
  const FORBIDDEN_DECLARATION = /(?:Service|Provider|Resolver|Controller|Handler|Repository|Entity|UseCase|Command|Query|Listener|Consumer|Processor)$/;
@@ -73,6 +74,18 @@ function roleEvidence(ts, sourceFile) {
73
74
 
74
75
  function transportFrameworkEvidence(ts, sourceFile, checker) {
75
76
  const found = [];
77
+ // The destructured binding elements naming a transport export (or a computed binding, which cannot be
78
+ // proved to exclude one): `const { Controller, [x]: y } = ...`. `rest` elements are always opaque.
79
+ const destructuredBindings = (elements, detail) => elements.flatMap(element => {
80
+ const selected = element.dotDotDotToken
81
+ ? null
82
+ : ts.isIdentifier(element.propertyName ?? element.name)
83
+ ? (element.propertyName ?? element.name).text
84
+ : ts.isStringLiteralLike(element.propertyName)
85
+ ? element.propertyName.text
86
+ : null;
87
+ return selected === null || NEST_COMMON_TRANSPORT.has(selected) ? [{ node: element, detail: selected ?? detail }] : [];
88
+ });
76
89
  const namespaceUsages = binding => {
77
90
  const usages = [];
78
91
  const alias = binding.text;
@@ -90,18 +103,7 @@ function transportFrameworkEvidence(ts, sourceFile, checker) {
90
103
  if (!ts.isObjectBindingPattern(node.name)) {
91
104
  usages.push({ node: node.name, detail: 'escaped @nestjs/common namespace access' });
92
105
  } else {
93
- for (const element of node.name.elements) {
94
- const selected = element.dotDotDotToken
95
- ? null
96
- : ts.isIdentifier(element.propertyName ?? element.name)
97
- ? (element.propertyName ?? element.name).text
98
- : ts.isStringLiteralLike(element.propertyName)
99
- ? element.propertyName.text
100
- : null;
101
- if (selected === null || NEST_COMMON_TRANSPORT.has(selected)) {
102
- usages.push({ node: element, detail: selected ?? 'computed @nestjs/common namespace destructuring' });
103
- }
104
- }
106
+ usages.push(...destructuredBindings(node.name.elements, 'computed @nestjs/common namespace destructuring'));
105
107
  }
106
108
  }
107
109
  ts.forEachChild(node, visit);
@@ -168,16 +170,7 @@ function transportFrameworkEvidence(ts, sourceFile, checker) {
168
170
  if (ts.isIdentifier(node.name)) {
169
171
  found.push(...namespaceUsages(node.name).map(item => ({ ...item, specifier })));
170
172
  } else if (ts.isObjectBindingPattern(node.name)) {
171
- for (const element of node.name.elements) {
172
- const selected = element.dotDotDotToken
173
- ? null
174
- : ts.isIdentifier(element.propertyName ?? element.name)
175
- ? (element.propertyName ?? element.name).text
176
- : ts.isStringLiteralLike(element.propertyName)
177
- ? element.propertyName.text
178
- : null;
179
- if (selected === null || NEST_COMMON_TRANSPORT.has(selected)) found.push({ node: element, specifier, detail: selected ?? 'computed @nestjs/common require destructuring' });
180
- }
173
+ found.push(...destructuredBindings(node.name.elements, 'computed @nestjs/common require destructuring').map(item => ({ ...item, specifier })));
181
174
  }
182
175
  }
183
176
  }
@@ -3,29 +3,26 @@ import { machineKit } from './machine-ast.mjs';
3
3
 
4
4
  /**
5
5
  * R46 `background-unowned` (BE_BACKGROUND_UNOWNED, BE-CONVENTION 1.6 and 1.13). Background work is a transport that only
6
- * a worker app runs:
6
+ * a worker or api app runs (an api app composes the message transport of its own service; a separate worker is opt-in):
7
7
  *
8
- * - every job (`*.job.ts`) and consumer (`*.consumer.ts`) is composed by an app of kind `worker`: the file is reachable,
9
- * through runtime imports, from the root module of some worker app declared in hfs.json; one no worker reaches never runs;
10
- * - no scheduler outside `platform/scheduling`: a `@Cron`, `@Interval` or `@Timeout` decorator of `@nestjs/schedule` and a
11
- * `setInterval` call are refused everywhere else (platform/scheduling owns the lease, the fencing and the overlap guard);
8
+ * - every job processor (`*.processor.ts`, the jobs kind) and consumer (`*.consumer.ts`) is composed by an app of kind `worker` or
9
+ * `api`: the file is reachable, through runtime imports, from the root module of some such app declared in hfs.json; one no app
10
+ * reaches never runs. A schedule is a BullMQ job scheduler of a queue, so no scheduler decorator or timer is judged here: `@nestjs/schedule`
11
+ * and the timers are owned by platform capabilities (R90 BE_INFRA_OWNER);
12
12
  * - a method whose name is one of the words the law names (`sweep`, `deliver`, `reconcile`, `retry`, alone or as the
13
- * first word of a camelCase name) in a domain, integrations or feature class is reachable from a composed job or
14
- * consumer: from the job or consumer files and everything they import, and from the files of the feature that holds them
15
- * (its handlers are registered with the command bus, not imported by the job). Mechanisms of platform are exempt.
13
+ * first word of a camelCase name) in a domain, integrations or feature class is reachable from a composed processor or
14
+ * consumer: from the processor or consumer files and everything they import, and from the files of the feature that holds them
15
+ * (its handlers are registered with the command bus, not imported by the processor). Mechanisms of platform are exempt.
16
16
  *
17
- * Reachability is static (imports, re-exports and the feature of the job); it proves a job or consumer could call the method,
17
+ * Reachability is static (imports, re-exports and the feature of the processor); it proves a processor or consumer could call the method,
18
18
  * not that it does.
19
19
  */
20
20
  export const BACKGROUND_UNOWNED_RULE_IDS = ['BE_BACKGROUND_UNOWNED'];
21
21
 
22
22
  const RULE = 'BE_BACKGROUND_UNOWNED';
23
- const BACKGROUND_ROLES = ['job', 'consumer'];
23
+ const BACKGROUND_ROLES = ['processor', 'consumer'];
24
24
  /** The words the law names for background methods (knowledge/hfs/README.md 5.7). */
25
25
  const BACKGROUND_WORDS = ['sweep', 'deliver', 'reconcile', 'retry'];
26
- const SCHEDULER_DECORATORS = ['Cron', 'Interval', 'Timeout'];
27
- const SCHEDULER_PACKAGE = '@nestjs/schedule';
28
- const SCHEDULING_CAPABILITY = 'scheduling';
29
26
 
30
27
  const roleOf = rel => {
31
28
  const parts = path.posix.basename(rel).split('.');
@@ -52,17 +49,18 @@ export function checkBackgroundUnowned(input) {
52
49
  return seen;
53
50
  };
54
51
 
55
- const workerRoots = resolver.repo.apps.filter(app => app.kind === 'worker').map(app => `apps/${app.name}/src/app.module.ts`).filter(rel => graph.files.has(rel));
56
- const composed = closure(workerRoots);
52
+ const rootOf = app => `apps/${app.name}/src/app.module.ts`;
53
+ const workerRoots = resolver.repo.apps.filter(app => app.kind === 'worker').map(rootOf).filter(rel => graph.files.has(rel));
54
+ const composed = closure([...workerRoots, ...resolver.repo.apps.filter(app => app.kind === 'api').map(rootOf).filter(rel => graph.files.has(rel))]);
57
55
  const background = [...graph.files.values()].filter(file => roleOf(file.rel) && file.tier === 'feature');
58
56
  const running = background.filter(file => composed.has(file.rel));
59
57
  for (const file of background) {
60
58
  if (composed.has(file.rel)) continue;
61
59
  const role = roleOf(file.rel);
62
- report(file, file.sourceFile, `${file.rel} is a ${role} that no worker app composes${workerRoots.length ? '' : ' (hfs.json declares no worker app)'}; a ${role} runs only when the ${role === 'job' ? 'schedule' : 'message'} module of its feature is imported by the root module of an app of kind worker.`, { role });
60
+ report(file, file.sourceFile, `${file.rel} is a ${role} that no service app composes${workerRoots.length ? '' : ' (hfs.json declares no worker app)'}; a ${role} runs only when the ${role === 'processor' ? 'job' : 'message'} module of its feature is imported by the root module of an app of kind worker or api.`, { role });
63
61
  }
64
62
 
65
- // Everything a composed job or consumer can reach: its imports, and the files of its own feature (command handlers).
63
+ // Everything a composed processor or consumer can reach: its imports, and the files of its own feature (command handlers).
66
64
  const roots = new Set();
67
65
  for (const file of running) {
68
66
  roots.add(file.rel);
@@ -73,31 +71,15 @@ export function checkBackgroundUnowned(input) {
73
71
 
74
72
  let methods = 0;
75
73
  for (const file of graph.files.values()) {
76
- const checker = kit.checkerOf(file.sourceFile);
77
- const inScheduling = file.owner?.slot === 'be.platform' && path.posix.basename(file.owner.root) === SCHEDULING_CAPABILITY;
78
74
  const mechanism = file.tier === 'platform';
79
75
  kit.walk(file.sourceFile, node => {
80
- if (!inScheduling && ts.isDecorator(node)) {
81
- const call = ts.isCallExpression(node.expression) ? node.expression.expression : node.expression;
82
- const binding = kit.importBinding(checker, call);
83
- if (binding && binding.module === SCHEDULER_PACKAGE && SCHEDULER_DECORATORS.includes(binding.name)) {
84
- report(file, node, `@${binding.name} schedules work inside a class outside platform/scheduling. Write a job in transport/schedule/<job>.job.ts that dispatches one command or query, and let platform/scheduling and a worker app run it.`, { scheduler: binding.name });
85
- }
86
- }
87
- if (!inScheduling && ts.isCallExpression(node)) {
88
- const callee = node.expression;
89
- const bare = ts.isIdentifier(callee) && callee.text === 'setInterval' && kit.declarationsOf(checker, callee).every(declaration => declaration.getSourceFile().isDeclarationFile);
90
- const member = ts.isPropertyAccessExpression(callee) && callee.name.text === 'setInterval' && ts.isIdentifier(callee.expression) && ['globalThis', 'window', 'global'].includes(callee.expression.text);
91
- const global = bare || member;
92
- if (global) report(file, node, 'setInterval starts a scheduler outside platform/scheduling. Write a job in transport/schedule/<job>.job.ts and let platform/scheduling and a worker app run it.', { scheduler: 'setInterval' });
93
- }
94
76
  if (!mechanism && ts.isClassDeclaration(node)) {
95
77
  for (const member of node.members) {
96
78
  if (!ts.isMethodDeclaration(member) || !member.name) continue;
97
79
  const name = kit.propertyNameText(member.name);
98
80
  if (!name || !isBackgroundName(name)) continue;
99
81
  methods += 1;
100
- if (!reachable.has(file.rel)) report(file, member.name, `${name} is background work (a ${BACKGROUND_WORDS.join(', ')} method) that no job or consumer composed by a worker app can reach. Add a job in transport/schedule or a consumer in transport/message of a feature that runs it, and compose that transport module in a worker app.`, { method: name });
82
+ if (!reachable.has(file.rel)) report(file, member.name, `${name} is background work (a ${BACKGROUND_WORDS.join(', ')} method) that no processor or consumer composed by a worker or api app can reach. Add a processor in features/jobs/<job> or a consumer in transport/message of a feature that runs it, and compose its module in a worker or api app.`, { method: name });
101
83
  }
102
84
  }
103
85
  return true;
@@ -1,5 +1,6 @@
1
1
  import { builtinModules } from 'node:module';
2
- import { sourceLocation } from './typescript.mjs';
2
+ import { sourceLocation } from '../../lib/ts-ast.mjs';
3
+ import { isServerActionModule } from './server-action.mjs';
3
4
 
4
5
  /**
5
6
  * R55 `client-reaches-server` (FE_CLIENT_REACHES_SERVER), the repository half of the eslint rule `client-no-server-import`,
@@ -75,6 +76,7 @@ export function checkClientReachesServer({ graph, context }) {
75
76
  const queue = [entry.rel];
76
77
  for (let head = 0; head < queue.length; head += 1) {
77
78
  const rel = queue[head];
79
+ if (isServerActionModule(ts, graph.files.get(rel).sourceFile)) continue;
78
80
  const state = seen.get(rel);
79
81
  for (const hit of serverOf(rel)) {
80
82
  const at = state.first ?? sourceLocation(graph.files.get(rel).sourceFile, hit.node);
@@ -16,7 +16,7 @@
16
16
  *
17
17
  * Two kinds of file are uniform by design and are not compared, both found through the slot resolver: the entry main.ts of
18
18
  * an app (slot appKind + its required main.ts) and the transport doors of a feature (a *.controller.ts, *.resolver.ts,
19
- * *.gateway.ts, *.consumer.ts, *.job.ts or *.cli.ts of a slot that a protocol app composes). This is not a loophole: logic
19
+ * *.gateway.ts, *.consumer.ts, *.processor.ts or *.cli.ts of a slot that a protocol app composes). This is not a loophole: logic
20
20
  * cannot live in either. A door body is dispatch only (eslint transport-is-thin, R88), and main.ts cannot share a
21
21
  * helper because NestFactory is allowed only in main.ts (BE_ENTRYPOINT_ONLY_IN_APPS), so what repeats there is the
22
22
  * canonical shape of section 5 of BE-CONVENTION. A door or main.ts that grows logic is refused by those enforcers, not
@@ -25,12 +25,12 @@
25
25
  export const CLONE_RULE_IDS = ['HFS_DUPLICATE_CODE'];
26
26
 
27
27
  const MAX_VIOLATIONS = 200;
28
- const DOOR_ROLES = new Set(['controller', 'resolver', 'gateway', 'consumer', 'job', 'cli']);
28
+ const DOOR_ROLES = new Set(['controller', 'resolver', 'gateway', 'consumer', 'processor', 'cli']);
29
29
  const APP_ENTRY = 'main.ts';
30
30
  const ID = -1;
31
31
  const LIT = -2;
32
32
 
33
- function tokenize(ts, sourceFile) {
33
+ export function tokenize(ts, sourceFile) {
34
34
  const { SyntaxKind } = ts;
35
35
  const literalKinds = new Set([SyntaxKind.StringLiteral, SyntaxKind.NumericLiteral, SyntaxKind.BigIntLiteral, SyntaxKind.RegularExpressionLiteral,
36
36
  SyntaxKind.NoSubstitutionTemplateLiteral, SyntaxKind.TemplateHead, SyntaxKind.TemplateMiddle, SyntaxKind.TemplateTail, SyntaxKind.JsxText]);
@@ -76,7 +76,7 @@ function uniformByDesign(resolver, rel) {
76
76
  const base = rel.slice(rel.lastIndexOf('/') + 1);
77
77
  if (slot.appKind !== undefined) return base === APP_ENTRY && (slot.requires ?? []).includes(APP_ENTRY);
78
78
  const role = base.split('.').at(-2);
79
- return (slot.composedBy?.length ?? 0) > 0 && DOOR_ROLES.has(role) && base.endsWith('.ts');
79
+ return !slot.owner && (slot.composedBy?.length ?? 0) > 0 && DOOR_ROLES.has(role) && base.endsWith('.ts');
80
80
  }
81
81
 
82
82
  function homeText(profile, sameOwner, crossApp) {
@@ -120,9 +120,10 @@ function workspaceDirectories(root, { packageRoot: appPackageRoot = root, side =
120
120
  }
121
121
  candidates = next;
122
122
  }
123
- if (!candidates.length) throw Error(`Workspace pattern matched no package directories: ${normalized}.`);
124
123
  // npm expands a `*` segment to the directories that hold a package.json and skips the rest (an empty or untracked folder is
125
- // HFS_EMPTY_DIR / HFS_SLOT_UNDECLARED, never a reason to analyse nothing); a literal workspace path must resolve.
124
+ // HFS_EMPTY_DIR / HFS_SLOT_UNDECLARED, never a reason to analyse nothing); a literal workspace path must resolve. A `*`
125
+ // pattern may match nothing: every app declares the same workspaces (fe/apps/*, fe/packages/*, HFS_MONO_WORKSPACES) whether
126
+ // or not it has a package yet, and the root patterns are held to that fixed list by the rule, not here.
126
127
  const wildcard = segments.includes('*');
127
128
  for (const candidate of candidates) admit(candidate, `workspace ${normalized}`, !wildcard);
128
129
  }
@@ -262,13 +263,13 @@ const GRAMMAR_PACKAGE = '@starci/grammar';
262
263
  function derivedGrammar(root, packageRoot, workspaces, apps = []) {
263
264
  const styleSources = apps.map(app => `apps/${app.name}/src/app/globals.css`).filter(relative => existingRegularFile(root, relative));
264
265
  if (!styleSources.length) return null;
265
- // The apps have no package.json of their own: the app root's one manifest declares their dependencies, and it is a consumer
266
- // when it declares the Grammar package; so is every workspace package that does.
266
+ // Every fe app and package is an npm workspace that declares what it imports (HFS_MONO_WORKSPACE_DEP): each workspace that
267
+ // declares the Grammar package is a consumer, and so is the app root's manifest when it does.
267
268
  const declaresGrammar = (pkg) => Boolean(pkg && [pkg.dependencies, pkg.peerDependencies, pkg.devDependencies].some(section => section && Object.hasOwn(section, GRAMMAR_PACKAGE)));
268
269
  const appManifest = slash(path.relative(root, path.join(packageRoot, 'package.json')));
269
270
  const consumerManifests = declaresGrammar(readJson(path.join(packageRoot, 'package.json'))) ? [appManifest] : [];
270
271
  for (const workspace of workspaces) {
271
- if (!workspace.startsWith('packages/')) continue;
272
+ if (!workspace.startsWith('packages/') && !workspace.startsWith('apps/')) continue;
272
273
  const manifest = `${workspace}/package.json`;
273
274
  const pkg = readJson(path.join(root, ...manifest.split('/')));
274
275
  if (declaresGrammar(pkg)) consumerManifests.push(manifest);
@@ -298,7 +299,9 @@ export function loadArchitectureConfig(repositoryRoot, { hfs } = {}) {
298
299
  if (!projects.length) throw Error('The repository has no tsconfig.json to derive a TypeScript project from.');
299
300
  const backend = {
300
301
  modules: ['src/modules'],
301
- features: ['src/features'],
302
+ // The layered feature root is the api kind (src/features/api/<feature>/: application/ and transport/<protocol>/); the cli
303
+ // feature root (src/features/cli/) has its own shape, held by its slot and the cli rules.
304
+ features: ['src/features/api'],
302
305
  apps: ['apps'],
303
306
  moduleRegistration: { providerIdentity: 'exported-class-token', handlerDecorators: [...DECLARED_HANDLER_DECORATORS] },
304
307
  };
@@ -14,11 +14,14 @@ import { locateDeclaration } from '../slots.mjs';
14
14
  * - a connection file for a name hfs.json does not declare is refused;
15
15
  * - `getEntityManagerToken(` and `InjectEntityManager(` (from @nestjs/typeorm) resolve their argument through the
16
16
  * checker to a connection name and may be called only in that connection's decorators file, once; the DataSource
17
- * forms only in platform/database, the migrate app and the test fixtures; an exported `Inject*EntityManager`
17
+ * forms only in platform/database, the cli app and the test fixtures; an exported `Inject*EntityManager`
18
18
  * anywhere else (a capability-specific injector, a duplicate) is refused;
19
19
  * - inside one app every connection is passed to the platform/database module registration once;
20
+ * - a connection whose hfs.json `isolation` is `schema` reads `<PREFIX>_SCHEMA` in its config (a context is its own database or its own
21
+ * schema of a shared one; splitting it out later is an env change only);
20
22
  * - in every `.starcistacks/<env>/runtime/env` two connections whose `<PREFIX>_HOST`, `<PREFIX>_PORT` and
21
- * `<PREFIX>_NAME` (or `_DATABASE`, whichever the config reads) resolve to one triple are the same database.
23
+ * `<PREFIX>_NAME` (or `_DATABASE`, whichever the config reads) resolve to one triple are the same database, unless both are
24
+ * `isolation: schema` with different `<PREFIX>_SCHEMA` values (two contexts as schemas of one database).
22
25
  */
23
26
  export const CONNECTION_RULE_IDS = ['BE_CONNECTION_DUPLICATE'];
24
27
 
@@ -92,12 +95,14 @@ export function checkConnectionMap(input) {
92
95
  report(configRel, `Connection ${connection.name} needs ${configRel}, the one place its ${connection.envPrefix}_* keys are read.`, { connection: connection.name });
93
96
  continue;
94
97
  }
98
+ let readsSchema = false;
95
99
  kit.walk(configFile.sourceFile, node => {
96
100
  let key = null;
97
101
  if (ts.isStringLiteralLike(node) && ENV_KEY.test(node.text)) key = node.text;
98
102
  else if (ts.isPropertyAccessExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === 'env'
99
103
  && ts.isIdentifier(node.expression.expression) && node.expression.expression.text === 'process' && ENV_KEY.test(node.name.text)) key = node.name.text;
100
104
  if (key === null) return true;
105
+ if (key === `${connection.envPrefix}_SCHEMA`) readsSchema = true;
101
106
  const own = key.startsWith(`${connection.envPrefix}_`);
102
107
  const foreign = declared.find(other => other !== connection && key.startsWith(`${other.envPrefix}_`));
103
108
  if (!own || foreign) {
@@ -105,6 +110,7 @@ export function checkConnectionMap(input) {
105
110
  }
106
111
  return false;
107
112
  });
113
+ if (connection.isolation === 'schema' && !readsSchema) report(configRel, `Connection ${connection.name} is isolated as a schema of a shared database (hfs.json isolation), so ${configRel} must read ${connection.envPrefix}_SCHEMA and hand it to the data source; splitting the context out into its own database is then an env change only.`, { connection: connection.name });
108
114
  }
109
115
  for (const file of databaseFiles) {
110
116
  const match = /^(.+)\.(connection|decorators|config)\.ts$/u.exec(path.posix.basename(file.rel));
@@ -133,8 +139,8 @@ export function checkConnectionMap(input) {
133
139
  if (perDecorators.get(value) > 1) report(file.rel, `${home} resolves connection ${value} more than once; one connection has one injector.`, { ...kit.at(file.rel, file.sourceFile, node), connection: value });
134
140
  }
135
141
  } else if (binding?.module === TYPEORM && SOURCE_CALLS.has(binding.name)) {
136
- const allowed = file.rel.startsWith(`${DATABASE_DIR}/`) || slotIs('be.app.migrate') || slotIs('be.tests.fixtures');
137
- if (!allowed) report(file.rel, `${binding.name}() reaches the DataSource outside platform/database and the migrate app; inject the shared EntityManager of the connection instead.`, kit.at(file.rel, file.sourceFile, node));
142
+ const allowed = file.rel.startsWith(`${DATABASE_DIR}/`) || slotIs('be.app.cli') || slotIs('be.cli') || slotIs('be.tests.fixtures');
143
+ if (!allowed) report(file.rel, `${binding.name}() reaches the DataSource outside platform/database and the cli; inject the shared EntityManager of the connection instead.`, kit.at(file.rel, file.sourceFile, node));
138
144
  }
139
145
  }
140
146
  const exportedName = (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && ts.isVariableStatement(node.parent?.parent) && kit.isExported(node.parent.parent)) ? node.name.text
@@ -189,7 +195,7 @@ export function checkConnectionMap(input) {
189
195
  const rel = path.relative(config.root, path.join(stacksRoot, env, 'runtime', 'env')).split(path.sep).join('/');
190
196
  const values = readEnvFiles(path.join(stacksRoot, env, 'runtime', 'env'));
191
197
  if (!values || values.size === 0) { stacksSkipped += 1; continue; }
192
- const seen = new Map();
198
+ const seen = new Map(); // host|port|database -> [{name, isolation, schema}]
193
199
  let resolved = 0;
194
200
  for (const connection of declared) {
195
201
  const configFile = kit.graphFile(`${DATABASE_DIR}/${connection.name}.config.ts`);
@@ -200,9 +206,15 @@ export function checkConnectionMap(input) {
200
206
  if (!host || !port || !database) continue;
201
207
  resolved += 1;
202
208
  const triple = `${host.toLowerCase()}|${port}|${database}`;
203
- if (seen.has(triple)) {
204
- report(rel, `Connections ${seen.get(triple)} and ${connection.name} resolve to the same database (${host}:${port}/${database}) in stack ${env}; one physical database is one connection.`, { connection: connection.name, env });
205
- } else seen.set(triple, connection.name);
209
+ const schema = connection.isolation === 'schema' ? (values.get(`${connection.envPrefix}_SCHEMA`) ?? '') : null;
210
+ const sharing = seen.get(triple) ?? [];
211
+ // Two contexts may share one database only as distinct, named schemas; a context that is its own database shares it with nobody.
212
+ const clash = sharing.find(other => schema === null || other.schema === null || other.schema === schema);
213
+ if (clash) {
214
+ report(rel, `Connections ${clash.name} and ${connection.name} resolve to the same database (${host}:${port}/${database}${schema !== null && clash.schema === schema ? `, schema ${schema || '(none)'}` : ''}) in stack ${env}; contexts share a database only as distinct schemas (isolation schema, a different ${connection.envPrefix}_SCHEMA), otherwise one physical database is one connection.`, { connection: connection.name, env });
215
+ }
216
+ sharing.push({ name: connection.name, schema });
217
+ seen.set(triple, sharing);
206
218
  }
207
219
  if (resolved) stacksChecked += 1; else stacksSkipped += 1;
208
220
  }