@starci/hfs 4.3.0 → 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 (515) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/db/migrate.mjs +361 -0
  3. package/db/post-cutover.mjs +136 -0
  4. package/emit/db-types.mjs +10 -1
  5. package/lint/run.mjs +1 -1
  6. package/package.json +4 -1
  7. package/release/gate.mjs +298 -0
  8. package/release/index.mjs +103 -0
  9. package/release/push.mjs +110 -0
  10. package/runtime/knowledge/hfs/canon-pins.yaml +11 -11
  11. package/runtime/knowledge/hfs/fe-stack.yaml +22 -1
  12. package/runtime/knowledge/hfs/rules.yaml +3401 -152
  13. package/runtime/knowledge/hfs/slots.yaml +344 -62
  14. package/runtime/knowledge/patterns/be/api.yaml +32 -5
  15. package/runtime/knowledge/patterns/be/cli.yaml +14 -4
  16. package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
  17. package/runtime/knowledge/patterns/be/jobs.yaml +20 -2
  18. package/runtime/knowledge/patterns/be/projections.yaml +4 -2
  19. package/runtime/knowledge/patterns/be/queues.yaml +11 -3
  20. package/runtime/knowledge/patterns/be/reactors.yaml +20 -1
  21. package/runtime/knowledge/patterns/be/realtime.yaml +25 -3
  22. package/runtime/knowledge/patterns/be/saga.yaml +20 -3
  23. package/runtime/knowledge/patterns/be/webhooks.yaml +23 -4
  24. package/runtime/knowledge/patterns/fe/folder.yaml +1 -0
  25. package/runtime/knowledge/sonar-gate.yaml +1 -1
  26. package/runtime/modules/kernel/failure-codes.yaml +3113 -43
  27. package/runtime/modules/models/runtimes.yaml +1 -1
  28. package/runtime/modules/schemas/application-stacks.schema.yaml +663 -0
  29. package/runtime/packages/cli/src/catalog.generated.mjs +14104 -0
  30. package/runtime/packages/cli/src/validate-args.mjs +213 -0
  31. package/runtime/scripts/api/git/add.mjs +5 -0
  32. package/runtime/scripts/api/git/init.mjs +7 -0
  33. package/runtime/scripts/api/git/lib.mjs +3 -0
  34. package/runtime/scripts/api/git/tag.mjs +5 -0
  35. package/runtime/scripts/api/node/lib.mjs +20 -0
  36. package/runtime/scripts/api/node/run-node.mjs +6 -0
  37. package/runtime/scripts/api/sops/lib.mjs +41 -0
  38. package/runtime/scripts/api/sops/selected-age-recipient.mjs +7 -0
  39. package/runtime/scripts/api/supabase/database-dump.mjs +5 -0
  40. package/runtime/scripts/api/supabase/database-query.mjs +5 -0
  41. package/runtime/scripts/api/supabase/lib.mjs +44 -0
  42. package/runtime/scripts/api/supabase/migration-repair.mjs +5 -0
  43. package/runtime/scripts/hfs/app-wiring.mjs +201 -0
  44. package/runtime/scripts/hfs/architecture/backend-app-files.mjs +1 -8
  45. package/runtime/scripts/hfs/architecture/backend.mjs +2 -7
  46. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +6 -3
  47. package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +149 -0
  48. package/runtime/scripts/hfs/architecture/dead-exports.mjs +9 -41
  49. package/runtime/scripts/hfs/architecture/default-deny.mjs +8 -6
  50. package/runtime/scripts/hfs/architecture/frontend-world.mjs +1 -1
  51. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +153 -0
  52. package/runtime/scripts/hfs/architecture/hfs.mjs +3 -3
  53. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +28 -13
  54. package/runtime/scripts/hfs/architecture/next-data-inference.mjs +176 -0
  55. package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +26 -0
  56. package/runtime/scripts/hfs/architecture/next-data.mjs +31 -23
  57. package/runtime/scripts/hfs/architecture/reachability.mjs +14 -0
  58. package/runtime/scripts/hfs/architecture/register-once.mjs +2 -2
  59. package/runtime/scripts/hfs/architecture/source-names.mjs +48 -3
  60. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +211 -11
  61. package/runtime/scripts/hfs/architecture/supabase-be.mjs +1 -1
  62. package/runtime/scripts/hfs/architecture/supabase.mjs +59 -21
  63. package/runtime/scripts/hfs/architecture/surface.mjs +2 -1
  64. package/runtime/scripts/hfs/architecture/tiers.mjs +3 -3
  65. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +8 -2
  66. package/runtime/scripts/hfs/check-results.mjs +28 -0
  67. package/runtime/scripts/hfs/check.mjs +46 -47
  68. package/runtime/scripts/hfs/coverage-from.mjs +70 -0
  69. package/runtime/scripts/hfs/db-baseline-verb.mjs +276 -0
  70. package/runtime/scripts/hfs/db-plan-verb.mjs +254 -0
  71. package/runtime/scripts/hfs/db-restamp-verb.mjs +60 -0
  72. package/runtime/scripts/hfs/declaration-shape.mjs +39 -3
  73. package/runtime/scripts/hfs/doc-commands.mjs +367 -0
  74. package/runtime/scripts/hfs/edition-slots.mjs +2 -2
  75. package/runtime/scripts/hfs/example-secrets.mjs +158 -0
  76. package/runtime/scripts/hfs/manifest-shape.mjs +67 -7
  77. package/runtime/scripts/hfs/refusal-codes.mjs +6 -0
  78. package/runtime/scripts/hfs/rule-catalog.mjs +156 -11
  79. package/runtime/scripts/hfs/rule-params-shape.mjs +22 -10
  80. package/runtime/scripts/hfs/rules/a15-findings.mjs +88 -0
  81. package/runtime/scripts/hfs/rules/app-env-required.mjs +20 -0
  82. package/runtime/scripts/hfs/rules/app-root.mjs +2 -2
  83. package/runtime/scripts/hfs/rules/assets.mjs +79 -0
  84. package/runtime/scripts/hfs/rules/boot-proof.mjs +16 -0
  85. package/runtime/scripts/hfs/rules/canon-copy-drift.mjs +59 -0
  86. package/runtime/scripts/hfs/rules/contract.mjs +66 -5
  87. package/runtime/scripts/hfs/rules/coverage-below.mjs +230 -0
  88. package/runtime/scripts/hfs/rules/database-access.mjs +343 -0
  89. package/runtime/scripts/hfs/rules/database-baseline.mjs +142 -0
  90. package/runtime/scripts/hfs/rules/database-config.mjs +250 -2
  91. package/runtime/scripts/hfs/rules/database-constants.mjs +9 -0
  92. package/runtime/scripts/hfs/rules/database-migrations.mjs +286 -7
  93. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +119 -9
  94. package/runtime/scripts/hfs/rules/database-queue-sources.mjs +176 -0
  95. package/runtime/scripts/hfs/rules/database-queues.mjs +361 -0
  96. package/runtime/scripts/hfs/rules/database-sql.mjs +91 -9
  97. package/runtime/scripts/hfs/rules/database.mjs +9 -0
  98. package/runtime/scripts/hfs/rules/deploy-scan.mjs +192 -0
  99. package/runtime/scripts/hfs/rules/deploy-workflow.mjs +219 -0
  100. package/runtime/scripts/hfs/rules/doc-fresh.mjs +286 -0
  101. package/runtime/scripts/hfs/rules/docker-health.mjs +249 -0
  102. package/runtime/scripts/hfs/rules/docker.mjs +24 -7
  103. package/runtime/scripts/hfs/rules/edition.mjs +9 -8
  104. package/runtime/scripts/hfs/rules/env-parity.mjs +373 -0
  105. package/runtime/scripts/hfs/rules/env-value-shape.mjs +113 -0
  106. package/runtime/scripts/hfs/rules/event-bus.mjs +3 -2
  107. package/runtime/scripts/hfs/rules/event-tables.mjs +100 -0
  108. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +115 -15
  109. package/runtime/scripts/hfs/rules/fe-stack.mjs +57 -0
  110. package/runtime/scripts/hfs/rules/feature-files.mjs +28 -0
  111. package/runtime/scripts/hfs/rules/frontend-tree.mjs +54 -35
  112. package/runtime/scripts/hfs/rules/integration-specs.mjs +3 -2
  113. package/runtime/scripts/hfs/rules/lexicon.mjs +62 -0
  114. package/runtime/scripts/hfs/rules/lite-proof-lane.mjs +89 -0
  115. package/runtime/scripts/hfs/rules/monorepo.mjs +14 -1
  116. package/runtime/scripts/hfs/rules/pipeline.mjs +8 -1
  117. package/runtime/scripts/hfs/rules/release-provenance.mjs +211 -0
  118. package/runtime/scripts/hfs/rules/saga-delivery.mjs +66 -0
  119. package/runtime/scripts/hfs/rules/saga.mjs +8 -16
  120. package/runtime/scripts/hfs/rules/script-hygiene.mjs +151 -0
  121. package/runtime/scripts/hfs/rules/secret-custody-only.mjs +65 -0
  122. package/runtime/scripts/hfs/rules/secrets.mjs +13 -0
  123. package/runtime/scripts/hfs/rules/services.mjs +4 -4
  124. package/runtime/scripts/hfs/rules/stack-tooling.mjs +93 -0
  125. package/runtime/scripts/hfs/rules/stacks-deploy.mjs +76 -0
  126. package/runtime/scripts/hfs/rules/stacks-ingress.mjs +87 -0
  127. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +111 -0
  128. package/runtime/scripts/hfs/rules/stacks-release.mjs +366 -0
  129. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +41 -1
  130. package/runtime/scripts/hfs/rules/stacks-swarm.mjs +417 -0
  131. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +1 -1
  132. package/runtime/scripts/hfs/rules/supply-chain.mjs +167 -0
  133. package/runtime/scripts/hfs/rules/unused-symbol-flow.mjs +101 -0
  134. package/runtime/scripts/hfs/rules/unused-symbol-ledger.mjs +77 -0
  135. package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +165 -0
  136. package/runtime/scripts/hfs/rules/unused-symbol-values.mjs +103 -0
  137. package/runtime/scripts/hfs/rules/unused-symbol.mjs +320 -0
  138. package/runtime/scripts/hfs/runtime-rules/source-ast.mjs +185 -0
  139. package/runtime/scripts/hfs/runtime-rules/workflow-source.mjs +19 -0
  140. package/runtime/scripts/hfs/sealed-members.mjs +101 -0
  141. package/runtime/scripts/hfs/secret-bootstrap-crypto.mjs +200 -0
  142. package/runtime/scripts/hfs/secret-edit.mjs +170 -0
  143. package/runtime/scripts/hfs/secret-init.mjs +127 -19
  144. package/runtime/scripts/hfs/secret-sops.mjs +38 -6
  145. package/runtime/scripts/hfs/secret-store.mjs +56 -54
  146. package/runtime/scripts/hfs/secret.mjs +34 -5
  147. package/runtime/scripts/hfs/slot-imports.mjs +18 -5
  148. package/runtime/scripts/hfs/slots.mjs +1 -0
  149. package/runtime/scripts/hfs/stack-config-kit.mjs +34 -0
  150. package/runtime/scripts/hfs/stack-deploy-config.mjs +313 -0
  151. package/runtime/scripts/hfs/stack-ingress-config.mjs +237 -0
  152. package/runtime/scripts/hfs/stacks-gate.mjs +414 -0
  153. package/runtime/scripts/hfs/starcistacks-runtime.mjs +61 -0
  154. package/runtime/scripts/lib/command-tokens.mjs +126 -0
  155. package/runtime/scripts/lib/compose-values.mjs +11 -0
  156. package/runtime/scripts/lib/deployment-findings.mjs +7 -0
  157. package/runtime/scripts/lib/json-clone.mjs +61 -0
  158. package/runtime/scripts/lib/list.mjs +4 -0
  159. package/runtime/scripts/lib/runtime-example.mjs +13 -0
  160. package/runtime/scripts/lib/secret-patterns.mjs +60 -5
  161. package/runtime/scripts/lib/sql-ast.mjs +32 -0
  162. package/runtime/scripts/lib/stack-services.mjs +1 -1
  163. package/runtime/scripts/lib/swarm-stack.mjs +5 -18
  164. package/runtime/scripts/lib/trim-end.mjs +8 -0
  165. package/runtime/scripts/lib/ts-analysis.mjs +5 -2
  166. package/runtime/scripts/lib/ts-ast.mjs +27 -2
  167. package/runtime/scripts/lib/workflow-events.mjs +8 -0
  168. package/scaffold/add-app.mjs +77 -59
  169. package/scaffold/add-cli-lite.mjs +14 -6
  170. package/scaffold/add-compose.mjs +79 -0
  171. package/scaffold/add-database-lite.mjs +14 -0
  172. package/scaffold/add-fe-overlay.mjs +122 -0
  173. package/scaffold/add-fe-page.mjs +269 -0
  174. package/scaffold/add-inbox-lite.mjs +74 -42
  175. package/scaffold/add-table.mjs +110 -87
  176. package/scaffold/add.mjs +159 -44
  177. package/scaffold/app.mjs +124 -39
  178. package/scaffold/edition-gate.mjs +3 -3
  179. package/scaffold/lite-exports.mjs +2 -1
  180. package/scaffold/service.mjs +2 -2
  181. package/src/main.mjs +229 -116
  182. package/stack/age-install.mjs +37 -0
  183. package/stack/backup.mjs +100 -0
  184. package/stack/build-args.mjs +42 -0
  185. package/stack/build.mjs +173 -0
  186. package/stack/cli.mjs +130 -0
  187. package/stack/config.mjs +57 -0
  188. package/stack/context.mjs +57 -0
  189. package/stack/converge.mjs +107 -0
  190. package/stack/deploy.mjs +301 -0
  191. package/stack/digest.mjs +16 -0
  192. package/stack/docker-transport.mjs +53 -0
  193. package/stack/edge-alias.mjs +118 -0
  194. package/stack/errors.mjs +21 -0
  195. package/stack/image-scan.mjs +152 -0
  196. package/stack/ingress-create.mjs +114 -0
  197. package/stack/ingress-publish.mjs +273 -0
  198. package/stack/ingress.mjs +706 -0
  199. package/stack/jobs.mjs +51 -0
  200. package/stack/live-restore.mjs +177 -0
  201. package/stack/prepare.mjs +266 -0
  202. package/stack/private-file.mjs +22 -0
  203. package/stack/provision.mjs +90 -0
  204. package/stack/prune.mjs +46 -0
  205. package/stack/release-store.mjs +87 -0
  206. package/stack/remote.mjs +205 -0
  207. package/stack/render.mjs +149 -0
  208. package/stack/retire.mjs +47 -0
  209. package/stack/sealed.mjs +149 -0
  210. package/stack/task.mjs +42 -0
  211. package/stack/verify.mjs +99 -0
  212. package/sync/ci-commands.mjs +13 -0
  213. package/sync/index.mjs +108 -52
  214. package/sync/stack-images.mjs +127 -0
  215. package/templates/app/ci-workflows/github/workflows/ci.yml +5 -1
  216. package/templates/app/ci-workflows/github/workflows/images.yml +15 -3
  217. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +148 -0
  218. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +7 -3
  219. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +38 -6
  220. package/templates/app/ci-workflows-lite/github/workflows/images.yml +14 -2
  221. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +148 -0
  222. package/templates/app/codegen-graphql.mjs.partial +13 -2
  223. package/templates/app/codegen-lite/scripts/codegen.mjs +6 -1
  224. package/templates/app/codegen-prelude.mjs.partial +8 -0
  225. package/templates/app/gitignore +3 -0
  226. package/templates/app/gitignore-lite +3 -0
  227. package/templates/app/package-scripts/package.json +1 -0
  228. package/templates/app/package-scripts/supabase.json +7 -0
  229. package/templates/app/package-scripts-lite/package.json +1 -7
  230. package/templates/app/skeleton/.editorconfig +1 -0
  231. package/templates/app/skeleton/.gitattributes +7 -1
  232. package/templates/app/skeleton/.github/dependabot.yml +17 -0
  233. package/templates/app/skeleton/.nvmrc +1 -1
  234. package/templates/app/skeleton/.starcistacks/application-stacks.yaml +134 -1
  235. package/templates/app/skeleton/.starcistacks/dev/README.md +16 -0
  236. package/templates/app/skeleton/.starcistacks/dev/environment.json +5 -0
  237. package/templates/app/skeleton/.starcistacks/dev/infra/compose/stack.yaml +71 -0
  238. package/templates/app/skeleton/.starcistacks/dev/runtime/env/KEYS.md +14 -0
  239. package/templates/app/skeleton/.starcistacks/production/README.md +37 -0
  240. package/templates/app/skeleton/.starcistacks/production/environment.json +5 -0
  241. package/templates/app/skeleton/.starcistacks/production/infra/swarm/Caddyfile +15 -0
  242. package/templates/app/skeleton/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  243. package/templates/app/skeleton/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  244. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  245. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/images.lock.yaml +9 -0
  246. package/templates/app/skeleton/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  247. package/templates/app/skeleton/.starcistacks/production/infra/swarm/stack.yaml +142 -0
  248. package/templates/app/skeleton/.starcistacks/production/runtime/env/KEYS.md +14 -0
  249. package/templates/app/skeleton/README.md +6 -6
  250. package/templates/app/skeleton/scripts/codegen.mjs +1 -2
  251. package/templates/app/skeleton-lite/.editorconfig +16 -0
  252. package/templates/app/skeleton-lite/.gitattributes +8 -0
  253. package/templates/app/skeleton-lite/.github/dependabot.yml +17 -0
  254. package/templates/app/skeleton-lite/.nvmrc +1 -0
  255. package/templates/app/skeleton-lite/.sops.yaml +3 -2
  256. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +178 -1
  257. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +20 -0
  258. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +2 -1
  259. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/Caddyfile +16 -0
  260. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/owner-actions.md +11 -0
  261. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +7 -0
  262. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/current.yaml +6 -0
  263. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/images.lock.yaml +8 -0
  264. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/releases/previous.yaml +6 -0
  265. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +120 -0
  266. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -0
  267. package/templates/app/skeleton-lite/.starcistacks/production/README.md +38 -0
  268. package/templates/app/skeleton-lite/.starcistacks/production/environment.json +6 -0
  269. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/Caddyfile +11 -0
  270. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/owner-actions.md +11 -0
  271. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +7 -0
  272. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/current.yaml +6 -0
  273. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/images.lock.yaml +8 -0
  274. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/releases/previous.yaml +6 -0
  275. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +121 -0
  276. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +21 -0
  277. package/templates/app/skeleton-lite/.starciwork/brand/index.yaml +12 -0
  278. package/templates/app/skeleton-lite/README.md +5 -3
  279. package/templates/app/skeleton-lite/supabase/config.toml +8 -1
  280. package/templates/app/skeleton-lite/supabase/migrations/__timestamp___baseline.sql +15 -6
  281. package/templates/app/upgrade-full/.starcistacks/dev/README.md +13 -7
  282. package/templates/be/image/worker/Dockerfile +1 -1
  283. package/templates/be/inbox/database-client.spec.ts.tpl +17 -18
  284. package/templates/be/inbox/database-client.ts.tpl +12 -11
  285. package/templates/be/inbox/delivery-recorded.event.ts.tpl +36 -0
  286. package/templates/be/inbox/event-index.ts.tpl +1 -0
  287. package/templates/be/inbox/events.json.tpl +10 -0
  288. package/templates/be/inbox/migration.sql.tpl +1 -0
  289. package/templates/be/inbox/platform/event-bus/event-bus.contracts.ts.tpl +42 -0
  290. package/templates/be/inbox/platform/event-bus/event-bus.decorators.ts.tpl +9 -0
  291. package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +6 -0
  292. package/templates/be/inbox/platform/event-bus/event-bus.module.ts.tpl +19 -0
  293. package/templates/be/inbox/platform/event-bus/event-bus.options.ts.tpl +2 -0
  294. package/templates/be/inbox/platform/event-bus/event-bus.port.ts.tpl +16 -0
  295. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +45 -0
  296. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +15 -0
  297. package/templates/be/inbox/platform/event-bus/index.ts.tpl +5 -0
  298. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +5 -0
  299. package/templates/be/inbox/platform/inbox/inbox.decorators.ts.tpl +9 -0
  300. package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +6 -0
  301. package/templates/be/inbox/platform/inbox/inbox.module.ts.tpl +19 -0
  302. package/templates/be/inbox/platform/inbox/inbox.options.ts.tpl +2 -0
  303. package/templates/be/inbox/platform/inbox/inbox.port.ts.tpl +10 -0
  304. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +41 -0
  305. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +19 -0
  306. package/templates/be/inbox/platform/inbox/index.ts.tpl +3 -0
  307. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +7 -0
  308. package/templates/be/inbox/platform/migration.sql.tpl +48 -0
  309. package/templates/be/inbox/service.spec.ts.tpl +42 -9
  310. package/templates/be/inbox/service.ts.tpl +23 -8
  311. package/templates/be/inbox/webhook-door.spec.ts.tpl +1 -1
  312. package/templates/be/inbox/webhook-door.ts.tpl +2 -2
  313. package/templates/be/patterns/api/feature.module-definition.ts.tpl +6 -0
  314. package/templates/be/patterns/api/feature.module.ts.tpl +2 -1
  315. package/templates/be/patterns/api/feature.options.ts.tpl +2 -0
  316. package/templates/be/patterns/api/graphql.module-definition.ts.tpl +6 -0
  317. package/templates/be/patterns/api/graphql.module.ts.tpl +3 -2
  318. package/templates/be/patterns/api/graphql.options.ts.tpl +2 -0
  319. package/templates/be/patterns/cli/group.module-definition.ts.tpl +6 -0
  320. package/templates/be/patterns/cli/group.module.ts.tpl +2 -1
  321. package/templates/be/patterns/cli/group.options.ts.tpl +2 -0
  322. package/templates/be/patterns/event-bus/platform/errors/event-bus.error.ts.tpl +0 -3
  323. package/templates/be/patterns/event-bus/platform/messages/event-bus.messages.ts.tpl +0 -2
  324. package/templates/be/patterns/jobs/job.module-definition.ts.tpl +6 -0
  325. package/templates/be/patterns/jobs/job.module.ts.tpl +2 -1
  326. package/templates/be/patterns/jobs/job.options.ts.tpl +2 -0
  327. package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +6 -0
  328. package/templates/be/patterns/jobs/job.queue.module.ts.tpl +6 -3
  329. package/templates/be/patterns/jobs/job.queue.options.ts.tpl +2 -0
  330. package/templates/be/patterns/jobs/platform/fenced.processor.spec.ts.tpl +3 -1
  331. package/templates/be/patterns/outbox/platform/outbox-relay.policy.spec.ts.tpl +25 -9
  332. package/templates/be/patterns/queues/platform/queue.config.ts.tpl +1 -1
  333. package/templates/be/patterns/queues/platform/queue.index.ts.tpl +1 -1
  334. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +2 -2
  335. package/templates/be/patterns/queues/platform/queue.module.ts.tpl +11 -0
  336. package/templates/be/patterns/queues/platform/queue.options.ts.tpl +10 -0
  337. package/templates/be/patterns/queues/queue.module-definition.ts.tpl +6 -0
  338. package/templates/be/patterns/queues/queue.module.ts.tpl +2 -1
  339. package/templates/be/patterns/queues/queue.options.ts.tpl +2 -0
  340. package/templates/be/patterns/reactors/message.module-definition.ts.tpl +6 -0
  341. package/templates/be/patterns/reactors/message.module.ts.tpl +6 -3
  342. package/templates/be/patterns/reactors/message.options.ts.tpl +2 -0
  343. package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +6 -0
  344. package/templates/be/patterns/reactors/reactor.module.ts.tpl +2 -1
  345. package/templates/be/patterns/reactors/reactor.options.ts.tpl +2 -0
  346. package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +188 -0
  347. package/templates/be/patterns/realtime/channel.gateway.ts.tpl +22 -0
  348. package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +6 -0
  349. package/templates/be/patterns/realtime/channel.graphql.module.ts.tpl +2 -1
  350. package/templates/be/patterns/realtime/channel.graphql.options.ts.tpl +2 -0
  351. package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +6 -0
  352. package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +110 -0
  353. package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +7 -0
  354. package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +6 -0
  355. package/templates/be/patterns/saga/saga.message.module.ts.tpl +6 -3
  356. package/templates/be/patterns/saga/saga.message.options.ts.tpl +2 -0
  357. package/templates/be/patterns/saga/saga.module-definition.ts.tpl +6 -0
  358. package/templates/be/patterns/saga/saga.module.ts.tpl +2 -1
  359. package/templates/be/patterns/saga/saga.options.ts.tpl +2 -0
  360. package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +6 -0
  361. package/templates/be/patterns/webhooks/webhook.http.module.ts.tpl +2 -1
  362. package/templates/be/patterns/webhooks/webhook.http.options.ts.tpl +2 -0
  363. package/templates/be/platform/graphql/localize-errors.mapper.spec.ts +80 -26
  364. package/templates/be/platform/graphql/localize-errors.mapper.ts +2 -5
  365. package/templates/be/skeleton/apps/__app__/src/app.module.ts +11 -9
  366. package/templates/be/skeleton/apps/__app__/src/compose/api.ts +4 -0
  367. package/templates/be/skeleton/apps/__app__/src/compose/domain.ts +11 -0
  368. package/templates/be/skeleton/apps/__app__/src/compose/platform.ts +2 -0
  369. package/templates/be/skeleton/apps/__app__/src/compose/system.ts +5 -0
  370. package/templates/be/skeleton/apps/__app__/src/compose/webhooks.ts +4 -0
  371. package/templates/be/skeleton/apps/__app__/src/main.ts +15 -1
  372. package/templates/be/skeleton/apps/cli/src/app.module.ts +3 -13
  373. package/templates/be/skeleton/apps/cli/src/cli.options.ts +6 -14
  374. package/templates/be/skeleton/apps/cli/src/compose/cli.ts +5 -0
  375. package/templates/be/skeleton/apps/cli/src/compose/platform.ts +24 -0
  376. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.contracts.ts +2 -0
  377. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.handler.ts +22 -0
  378. package/templates/be/skeleton/src/features/api/system-health/application/check-readiness.query.ts +11 -0
  379. package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +6 -0
  380. package/templates/be/skeleton/src/features/api/system-health/system-health.module.ts +5 -3
  381. package/templates/be/skeleton/src/features/api/system-health/system-health.options.ts +2 -0
  382. package/templates/be/skeleton/src/features/api/system-health/transport/http/ready.controller.ts +19 -0
  383. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +6 -0
  384. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module.ts +8 -3
  385. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.options.ts +2 -0
  386. package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +6 -0
  387. package/templates/be/skeleton/src/features/cli/cli.module.ts +3 -2
  388. package/templates/be/skeleton/src/features/cli/cli.options.ts +2 -0
  389. package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +6 -0
  390. package/templates/be/skeleton/src/features/cli/migrate/migrate.module.ts +2 -1
  391. package/templates/be/skeleton/src/features/cli/migrate/migrate.options.ts +2 -0
  392. package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +6 -0
  393. package/templates/be/skeleton/src/features/cli/seed/seed.module.ts +2 -1
  394. package/templates/be/skeleton/src/features/cli/seed/seed.options.ts +2 -0
  395. package/templates/be/skeleton/src/features/cli/seed/subs/run.cli.spec.ts +5 -6
  396. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.spec.ts +56 -4
  397. package/templates/be/skeleton/src/modules/domain/identity/admission.policy.ts +36 -5
  398. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.spec.ts +39 -8
  399. package/templates/be/skeleton/src/modules/domain/identity/auth.guard.ts +14 -8
  400. package/templates/be/skeleton/src/modules/domain/identity/credential.contracts.ts +26 -0
  401. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.spec.ts +119 -0
  402. package/templates/be/skeleton/src/modules/domain/identity/credential.guard.ts +43 -0
  403. package/templates/be/skeleton/src/modules/domain/identity/identity.contracts.ts +16 -0
  404. package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -1
  405. package/templates/be/skeleton/src/modules/domain/identity/identity.module.ts +22 -4
  406. package/templates/be/skeleton/src/modules/domain/identity/identity.options.ts +8 -2
  407. package/templates/be/skeleton/src/modules/domain/identity/index.ts +1 -0
  408. package/templates/be/skeleton/src/modules/platform/database/connection-source.client.spec.ts +11 -4
  409. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.spec.ts +32 -0
  410. package/templates/be/skeleton/src/modules/platform/database/database-probe.service.ts +14 -0
  411. package/templates/be/skeleton/src/modules/platform/database/database.module.ts +7 -1
  412. package/templates/be/skeleton/src/modules/platform/database/index.ts +2 -0
  413. package/templates/be/skeleton/src/modules/platform/database/primary.decorators.ts +8 -0
  414. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.spec.ts +25 -6
  415. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +16 -6
  416. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.spec.ts +130 -0
  417. package/templates/be/skeleton/src/modules/platform/http-security/edge-security.service.ts +58 -0
  418. package/templates/be/skeleton/src/modules/platform/http-security/errors/http-security.error.ts +3 -0
  419. package/templates/be/skeleton/src/modules/platform/http-security/execution-request.mapper.spec.ts +16 -4
  420. package/templates/be/skeleton/src/modules/platform/http-security/http-security.config.ts +16 -10
  421. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module.ts +3 -1
  422. package/templates/be/skeleton/src/modules/platform/http-security/http-security.options.ts +2 -0
  423. package/templates/be/skeleton/src/modules/platform/http-security/index.ts +5 -0
  424. package/templates/be/skeleton/src/modules/platform/http-security/messages/http-security.messages.ts +12 -5
  425. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +13 -8
  426. package/templates/be/skeleton/src/modules/platform/http-security/readiness.contracts.ts +5 -0
  427. package/templates/be/skeleton/src/modules/platform/http-security/readiness.decorators.ts +7 -0
  428. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.spec.ts +86 -0
  429. package/templates/be/skeleton/src/modules/platform/http-security/readiness.service.ts +39 -0
  430. package/templates/be/skeleton/src/modules/platform/http-security/webhook-signature.service.spec.ts +139 -131
  431. package/templates/be/skeleton/src/modules/platform/i18n/index.ts +1 -1
  432. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.spec.ts +91 -67
  433. package/templates/be/skeleton/src/modules/platform/logging/json-logger.service.ts +5 -4
  434. package/templates/be/skeleton/src/modules/platform/logging/logging.log-events.ts +0 -2
  435. package/templates/be/skeleton/src/modules/platform/primitives/outcome.mapper.spec.ts +20 -6
  436. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +16 -4
  437. package/templates/be/skeleton/src/modules/platform/retry/index.ts +3 -0
  438. package/templates/be/skeleton/src/modules/platform/retry/retry.decorators.ts +9 -0
  439. package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +7 -0
  440. package/templates/be/skeleton/src/modules/platform/retry/retry.module.ts +19 -0
  441. package/templates/be/skeleton/src/modules/platform/retry/retry.options.ts +2 -0
  442. package/templates/be/skeleton/src/modules/platform/retry/retry.port.ts +11 -0
  443. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.spec.ts +36 -0
  444. package/templates/be/skeleton/src/modules/platform/retry/system-timer.service.ts +10 -0
  445. package/templates/be/skeleton/src/tests/contract/http-security/http-security.contract-spec.ts +54 -0
  446. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +14 -18
  447. package/templates/be/skeleton-lite/apps/api/src/compose/api.ts +4 -0
  448. package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +10 -0
  449. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +16 -0
  450. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +2 -0
  451. package/templates/be/skeleton-lite/apps/api/src/compose/system.ts +5 -0
  452. package/templates/be/skeleton-lite/apps/api/src/compose/webhooks.ts +4 -0
  453. package/templates/be/skeleton-lite/apps/api/src/main.ts +2 -3
  454. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +12 -0
  455. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.spec.ts +71 -5
  456. package/templates/be/skeleton-lite/src/modules/domain/identity/admission.policy.ts +29 -2
  457. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +68 -15
  458. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +14 -9
  459. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +9 -2
  460. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +16 -1
  461. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +19 -4
  462. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +5 -1
  463. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +2 -1
  464. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -3
  465. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +17 -5
  466. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +24 -13
  467. package/templates/be/skeleton-lite/src/modules/platform/database/database.port.ts +5 -0
  468. package/templates/be/skeleton-lite/src/modules/platform/database/errors/database.error.ts +16 -0
  469. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +3 -0
  470. package/templates/be/skeleton-lite/src/modules/platform/http-security/http-security.module.ts +5 -2
  471. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +4 -1
  472. package/templates/be/skeleton-lite/src/modules/platform/i18n/index.ts +1 -1
  473. package/templates/be/skeleton-lite/src/modules/platform/logging/logging.log-events.ts +7 -0
  474. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +29 -0
  475. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +16 -0
  476. package/templates/be/table/api/feature.module-definition.ts.tpl +6 -0
  477. package/templates/be/table/api/feature.module.ts.tpl +2 -1
  478. package/templates/be/table/api/feature.options.ts.tpl +2 -0
  479. package/templates/be/table/api/graphql.module-definition.ts.tpl +6 -0
  480. package/templates/be/table/api/graphql.module.ts.tpl +3 -2
  481. package/templates/be/table/api/graphql.options.ts.tpl +2 -0
  482. package/templates/be/table/migration.sql.tpl +1 -0
  483. package/templates/be/upgrade-full/apps/cli/src/app.module.ts +4 -6
  484. package/templates/be/upgrade-full/apps/cli/src/cli.env.ts +7 -0
  485. package/templates/be/upgrade-full/apps/cli/src/compose/cli.ts +5 -0
  486. package/templates/be/upgrade-full/apps/cli/src/compose/platform.ts +9 -0
  487. package/templates/be/upgrade-full/apps/cli/src/main.ts +3 -0
  488. package/templates/be/upgrade-full/src/features/cli/cli.module.ts +3 -2
  489. package/templates/fe/auth/supabase-client.ts.tpl +34 -0
  490. package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +11 -0
  491. package/templates/fe/skeleton/apps/__app__/src/components/blocks/SignInForm/index.tsx +6 -1
  492. package/templates/fe/skeleton/apps/__app__/src/components/leaves/Icon/index.tsx +4 -0
  493. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/component.tsx +31 -0
  494. package/templates/fe/skeleton/apps/__app__/src/features/overlays/AppGuideOverlay/index.tsx +24 -0
  495. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSession.ts +6 -1
  496. package/templates/fe/skeleton/apps/__app__/src/hooks/auth/useSignOut.ts +3 -4
  497. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/index.ts +1 -0
  498. package/templates/fe/skeleton/apps/__app__/src/hooks/zustand/appGuide/store.ts +15 -0
  499. package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +18 -0
  500. package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +19 -0
  501. package/templates/fe/skeleton/apps/app/src/features/pages/AppHomePage/index.tsx +7 -1
  502. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/en.json +57 -0
  503. package/templates/fe/skeleton/apps/app/src/modules/i18n/messages/vi.json +6 -0
  504. package/templates/fe/skeleton/apps/landing/src/modules/i18n/messages/en.json +31 -0
  505. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +1 -1
  506. package/templates/fe/skeleton/packages/__project__-ui/src/index.ts +1 -0
  507. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +1 -34
  508. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +7 -1
  509. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +57 -0
  510. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +30 -24
  511. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +1 -1
  512. package/templates/fe/transport/graphql/clients/clients/auth.ts +1 -1
  513. package/templates/fe/transport/graphql/clients/links/locale.ts +2 -2
  514. package/upgrade/check.mjs +3 -1
  515. package/upgrade/index.mjs +53 -40
@@ -8,6 +8,7 @@ create table public.{{tableSql}} (
8
8
  create index {{table}}_owner_id_idx on public.{{tableSql}} (owner_id);
9
9
 
10
10
  alter table public.{{tableSql}} enable row level security;
11
+ alter table public.{{tableSql}} force row level security;
11
12
 
12
13
  create trigger {{table}}_set_updated_at
13
14
  before update on public.{{tableSql}}
@@ -1,13 +1,11 @@
1
1
  import { Module } from "@nestjs/common";
2
- import { CliModule } from "@features/cli";
3
- import { ClockModule } from "@modules/platform/clock";
4
- import { LoggingModule } from "@modules/platform/logging";
2
+ import { cliModules } from "./compose/cli";
3
+ import { platformModules } from "./compose/platform";
5
4
 
6
5
  @Module({
7
6
  imports: [
8
- ClockModule.register({ isGlobal: true }),
9
- LoggingModule.register({ isGlobal: true }),
10
- CliModule,
7
+ ...platformModules(),
8
+ ...cliModules(),
11
9
  ],
12
10
  })
13
11
  /** The cli composition root; database schema changes remain delegated to the Supabase CLI. */
@@ -0,0 +1,7 @@
1
+ import type { EnvSource } from "@modules/platform/config"
2
+
3
+ /** This command app delegates migration configuration to the managed Supabase CLI. */
4
+ export interface CliEnv {}
5
+
6
+ /** Keeps the command app's environment boundary explicit while no command needs app configuration. */
7
+ export const readCliEnv = (_env: EnvSource): CliEnv => ({})
@@ -0,0 +1,5 @@
1
+ import type { DynamicModule, Type } from "@nestjs/common"
2
+ import { CliModule } from "@features/cli"
3
+
4
+ /** The command groups of the upgraded CLI app. */
5
+ export const cliModules = (): Array<Type<unknown> | DynamicModule> => [CliModule.register({})]
@@ -0,0 +1,9 @@
1
+ import type { DynamicModule } from "@nestjs/common"
2
+ import { ClockModule } from "@modules/platform/clock"
3
+ import { LoggingModule } from "@modules/platform/logging"
4
+
5
+ /** The shared clock and logger; migrations retain their Supabase CLI authority. */
6
+ export const platformModules = (): Array<DynamicModule> => [
7
+ ClockModule.register({ isGlobal: true }),
8
+ LoggingModule.register({ isGlobal: true }),
9
+ ]
@@ -1,11 +1,14 @@
1
1
  import "reflect-metadata";
2
2
  import { CommandFactory } from "nest-commander";
3
3
  import { SystemClockService } from "@modules/platform/clock";
4
+ import { EnvSource } from "@modules/platform/config";
4
5
  import { createJsonLogger, LoggingLogEvent } from "@modules/platform/logging";
5
6
  import { AppModule } from "./app.module";
7
+ import { readCliEnv } from "./cli.env";
6
8
 
7
9
  /** The one-off command app. Its migrate command delegates schema authority to the Supabase CLI. */
8
10
  async function bootstrap(): Promise<void> {
11
+ readCliEnv(EnvSource.fromProcess());
9
12
  const logger = createJsonLogger(new SystemClockService());
10
13
  await CommandFactory.run(AppModule, {
11
14
  logger: false,
@@ -1,6 +1,7 @@
1
1
  import { Module } from "@nestjs/common";
2
+ import { ConfigurableModuleClass } from "./cli.module-definition";
2
3
  import { MigrateModule } from "./migrate/migrate.module";
3
4
 
4
- @Module({ imports: [MigrateModule] })
5
+ @Module({ imports: [MigrateModule.register({})] })
5
6
  /** The cli feature root of the upgraded app. */
6
- export class CliModule {}
7
+ export class CliModule extends ConfigurableModuleClass {}
@@ -0,0 +1,34 @@
1
+ const sessionOf = (session: Session | null): AuthSession | undefined =>
2
+ session === null
3
+ ? undefined
4
+ : { userId: session.user.id, ...(session.user.email === undefined ? {} : { email: session.user.email }) }
5
+
6
+ /** A Supabase Auth error as the provider reports it: its HTTP status, its code and its message. */
7
+ type ProviderError = { readonly status?: number; readonly code?: string; readonly message: string }
8
+
9
+ /** The failure kind of a Supabase Auth error: refused credentials, rejected input, or the provider unavailable. */
10
+ const kindOf = (status: number | undefined): string => {
11
+ if (status === 400 || status === 401 || status === 403) return "refused"
12
+ return status === 422 ? "invalid" : "unavailable"
13
+ }
14
+
15
+ /** The failure of a Supabase Auth call, or undefined when it succeeded. */
16
+ const failureOf = (error: ProviderError | null): AuthFailure | undefined =>
17
+ error === null
18
+ ? undefined
19
+ : { code: error.code ?? String(error.status ?? "auth"), kind: kindOf(error.status), message: error.message }
20
+
21
+ /** The Supabase Auth adapter shared by browser and cookie-backed PKCE flows. */
22
+ export const authClient: AuthClient = {
23
+ session: async () => sessionOf((await supabase().auth.getSession()).data.session),
24
+ accessToken: async () => (await supabase().auth.getSession()).data.session?.access_token,
25
+ refreshAccessToken: async () => (await supabase().auth.refreshSession()).data.session?.access_token,
26
+ signInWithPassword: async (email, password) =>
27
+ failureOf((await supabase().auth.signInWithPassword({ email, password })).error),
28
+ signOut: async () => failureOf((await supabase().auth.signOut()).error),
29
+ // Supabase reports INITIAL_SESSION on subscribe, then every change: the subscription alone keeps the session current.
30
+ onChange: (listener) => {
31
+ const { data } = supabase().auth.onAuthStateChange((_event, session) => listener(sessionOf(session)))
32
+ return () => data.subscription.unsubscribe()
33
+ },
34
+ }
@@ -0,0 +1,11 @@
1
+ import { NextResponse } from "next/server"
2
+ import { exchangeAuthCode } from "@/modules/auth/server"
3
+ import { APP_ROUTES } from "@/modules/routes"
4
+
5
+ /** The opt-in Supabase PKCE callback only exchanges the code and redirects home. */
6
+ export async function GET(request: Request): Promise<NextResponse> {
7
+ const url = new URL(request.url)
8
+ const code = url.searchParams.get("code")
9
+ if (code) await exchangeAuthCode(code)
10
+ return NextResponse.redirect(new URL(APP_ROUTES.home, url.origin))
11
+ }
@@ -24,6 +24,11 @@ export const SignInForm = () => {
24
24
  const t = useTranslations("app.signIn")
25
25
  const { failure, pending, signIn, reject } = useSignIn()
26
26
  if (session.status !== "signed-out") return null
27
+ const failureMessages = {
28
+ refused: t("refused"),
29
+ invalid: t("invalid"),
30
+ unavailable: t("unavailable"),
31
+ } satisfies Record<SignInFailure, string>
27
32
  const submit = (input: FormData) => {
28
33
  const credentials = credentialsOf(input)
29
34
  if (credentials === undefined) reject()
@@ -37,7 +42,7 @@ export const SignInForm = () => {
37
42
  emailLabel: t("email"),
38
43
  passwordLabel: t("password"),
39
44
  submitLabel: pending ? t("working") : t("submit"),
40
- failure: failure === undefined ? undefined : t(failure),
45
+ failure: failure === undefined ? undefined : failureMessages[failure],
41
46
  }}
42
47
  on={{ submit }}
43
48
  />
@@ -0,0 +1,4 @@
1
+ import { CircleIcon } from "@starci/heroicons/24/outline"
2
+
3
+ /** The decorative completion ring, delegated to the custom Heroicons cut. Visible copy supplies its meaning. */
4
+ export const Icon = () => <CircleIcon width={24} height={24} aria-hidden="true" />
@@ -0,0 +1,31 @@
1
+ import { Button, Dialog, Text } from "@starci/grammar/common"
2
+ import { Icon } from "@/components/leaves/Icon"
3
+
4
+ /** The guide's copy, local open state and action supplied by the connected overlay. */
5
+ export type AppGuideOverlayBaseProps = {
6
+ readonly props: {
7
+ readonly isOpen: boolean
8
+ readonly triggerLabel: string
9
+ readonly title: string
10
+ readonly description: string
11
+ readonly closeLabel: string
12
+ }
13
+ readonly on: { readonly openChange: (isOpen: boolean) => void }
14
+ }
15
+
16
+ /** A keyboard-accessible grammar dialog whose trigger and dismiss actions update the same local state. */
17
+ export const AppGuideOverlayBase = (props: AppGuideOverlayBaseProps) => (
18
+ <Dialog
19
+ isOpen={props.props.isOpen}
20
+ onOpenChange={props.on.openChange}
21
+ title={props.props.title}
22
+ closeLabel={props.props.closeLabel}
23
+ trigger={
24
+ <Button variant="secondary" startContent={<Icon />}>
25
+ {props.props.triggerLabel}
26
+ </Button>
27
+ }
28
+ >
29
+ <Text>{props.props.description}</Text>
30
+ </Dialog>
31
+ )
@@ -0,0 +1,24 @@
1
+ "use client"
2
+
3
+ import { useTranslations } from "next-intl"
4
+ import { useAppGuideStore } from "@/hooks/zustand/appGuide"
5
+ import { AppGuideOverlayBase } from "./component"
6
+
7
+ /** The home guide connects localized copy to its feature-local zustand store. */
8
+ export const AppGuideOverlay = () => {
9
+ const t = useTranslations("app.guide")
10
+ const isOpen = useAppGuideStore((state) => state.isOpen)
11
+ const setOpen = useAppGuideStore((state) => state.setOpen)
12
+ return (
13
+ <AppGuideOverlayBase
14
+ props={{
15
+ isOpen,
16
+ triggerLabel: t("open"),
17
+ title: t("title"),
18
+ description: t("description"),
19
+ closeLabel: t("close"),
20
+ }}
21
+ on={{ openChange: setOpen }}
22
+ />
23
+ )
24
+ }
@@ -1,6 +1,11 @@
1
1
  "use client"
2
2
 
3
+ import { useDebugValue } from "react"
3
4
  import { useAppSelector } from "@/redux"
4
5
 
5
6
  /** The reader's session as the app shares it (redux, kept by the auth session effect): unknown, signed in or signed out. */
6
- export const useSession = () => useAppSelector((state) => state.session)
7
+ export const useSession = () => {
8
+ const session = useAppSelector((state) => state.session)
9
+ useDebugValue(session.status)
10
+ return session
11
+ }
@@ -1,16 +1,15 @@
1
1
  "use client"
2
2
 
3
3
  import { useTransition } from "react"
4
- import type { GraphQLResponse } from "@/modules/api"
5
4
  import { authClient } from "@/modules/auth"
6
5
  import type { AuthFailure } from "@/modules/auth"
7
6
  import { useGraphQLWithToast } from "@/modules/toast"
8
7
 
9
8
  /** The app's one outcome of an auth call, so modules/toast reports it like any other write. */
10
- const responseOf = (failure: AuthFailure | undefined): GraphQLResponse<null> =>
9
+ const responseOf = (failure: AuthFailure | undefined) =>
11
10
  failure === undefined
12
- ? { success: true, data: null }
13
- : { success: false, error: failure.code, kind: failure.kind, message: failure.message }
11
+ ? { success: true as const, data: null }
12
+ : { success: false as const, error: failure.code, kind: failure.kind, message: failure.message }
14
13
 
15
14
  /**
16
15
  * Ends the reader's session through modules/auth: whether the request runs and the sign-out itself. A failure is reported by
@@ -0,0 +1 @@
1
+ export { useAppGuideStore } from "./store"
@@ -0,0 +1,15 @@
1
+ "use client"
2
+
3
+ import { create } from "zustand"
4
+
5
+ /** The home guide's local UI state; it carries no session or server data. */
6
+ type AppGuideState = {
7
+ readonly isOpen: boolean
8
+ readonly setOpen: (isOpen: boolean) => void
9
+ }
10
+
11
+ /** One feature-local store, shared by the guide trigger and its controlled dialog. */
12
+ export const useAppGuideStore = create<AppGuideState>()((set) => ({
13
+ isOpen: false,
14
+ setOpen: (isOpen) => set({ isOpen }),
15
+ }))
@@ -0,0 +1,18 @@
1
+ import { createBrowserClient } from "@supabase/ssr"
2
+ import type { Session, SupabaseClient } from "@supabase/supabase-js"
3
+ import { supabaseAnonKey, supabaseUrl } from "@/modules/config"
4
+ import type { AuthClient, AuthFailure, AuthSession } from "./contracts"
5
+
6
+ let client: SupabaseClient | undefined
7
+
8
+ /**
9
+ * The cookie-backed browser client of the opt-in server PKCE flow, used for Supabase Auth only.
10
+ * The callback exchanges the code; this client shares its verifier and session cookies. It never calls `.from()`, `.rpc()` or storage:
11
+ * data goes through modules/api to the back end, which verifies the access token.
12
+ */
13
+ const supabase = (): SupabaseClient =>
14
+ (client ??= createBrowserClient(supabaseUrl(), supabaseAnonKey(), {
15
+ auth: { flowType: "pkce", detectSessionInUrl: false, persistSession: true, autoRefreshToken: true },
16
+ }))
17
+
18
+ // {{> fe/auth/supabase-client.ts.tpl}}
@@ -0,0 +1,19 @@
1
+ import "server-only"
2
+ import { createServerClient } from "@supabase/ssr"
3
+ import { cookies } from "next/headers"
4
+ import { supabaseAnonKey, supabaseUrl } from "@/modules/config"
5
+
6
+ /** The server auth owner reads the PKCE verifier and persists the exchanged session in cookies. */
7
+ export async function exchangeAuthCode(code: string): Promise<void> {
8
+ const cookieStore = await cookies()
9
+ const client = createServerClient(supabaseUrl(), supabaseAnonKey(), {
10
+ cookies: {
11
+ getAll: () => cookieStore.getAll(),
12
+ setAll: (values) => {
13
+ for (const { name, value, options } of values) cookieStore.set(name, value, options)
14
+ },
15
+ },
16
+ })
17
+ const { error } = await client.auth.exchangeCodeForSession(code)
18
+ if (error) throw error
19
+ }
@@ -1,5 +1,6 @@
1
1
  import type { Metadata } from "next"
2
2
  import { getTranslations } from "next-intl/server"
3
+ import { AppGuideOverlay } from "@/features/overlays/AppGuideOverlay"
3
4
  import { AppHomePageBase } from "./component"
4
5
 
5
6
  /** The document title of the home page, from the catalog of the requested locale. */
@@ -11,5 +12,10 @@ export const appHomeMetadata = async (): Promise<Metadata> => {
11
12
  /** The first page of the product app: a server component; the session surface it draws runs on the client. */
12
13
  export const AppHomePage = async () => {
13
14
  const t = await getTranslations("app.home")
14
- return <AppHomePageBase props={{ title: t("title") }} />
15
+ return (
16
+ <>
17
+ <AppHomePageBase props={{ title: t("title") }} />
18
+ <AppGuideOverlay />
19
+ </>
20
+ )
15
21
  }
@@ -0,0 +1,57 @@
1
+ {
2
+ "app": {
3
+ "title": "{{project}}",
4
+ "shell": {
5
+ "brand": "{{project}}"
6
+ },
7
+ "home": {
8
+ "title": "Home"
9
+ },
10
+ "signIn": {
11
+ "title": "Sign in",
12
+ "email": "Email",
13
+ "password": "Password",
14
+ "submit": "Continue",
15
+ "working": "Signing in",
16
+ "refused": "The credentials were refused.",
17
+ "invalid": "Enter a valid email and password.",
18
+ "unavailable": "Sign-in is unavailable. Try again shortly."
19
+ },
20
+ "signOut": {
21
+ "signedIn": "Signed in",
22
+ "submit": "Sign out",
23
+ "working": "Signing out"
24
+ },
25
+ "toast": {
26
+ "successTitle": "Done",
27
+ "errorTitle": "Error",
28
+ "unauthorizedTitle": "Not signed in",
29
+ "unauthorizedDescription": "Sign in to continue.",
30
+ "defaultSuccess": "The change was saved.",
31
+ "defaultError": "Something went wrong."
32
+ },
33
+ "loading": {
34
+ "message": "Loading"
35
+ },
36
+ "notFound": {
37
+ "title": "Page not found",
38
+ "home": "Return home"
39
+ },
40
+ "errors": {
41
+ "page": {
42
+ "title": "Something went wrong",
43
+ "retry": "Try again"
44
+ },
45
+ "global": {
46
+ "title": "The application could not continue",
47
+ "retry": "Try again"
48
+ }
49
+ },
50
+ "guide": {
51
+ "open": "View guide",
52
+ "title": "Getting started",
53
+ "description": "Sign in to use the application. You can close this guide and open it again at any time.",
54
+ "close": "Close"
55
+ }
56
+ }
57
+ }
@@ -46,6 +46,12 @@
46
46
  "title": "Đã xảy ra lỗi nghiêm trọng",
47
47
  "retry": "Thử lại"
48
48
  }
49
+ },
50
+ "guide": {
51
+ "open": "Xem hướng dẫn",
52
+ "title": "Bắt đầu",
53
+ "description": "Đăng nhập để sử dụng ứng dụng. Bạn có thể đóng hướng dẫn này và mở lại bất cứ lúc nào.",
54
+ "close": "Đóng"
49
55
  }
50
56
  }
51
57
  }
@@ -0,0 +1,31 @@
1
+ {
2
+ "landing": {
3
+ "title": "{{project}}",
4
+ "description": "The introduction to {{project}}.",
5
+ "shell": {
6
+ "brand": "{{project}}"
7
+ },
8
+ "home": {
9
+ "title": "Welcome to {{project}}",
10
+ "lede": "Everything you need is in the application.",
11
+ "openApp": "Open the application"
12
+ },
13
+ "loading": {
14
+ "message": "Loading"
15
+ },
16
+ "notFound": {
17
+ "title": "Page not found",
18
+ "home": "Return home"
19
+ },
20
+ "errors": {
21
+ "page": {
22
+ "title": "Something went wrong",
23
+ "retry": "Try again"
24
+ },
25
+ "global": {
26
+ "title": "The application could not continue",
27
+ "retry": "Try again"
28
+ }
29
+ }
30
+ }
31
+ }
@@ -1,7 +1,7 @@
1
1
  import { defineRouting } from "next-intl/routing"
2
2
 
3
3
  /** The locales every app of the product serves; every catalog of an app has the same keys in each. */
4
- const LOCALES = ["vi"] as const
4
+ const LOCALES = ["vi", "en"] as const
5
5
 
6
6
  /** The locale served at the unprefixed path, and the language of a document no locale reached (the global error boundary). */
7
7
  export const DEFAULT_LOCALE = "vi"
@@ -4,3 +4,4 @@ export { LoadingNotice } from "./composites/LoadingNotice"
4
4
  export { LocaleShell } from "./composites/LocaleShell"
5
5
  export { NotFoundNotice } from "./composites/NotFoundNotice"
6
6
  export { SiteShell } from "./composites/SiteShell"
7
+ export { Icon } from "./leaves/Icon"
@@ -15,37 +15,4 @@ const supabase = (): SupabaseClient =>
15
15
  auth: { flowType: "pkce", detectSessionInUrl: true, persistSession: true, autoRefreshToken: true },
16
16
  }))
17
17
 
18
- const sessionOf = (session: Session | null): AuthSession | undefined =>
19
- session === null
20
- ? undefined
21
- : { userId: session.user.id, ...(session.user.email === undefined ? {} : { email: session.user.email }) }
22
-
23
- /** A Supabase Auth error as the provider reports it: its HTTP status, its code and its message. */
24
- type ProviderError = { readonly status?: number; readonly code?: string; readonly message: string }
25
-
26
- /** The failure kind of a Supabase Auth error: refused credentials, rejected input, or the provider unavailable. */
27
- const kindOf = (status: number | undefined): string => {
28
- if (status === 400 || status === 401 || status === 403) return "refused"
29
- return status === 422 ? "invalid" : "unavailable"
30
- }
31
-
32
- /** The failure of a Supabase Auth call, or undefined when it succeeded. */
33
- const failureOf = (error: ProviderError | null): AuthFailure | undefined =>
34
- error === null
35
- ? undefined
36
- : { code: error.code ?? String(error.status ?? "auth"), kind: kindOf(error.status), message: error.message }
37
-
38
- /** The identity-provider client of a lite app: Supabase Auth in the browser. */
39
- export const authClient: AuthClient = {
40
- session: async () => sessionOf((await supabase().auth.getSession()).data.session),
41
- accessToken: async () => (await supabase().auth.getSession()).data.session?.access_token,
42
- refreshAccessToken: async () => (await supabase().auth.refreshSession()).data.session?.access_token,
43
- signInWithPassword: async (email, password) =>
44
- failureOf((await supabase().auth.signInWithPassword({ email, password })).error),
45
- signOut: async () => failureOf((await supabase().auth.signOut()).error),
46
- // Supabase reports INITIAL_SESSION on subscribe, then every change: the subscription alone keeps the session current.
47
- onChange: (listener) => {
48
- const { data } = supabase().auth.onAuthStateChange((_event, session) => listener(sessionOf(session)))
49
- return () => data.subscription.unsubscribe()
50
- },
51
- }
18
+ // {{> fe/auth/supabase-client.ts.tpl}}
@@ -1,5 +1,6 @@
1
1
  import type { Metadata } from "next"
2
2
  import { getTranslations } from "next-intl/server"
3
+ import { AppGuideOverlay } from "@/features/overlays/AppGuideOverlay"
3
4
  import { AppHomePageBase } from "./component"
4
5
 
5
6
  /** The document title of the home page. */
@@ -11,5 +12,10 @@ export const appHomeMetadata = async (): Promise<Metadata> => {
11
12
  /** The front door (the session is client-side, drawn by the blocks the page composes). */
12
13
  export const AppHomePage = async () => {
13
14
  const t = await getTranslations("app.home")
14
- return <AppHomePageBase props={{ title: t("title") }} />
15
+ return (
16
+ <>
17
+ <AppHomePageBase props={{ title: t("title") }} />
18
+ <AppGuideOverlay />
19
+ </>
20
+ )
15
21
  }
@@ -0,0 +1,57 @@
1
+ {
2
+ "app": {
3
+ "title": "{{project}}",
4
+ "shell": {
5
+ "brand": "{{project}}"
6
+ },
7
+ "home": {
8
+ "title": "Home"
9
+ },
10
+ "signIn": {
11
+ "title": "Sign in",
12
+ "email": "Email",
13
+ "password": "Password",
14
+ "submit": "Continue",
15
+ "working": "Signing in",
16
+ "refused": "The credentials were refused.",
17
+ "invalid": "Enter a valid email and password.",
18
+ "unavailable": "Sign-in is unavailable. Try again shortly."
19
+ },
20
+ "signOut": {
21
+ "signedIn": "Signed in",
22
+ "submit": "Sign out",
23
+ "working": "Signing out"
24
+ },
25
+ "toast": {
26
+ "successTitle": "Done",
27
+ "errorTitle": "Error",
28
+ "unauthorizedTitle": "Not signed in",
29
+ "unauthorizedDescription": "Sign in to continue.",
30
+ "defaultSuccess": "The change was saved.",
31
+ "defaultError": "Something went wrong."
32
+ },
33
+ "loading": {
34
+ "message": "Loading"
35
+ },
36
+ "notFound": {
37
+ "title": "Page not found",
38
+ "home": "Return home"
39
+ },
40
+ "errors": {
41
+ "page": {
42
+ "title": "Something went wrong",
43
+ "retry": "Try again"
44
+ },
45
+ "global": {
46
+ "title": "The application could not continue",
47
+ "retry": "Try again"
48
+ }
49
+ },
50
+ "guide": {
51
+ "open": "View guide",
52
+ "title": "Getting started",
53
+ "description": "Sign in to use the application. You can close this guide and open it again at any time.",
54
+ "close": "Close"
55
+ }
56
+ }
57
+ }
@@ -5,47 +5,53 @@
5
5
  "brand": "{{project}}"
6
6
  },
7
7
  "home": {
8
- "title": "Home"
8
+ "title": "Trang chủ"
9
9
  },
10
10
  "signIn": {
11
- "title": "Sign in",
11
+ "title": "Đăng nhập",
12
12
  "email": "Email",
13
- "password": "Password",
14
- "submit": "Continue",
15
- "working": "Signing in",
16
- "refused": "The credentials were refused.",
17
- "invalid": "Enter a valid email and password.",
18
- "unavailable": "Sign-in is unavailable. Try again shortly."
13
+ "password": "Mật khẩu",
14
+ "submit": "Tiếp tục",
15
+ "working": "Đang đăng nhập",
16
+ "refused": "Thông tin đăng nhập không đúng.",
17
+ "invalid": "Hãy nhập email và mật khẩu hợp lệ.",
18
+ "unavailable": "Chưa thể đăng nhập. Hãy thử lại sau."
19
19
  },
20
20
  "signOut": {
21
- "signedIn": "Signed in",
22
- "submit": "Sign out",
23
- "working": "Signing out"
21
+ "signedIn": "Đã đăng nhập",
22
+ "submit": "Đăng xuất",
23
+ "working": "Đang đăng xuất"
24
24
  },
25
25
  "toast": {
26
- "successTitle": "Done",
27
- "errorTitle": "Error",
28
- "unauthorizedTitle": "Not signed in",
29
- "unauthorizedDescription": "Sign in to continue.",
30
- "defaultSuccess": "The change was saved.",
31
- "defaultError": "Something went wrong."
26
+ "successTitle": "Thành công",
27
+ "errorTitle": "Lỗi",
28
+ "unauthorizedTitle": "Chưa đăng nhập",
29
+ "unauthorizedDescription": "Hãy đăng nhập để tiếp tục.",
30
+ "defaultSuccess": "Đã hoàn tất.",
31
+ "defaultError": "Đã xảy ra lỗi."
32
32
  },
33
33
  "loading": {
34
- "message": "Loading"
34
+ "message": "Đang tải"
35
35
  },
36
36
  "notFound": {
37
- "title": "Page not found",
38
- "home": "Return home"
37
+ "title": "Không tìm thấy trang",
38
+ "home": "Về trang chủ"
39
39
  },
40
40
  "errors": {
41
41
  "page": {
42
- "title": "Something went wrong",
43
- "retry": "Try again"
42
+ "title": "Đã xảy ra lỗi",
43
+ "retry": "Thử lại"
44
44
  },
45
45
  "global": {
46
- "title": "The application could not continue",
47
- "retry": "Try again"
46
+ "title": "Đã xảy ra lỗi nghiêm trọng",
47
+ "retry": "Thử lại"
48
48
  }
49
+ },
50
+ "guide": {
51
+ "open": "Xem hướng dẫn",
52
+ "title": "Bắt đầu",
53
+ "description": "Đăng nhập để sử dụng ứng dụng. Bạn có thể đóng hướng dẫn này và mở lại bất cứ lúc nào.",
54
+ "close": "Đóng"
49
55
  }
50
56
  }
51
57
  }