@starci/hfs 4.2.17 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (564) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +7 -0
  3. package/db/migrate.mjs +361 -0
  4. package/db/post-cutover.mjs +136 -0
  5. package/emit/db-types.mjs +10 -1
  6. package/lint/run.mjs +1 -1
  7. package/package.json +4 -1
  8. package/release/gate.mjs +298 -0
  9. package/release/index.mjs +103 -0
  10. package/release/push.mjs +110 -0
  11. package/runtime/knowledge/hfs/canon-pins.yaml +11 -11
  12. package/runtime/knowledge/hfs/fe-stack.yaml +22 -1
  13. package/runtime/knowledge/hfs/rules.yaml +3437 -160
  14. package/runtime/knowledge/hfs/slots.yaml +430 -92
  15. package/runtime/knowledge/patterns/be/api.yaml +92 -2
  16. package/runtime/knowledge/patterns/be/cli.yaml +37 -9
  17. package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
  18. package/runtime/knowledge/patterns/be/jobs.yaml +20 -2
  19. package/runtime/knowledge/patterns/be/projections.yaml +4 -2
  20. package/runtime/knowledge/patterns/be/queues.yaml +11 -3
  21. package/runtime/knowledge/patterns/be/reactors.yaml +20 -1
  22. package/runtime/knowledge/patterns/be/realtime.yaml +25 -3
  23. package/runtime/knowledge/patterns/be/saga.yaml +20 -3
  24. package/runtime/knowledge/patterns/be/webhooks.yaml +210 -36
  25. package/runtime/knowledge/patterns/fe/folder.yaml +4 -0
  26. package/runtime/knowledge/sonar-gate.yaml +1 -1
  27. package/runtime/modules/kernel/failure-codes.yaml +3128 -47
  28. package/runtime/modules/models/runtimes.yaml +1 -1
  29. package/runtime/modules/schemas/application-stacks.schema.yaml +663 -0
  30. package/runtime/packages/cli/src/catalog.generated.mjs +14104 -0
  31. package/runtime/packages/cli/src/validate-args.mjs +213 -0
  32. package/runtime/scripts/api/git/add.mjs +5 -0
  33. package/runtime/scripts/api/git/cat-file.mjs +5 -0
  34. package/runtime/scripts/api/git/init.mjs +7 -0
  35. package/runtime/scripts/api/git/lib.mjs +3 -0
  36. package/runtime/scripts/api/git/tag.mjs +5 -0
  37. package/runtime/scripts/api/node/lib.mjs +20 -0
  38. package/runtime/scripts/api/node/run-node.mjs +6 -0
  39. package/runtime/scripts/api/sops/lib.mjs +41 -0
  40. package/runtime/scripts/api/sops/resolve-sops.mjs +7 -0
  41. package/runtime/scripts/api/sops/seal.mjs +1 -1
  42. package/runtime/scripts/api/sops/selected-age-recipient.mjs +7 -0
  43. package/runtime/scripts/api/supabase/database-dump.mjs +5 -0
  44. package/runtime/scripts/api/supabase/database-query.mjs +5 -0
  45. package/runtime/scripts/api/supabase/lib.mjs +44 -0
  46. package/runtime/scripts/api/supabase/migration-repair.mjs +5 -0
  47. package/runtime/scripts/hfs/app-wiring.mjs +201 -0
  48. package/runtime/scripts/hfs/architecture/backend-app-files.mjs +103 -0
  49. package/runtime/scripts/hfs/architecture/backend.mjs +22 -10
  50. package/runtime/scripts/hfs/architecture/background-unowned.mjs +157 -41
  51. package/runtime/scripts/hfs/architecture/canon-clones.mjs +240 -0
  52. package/runtime/scripts/hfs/architecture/canon-jsx-clones.mjs +143 -0
  53. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +6 -3
  54. package/runtime/scripts/hfs/architecture/clones.mjs +17 -8
  55. package/runtime/scripts/hfs/architecture/connection-map.mjs +14 -12
  56. package/runtime/scripts/hfs/architecture/context-map.mjs +25 -14
  57. package/runtime/scripts/hfs/architecture/context-owner.mjs +1 -2
  58. package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +149 -0
  59. package/runtime/scripts/hfs/architecture/dead-exports.mjs +23 -50
  60. package/runtime/scripts/hfs/architecture/default-deny.mjs +8 -6
  61. package/runtime/scripts/hfs/architecture/frontend-block-render.mjs +104 -0
  62. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +30 -11
  63. package/runtime/scripts/hfs/architecture/frontend-world.mjs +1 -1
  64. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +153 -0
  65. package/runtime/scripts/hfs/architecture/hfs.mjs +3 -3
  66. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +16 -3
  67. package/runtime/scripts/hfs/architecture/machine-ast.mjs +17 -0
  68. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +49 -25
  69. package/runtime/scripts/hfs/architecture/next-data-inference.mjs +176 -0
  70. package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +26 -0
  71. package/runtime/scripts/hfs/architecture/next-data.mjs +31 -23
  72. package/runtime/scripts/hfs/architecture/reachability.mjs +14 -0
  73. package/runtime/scripts/hfs/architecture/register-once.mjs +15 -7
  74. package/runtime/scripts/hfs/architecture/required-files.mjs +2 -1
  75. package/runtime/scripts/hfs/architecture/source-names.mjs +48 -3
  76. package/runtime/scripts/hfs/architecture/staged-consumers.mjs +37 -0
  77. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +214 -17
  78. package/runtime/scripts/hfs/architecture/supabase-be.mjs +1 -6
  79. package/runtime/scripts/hfs/architecture/supabase.mjs +59 -21
  80. package/runtime/scripts/hfs/architecture/surface.mjs +2 -1
  81. package/runtime/scripts/hfs/architecture/tiers.mjs +11 -4
  82. package/runtime/scripts/hfs/architecture/typescript.mjs +1 -8
  83. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +9 -13
  84. package/runtime/scripts/hfs/check-results.mjs +28 -0
  85. package/runtime/scripts/hfs/check.mjs +63 -72
  86. package/runtime/scripts/hfs/coverage-from.mjs +70 -0
  87. package/runtime/scripts/hfs/db-baseline-verb.mjs +276 -0
  88. package/runtime/scripts/hfs/db-plan-verb.mjs +254 -0
  89. package/runtime/scripts/hfs/db-restamp-verb.mjs +60 -0
  90. package/runtime/scripts/hfs/declaration-shape.mjs +39 -3
  91. package/runtime/scripts/hfs/doc-commands.mjs +367 -0
  92. package/runtime/scripts/hfs/edition-slots.mjs +2 -2
  93. package/runtime/scripts/hfs/example-secrets.mjs +158 -0
  94. package/runtime/scripts/hfs/manifest-shape.mjs +91 -6
  95. package/runtime/scripts/hfs/path-findings.mjs +32 -2
  96. package/runtime/scripts/hfs/refusal-codes.mjs +6 -0
  97. package/runtime/scripts/hfs/rule-catalog.mjs +156 -11
  98. package/runtime/scripts/hfs/rule-params-shape.mjs +22 -10
  99. package/runtime/scripts/hfs/rules/a15-findings.mjs +88 -0
  100. package/runtime/scripts/hfs/rules/app-env-required.mjs +20 -0
  101. package/runtime/scripts/hfs/rules/app-root.mjs +2 -2
  102. package/runtime/scripts/hfs/rules/assets.mjs +79 -0
  103. package/runtime/scripts/hfs/rules/boot-proof.mjs +16 -0
  104. package/runtime/scripts/hfs/rules/canon-copy-drift.mjs +59 -0
  105. package/runtime/scripts/hfs/rules/contract.mjs +66 -5
  106. package/runtime/scripts/hfs/rules/coverage-below.mjs +230 -0
  107. package/runtime/scripts/hfs/rules/database-access.mjs +343 -0
  108. package/runtime/scripts/hfs/rules/database-baseline.mjs +142 -0
  109. package/runtime/scripts/hfs/rules/database-config.mjs +250 -2
  110. package/runtime/scripts/hfs/rules/database-constants.mjs +9 -0
  111. package/runtime/scripts/hfs/rules/database-migrations.mjs +317 -37
  112. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +119 -9
  113. package/runtime/scripts/hfs/rules/database-queue-sources.mjs +176 -0
  114. package/runtime/scripts/hfs/rules/database-queues.mjs +361 -0
  115. package/runtime/scripts/hfs/rules/database-sql.mjs +91 -9
  116. package/runtime/scripts/hfs/rules/database.mjs +11 -2
  117. package/runtime/scripts/hfs/rules/deploy-scan.mjs +192 -0
  118. package/runtime/scripts/hfs/rules/deploy-workflow.mjs +219 -0
  119. package/runtime/scripts/hfs/rules/doc-fresh.mjs +286 -0
  120. package/runtime/scripts/hfs/rules/docker-health.mjs +249 -0
  121. package/runtime/scripts/hfs/rules/docker.mjs +24 -7
  122. package/runtime/scripts/hfs/rules/edition.mjs +11 -10
  123. package/runtime/scripts/hfs/rules/env-parity.mjs +373 -0
  124. package/runtime/scripts/hfs/rules/env-value-shape.mjs +113 -0
  125. package/runtime/scripts/hfs/rules/event-bus.mjs +3 -2
  126. package/runtime/scripts/hfs/rules/event-tables.mjs +100 -0
  127. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +115 -15
  128. package/runtime/scripts/hfs/rules/fe-stack.mjs +57 -0
  129. package/runtime/scripts/hfs/rules/feature-files.mjs +28 -0
  130. package/runtime/scripts/hfs/rules/frontend-tree.mjs +54 -35
  131. package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -2
  132. package/runtime/scripts/hfs/rules/lexicon.mjs +62 -0
  133. package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +89 -0
  134. package/runtime/scripts/hfs/rules/monorepo.mjs +14 -1
  135. package/runtime/scripts/hfs/rules/pipeline.mjs +8 -1
  136. package/runtime/scripts/hfs/rules/proof-commands.mjs +2 -0
  137. package/runtime/scripts/hfs/rules/release-provenance.mjs +211 -0
  138. package/runtime/scripts/hfs/rules/saga-delivery.mjs +66 -0
  139. package/runtime/scripts/hfs/rules/saga.mjs +8 -16
  140. package/runtime/scripts/hfs/rules/script-hygiene.mjs +151 -0
  141. package/runtime/scripts/hfs/rules/secret-custody-only.mjs +65 -0
  142. package/runtime/scripts/hfs/rules/secrets.mjs +26 -0
  143. package/runtime/scripts/hfs/rules/services.mjs +4 -4
  144. package/runtime/scripts/hfs/rules/stack-tooling.mjs +93 -0
  145. package/runtime/scripts/hfs/rules/stacks-deploy.mjs +76 -0
  146. package/runtime/scripts/hfs/rules/stacks-ingress.mjs +87 -0
  147. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +111 -0
  148. package/runtime/scripts/hfs/rules/stacks-release.mjs +366 -0
  149. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +168 -0
  150. package/runtime/scripts/hfs/rules/stacks-swarm.mjs +417 -0
  151. package/runtime/scripts/hfs/rules/stacks.mjs +13 -8
  152. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +1 -1
  153. package/runtime/scripts/hfs/rules/supply-chain.mjs +167 -0
  154. package/runtime/scripts/hfs/rules/unused-symbol-flow.mjs +101 -0
  155. package/runtime/scripts/hfs/rules/unused-symbol-ledger.mjs +77 -0
  156. package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +165 -0
  157. package/runtime/scripts/hfs/rules/unused-symbol-values.mjs +103 -0
  158. package/runtime/scripts/hfs/rules/unused-symbol.mjs +320 -0
  159. package/runtime/scripts/hfs/runtime-rules/source-ast.mjs +185 -0
  160. package/runtime/scripts/hfs/runtime-rules/workflow-source.mjs +19 -0
  161. package/runtime/scripts/hfs/sealed-members.mjs +101 -0
  162. package/runtime/scripts/hfs/secret-bootstrap-crypto.mjs +200 -0
  163. package/runtime/scripts/hfs/secret-document.mjs +97 -0
  164. package/runtime/scripts/hfs/secret-edit.mjs +170 -0
  165. package/runtime/scripts/hfs/secret-errors.mjs +20 -0
  166. package/runtime/scripts/hfs/secret-files.mjs +124 -0
  167. package/runtime/scripts/hfs/secret-init.mjs +300 -0
  168. package/runtime/scripts/hfs/secret-output.mjs +100 -0
  169. package/runtime/scripts/hfs/secret-sops.mjs +130 -0
  170. package/runtime/scripts/hfs/secret-store.mjs +163 -0
  171. package/runtime/scripts/hfs/secret.mjs +162 -193
  172. package/runtime/scripts/hfs/slot-imports.mjs +18 -3
  173. package/runtime/scripts/hfs/slot-required.mjs +13 -0
  174. package/runtime/scripts/hfs/slot-semantic-problems.mjs +2 -1
  175. package/runtime/scripts/hfs/slots.mjs +1 -0
  176. package/runtime/scripts/hfs/stack-config-kit.mjs +34 -0
  177. package/runtime/scripts/hfs/stack-deploy-config.mjs +313 -0
  178. package/runtime/scripts/hfs/stack-ingress-config.mjs +237 -0
  179. package/runtime/scripts/hfs/stacks-gate.mjs +414 -0
  180. package/runtime/scripts/hfs/starcistacks-runtime.mjs +61 -0
  181. package/runtime/scripts/hfs/tree-findings.mjs +22 -0
  182. package/runtime/scripts/lib/command-tokens.mjs +126 -0
  183. package/runtime/scripts/lib/compose-values.mjs +11 -0
  184. package/runtime/scripts/lib/deployment-findings.mjs +7 -0
  185. package/runtime/scripts/lib/json-clone.mjs +61 -0
  186. package/runtime/scripts/lib/list.mjs +4 -0
  187. package/runtime/scripts/lib/roots.mjs +59 -0
  188. package/runtime/scripts/lib/runtime-example.mjs +13 -0
  189. package/runtime/scripts/lib/secret-patterns.mjs +60 -5
  190. package/runtime/scripts/lib/sql-ast.mjs +32 -0
  191. package/runtime/scripts/lib/stack-services.mjs +1 -1
  192. package/runtime/scripts/lib/swarm-stack.mjs +126 -0
  193. package/runtime/scripts/lib/trim-end.mjs +8 -0
  194. package/runtime/scripts/lib/ts-analysis.mjs +23 -0
  195. package/runtime/scripts/lib/ts-ast.mjs +27 -2
  196. package/runtime/scripts/lib/workflow-events.mjs +8 -0
  197. package/scaffold/add-app.mjs +77 -59
  198. package/scaffold/add-cli-lite.mjs +24 -17
  199. package/scaffold/add-compose.mjs +79 -0
  200. package/scaffold/add-database-lite.mjs +14 -0
  201. package/scaffold/add-fe-overlay.mjs +122 -0
  202. package/scaffold/add-fe-page.mjs +269 -0
  203. package/scaffold/add-inbox-lite.mjs +87 -124
  204. package/scaffold/add-plan.mjs +183 -0
  205. package/scaffold/add-table.mjs +132 -70
  206. package/scaffold/add.mjs +186 -204
  207. package/scaffold/app.mjs +128 -43
  208. package/scaffold/edition-gate.mjs +3 -3
  209. package/scaffold/image.mjs +2 -2
  210. package/scaffold/lite-exports.mjs +2 -1
  211. package/scaffold/service.mjs +2 -2
  212. package/src/main.mjs +238 -119
  213. package/stack/age-install.mjs +37 -0
  214. package/stack/backup.mjs +100 -0
  215. package/stack/build-args.mjs +42 -0
  216. package/stack/build.mjs +173 -0
  217. package/stack/cli.mjs +130 -0
  218. package/stack/config.mjs +57 -0
  219. package/stack/context.mjs +57 -0
  220. package/stack/converge.mjs +107 -0
  221. package/stack/deploy.mjs +301 -0
  222. package/stack/digest.mjs +16 -0
  223. package/stack/docker-transport.mjs +53 -0
  224. package/stack/edge-alias.mjs +118 -0
  225. package/stack/errors.mjs +21 -0
  226. package/stack/image-scan.mjs +152 -0
  227. package/stack/ingress-create.mjs +114 -0
  228. package/stack/ingress-publish.mjs +273 -0
  229. package/stack/ingress.mjs +706 -0
  230. package/stack/jobs.mjs +51 -0
  231. package/stack/live-restore.mjs +177 -0
  232. package/stack/prepare.mjs +266 -0
  233. package/stack/private-file.mjs +22 -0
  234. package/stack/provision.mjs +90 -0
  235. package/stack/prune.mjs +46 -0
  236. package/stack/release-store.mjs +87 -0
  237. package/stack/remote.mjs +205 -0
  238. package/stack/render.mjs +149 -0
  239. package/stack/retire.mjs +47 -0
  240. package/stack/sealed.mjs +149 -0
  241. package/stack/task.mjs +42 -0
  242. package/stack/verify.mjs +99 -0
  243. package/sync/ci-commands.mjs +13 -0
  244. package/sync/index.mjs +127 -64
  245. package/sync/stack-images.mjs +127 -0
  246. package/templates/app/ci-workflows/github/workflows/ci.yml +5 -1
  247. package/templates/app/ci-workflows/github/workflows/images.yml +15 -3
  248. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +148 -0
  249. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +7 -3
  250. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +38 -6
  251. package/templates/app/ci-workflows-lite/github/workflows/images.yml +14 -2
  252. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +148 -0
  253. package/templates/app/codegen-graphql.mjs.partial +13 -2
  254. package/templates/app/codegen-lite/scripts/codegen.mjs +6 -1
  255. package/templates/app/codegen-prelude.mjs.partial +8 -0
  256. package/templates/app/gitignore +4 -0
  257. package/templates/app/gitignore-lite +4 -0
  258. package/templates/app/hooks/husky/pre-commit +7 -12
  259. package/templates/app/package-scripts/package.json +1 -0
  260. package/templates/app/package-scripts/supabase.json +7 -0
  261. package/templates/app/package-scripts-lite/package.json +1 -7
  262. package/templates/app/skeleton/.editorconfig +1 -0
  263. package/templates/app/skeleton/.gitattributes +7 -1
  264. package/templates/app/skeleton/.github/dependabot.yml +17 -0
  265. package/templates/app/skeleton/.nvmrc +1 -1
  266. package/templates/app/skeleton/.sops.yaml +1 -1
  267. package/templates/app/skeleton/.starcistacks/application-stacks.yaml +134 -1
  268. package/templates/app/skeleton/.starcistacks/dev/README.md +16 -0
  269. package/templates/app/skeleton/.starcistacks/dev/environment.json +5 -0
  270. package/templates/app/skeleton/.starcistacks/dev/infra/compose/stack.yaml +71 -0
  271. package/templates/app/skeleton/.starcistacks/dev/runtime/env/KEYS.md +14 -0
  272. package/templates/app/skeleton/.starcistacks/production/README.md +37 -0
  273. package/templates/app/skeleton/.starcistacks/production/environment.json +5 -0
  274. package/templates/app/skeleton/.starcistacks/production/infra/swarm/Caddyfile +15 -0
  275. package/templates/app/skeleton/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  276. package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  277. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  278. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +9 -0
  279. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  280. package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +142 -0
  281. package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +14 -0
  282. package/templates/app/skeleton/README.md +6 -6
  283. package/templates/app/skeleton/scripts/codegen.mjs +1 -2
  284. package/templates/app/skeleton-lite/.editorconfig +16 -0
  285. package/templates/app/skeleton-lite/.gitattributes +8 -0
  286. package/templates/app/skeleton-lite/.github/dependabot.yml +17 -0
  287. package/templates/app/skeleton-lite/.nvmrc +1 -0
  288. package/templates/app/skeleton-lite/.sops.yaml +3 -2
  289. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +193 -0
  290. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +20 -0
  291. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +2 -1
  292. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +16 -0
  293. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +11 -0
  294. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +7 -0
  295. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +6 -0
  296. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +8 -0
  297. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +6 -0
  298. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +120 -0
  299. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -0
  300. package/templates/app/skeleton-lite/.starcistacks/production/README.md +38 -0
  301. package/templates/app/skeleton-lite/.starcistacks/production/environment.json +6 -0
  302. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/Caddyfile +11 -0
  303. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  304. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  305. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  306. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/images.lock.yaml +8 -0
  307. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  308. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +121 -0
  309. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +21 -0
  310. package/templates/app/skeleton-lite/.starciwork/brand/index.yaml +12 -0
  311. package/templates/app/skeleton-lite/.starciwork/workspace.yaml +9 -0
  312. package/templates/app/skeleton-lite/README.md +8 -4
  313. package/templates/app/skeleton-lite/supabase/config.toml +8 -1
  314. package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +15 -6
  315. package/templates/app/starciwork.gitignore-lite +6 -0
  316. package/templates/app/upgrade-full/.starcistacks/dev/README.md +13 -7
  317. package/templates/be/image/worker/Dockerfile +1 -1
  318. package/templates/be/inbox/database-client.spec.ts.tpl +22 -28
  319. package/templates/be/inbox/database-client.ts.tpl +14 -28
  320. package/templates/be/inbox/delivery-recorded.event.ts.tpl +36 -0
  321. package/templates/be/inbox/event-index.ts.tpl +1 -0
  322. package/templates/be/inbox/events.json.tpl +10 -0
  323. package/templates/be/inbox/migration.sql.tpl +1 -0
  324. package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +42 -0
  325. package/templates/be/inbox/platform/event-bus/event-bus.decorators.ts.tpl +9 -0
  326. package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +6 -0
  327. package/templates/be/inbox/platform/event-bus/event-bus.module.ts.tpl +19 -0
  328. package/templates/be/inbox/platform/event-bus/event-bus.options.ts.tpl +2 -0
  329. package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +16 -0
  330. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +45 -0
  331. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +15 -0
  332. package/templates/be/inbox/platform/event-bus/index.ts.tpl +5 -0
  333. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +5 -0
  334. package/templates/be/inbox/platform/inbox/inbox.decorators.ts.tpl +9 -0
  335. package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +6 -0
  336. package/templates/be/inbox/platform/inbox/inbox.module.ts.tpl +19 -0
  337. package/templates/be/inbox/platform/inbox/inbox.options.ts.tpl +2 -0
  338. package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +10 -0
  339. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +41 -0
  340. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +19 -0
  341. package/templates/be/inbox/platform/inbox/index.ts.tpl +3 -0
  342. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +7 -0
  343. package/templates/be/inbox/platform/migration.sql.tpl +48 -0
  344. package/templates/be/inbox/service.spec.ts.tpl +49 -20
  345. package/templates/be/inbox/service.ts.tpl +27 -16
  346. package/templates/be/inbox/sql.ts.tpl +2 -2
  347. package/templates/be/inbox/webhook-door.spec.ts.tpl +3 -3
  348. package/templates/be/inbox/webhook-door.ts.tpl +4 -3
  349. package/templates/be/patterns/api/feature.module-definition.ts.tpl +6 -0
  350. package/templates/be/patterns/api/feature.module.ts.tpl +2 -1
  351. package/templates/be/patterns/api/feature.options.ts.tpl +2 -0
  352. package/templates/be/patterns/api/graphql.module-definition.ts.tpl +6 -0
  353. package/templates/be/patterns/api/graphql.module.ts.tpl +3 -2
  354. package/templates/be/patterns/api/graphql.options.ts.tpl +2 -0
  355. package/templates/be/patterns/cli/group.module-definition.ts.tpl +6 -0
  356. package/templates/be/patterns/cli/group.module.ts.tpl +2 -1
  357. package/templates/be/patterns/cli/group.options.ts.tpl +2 -0
  358. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +0 -3
  359. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +0 -2
  360. package/templates/be/patterns/jobs/job.module-definition.ts.tpl +6 -0
  361. package/templates/be/patterns/jobs/job.module.ts.tpl +2 -1
  362. package/templates/be/patterns/jobs/job.options.ts.tpl +2 -0
  363. package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +6 -0
  364. package/templates/be/patterns/jobs/job.queue.module.ts.tpl +6 -3
  365. package/templates/be/patterns/jobs/job.queue.options.ts.tpl +2 -0
  366. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +3 -1
  367. package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +25 -9
  368. package/templates/be/patterns/queues/platform/queue.config.ts.tpl +1 -1
  369. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +1 -1
  370. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +2 -2
  371. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +11 -0
  372. package/templates/be/patterns/queues/platform/queue.options.ts.tpl +10 -0
  373. package/templates/be/patterns/queues/queue.module-definition.ts.tpl +6 -0
  374. package/templates/be/patterns/queues/queue.module.ts.tpl +2 -1
  375. package/templates/be/patterns/queues/queue.options.ts.tpl +2 -0
  376. package/templates/be/patterns/reactors/message.module-definition.ts.tpl +6 -0
  377. package/templates/be/patterns/reactors/message.module.ts.tpl +6 -3
  378. package/templates/be/patterns/reactors/message.options.ts.tpl +2 -0
  379. package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +6 -0
  380. package/templates/be/patterns/reactors/reactor.module.ts.tpl +2 -1
  381. package/templates/be/patterns/reactors/reactor.options.ts.tpl +2 -0
  382. package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +188 -0
  383. package/templates/be/patterns/realtime/channel.gateway.ts.tpl +22 -0
  384. package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +6 -0
  385. package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +2 -1
  386. package/templates/be/patterns/realtime/channel.graphql.options.ts.tpl +2 -0
  387. package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +6 -0
  388. package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +110 -0
  389. package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +7 -0
  390. package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +6 -0
  391. package/templates/be/patterns/saga/saga.message.module.ts.tpl +6 -3
  392. package/templates/be/patterns/saga/saga.message.options.ts.tpl +2 -0
  393. package/templates/be/patterns/saga/saga.module-definition.ts.tpl +6 -0
  394. package/templates/be/patterns/saga/saga.module.ts.tpl +2 -1
  395. package/templates/be/patterns/saga/saga.options.ts.tpl +2 -0
  396. package/templates/be/patterns/webhooks/webhook.door.spec.ts.tpl +3 -3
  397. package/templates/be/patterns/webhooks/webhook.door.ts.tpl +3 -3
  398. package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +6 -0
  399. package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +2 -1
  400. package/templates/be/patterns/webhooks/webhook.http.options.ts.tpl +2 -0
  401. package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +80 -26
  402. package/templates/be/platform/graphql/localize-errors.mapper.ts +2 -5
  403. package/templates/be/skeleton/apps/__app__/src/__app__.env.ts +21 -0
  404. package/templates/be/skeleton/apps/__app__/src/__app__.options.ts +1 -1
  405. package/templates/be/skeleton/apps/__app__/src/app.module.ts +15 -45
  406. package/templates/be/skeleton/apps/__app__/src/compose/api.ts +4 -0
  407. package/templates/be/skeleton/apps/__app__/src/compose/domain.ts +11 -0
  408. package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +44 -0
  409. package/templates/be/skeleton/apps/__app__/src/compose/system.ts +5 -0
  410. package/templates/be/skeleton/apps/__app__/src/compose/webhooks.ts +4 -0
  411. package/templates/be/skeleton/apps/__app__/src/main.ts +18 -10
  412. package/templates/be/skeleton/apps/cli/src/app.module.ts +3 -13
  413. package/templates/be/skeleton/apps/cli/src/cli.env.ts +13 -0
  414. package/templates/be/skeleton/apps/cli/src/cli.options.ts +7 -17
  415. package/templates/be/skeleton/apps/cli/src/compose/cli.ts +5 -0
  416. package/templates/be/skeleton/apps/cli/src/compose/platform.ts +24 -0
  417. package/templates/be/skeleton/apps/cli/src/main.ts +3 -2
  418. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.contracts.ts +2 -0
  419. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.handler.ts +22 -0
  420. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.query.ts +11 -0
  421. package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +6 -0
  422. package/templates/be/skeleton/src/features/api/system-health/system-health.module.ts +5 -3
  423. package/templates/be/skeleton/src/features/api/system-health/system-health.options.ts +2 -0
  424. package/templates/be/skeleton/src/features/api/system-health/transport/http/ready.controller.ts +19 -0
  425. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +6 -0
  426. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module.ts +8 -3
  427. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.options.ts +2 -0
  428. package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +6 -0
  429. package/templates/be/skeleton/src/features/cli/cli.module.ts +3 -2
  430. package/templates/be/skeleton/src/features/cli/cli.options.ts +2 -0
  431. package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +6 -0
  432. package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +2 -1
  433. package/templates/be/skeleton/src/features/cli/migrate/migrate.options.ts +2 -0
  434. package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +6 -0
  435. package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +2 -1
  436. package/templates/be/skeleton/src/features/cli/seed/seed.options.ts +2 -0
  437. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +5 -6
  438. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +56 -4
  439. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.ts +36 -5
  440. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +39 -8
  441. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.ts +14 -8
  442. package/templates/be/skeleton/src/modules/domain/identity/credential.contracts.ts +26 -0
  443. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.spec.ts +119 -0
  444. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.ts +43 -0
  445. package/templates/be/skeleton/src/modules/domain/identity/identity.contracts.ts +16 -0
  446. package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -1
  447. package/templates/be/skeleton/src/modules/domain/identity/identity.module.ts +22 -4
  448. package/templates/be/skeleton/src/modules/domain/identity/identity.options.ts +8 -2
  449. package/templates/be/skeleton/src/modules/domain/identity/index.ts +1 -0
  450. package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +6 -6
  451. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +11 -4
  452. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.spec.ts +32 -0
  453. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.ts +14 -0
  454. package/templates/be/skeleton/src/modules/platform/database/database.module.ts +7 -1
  455. package/templates/be/skeleton/src/modules/platform/database/index.ts +2 -0
  456. package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +8 -0
  457. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +25 -6
  458. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +16 -6
  459. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.spec.ts +130 -0
  460. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.ts +58 -0
  461. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +3 -0
  462. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -4
  463. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +16 -10
  464. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +3 -1
  465. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +2 -0
  466. package/templates/be/skeleton/src/modules/platform/http-security/index.ts +5 -0
  467. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +12 -5
  468. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +13 -8
  469. package/templates/be/skeleton/src/modules/platform/http-security/readiness.contracts.ts +5 -0
  470. package/templates/be/skeleton/src/modules/platform/http-security/readiness.decorators.ts +7 -0
  471. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.spec.ts +86 -0
  472. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.ts +39 -0
  473. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +139 -131
  474. package/templates/be/skeleton/src/modules/platform/i18n/index.ts +1 -1
  475. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +91 -67
  476. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +5 -4
  477. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +0 -2
  478. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +20 -6
  479. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +16 -4
  480. package/templates/be/skeleton/src/modules/platform/retry/index.ts +3 -0
  481. package/templates/be/skeleton/src/modules/platform/retry/retry.decorators.ts +9 -0
  482. package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +7 -0
  483. package/templates/be/skeleton/src/modules/platform/retry/retry.module.ts +19 -0
  484. package/templates/be/skeleton/src/modules/platform/retry/retry.options.ts +2 -0
  485. package/templates/be/skeleton/src/modules/platform/retry/retry.port.ts +11 -0
  486. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.spec.ts +36 -0
  487. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.ts +10 -0
  488. package/templates/be/skeleton/src/tests/contract/http-security/http-security.contract-spec.ts +54 -0
  489. package/templates/be/skeleton-lite/apps/api/src/api.env.ts +25 -0
  490. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +1 -1
  491. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +18 -58
  492. package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +4 -0
  493. package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +10 -0
  494. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +16 -0
  495. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +47 -0
  496. package/templates/be/skeleton-lite/apps/api/src/compose/system.ts +5 -0
  497. package/templates/be/skeleton-lite/apps/api/src/compose/webhooks.ts +4 -0
  498. package/templates/be/skeleton-lite/apps/api/src/main.ts +5 -13
  499. package/templates/be/skeleton-lite/apps/cli/src/cli.env.ts +17 -0
  500. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +12 -0
  501. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.spec.ts +71 -5
  502. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +29 -2
  503. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +68 -15
  504. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +14 -9
  505. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +9 -2
  506. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +16 -1
  507. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +19 -4
  508. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +5 -1
  509. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -1
  510. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -3
  511. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +17 -5
  512. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +24 -13
  513. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +5 -0
  514. package/templates/be/skeleton-lite/src/modules/platform/database/errors/database.error.ts +16 -0
  515. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +3 -0
  516. package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +5 -2
  517. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +4 -1
  518. package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +1 -1
  519. package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +7 -0
  520. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +29 -0
  521. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +16 -0
  522. package/templates/be/table/api/feature.module-definition.ts.tpl +6 -0
  523. package/templates/be/table/api/feature.module.ts.tpl +2 -1
  524. package/templates/be/table/api/feature.options.ts.tpl +2 -0
  525. package/templates/be/table/api/graphql.module-definition.ts.tpl +6 -0
  526. package/templates/be/table/api/graphql.module.ts.tpl +3 -2
  527. package/templates/be/table/api/graphql.options.ts.tpl +2 -0
  528. package/templates/be/table/migration.sql.tpl +1 -0
  529. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +4 -6
  530. package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +7 -0
  531. package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +5 -0
  532. package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +9 -0
  533. package/templates/be/upgrade-full/apps/cli/src/main.ts +3 -0
  534. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +3 -2
  535. package/templates/fe/auth/supabase-client.ts.tpl +34 -0
  536. package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +11 -0
  537. package/templates/fe/skeleton/apps/__app__/src/components/blocks/SignInForm/index.tsx +6 -1
  538. package/templates/fe/skeleton/apps/__app__/src/components/leaves/Icon/index.tsx +4 -0
  539. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/component.tsx +31 -0
  540. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/index.tsx +24 -0
  541. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +6 -1
  542. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +3 -4
  543. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/index.ts +1 -0
  544. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +15 -0
  545. package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +18 -0
  546. package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +19 -0
  547. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +7 -1
  548. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/en.json +57 -0
  549. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +6 -0
  550. package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/en.json +31 -0
  551. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +1 -1
  552. package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +1 -0
  553. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +1 -34
  554. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +7 -1
  555. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +57 -0
  556. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +30 -24
  557. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +1 -1
  558. package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
  559. package/templates/fe/transport/graphql/clients/links/locale.ts +2 -2
  560. package/upgrade/check.mjs +3 -1
  561. package/upgrade/file-state.mjs +68 -0
  562. package/upgrade/index.mjs +58 -94
  563. package/runtime/scripts/api/git/log.mjs +0 -5
  564. package/templates/app/skeleton-lite/.starciwork/shell/index.yaml +0 -55
@@ -0,0 +1,183 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createSlotResolver, loadSlotManifest, readRepoDeclaration, ruleParams } from '../runtime/scripts/hfs/slots.mjs';
4
+ import { parseYaml } from '../runtime/engine/yaml.mjs';
5
+ import { TEMPLATES_DIR } from '../sync/index.mjs';
6
+ import { ScaffoldError, pascalOf } from './service.mjs';
7
+ import { refuseFullAdapterInLite, refuseInEdition } from './edition-gate.mjs';
8
+ import { selectLiteWebhookEntries } from './add-inbox-lite.mjs';
9
+ import { ensureLiteCli } from './add-cli-lite.mjs';
10
+
11
+ const KEBAB = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
12
+ const VARIABLE = /<([A-Za-z][A-Za-z0-9]*)>/g;
13
+ const PLACEHOLDER = /@@([A-Za-z][A-Za-z0-9]*)@@/g;
14
+ const PLATFORM_PREFIX = 'src/modules/platform/';
15
+ const TOPICS_DIR = path.join(import.meta.dirname, '..', 'runtime', 'knowledge', 'patterns', 'be');
16
+ const PATTERN_TEMPLATES = path.join(TEMPLATES_DIR, 'be', 'patterns');
17
+ const camelOf = (name) => { const pascal = pascalOf(name); return pascal[0].toLowerCase() + pascal.slice(1); };
18
+
19
+ /** The noun's app declaration and the ruleParams entry that supplies its topic and options. */
20
+ export function readAddContext(input) {
21
+ const manifest = loadSlotManifest();
22
+ const params = ruleParams(manifest, 'be');
23
+ const spec = params.addKinds[input.noun];
24
+ if (!spec) throw new ScaffoldError('HFS_ADD_NOUN_UNKNOWN', `starci app add knows ${Object.keys(params.addKinds).join(', ')}; ${input.noun} is none of them`);
25
+ const declarationFile = path.join(input.repoRoot, 'hfs.json');
26
+ if (!fs.existsSync(declarationFile)) throw new ScaffoldError('HFS_ADD_NOT_AN_APP', 'starci app add runs at the app root (the folder of hfs.json)');
27
+ const repo = readRepoDeclaration(manifest, input.repoRoot);
28
+ if (!repo.sides?.be) throw new ScaffoldError('HFS_ADD_NO_BACK_END', 'this app has no back-end side');
29
+ return { ...input, manifest, params, spec, declarationFile, repo };
30
+ }
31
+
32
+ /** Lite's built-in migrate and seed groups come from its one CLI bootstrap, without a second instance tree. */
33
+ export function addBuiltinLiteCli({ noun, name, repo, repoRoot, manifest }) {
34
+ if (noun !== 'cli' || repo.edition !== 'lite' || !['migrate', 'seed'].includes(name)) return null;
35
+ if (repo.sides.be.apps.some(app => app.kind === 'cli')) throw new ScaffoldError('HFS_ADD_EXISTS', `the lite cli already carries its built-in ${name} group`);
36
+ return { created: ensureLiteCli({ root: repoRoot, manifest, repo }), registered: { patterns: [], kinds: ['cli'] } };
37
+ }
38
+
39
+ const readTopic = (topic) => ({ id: topic, doc: parseYaml(fs.readFileSync(path.join(TOPICS_DIR, `${topic}.yaml`), 'utf8')) });
40
+
41
+ function readTree(topic) {
42
+ const file = path.join(TOPICS_DIR, `${topic}.yaml`);
43
+ if (!fs.existsSync(file)) throw new ScaffoldError('HFS_ADD_TOPIC_MISSING', `the pattern topic ${topic} (knowledge/patterns/be/${topic}.yaml) is not carried by this package`);
44
+ const doc = parseYaml(fs.readFileSync(file, 'utf8'));
45
+ if (!Array.isArray(doc?.files)) throw new ScaffoldError('HFS_ADD_TREE_MISSING', `knowledge/patterns/be/${topic}.yaml has no files tree`);
46
+ return doc.files;
47
+ }
48
+
49
+ const selectLiteApiEntries = entries => entries.map(entry => {
50
+ if (entry.path === 'src/features/api/<feature>/application/<action>.command.ts') return { ...entry, template: 'api/application.command.principal.ts.tpl' };
51
+ if (entry.path === 'src/features/api/<feature>/application/<action>.handler.ts') return { ...entry, template: 'api/application.handler.principal.ts.tpl' };
52
+ return entry;
53
+ });
54
+
55
+ /** Extra lite api templates stay in the same template set as the pattern topics. */
56
+ export const liteApiTemplates = () => selectLiteApiEntries(readTree('api')).map(entry => entry.template).filter(Boolean);
57
+
58
+ function topicsOfPatterns(patterns) {
59
+ const topics = new Set();
60
+ for (const file of fs.readdirSync(TOPICS_DIR).filter((entry) => entry.endsWith('.yaml'))) {
61
+ const doc = parseYaml(fs.readFileSync(path.join(TOPICS_DIR, file), 'utf8'));
62
+ if (patterns.includes(doc?.pattern)) topics.add(file.slice(0, -'.yaml'.length));
63
+ }
64
+ return topics;
65
+ }
66
+
67
+ function requiredOption(need, value, noun) {
68
+ if (typeof value !== 'string' || !value) throw new ScaffoldError('HFS_ADD_OPTION_MISSING', `starci app add ${noun} needs --${need}`);
69
+ if (need === 'service') {
70
+ const [service, serviceModule] = value.split('=');
71
+ if (!service || !serviceModule) throw new ScaffoldError('HFS_ADD_OPTION_INVALID', '--service takes <ServiceClass>=<module specifier>');
72
+ return { service, serviceModule };
73
+ }
74
+ if (!KEBAB.test(value)) throw new ScaffoldError('HFS_ADD_OPTION_INVALID', `--${need} ${value} must be kebab-case`);
75
+ return { [need]: value };
76
+ }
77
+
78
+ function variablesOf({ spec, noun, name, options }) {
79
+ if (!KEBAB.test(name)) throw new ScaffoldError('HFS_ADD_NAME_INVALID', `${noun} name ${name} must be kebab-case (letters, digits and single dashes)`);
80
+ const values = { [spec.variable]: name };
81
+ for (const need of spec.needs ?? []) Object.assign(values, requiredOption(need, options[need], noun));
82
+ for (const [key, rule] of Object.entries(spec.defaults ?? {})) {
83
+ if (values[key] === undefined) values[key] = rule === '@name' ? name : String(rule);
84
+ }
85
+ return values;
86
+ }
87
+
88
+ export function formsOf(values) {
89
+ const forms = {};
90
+ for (const [name, value] of Object.entries(values)) {
91
+ const kebab = KEBAB.test(value);
92
+ forms[name] = value;
93
+ forms[name[0].toUpperCase() + name.slice(1)] = kebab ? pascalOf(value) : value[0].toUpperCase() + value.slice(1);
94
+ forms[`${name}Camel`] = kebab ? camelOf(value) : value[0].toLowerCase() + value.slice(1);
95
+ forms[`${name}Upper`] = kebab ? value.replaceAll('-', '_').toUpperCase() : value;
96
+ forms[`${name}Snake`] = kebab ? value.replaceAll('-', '_') : value;
97
+ }
98
+ return forms;
99
+ }
100
+
101
+ const fillPath = (text, values) => {
102
+ let missing = false;
103
+ const filled = text.replace(VARIABLE, (whole, name) => (values[name] === undefined ? ((missing = true), whole) : values[name]));
104
+ return missing ? null : filled;
105
+ };
106
+
107
+ const renderBody = (text, forms, template) => text.replace(PLACEHOLDER, (whole, name) => {
108
+ if (forms[name] === undefined) throw new ScaffoldError('HFS_ADD_PLACEHOLDER_UNKNOWN', `template ${template} uses ${whole}, which no variable of this noun provides`);
109
+ return forms[name];
110
+ });
111
+
112
+ /** Wiring may leave a placeholder unchanged; template bodies use the strict rendering refusal above. */
113
+ export const fillKnownForms = (text, forms) => text.replace(PLACEHOLDER, (whole, name) => forms[name] ?? whole);
114
+
115
+ function treesOf({ topics, repo, noun, spec }) {
116
+ const liteApi = noun === 'api' && repo.edition === 'lite';
117
+ return new Map([...topics].map((topic) => {
118
+ const entries = repo.edition === 'lite' ? selectLiteWebhookEntries(readTree(topic)) : readTree(topic);
119
+ return [topic, liteApi && topic === spec.topic ? selectLiteApiEntries(entries) : entries];
120
+ }));
121
+ }
122
+
123
+ function collectSlots(entries, platform, slots) {
124
+ for (const entry of entries) {
125
+ if (entry.path.startsWith(PLATFORM_PREFIX) === platform && entry.optional !== true && entry.slot) slots.add(entry.slot);
126
+ }
127
+ }
128
+
129
+ /** Optional entries gate nothing; the generated instance and platform slots decide the edition. */
130
+ function validateWrittenSlots({ specs, topics, trees, manifest, repo, noun }) {
131
+ const slots = new Set();
132
+ for (const [, spec] of specs) collectSlots(trees.get(spec.topic), false, slots);
133
+ for (const topic of topics) collectSlots(trees.get(topic), true, slots);
134
+ refuseInEdition({ resolver: createSlotResolver(manifest, repo), slotIds: [...slots], command: `add ${noun}` });
135
+ }
136
+
137
+ function planEntry(entry, { topic, instance, shared, beRoot, withStamp, forms }) {
138
+ if (entry.optional === true) return null;
139
+ const target = fillPath(entry.path, withStamp);
140
+ if (target === null) return null;
141
+ const absolute = path.join(beRoot, ...target.split('/'));
142
+ // A platform capability is whole or absent: its index prevents regeneration of all its files.
143
+ if (!instance && fs.existsSync(path.join(beRoot, ...target.split('/').slice(0, 4), 'index.ts'))) return null;
144
+ if ((!instance || shared) && fs.existsSync(absolute)) return null;
145
+ if (!entry.template) throw new ScaffoldError('HFS_ADD_TEMPLATE_MISSING', `${topic}.yaml lists ${entry.path} without a template, so it cannot be generated`);
146
+ const templateFile = path.join(PATTERN_TEMPLATES, ...entry.template.split('/'));
147
+ if (!fs.existsSync(templateFile)) throw new ScaffoldError('HFS_ADD_TEMPLATE_MISSING', `${topic}.yaml names template ${entry.template} for ${entry.path}, which the package does not carry`);
148
+ return { target, absolute, body: renderBody(fs.readFileSync(templateFile, 'utf8'), forms, entry.template), instance };
149
+ }
150
+
151
+ function appendEntries(entries, context, planned) {
152
+ for (const entry of entries) {
153
+ const file = planEntry(entry, context);
154
+ if (file) planned.push(file);
155
+ }
156
+ }
157
+
158
+ /** Renders all topic-owned paths and bodies before any instance file is written. */
159
+ export function planKind(context) {
160
+ const { noun, name, spec, params, repo, options, now } = context;
161
+ const specs = [noun, ...(spec.also ?? [])].map((each) => [each, params.addKinds[each]]);
162
+ const patterns = [...new Set(specs.flatMap(([, eachSpec]) => eachSpec.patterns))];
163
+ const topics = new Set([...specs.map(([, eachSpec]) => eachSpec.topic), ...topicsOfPatterns(patterns)]);
164
+ refuseFullAdapterInLite({ edition: repo.edition, topics: [...topics].map(readTopic), command: `add ${noun}` });
165
+ const trees = treesOf({ topics, repo, noun, spec });
166
+ validateWrittenSlots({ ...context, specs, topics, trees });
167
+ const values = Object.assign({}, ...specs.map(([each, eachSpec]) => variablesOf({ spec: eachSpec, noun: each, name, options })), variablesOf({ spec, noun, name, options }));
168
+ const withStamp = { ...values, epochMs13: String(now()).padStart(13, '0').slice(0, 13) };
169
+ const forms = formsOf(withStamp);
170
+ const beRoot = path.join(context.repoRoot, 'be');
171
+ const planned = [];
172
+ for (const [each, eachSpec] of specs) {
173
+ const entries = trees.get(eachSpec.topic).filter(entry => !entry.path.startsWith(PLATFORM_PREFIX));
174
+ appendEntries(entries, { topic: eachSpec.topic, instance: true, shared: each !== noun, beRoot, withStamp, forms }, planned);
175
+ }
176
+ for (const topic of topics) {
177
+ const entries = trees.get(topic).filter(entry => entry.path.startsWith(PLATFORM_PREFIX));
178
+ appendEntries(entries, { topic, instance: false, shared: false, beRoot, withStamp, forms }, planned);
179
+ }
180
+ const clash = planned.filter(file => file.instance && fs.existsSync(file.absolute)).map(file => file.target);
181
+ if (clash.length) throw new ScaffoldError('HFS_ADD_EXISTS', `${noun} ${name} already exists: ${clash.join(', ')}`);
182
+ return { values, forms, beRoot, patterns, planned };
183
+ }
@@ -10,6 +10,8 @@ import { ScaffoldError, pascalOf } from "./service.mjs";
10
10
  import { filesBelow, registerLiteDomainService, registerLiteExports, registrationFiles } from "./lite-exports.mjs";
11
11
  import { byCodeUnit } from "../report/order.mjs";
12
12
  import { coreFeApps } from "./app.mjs";
13
+ import { addModuleImport, planComposeGroup } from "./add-compose.mjs";
14
+ import { withSqlIdentifierError } from "./add-database-lite.mjs";
13
15
 
14
16
  const NAME = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
15
17
  const MIGRATION = /^(\d{14})_[a-z][a-z0-9]*(?:-[a-z0-9]+)*\.sql$/;
@@ -163,53 +165,53 @@ function removeEmptyParents(file, stop) {
163
165
  /** The app-root relative path of a file under the app (posix). */
164
166
  const at = (root, relative) => path.join(root, ...relative.split("/"));
165
167
 
168
+ /** A line that opens the error kinds array of the errors registration. */
169
+ const KINDS_LINE = /^\s*kinds: \[$/;
170
+
166
171
  /**
167
- * Registers `entry` in the `imports` array of a Nest module file (one import line after the last import, one array entry right
168
- * after `imports: [`), unless `symbol` is already imported. Returns the new text.
172
+ * The composition file of an api app that holds its error kinds array: the root `app.module.ts`, or else the composition group
173
+ * `compose/<area>.ts` of the same app that holds it (the platform group of a scaffolded app). Repository-relative.
169
174
  */
170
- function wireImport(text, relative, { importLine, symbol, entry, kinds }) {
175
+ function kindsHomeOf(root, appRelative) {
176
+ const holdsKinds = (relative) => fs.readFileSync(at(root, relative), "utf8").replace(/\r\n/g, "\n").split("\n").some((line) => KINDS_LINE.test(line));
177
+ if (holdsKinds(appRelative)) return appRelative;
178
+ const composeRelative = `${path.posix.dirname(appRelative)}/compose`;
179
+ const groups = fs.existsSync(at(root, composeRelative)) ? fs.readdirSync(at(root, composeRelative)).filter((file) => file.endsWith(".ts")).sort(byCodeUnit) : [];
180
+ const home = groups.map((file) => `${composeRelative}/${file}`).find(holdsKinds);
181
+ if (!home) throw new ScaffoldError("HFS_ADD_WIRE_INVALID", `add table could not find the error kinds array in ${appRelative} or a group of its compose/`);
182
+ return home;
183
+ }
184
+
185
+ /** A composition group after `kinds` is listed first in its error kinds array and imported from `specifier`. Returns the new text. */
186
+ function wireKinds(text, relative, { kinds, specifier }) {
171
187
  const lines = text.replace(/\r\n/g, "\n").split("\n");
172
- if (lines.some((line) => line.startsWith("import ") && new RegExp(`\\b${symbol}\\b`).test(line))) return text;
188
+ if (lines.some((line) => line.startsWith("import ") && new RegExp(String.raw`\b${kinds}\b`).test(line))) return text;
173
189
  const lastImport = lines.reduce((last, line, index) => (line.startsWith("import ") ? index : last), -1);
174
- if (lastImport < 0 || lines.findIndex((line) => /^\s*imports: \[$/.test(line)) < 0)
175
- throw new ScaffoldError("HFS_ADD_WIRE_INVALID", `add table could not find the imports array in ${relative}`);
176
- lines.splice(lastImport + 1, 0, importLine);
177
- const imports = lines.findIndex((line) => /^\s*imports: \[$/.test(line));
178
- const indent = /^(\s*)/.exec(lines[imports])?.[1] ?? "";
179
- lines.splice(imports + 1, 0, `${indent} ${entry},`);
180
- if (kinds !== undefined) {
181
- const kindsAt = lines.findIndex((line) => /^\s*kinds: \[$/.test(line));
182
- if (kindsAt < 0) throw new ScaffoldError("HFS_ADD_WIRE_INVALID", `add table could not find the error kinds array in ${relative}`);
183
- const kindsIndent = /^(\s*)/.exec(lines[kindsAt])?.[1] ?? "";
184
- lines.splice(kindsAt + 1, 0, `${kindsIndent} ${kinds},`);
185
- }
186
- return `${lines.join("\n").replace(/\n+$/, "")}\n`;
190
+ const kindsAt = lines.findIndex((line) => KINDS_LINE.test(line));
191
+ if (lastImport < 0 || kindsAt < 0) throw new ScaffoldError("HFS_ADD_WIRE_INVALID", `add table could not find the error kinds array in ${relative}`);
192
+ const kindsIndent = /^(\s*)/.exec(lines[kindsAt])?.[1] ?? "";
193
+ lines.splice(kindsAt + 1, 0, `${kindsIndent} ${kinds},`);
194
+ lines.splice(lastImport + 1, 0, `import { ${kinds} } from "${specifier}"`);
195
+ return `${lines.join("\n").trimEnd()}\n`;
187
196
  }
188
197
 
189
- /** The api app root after the table's domain module is registered (its module and error kinds). */
190
- const wireDomainModule = (text, relative, name) =>
191
- wireImport(text, relative, {
192
- importLine: `import { ${snakeOf(name).toUpperCase()}_ERROR_KINDS, ${pascalOf(name)}Module } from "@modules/domain/${name}"`,
193
- symbol: `${pascalOf(name)}Module`,
194
- entry: `${pascalOf(name)}Module.register({ isGlobal: true })`,
195
- kinds: `${snakeOf(name).toUpperCase()}_ERROR_KINDS`,
198
+ /** Plans the domain and API area groups of the connection owner, keeping registrations out of app.module.ts. */
199
+ function tableComposition({ root, repo, owner, feature, fe, ownerRelative }) {
200
+ const domain = { importLine: `import { ${pascalOf(feature)}Module } from "@modules/domain/${feature}"`, entry: `${pascalOf(feature)}Module.register({ isGlobal: true })` };
201
+ const groups = [['domain', [domain]]];
202
+ if (fe) groups.push(['api', [
203
+ { importLine: 'import { GraphqlModule } from "@modules/platform/graphql"', entry: 'GraphqlModule.register({ isGlobal: true })' },
204
+ { importLine: `import { ${pascalOf(feature)}GraphqlModule } from "@features/api/${feature}"`, entry: `${pascalOf(feature)}GraphqlModule.register({})` },
205
+ ]]);
206
+ const lines = fs.readFileSync(at(root, ownerRelative), 'utf8').replace(/\r\n/g, '\n').split('\n');
207
+ const patches = groups.map(([area, entries]) => {
208
+ const argument = area === 'domain' && repo.edition === 'lite' ? 'verifyAccessToken' : '';
209
+ addModuleImport(lines, { importLine: `import { ${area}Modules } from "./compose/${area}"`, entry: `...${area}Modules(${argument})`, relative: ownerRelative, noun: 'table' });
210
+ return planComposeGroup({ repoRoot: root, repo, app: owner.name, area, noun: 'table', entries });
196
211
  });
197
-
198
- /** The api app root after the GraphQL server (platform/graphql) and the table's GraphQL transport are registered. */
199
- const wireGraphqlFeature = (text, relative, name) =>
200
- wireImport(
201
- wireImport(text, relative, {
202
- importLine: 'import { GraphqlModule } from "@modules/platform/graphql"',
203
- symbol: "GraphqlModule",
204
- entry: "GraphqlModule.register({ isGlobal: true })",
205
- }),
206
- relative,
207
- {
208
- importLine: `import { ${pascalOf(name)}GraphqlModule } from "@features/api/${name}"`,
209
- symbol: `${pascalOf(name)}GraphqlModule`,
210
- entry: `${pascalOf(name)}GraphqlModule`,
211
- },
212
- );
212
+ patches.push({ relative: ownerRelative, text: lines.join('\n') });
213
+ return patches;
214
+ }
213
215
 
214
216
  /** The platform GraphQL server capability (templates/be/platform/graphql), whole or absent: written once, by the first GraphQL feature. */
215
217
  const GRAPHQL_PLATFORM = "be/src/modules/platform/graphql";
@@ -252,7 +254,7 @@ const GRAPHQL_URL_READER = [
252
254
 
253
255
  /** An app's modules/config text with graphqlUrl appended once. */
254
256
  const withGraphqlUrl = (text) =>
255
- /^export const graphqlUrl\b/m.test(text) ? text : `${text.replace(/\s*$/, "")}\n${GRAPHQL_URL_READER}\n`;
257
+ /^export const graphqlUrl\b/m.test(text) ? text : `${text.trimEnd()}\n${GRAPHQL_URL_READER}\n`;
256
258
 
257
259
  /** The be api feature of a table (owner ruling 2026-10-13, lite = full): get-<singular> and create-<singular> over GraphQL. */
258
260
  const apiFeatureFiles = (values) => {
@@ -261,6 +263,8 @@ const apiFeatureFiles = (values) => {
261
263
  const create = `create-${values.singular}`;
262
264
  return [
263
265
  [`${base}/index.ts`, "feature.index.ts.tpl"],
266
+ [`${base}/${values.name}.options.ts`, "feature.options.ts.tpl"],
267
+ [`${base}/${values.name}.module-definition.ts`, "feature.module-definition.ts.tpl"],
264
268
  [`${base}/${values.name}.module.ts`, "feature.module.ts.tpl"],
265
269
  [`${base}/application/${get}.query.ts`, "get.query.ts.tpl"],
266
270
  [`${base}/application/${get}.handler.ts`, "get.handler.ts.tpl"],
@@ -275,6 +279,8 @@ const apiFeatureFiles = (values) => {
275
279
  [`${base}/transport/graphql/${create}.resolver.ts`, "create.resolver.ts.tpl"],
276
280
  [`${base}/transport/graphql/${create}.mapper.ts`, "create.mapper.ts.tpl"],
277
281
  [`${base}/transport/graphql/dto/${create}.type.ts`, "create.type.ts.tpl"],
282
+ [`${base}/transport/graphql/${values.name}-graphql.options.ts`, "graphql.options.ts.tpl"],
283
+ [`${base}/transport/graphql/${values.name}-graphql.module-definition.ts`, "graphql.module-definition.ts.tpl"],
278
284
  [`${base}/transport/graphql/${values.name}-graphql.module.ts`, "graphql.module.ts.tpl"],
279
285
  ].map(([relative, file]) => ({ relative, body: template(`be/table/api/${file}`, values) }));
280
286
  };
@@ -316,7 +322,7 @@ const feBarrels = (app, values) => {
316
322
  const withExport = (text, line) => {
317
323
  const lines = text.replace(/\r\n/g, "\n").split("\n").filter(Boolean);
318
324
  if (lines.includes(line)) return text;
319
- return `${[...lines, line].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)).join("\n")}\n`;
325
+ return `${[...lines, line].sort(byCodeUnit).join("\n")}\n`;
320
326
  };
321
327
 
322
328
  /**
@@ -324,13 +330,7 @@ const withExport = (text, line) => {
324
330
  * server capability on first use) and, in every product Next app, its operation files and SWR hooks.
325
331
  * `emitTypes` returns the generated database.types.ts text; false is the explicit `--no-types` seam.
326
332
  */
327
- export function addTable({
328
- root,
329
- name,
330
- fe = false,
331
- emitTypes = emitDbTypes,
332
- now = Date.now,
333
- }) {
333
+ function tableContext({ root, name }) {
334
334
  const requestedName = String(name);
335
335
  if (!NAME.test(requestedName))
336
336
  throw new ScaffoldError(
@@ -349,12 +349,16 @@ export function addTable({
349
349
  );
350
350
  }
351
351
 
352
+ return { root, name, requestedName, feature, table, repo, connection };
353
+ }
354
+
355
+ function nextMigration({ root, feature, requestedName, now }) {
352
356
  const migrationsDir = path.join(root, "supabase", "migrations");
353
357
  const migrations = fs.existsSync(migrationsDir)
354
358
  ? fs
355
359
  .readdirSync(migrationsDir)
356
360
  .filter((file) => MIGRATION.test(file))
357
- .sort()
361
+ .sort(byCodeUnit)
358
362
  : [];
359
363
  if (migrations.some((file) => file.endsWith(`_${feature}.sql`)))
360
364
  throw new ScaffoldError(
@@ -369,6 +373,10 @@ export function addTable({
369
373
  `the current UTC timestamp ${stamp} is not later than the latest migration ${latest}; fix the clock or wait for the next second`,
370
374
  );
371
375
 
376
+ return stamp;
377
+ }
378
+
379
+ function tableValues({ feature, table }) {
372
380
  const singular = singularOf(feature);
373
381
  const values = {
374
382
  name: feature,
@@ -382,12 +390,20 @@ export function addTable({
382
390
  singularCamel: camelOf(singular),
383
391
  singularUpper: snakeOf(singular).toUpperCase(),
384
392
  };
393
+ return values;
394
+ }
395
+
396
+ function productApps(repo, fe) {
385
397
  const apps = fe ? coreFeApps(repo.sides?.fe?.apps ?? []) : [];
386
398
  if (fe && !apps.length)
387
399
  throw new ScaffoldError(
388
400
  "HFS_ADD_NO_FRONT_END",
389
401
  "add table --fe needs at least one declared product Next app",
390
402
  );
403
+ return apps;
404
+ }
405
+
406
+ function tableFiles({ root, repo, feature, stamp, values, fe, apps }) {
391
407
  const api = fe ? ".api" : "";
392
408
  const domain = `be/src/modules/domain/${feature}`;
393
409
  const planned = [
@@ -420,6 +436,10 @@ export function addTable({
420
436
  }
421
437
  }
422
438
 
439
+ return planned;
440
+ }
441
+
442
+ function tableOwner({ root, repo, connection, name, requestedName, planned }) {
423
443
  const clash = planned
424
444
  .filter((file) => fs.existsSync(at(root, file.relative)))
425
445
  .map((file) => file.relative);
@@ -435,8 +455,10 @@ export function addTable({
435
455
  if (!fs.existsSync(at(root, ownerRelative)))
436
456
  throw new ScaffoldError("HFS_ADD_WIRE_MISSING", `add table registers ${name} in ${ownerRelative}, which does not exist`);
437
457
 
438
- // Every existing file this run edits, as it was: restored on any failure, with every created file removed.
439
- const touched = new Map();
458
+ return { owner, ownerRelative };
459
+ }
460
+
461
+ function tableEditor(root, touched) {
440
462
  const edit = (relative, change) => {
441
463
  const file = at(root, relative);
442
464
  if (!touched.has(file)) touched.set(file, fs.existsSync(file) ? fs.readFileSync(file) : null);
@@ -446,34 +468,51 @@ export function addTable({
446
468
  fs.mkdirSync(path.dirname(file), { recursive: true });
447
469
  fs.writeFileSync(file, after);
448
470
  };
449
- const created = [];
450
- try {
451
- for (const file of planned) {
452
- const target = at(root, file.relative);
453
- fs.mkdirSync(path.dirname(target), { recursive: true });
454
- fs.writeFileSync(target, file.body);
455
- created.push(target);
456
- }
457
- if (typeof emitTypes === "function") {
458
- const generated = emitTypes({ root });
459
- if (typeof generated === "string") edit(dbTypesPath, () => generated);
460
- }
461
- edit(ownerRelative, (text) => wireDomainModule(text, ownerRelative, feature));
471
+ return edit;
472
+ }
473
+
474
+ function writeTableFiles({ root, planned, emitTypes, edit }, created) {
475
+ for (const file of planned) {
476
+ const target = at(root, file.relative);
477
+ fs.mkdirSync(path.dirname(target), { recursive: true });
478
+ fs.writeFileSync(target, file.body);
479
+ created.push(target);
480
+ }
481
+ if (typeof emitTypes === "function") {
482
+ const generated = emitTypes({ root });
483
+ if (typeof generated === "string") edit(dbTypesPath, () => generated);
484
+ }
485
+
486
+ }
487
+
488
+ function tableRegistrations({ root, repo, fe, edit }) {
489
+ const errorFile = "be/src/modules/platform/database/errors/database.error.ts";
490
+ if (repo.edition === "lite" && fs.existsSync(at(root, errorFile))) edit(errorFile, withSqlIdentifierError);
462
491
  const registrations = [
463
492
  ...(repo.edition === "lite" ? ["table"] : []),
464
493
  ...(fe ? ["api", "graphql"] : []),
465
494
  ];
466
495
  for (const generator of registrations) for (const relative of registrationFiles(generator)) edit(relative, (text) => text);
467
496
  for (const generator of registrations) registerLiteExports({ root, generator });
468
- if (fe) {
497
+ }
498
+
499
+ function wireTableFrontends({ root, feature, values, apps, edit }) {
469
500
  registerLiteDomainService({ root, moduleSpecifier: `@modules/domain/${feature}`, service: `${values.Name}Service` });
470
- edit(ownerRelative, (text) => wireGraphqlFeature(text, ownerRelative, feature));
471
501
  for (const app of apps) {
472
502
  for (const [relative, line] of feBarrels(app.name, values)) edit(relative, (text) => withExport(text, line));
473
503
  edit(`fe/apps/${app.name}/src/modules/config/index.ts`, withGraphqlUrl);
474
504
  }
475
- }
476
- } catch (error) {
505
+ }
506
+
507
+ function wireTable({ root, repo, feature, values, ownerRelative, composition, fe, apps, edit }) {
508
+ const kindsHome = kindsHomeOf(root, ownerRelative);
509
+ for (const patch of composition) edit(patch.relative, () => patch.text);
510
+ edit(kindsHome, (text) => wireKinds(text, kindsHome, { kinds: `${snakeOf(feature).toUpperCase()}_ERROR_KINDS`, specifier: `@modules/domain/${feature}` }));
511
+ tableRegistrations({ root, repo, fe, edit });
512
+ if (fe) wireTableFrontends({ root, feature, values, apps, edit });
513
+ }
514
+
515
+ function rollbackTable(root, touched, created) {
477
516
  for (const [file, before] of touched) {
478
517
  if (before === null) fs.rmSync(file, { force: true });
479
518
  else fs.writeFileSync(file, before);
@@ -483,10 +522,33 @@ export function addTable({
483
522
  removeEmptyParents(file, root);
484
523
  }
485
524
  for (const [file, before] of touched) if (before === null) removeEmptyParents(file, root);
525
+ }
526
+
527
+ function applyTable(context) {
528
+ const touched = new Map();
529
+ const created = [];
530
+ const edit = tableEditor(context.root, touched);
531
+ try {
532
+ writeTableFiles({ ...context, edit }, created);
533
+ wireTable({ ...context, edit });
534
+ } catch (error) {
535
+ rollbackTable(context.root, touched, created);
486
536
  throw error;
487
537
  }
538
+ }
539
+
540
+ /** Adds one table through typed domain/API groups and restores every touched byte on failure. */
541
+ export function addTable({ root, name, fe = false, emitTypes = emitDbTypes, now = Date.now }) {
542
+ const context = tableContext({ root, name });
543
+ const stamp = nextMigration({ ...context, now });
544
+ const values = tableValues(context);
545
+ const apps = productApps(context.repo, fe);
546
+ const planned = tableFiles({ ...context, stamp, values, apps, fe });
547
+ const owner = tableOwner({ ...context, planned });
548
+ const composition = tableComposition({ ...context, ...owner, fe });
549
+ applyTable({ ...context, ...owner, planned, composition, apps, values, fe, emitTypes });
488
550
  return {
489
- created: planned.map((file) => file.relative),
551
+ created: [...planned.map((file) => file.relative), ...composition.filter((patch) => patch.exists === false).map((patch) => patch.relative)],
490
552
  types: typeof emitTypes === "function" ? dbTypesPath : "skipped",
491
553
  };
492
554
  }