@starci/hfs 4.9.9 → 4.10.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 (241) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +20 -8
  3. package/db/backup-receipt.mjs +10 -47
  4. package/db/drift.mjs +289 -0
  5. package/db/dump.mjs +12 -11
  6. package/db/entity-list.mjs +9 -0
  7. package/db/migrate.mjs +84 -260
  8. package/db/new.mjs +46 -0
  9. package/db/post-cutover.mjs +13 -17
  10. package/db/postgres.mjs +80 -0
  11. package/emit/contracts.mjs +5 -9
  12. package/lint/run.mjs +12 -2
  13. package/package.json +5 -3
  14. package/release/gate.mjs +82 -31
  15. package/release/index.mjs +19 -8
  16. package/release/push.mjs +4 -4
  17. package/release/records.mjs +54 -0
  18. package/release/urgent.mjs +129 -0
  19. package/runtime/knowledge/hfs/canon-pins.yaml +54 -24
  20. package/runtime/knowledge/hfs/design-truths.yaml +1 -1
  21. package/runtime/knowledge/hfs/fe-stack.yaml +4 -11
  22. package/runtime/knowledge/hfs/rules.yaml +250 -157
  23. package/runtime/knowledge/hfs/slots.yaml +199 -174
  24. package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
  25. package/runtime/knowledge/patterns/be/idempotency.yaml +3 -3
  26. package/runtime/knowledge/patterns/be/jobs.yaml +4 -4
  27. package/runtime/knowledge/patterns/be/projections.yaml +7 -5
  28. package/runtime/knowledge/patterns/be/queues.yaml +8 -8
  29. package/runtime/knowledge/patterns/be/reactors.yaml +2 -2
  30. package/runtime/knowledge/patterns/be/realtime.yaml +6 -6
  31. package/runtime/knowledge/patterns/be/saga.yaml +3 -3
  32. package/runtime/knowledge/patterns/be/webhooks.yaml +1 -1
  33. package/runtime/modules/kernel/command-policy.yaml +1 -1
  34. package/runtime/modules/kernel/failure-codes.yaml +86 -115
  35. package/runtime/modules/schemas/application-stacks.schema.yaml +5 -5
  36. package/runtime/scripts/hfs/app-wiring.mjs +23 -2
  37. package/runtime/scripts/hfs/architecture/canon-clones.mjs +1 -1
  38. package/runtime/scripts/hfs/architecture/{supabase-be.mjs → identity.mjs} +45 -15
  39. package/runtime/scripts/hfs/architecture/index.mjs +2 -3
  40. package/runtime/scripts/hfs/architecture/{supabase-ast.mjs → jwt-ast.mjs} +2 -103
  41. package/runtime/scripts/hfs/architecture/register-once.mjs +1 -1
  42. package/runtime/scripts/hfs/architecture/schema-owner.mjs +29 -4
  43. package/runtime/scripts/hfs/architecture/server-action.mjs +3 -4
  44. package/runtime/scripts/hfs/architecture/side-boundary.mjs +1 -1
  45. package/runtime/scripts/hfs/architecture/{supabase-keys.mjs → sql-keys.mjs} +1 -1
  46. package/runtime/scripts/hfs/architecture/sql-owner.mjs +25 -27
  47. package/runtime/scripts/hfs/architecture/transport-owner.mjs +7 -3
  48. package/runtime/scripts/hfs/check.mjs +8 -8
  49. package/runtime/scripts/hfs/coverage-scope.mjs +1 -1
  50. package/runtime/scripts/hfs/db-baseline-verb.mjs +48 -30
  51. package/runtime/scripts/hfs/db-plan-verb.mjs +17 -106
  52. package/runtime/scripts/hfs/db-restamp-verb.mjs +11 -10
  53. package/runtime/scripts/hfs/declaration-shape.mjs +15 -18
  54. package/runtime/scripts/hfs/manifest-shape.mjs +6 -5
  55. package/runtime/scripts/hfs/rule-params-shape.mjs +4 -1
  56. package/runtime/scripts/hfs/rules/a15-findings.mjs +1 -1
  57. package/runtime/scripts/hfs/rules/cli.mjs +1 -1
  58. package/runtime/scripts/hfs/rules/database-access.mjs +8 -74
  59. package/runtime/scripts/hfs/rules/database-baseline.mjs +3 -3
  60. package/runtime/scripts/hfs/rules/database-config.mjs +6 -214
  61. package/runtime/scripts/hfs/rules/database-constants.mjs +8 -10
  62. package/runtime/scripts/hfs/rules/database-migrations.mjs +3 -3
  63. package/runtime/scripts/hfs/rules/database-queues.mjs +4 -5
  64. package/runtime/scripts/hfs/rules/database-sql.mjs +1 -1
  65. package/runtime/scripts/hfs/rules/database.mjs +36 -39
  66. package/runtime/scripts/hfs/rules/edition.mjs +1 -1
  67. package/runtime/scripts/hfs/rules/{supabase-secrets.mjs → lite-secrets.mjs} +6 -6
  68. package/runtime/scripts/hfs/rules/script-hygiene.mjs +1 -10
  69. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +5 -4
  70. package/runtime/scripts/hfs/rules/stacks-release.mjs +3 -2
  71. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +2 -2
  72. package/runtime/scripts/hfs/rules/stacks.mjs +1 -1
  73. package/runtime/scripts/hfs/slot-app-view.mjs +1 -1
  74. package/runtime/scripts/hfs/slots.mjs +8 -2
  75. package/runtime/scripts/hfs/sql/pg-parse.mjs +2 -2
  76. package/runtime/scripts/lib/secret-patterns.mjs +2 -2
  77. package/runtime/scripts/lib/swarm-stack.mjs +7 -4
  78. package/scaffold/add-app.mjs +2 -10
  79. package/scaffold/add-cli-lite.mjs +6 -6
  80. package/scaffold/add-compose.mjs +1 -1
  81. package/scaffold/add-inbox-lite.mjs +4 -8
  82. package/scaffold/add-plan.mjs +1 -1
  83. package/scaffold/add-queue-lite.mjs +16 -24
  84. package/scaffold/add-table.mjs +34 -26
  85. package/scaffold/add.mjs +2 -2
  86. package/scaffold/app.mjs +19 -86
  87. package/scaffold/lite-exports.mjs +1 -1
  88. package/src/main.mjs +24 -22
  89. package/stack/cli.mjs +10 -2
  90. package/stack/deploy.mjs +21 -4
  91. package/stack/target-binding.mjs +0 -60
  92. package/stack/urgent-floor.mjs +147 -0
  93. package/sync/ci-commands.mjs +3 -1
  94. package/sync/index.mjs +32 -8
  95. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +4 -1
  96. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +1 -17
  97. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +6 -8
  98. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +4 -1
  99. package/templates/app/codegen-lite/scripts/codegen.mjs +2 -39
  100. package/templates/app/db-bootstrap/db/bootstrap.sql +139 -0
  101. package/templates/app/gitignore-lite +0 -3
  102. package/templates/app/package-scripts/db.json +9 -0
  103. package/templates/app/package-scripts/package.json +0 -1
  104. package/templates/app/package-scripts-lite/package.json +1 -1
  105. package/templates/app/skeleton/.starcistacks/dev/README.md +1 -1
  106. package/templates/app/skeleton/.starcistacks/production/README.md +1 -1
  107. package/templates/app/skeleton/README.md +1 -1
  108. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +84 -19
  109. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +5 -5
  110. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +1 -1
  111. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +1 -1
  112. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +69 -6
  113. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -7
  114. package/templates/app/skeleton-lite/.starcistacks/production/README.md +6 -6
  115. package/templates/app/skeleton-lite/.starcistacks/production/environment.json +1 -1
  116. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +2 -2
  117. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +69 -6
  118. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +17 -9
  119. package/templates/app/skeleton-lite/README.md +11 -11
  120. package/templates/app/skeleton-lite/{supabase → db}/migrations/__timestamp___baseline.sql +21 -1
  121. package/templates/app/tool-config-lite/prettierignore +0 -1
  122. package/templates/be/inbox/database-client.spec.ts.tpl +9 -5
  123. package/templates/be/inbox/database-client.ts.tpl +13 -4
  124. package/templates/be/inbox/platform/client-operation-outcomes.sql.tpl +47 -0
  125. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +3 -1
  126. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +2 -2
  127. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +6 -1
  128. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +39 -7
  129. package/templates/be/inbox/platform/migration.sql.tpl +22 -0
  130. package/templates/be/inbox/service.spec.ts.tpl +2 -2
  131. package/templates/be/inbox/service.ts.tpl +1 -2
  132. package/templates/be/patterns/jobs/platform-lite/job-claim.service.spec.ts.tpl +156 -0
  133. package/templates/be/patterns/jobs/platform-lite/job-claim.service.ts.tpl +114 -0
  134. package/templates/be/patterns/jobs/platform-lite/jobs.sql.tpl +51 -0
  135. package/templates/be/patterns/queues/platform-lite/queue-outbox.service.spec.ts.tpl +62 -0
  136. package/templates/be/patterns/queues/platform-lite/queue-outbox.service.ts.tpl +20 -0
  137. package/templates/be/patterns/queues/platform-lite/queue-outbox.sql.tpl +35 -0
  138. package/templates/be/patterns/queues/platform-lite/queue-relay.service.spec.ts.tpl +4 -1
  139. package/templates/be/patterns/queues/platform-lite/queue-relay.service.ts.tpl +16 -5
  140. package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +8 -8
  141. package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +3 -3
  142. package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +2 -2
  143. package/templates/be/skeleton/src/modules/domain/identity/identity.decorators.ts +18 -7
  144. package/templates/be/skeleton/src/modules/platform/composition/composition.decorators.ts +3 -0
  145. package/templates/be/skeleton/src/modules/platform/composition/index.ts +1 -1
  146. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.spec.ts +15 -1
  147. package/templates/be/skeleton/src/modules/platform/errors/errors.filter.ts +10 -9
  148. package/templates/be/skeleton/src/modules/platform/http-security/index.ts +1 -1
  149. package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.spec.ts +34 -18
  150. package/templates/be/skeleton/src/modules/platform/http-security/origin.guard.ts +29 -8
  151. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.spec.ts +80 -4
  152. package/templates/be/skeleton/src/modules/platform/http-security/rate-limit.guard.ts +39 -11
  153. package/templates/be/skeleton-lite/apps/api/Dockerfile +0 -1
  154. package/templates/be/skeleton-lite/apps/api/src/api.env.ts +9 -5
  155. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +7 -4
  156. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +4 -4
  157. package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +3 -3
  158. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +9 -13
  159. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +4 -2
  160. package/templates/be/skeleton-lite/apps/cli/src/cli.env.ts +2 -2
  161. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +3 -2
  162. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +4 -4
  163. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +3 -3
  164. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +1 -1
  165. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +23 -13
  166. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +3 -3
  167. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/index.ts +4 -0
  168. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.spec.ts +22 -0
  169. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts +25 -0
  170. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.config.ts +12 -0
  171. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.decorators.ts +9 -0
  172. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.options.ts +35 -0
  173. package/templates/be/skeleton-lite/src/modules/integrations/passport/errors/passport.error.ts +22 -0
  174. package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +12 -0
  175. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.ts +23 -0
  176. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.ts +29 -0
  177. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.decorators.ts +22 -0
  178. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.options.ts +63 -0
  179. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.ts +11 -0
  180. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.spec.ts +32 -0
  181. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts +69 -0
  182. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +37 -0
  183. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +36 -0
  184. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/oauth-state.store.ts +59 -0
  185. package/templates/be/skeleton-lite/src/modules/platform/database/database.functions.ts +24 -0
  186. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +2 -1
  187. package/templates/be/skeleton-lite/src/modules/platform/database/database.options.ts +4 -2
  188. package/templates/be/skeleton-lite/src/modules/platform/database/database.sql.ts +19 -6
  189. package/templates/be/skeleton-lite/src/modules/platform/database/entities.ts +6 -0
  190. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
  191. package/templates/be/skeleton-lite/src/modules/platform/database/migration-runner.service.ts +1 -1
  192. package/templates/be/skeleton-lite/src/modules/platform/database/primary.config.ts +2 -2
  193. package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +2 -2
  194. package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +1 -1
  195. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +18 -5
  196. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +1 -1
  197. package/templates/be/table/entities.index.ts.tpl +4 -0
  198. package/templates/be/table/entity.ts.tpl +21 -0
  199. package/templates/be/table/index.ts.tpl +1 -0
  200. package/templates/be/table/module.ts.tpl +1 -1
  201. package/templates/be/table/service.api.ts.tpl +10 -11
  202. package/templates/be/table/service.ts.tpl +5 -6
  203. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/auth-door.ts +30 -0
  204. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +46 -14
  205. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/token-store.ts +31 -0
  206. package/templates/fe/skeleton-lite/apps/web/src/modules/config/index.ts +2 -5
  207. package/db/local-migrate.mjs +0 -194
  208. package/db/reset.mjs +0 -125
  209. package/emit/db-types.mjs +0 -271
  210. package/runtime/scripts/api/supabase/database-dump.mjs +0 -5
  211. package/runtime/scripts/api/supabase/database-push.mjs +0 -5
  212. package/runtime/scripts/api/supabase/database-query.mjs +0 -5
  213. package/runtime/scripts/api/supabase/lib.mjs +0 -55
  214. package/runtime/scripts/api/supabase/migration-repair.mjs +0 -5
  215. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +0 -50
  216. package/runtime/scripts/hfs/architecture/supabase.mjs +0 -226
  217. package/scaffold/supabase-ports.mjs +0 -26
  218. package/templates/app/package-scripts/supabase.json +0 -7
  219. package/templates/app/skeleton-lite/supabase/config.toml +0 -48
  220. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +0 -5
  221. package/templates/be/inbox/platform/inbox/persistence/inbox.rows.ts.tpl +0 -33
  222. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +0 -33
  223. package/templates/be/inbox/sql.ts.tpl +0 -16
  224. package/templates/be/patterns/jobs/platform-lite/persistence.jobs.sql.ts.tpl +0 -33
  225. package/templates/be/patterns/queues/platform-lite/persistence.queue.sql.ts.tpl +0 -12
  226. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -16
  227. package/templates/be/skeleton-lite/src/modules/integrations/supabase/index.ts +0 -6
  228. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +0 -27
  229. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.ts +0 -24
  230. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.config.ts +0 -8
  231. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.decorators.ts +0 -17
  232. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.jwks.ts +0 -32
  233. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.options.ts +0 -24
  234. package/templates/be/table/rows.ts.tpl +0 -4
  235. package/templates/be/table/sql.api.ts.tpl +0 -7
  236. package/templates/be/table/sql.ts.tpl +0 -4
  237. package/templates/fe/auth/supabase-client.ts.tpl +0 -38
  238. package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +0 -11
  239. package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +0 -18
  240. package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +0 -19
  241. /package/templates/app/skeleton-lite/{supabase → db}/seed.sql +0 -0
@@ -1,5 +1,5 @@
1
1
  // `starci app add queue|job` in a lite app (ruling:R212/case-10): the queue is full's BullMQ adapter on the declared Redis, so lite
2
- // generates full's queue and jobs trees with three differences. The outbox and job tables are a Supabase migration instead of the
2
+ // generates full's queue and jobs trees with three differences. The outbox and job tables are a db migration instead of the
3
3
  // TypeORM entity, connection and migration; the SQL names the public schema; and the queue options read the Redis password of
4
4
  // its sealed secret. The verb also declares redis in hfs.json sides.be.backing with its bullmq client, and renders the one
5
5
  // declared Redis into every docker-swarm environment of the stack: the stateful component, its backup and restore record, the
@@ -7,7 +7,6 @@
7
7
  import fs from 'node:fs';
8
8
  import path from 'node:path';
9
9
  import { parseYaml } from '../runtime/engine/yaml.mjs';
10
- import { dbTypesPath, emitDbTypes } from '../emit/db-types.mjs';
11
10
  import { configuredRecipients } from '../runtime/scripts/hfs/secret-init.mjs';
12
11
  import { materializeDeclaredPlaceholders } from '../runtime/scripts/hfs/sealed-members.mjs';
13
12
  import { REDIS_IMAGE, TEMPLATES_DIR, redisDevScripts, render } from '../sync/index.mjs';
@@ -35,13 +34,18 @@ const LITE_TEMPLATES = Object.freeze({
35
34
  'queues/platform/queue-relay.service.ts.tpl': 'queues/platform-lite/queue-relay.service.ts.tpl',
36
35
  'queues/platform/queue-relay.service.spec.ts.tpl': 'queues/platform-lite/queue-relay.service.spec.ts.tpl',
37
36
  'queues/platform/queue.module.ts.tpl': 'queues/platform-lite/queue.module.ts.tpl',
38
- 'queues/platform/persistence.queue.sql.ts.tpl': 'queues/platform-lite/persistence.queue.sql.ts.tpl',
37
+ 'queues/platform/queue-outbox.service.ts.tpl': 'queues/platform-lite/queue-outbox.service.ts.tpl',
38
+ 'queues/platform/queue-outbox.service.spec.ts.tpl': 'queues/platform-lite/queue-outbox.service.spec.ts.tpl',
39
39
  'jobs/platform/jobs.index.ts.tpl': 'jobs/platform-lite/jobs.index.ts.tpl',
40
- 'jobs/platform/persistence.jobs.sql.ts.tpl': 'jobs/platform-lite/persistence.jobs.sql.ts.tpl',
40
+ 'jobs/platform/job-claim.service.ts.tpl': 'jobs/platform-lite/job-claim.service.ts.tpl',
41
+ 'jobs/platform/job-claim.service.spec.ts.tpl': 'jobs/platform-lite/job-claim.service.spec.ts.tpl',
41
42
  });
42
43
 
43
- /** The TypeORM-only files of a platform capability: lite's schema authority is supabase/migrations (be.persistence lite overlay). */
44
- const TYPEORM_ONLY = /\/persistence\/(?:connection\.ts$|entities\/|migrations\/)/;
44
+ /**
45
+ * The persistence files lite leaves out: the TypeORM schema (its authority is db/migrations) and the SQL text and row files (the
46
+ * statements are database functions of the lite migration, called through callDbFunction; R334 and the be.persistence lite overlay).
47
+ */
48
+ const TYPEORM_ONLY = /\/persistence\/(?:connection\.ts$|entities\/|migrations\/|[^/]+\.(?:sql|rows)\.ts$)/;
45
49
 
46
50
  /** The e2e proof slot lite forbids (R212); a lite pattern proves itself with optional integration specs (the topic's liteAdapter proof). */
47
51
  const E2E_SLOT = 'be.tests.e2e';
@@ -80,20 +84,20 @@ function skeletonFiles(repoRoot) {
80
84
  return { created, patches: after === before ? [] : [{ file: errorFile, text: after }] };
81
85
  }
82
86
 
83
- /** The capability tables a platform capability the verb adds for the first time needs, as one forward-only Supabase migration. */
87
+ /** The capability tables a platform capability the verb adds for the first time needs, as one forward-only db migration. */
84
88
  function migrationFiles({ repoRoot, beRoot, patterns, now }) {
85
89
  const sections = [
86
90
  ['queue-outbox', 'queue', 'queues/platform-lite/queue-outbox.sql.tpl', 'queue'],
87
91
  ['jobs', 'fenced-job', 'jobs/platform-lite/jobs.sql.tpl', 'jobs'],
88
92
  ].filter(([, pattern, , capability]) => patterns.includes(pattern) && !fs.existsSync(path.join(beRoot, 'src', 'modules', 'platform', capability, 'index.ts')));
89
93
  if (!sections.length) return [];
90
- const folder = path.join(repoRoot, 'supabase', 'migrations');
94
+ const folder = path.join(repoRoot, 'db', 'migrations');
91
95
  const migrations = fs.existsSync(folder) ? fs.readdirSync(folder).filter((file) => MIGRATION.test(file)).sort(byCodeUnit) : [];
92
96
  const stamp = new Date(now()).toISOString().replace(/\D/g, '').slice(0, 14);
93
97
  const latest = migrations.at(-1)?.slice(0, 14);
94
98
  if (latest && stamp <= latest) throw new ScaffoldError('HFS_ADD_MIGRATION_ORDER', `the current UTC timestamp ${stamp} is not later than the latest migration ${latest}; fix the clock or wait for the next second`);
95
99
  const text = sections.map(([, , source]) => template(`be/patterns/${source}`)).join('\n');
96
- return [{ relative: `supabase/migrations/${stamp}_${sections.map(([name]) => name).join('-')}.sql`, text }];
100
+ return [{ relative: `db/migrations/${stamp}_${sections.map(([name]) => name).join('-')}.sql`, text }];
97
101
  }
98
102
 
99
103
  /** The queue imports bullmq, which loads ioredis as an optional peer and cannot connect without it. */
@@ -320,10 +324,9 @@ function stackFiles(repoRoot, { project, consumers }) {
320
324
 
321
325
  /**
322
326
  * Plans the lite additions of `add queue|job` before any file is written; returns null outside lite or for another noun. The
323
- * plan's apply() writes them and returns the app-relative paths it created or changed. A new migration regenerates the
324
- * database types through `emitTypes` (the local Supabase stack, as `add table`); false is the `--no-types` seam.
327
+ * plan's apply() writes them and returns the app-relative paths it created or changed.
325
328
  */
326
- export function planLiteQueue({ repoRoot, repo, noun, patterns, now, emitTypes = emitDbTypes }) {
329
+ export function planLiteQueue({ repoRoot, repo, noun, patterns, now }) {
327
330
  if (repo.edition !== 'lite' || !['queue', 'job'].includes(noun)) return null;
328
331
  const beRoot = path.join(repoRoot, 'be');
329
332
  const migrations = migrationFiles({ repoRoot, beRoot, patterns, now });
@@ -345,22 +348,11 @@ export function planLiteQueue({ repoRoot, repo, noun, patterns, now, emitTypes =
345
348
  const exported = registerLiteExports({ root: repoRoot, generator: 'queue' });
346
349
  const sealed = sealRedisMembers(repoRoot, stack.environments);
347
350
  const changed = patches.map((patch) => path.relative(repoRoot, patch.file).split(path.sep).join('/'));
348
- const types = migrations.length && typeof emitTypes === 'function' ? writeTypes(repoRoot, emitTypes) : [];
349
- return [...new Set([...created.map((entry) => entry.relative), ...changed, ...exported, ...sealed, ...types])];
351
+ return [...new Set([...created.map((entry) => entry.relative), ...changed, ...exported, ...sealed])];
350
352
  },
351
353
  };
352
354
  }
353
355
 
354
- /** The SQL of the new capabilities names public.queue and public.jobs, which the generated database types must declare. */
355
- function writeTypes(repoRoot, emitTypes) {
356
- const generated = emitTypes({ root: repoRoot });
357
- if (typeof generated !== 'string') return [];
358
- const target = path.join(repoRoot, ...dbTypesPath.split('/'));
359
- fs.mkdirSync(path.dirname(target), { recursive: true });
360
- fs.writeFileSync(target, generated);
361
- return [dbTypesPath];
362
- }
363
-
364
356
  /** Seals a placeholder redis-password member to the app's configured recipient; without one, `starci app secret init` seals it. */
365
357
  function sealRedisMembers(repoRoot, environments) {
366
358
  if (!environments.length || !fs.existsSync(path.join(repoRoot, '.sops.yaml'))) return [];
@@ -1,6 +1,5 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { emitDbTypes, dbTypesPath } from "../emit/db-types.mjs";
4
3
  import {
5
4
  loadSlotManifest,
6
5
  readRepoDeclaration,
@@ -288,7 +287,7 @@ const apiFeatureFiles = (values) => {
288
287
  /**
289
288
  * The fe data code of a table in one product app: one GraphQL operation file per operation (modules/api/graphql/{queries,
290
289
  * mutations}, its request/data types from the generated wire types) and one SWR hook per operation (hooks/swr/api/graphql).
291
- * Never a Server Action and never a Supabase reader: Supabase on the front end is the Auth client only.
290
+ * Never a Server Action and never a database reader: the front end reaches data only through the back end.
292
291
  */
293
292
  const feOperationFiles = (app, values) => {
294
293
  const api = `fe/apps/${app}/src/modules/api/graphql`;
@@ -326,9 +325,8 @@ const withExport = (text, line) => {
326
325
  };
327
326
 
328
327
  /**
329
- * Adds one Supabase table migration and its domain capability; with `fe`, the table's api feature over GraphQL (the GraphQL
328
+ * Adds one db table migration and its domain capability; with `fe`, the table's api feature over GraphQL (the GraphQL
330
329
  * server capability on first use) and, in every product Next app, its operation files and SWR hooks.
331
- * `emitTypes` returns the generated database.types.ts text; false is the explicit `--no-types` seam.
332
330
  */
333
331
  function tableContext({ root, name }) {
334
332
  const requestedName = String(name);
@@ -341,11 +339,11 @@ function tableContext({ root, name }) {
341
339
  const table = snakeOf(requestedName);
342
340
  const manifest = loadSlotManifest();
343
341
  const repo = readRepoDeclaration(manifest, root);
344
- const connection = repo.sides?.be?.connections?.find((candidate) => candidate.provider === "supabase");
342
+ const connection = repo.sides?.be?.connections?.find((candidate) => candidate.provider === "postgres");
345
343
  if (!connection) {
346
344
  throw new ScaffoldError(
347
345
  "HFS_ADD_TABLE_PROVIDER",
348
- "add table needs a back-end connection whose provider is supabase",
346
+ "add table needs a back-end connection whose provider is postgres",
349
347
  );
350
348
  }
351
349
 
@@ -353,7 +351,7 @@ function tableContext({ root, name }) {
353
351
  }
354
352
 
355
353
  function nextMigration({ root, feature, requestedName, now }) {
356
- const migrationsDir = path.join(root, "supabase", "migrations");
354
+ const migrationsDir = path.join(root, "db", "migrations");
357
355
  const migrations = fs.existsSync(migrationsDir)
358
356
  ? fs
359
357
  .readdirSync(migrationsDir)
@@ -408,7 +406,7 @@ function tableFiles({ root, repo, feature, stamp, values, fe, apps }) {
408
406
  const domain = `be/src/modules/domain/${feature}`;
409
407
  const planned = [
410
408
  {
411
- relative: `supabase/migrations/${stamp}_${feature}.sql`,
409
+ relative: `db/migrations/${stamp}_${feature}.sql`,
412
410
  body: template("be/table/migration.sql.tpl", values),
413
411
  },
414
412
  { relative: `${domain}/index.ts`, body: template("be/table/index.ts.tpl", values) },
@@ -417,16 +415,9 @@ function tableFiles({ root, repo, feature, stamp, values, fe, apps }) {
417
415
  { relative: `${domain}/${feature}.module-definition.ts`, body: template("be/table/module-definition.ts.tpl", values) },
418
416
  { relative: `${domain}/${feature}.options.ts`, body: template("be/table/options.ts.tpl", values) },
419
417
  { relative: `${domain}/${feature}.service.ts`, body: template(`be/table/service${api}.ts.tpl`, values) },
420
- { relative: `${domain}/persistence/${feature}.rows.ts`, body: template("be/table/rows.ts.tpl", values) },
421
- { relative: `${domain}/persistence/${feature}.sql.ts`, body: template(`be/table/sql${api}.ts.tpl`, values) },
418
+ { relative: `${domain}/persistence/entities/${feature}.entity.ts`, body: template("be/table/entity.ts.tpl", values) },
419
+ { relative: `${domain}/persistence/entities/index.ts`, body: template("be/table/entities.index.ts.tpl", values) },
422
420
  ];
423
- const databaseSql = "be/src/modules/platform/database/database.sql.ts";
424
- if (repo.edition === "lite" && !fs.existsSync(at(root, databaseSql))) {
425
- planned.splice(1, 0, {
426
- relative: databaseSql,
427
- body: render(fs.readFileSync(path.join(TEMPLATES_DIR, "be", "skeleton-lite", "src", "modules", "platform", "database", "database.sql.ts"), "utf8"), {}),
428
- });
429
- }
430
421
  if (fe) {
431
422
  planned.push(...apiFeatureFiles(values));
432
423
  if (!fs.existsSync(at(root, `${GRAPHQL_PLATFORM}/index.ts`))) planned.push(...graphqlPlatformFiles());
@@ -450,7 +441,7 @@ function tableOwner({ root, repo, connection, name, requestedName, planned }) {
450
441
  );
451
442
  const owner = repo.sides.be.apps.find((app) => app.name === connection.owner && app.kind === "api");
452
443
  if (!owner)
453
- throw new ScaffoldError("HFS_ADD_NO_API_OWNER", `add table needs the Supabase connection owner ${connection.owner} to be a declared api app`);
444
+ throw new ScaffoldError("HFS_ADD_NO_API_OWNER", `add table needs the database connection owner ${connection.owner} to be a declared api app`);
454
445
  const ownerRelative = `be/apps/${owner.name}/src/app.module.ts`;
455
446
  if (!fs.existsSync(at(root, ownerRelative)))
456
447
  throw new ScaffoldError("HFS_ADD_WIRE_MISSING", `add table registers ${name} in ${ownerRelative}, which does not exist`);
@@ -471,17 +462,13 @@ function tableEditor(root, touched) {
471
462
  return edit;
472
463
  }
473
464
 
474
- function writeTableFiles({ root, planned, emitTypes, edit }, created) {
465
+ function writeTableFiles({ root, planned, edit }, created) {
475
466
  for (const file of planned) {
476
467
  const target = at(root, file.relative);
477
468
  fs.mkdirSync(path.dirname(target), { recursive: true });
478
469
  fs.writeFileSync(target, file.body);
479
470
  created.push(target);
480
471
  }
481
- if (typeof emitTypes === "function") {
482
- const generated = emitTypes({ root });
483
- if (typeof generated === "string") edit(dbTypesPath, () => generated);
484
- }
485
472
 
486
473
  }
487
474
 
@@ -504,8 +491,30 @@ function wireTableFrontends({ root, feature, values, apps, edit }) {
504
491
  }
505
492
  }
506
493
 
494
+ /** The lite entity list: platform/database/entities.ts names the capability's `<c>Entities` in PRIMARY_ENTITIES (BE-PERSISTENCE-3). */
495
+ const PRIMARY_ENTITIES_FILE = "be/src/modules/platform/database/entities.ts";
496
+ const PRIMARY_ENTITIES = /(export const PRIMARY_ENTITIES[^=]*=\s*\[)([\s\S]*?)(\])/;
497
+
498
+ /** `text` of entities.ts with `<c>Entities` imported from the capability barrel and spread into PRIMARY_ENTITIES, once. */
499
+ function withPrimaryEntities(text, { feature, nameCamel }) {
500
+ const list = `${nameCamel}Entities`;
501
+ if (!PRIMARY_ENTITIES.test(text))
502
+ throw new ScaffoldError("HFS_ADD_WIRE_MISSING", `add table names ${list} in PRIMARY_ENTITIES of ${PRIMARY_ENTITIES_FILE}, which declares no PRIMARY_ENTITIES array`);
503
+ if (new RegExp(`\\.\\.\\.${list}\\b`).test(text)) return text;
504
+ const imported = `import { ${list} } from "@modules/domain/${feature}"\n`;
505
+ const withList = text.replace(PRIMARY_ENTITIES, (all, head, items, tail) => {
506
+ const entries = items.split(",").map((item) => item.trim()).filter(Boolean);
507
+ return `${head}${[...entries, `...${list}`].join(", ")}${tail}`;
508
+ });
509
+ const lines = withList.split("\n");
510
+ const lastImport = lines.reduce((at, line, index) => (line.startsWith("import ") ? index : at), -1);
511
+ if (lastImport >= 0) lines.splice(lastImport + 1, 0, imported.trimEnd());
512
+ return lastImport >= 0 ? lines.join("\n") : `${imported}${withList}`;
513
+ }
514
+
507
515
  function wireTable({ root, repo, feature, values, ownerRelative, composition, fe, apps, edit }) {
508
516
  const kindsHome = kindsHomeOf(root, ownerRelative);
517
+ if (repo.edition === "lite") edit(PRIMARY_ENTITIES_FILE, (text) => withPrimaryEntities(text, { feature, nameCamel: values.nameCamel }));
509
518
  for (const patch of composition) edit(patch.relative, () => patch.text);
510
519
  edit(kindsHome, (text) => wireKinds(text, kindsHome, { kinds: `${snakeOf(feature).toUpperCase()}_ERROR_KINDS`, specifier: `@modules/domain/${feature}` }));
511
520
  tableRegistrations({ root, repo, fe, edit });
@@ -538,7 +547,7 @@ function applyTable(context) {
538
547
  }
539
548
 
540
549
  /** 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 }) {
550
+ export function addTable({ root, name, fe = false, now = Date.now }) {
542
551
  const context = tableContext({ root, name });
543
552
  const stamp = nextMigration({ ...context, now });
544
553
  const values = tableValues(context);
@@ -546,9 +555,8 @@ export function addTable({ root, name, fe = false, emitTypes = emitDbTypes, now
546
555
  const planned = tableFiles({ ...context, stamp, values, apps, fe });
547
556
  const owner = tableOwner({ ...context, planned });
548
557
  const composition = tableComposition({ ...context, ...owner, fe });
549
- applyTable({ ...context, ...owner, planned, composition, apps, values, fe, emitTypes });
558
+ applyTable({ ...context, ...owner, planned, composition, apps, values, fe });
550
559
  return {
551
560
  created: [...planned.map((file) => file.relative), ...composition.filter((patch) => patch.exists === false).map((patch) => patch.relative)],
552
- types: typeof emitTypes === "function" ? dbTypesPath : "skipped",
553
561
  };
554
562
  }
package/scaffold/add.mjs CHANGED
@@ -52,7 +52,7 @@ const FE_OWNERS = Object.freeze({
52
52
  */
53
53
  export function addKind({ repoRoot, noun, name, options = {}, now = Date.now }) {
54
54
  if (noun === 'table') {
55
- const result = addTable({ root: repoRoot, name, fe: options.fe === true, emitTypes: options.noTypes === true ? false : options.emitTypes, now });
55
+ const result = addTable({ root: repoRoot, name, fe: options.fe === true, now });
56
56
  return { ...result, registered: { patterns: [], kinds: [] } };
57
57
  }
58
58
  if (noun === 'app') {
@@ -81,7 +81,7 @@ function registerFeatureExports({ repoRoot, repo, noun, values }) {
81
81
 
82
82
  function writeKind({ repoRoot, noun, name, now, options, manifest, repo, spec, declarationFile, values, forms, beRoot, patterns, planned }) {
83
83
  if (noun === 'api' || noun === 'webhook') preflightFeatureComposition({ repoRoot, repo, noun, name });
84
- const liteQueue = planLiteQueue({ repoRoot, repo, noun, patterns, now, emitTypes: options.noTypes === true ? false : options.emitTypes });
84
+ const liteQueue = planLiteQueue({ repoRoot, repo, noun, patterns, now });
85
85
  const bootstrapCreated = noun === 'cli' && repo.edition === 'lite' ? ensureLiteCli({ root: repoRoot, manifest, repo }) : [];
86
86
  for (const file of planned) {
87
87
  fs.mkdirSync(path.dirname(file.absolute), { recursive: true });
package/scaffold/app.mjs CHANGED
@@ -18,10 +18,8 @@ import { spawnSync } from 'node:child_process';
18
18
  import { loadSlotManifest, resolveRepoDeclaration } from '../runtime/scripts/hfs/slots.mjs';
19
19
  import { parseYaml } from '../runtime/engine/yaml.mjs';
20
20
  import { HARNESS_PACKAGE, canonPins } from '../runtime/scripts/hfs/canon-pins.mjs';
21
- import { dbTypesPath, generateDbTypes } from '../emit/db-types.mjs';
22
21
  import { TEMPLATES_DIR, appSource, imageFiles, render, renderTargets, writeTargets } from '../sync/index.mjs';
23
22
  import { ScaffoldError } from './service.mjs';
24
- import { SUPABASE_PORT_VARIABLES, supabasePortVars } from './supabase-ports.mjs';
25
23
  import { testWorldFiles } from './test-world.mjs';
26
24
  import { FE_APP_SCRIPTS, PACKAGE_MANAGER, WORKSPACES, WORKSPACE_LINT, feAppPackageName } from '../runtime/scripts/hfs/rules/monorepo.mjs';
27
25
  import { isLinkLike } from '../runtime/scripts/api/fs/is-link-like.mjs';
@@ -128,23 +126,14 @@ const STARTER_SIDES = Object.freeze({
128
126
  fe: Object.freeze({ grammar: 'core', apps: [{ name: 'landing', kind: 'next' }, { name: 'app', kind: 'next' }], reads: ['be/contracts/'], optionalSlots: ['fe.package.ui', 'fe.package.i18n'] }),
129
127
  });
130
128
 
131
- /** The lite starter is the upgrade-safe subset: one API, one web app and one Supabase schema authority. */
129
+ /** The lite starter is the upgrade-safe subset: one API, one web app and one SQL schema authority (db/migrations). */
132
130
  const LITE_STARTER_SIDES = Object.freeze({
133
131
  be: Object.freeze({
134
132
  apps: [{ name: 'api', kind: 'api' }],
135
133
  kinds: ['api'],
136
- connections: [{ name: 'primary', envPrefix: 'PRIMARY_DB', owner: 'api', isolation: 'schema', provider: 'supabase' }],
137
- reads: ['supabase/types/'],
134
+ connections: [{ name: 'primary', envPrefix: 'PRIMARY_DB', owner: 'api', isolation: 'schema', provider: 'postgres' }],
138
135
  }),
139
- fe: Object.freeze({ grammar: 'core', apps: [{ name: 'web', kind: 'next' }], reads: ['be/contracts/', 'supabase/types/'] }),
140
- });
141
-
142
- /** The checked local Supabase auth posture of every newly scaffolded lite app. */
143
- const LITE_STARTER_SUPABASE = Object.freeze({
144
- enableSignup: false,
145
- jwtExpiry: 3600,
146
- siteUrl: 'http://127.0.0.1:3000',
147
- redirectUrls: ['http://127.0.0.1:3000/'],
136
+ fe: Object.freeze({ grammar: 'core', apps: [{ name: 'web', kind: 'next' }], reads: ['be/contracts/'] }),
148
137
  });
149
138
 
150
139
  /**
@@ -175,16 +164,17 @@ const STARTER_DEPENDENCIES = Object.freeze({
175
164
  const LITE_STARTER_DEPENDENCIES = Object.freeze({
176
165
  dependencies: {
177
166
  '@apollo/server': null, '@nestjs/apollo': null, '@nestjs/common': null, '@nestjs/core': null, '@nestjs/cqrs': null, '@nestjs/graphql': null,
178
- '@nestjs/platform-express': null, '@nestjs/typeorm': null, '@supabase/supabase-js': null,
167
+ '@nestjs/platform-express': null, '@nestjs/typeorm': null, '@nestjs/passport': null, passport: null, 'passport-google-oauth20': null, 'passport-local': null,
168
+ '@aws-sdk/client-s3': null, '@aws-sdk/s3-request-presigner': null, bcryptjs: null,
179
169
  'class-transformer': null, 'class-validator': null, graphql: null, jose: null, pg: null,
180
170
  'reflect-metadata': null, rxjs: null, tslib: null, typeorm: null, zod: null,
181
171
  },
182
172
  devDependencies: {
183
173
  turbo: null, '@nestjs/testing': null, '@starci/eslint-canon-be': null, '@starci/eslint-canon-fe': null,
184
174
  '@starci/jest-preset': null, '@starci/prettier-config': null, '@starci/stylelint-canon': null, '@starci/tsconfig': null, 'starci-harness': null, '@tailwindcss/postcss': null,
185
- '@types/express': null, '@types/jest': null, '@types/node': null, '@types/react': null, '@types/react-dom': null,
175
+ '@types/express': null, '@types/jest': null, '@types/node': null, '@types/passport-google-oauth20': null, '@types/passport-local': null, '@types/react': null, '@types/react-dom': null,
186
176
  eslint: null, 'eslint-plugin-react-hooks': null, husky: null, jest: null, 'postcss-value-parser': null, prettier: null,
187
- stylelint: null, supabase: null, tailwindcss: null, 'ts-jest': null, 'ts-node-dev': null, 'tsc-alias': null,
177
+ stylelint: null, tailwindcss: null, 'ts-jest': null, 'ts-node-dev': null, 'tsc-alias': null,
188
178
  'tsconfig-paths': null, typescript: null,
189
179
  },
190
180
  });
@@ -201,12 +191,12 @@ const FE_APP_DEPENDENCIES = Object.freeze({
201
191
  });
202
192
 
203
193
  /**
204
- * The lite fe workspace: the same base without the shared packages, plus its infra adapter (owner ruling 2026-10-13: Supabase on
205
- * the front end is the Auth client of modules/auth only) and server-only for its own next-intl request config.
194
+ * The lite fe workspace: the same base without the shared packages, plus server-only for its own next-intl request config. It
195
+ * holds no identity SDK: modules/auth talks only to the app's own back-end auth doors.
206
196
  */
207
197
  const LITE_FE_APP_DEPENDENCIES = Object.freeze({
208
198
  '@heroicons/react': null, '@heroui/react': null, '@heroui/styles': null, '@starci/grammar': null, '@starci/heroicons': null, '@xyflow/react': null, 'react-aria-components': null,
209
- '@supabase/supabase-js': null, next: null, 'next-intl': null, react: null, 'react-dom': null, 'server-only': null,
199
+ next: null, 'next-intl': null, react: null, 'react-dom': null, 'server-only': null,
210
200
  });
211
201
 
212
202
  /**
@@ -229,51 +219,9 @@ export const starterDeclaration = (name, manifest = loadSlotManifest(), edition
229
219
  kind: 'app',
230
220
  ...(edition === 'full' ? {} : { edition }),
231
221
  project: name,
232
- ...(edition === 'lite' ? { supabase: structuredClone(LITE_STARTER_SUPABASE) } : {}),
233
222
  sides: structuredClone(edition === 'lite' ? LITE_STARTER_SIDES : STARTER_SIDES),
234
223
  });
235
224
 
236
- /** Opting into a server PKCE callback also selects Supabase Auth and declares its route slot. */
237
- function enableAuthCallback(declaration) {
238
- requireAuthCallbackConnection(declaration);
239
- declaration.sides.fe.optionalSlots = [...(declaration.sides.fe.optionalSlots ?? []), 'fe.route.callback'];
240
- declaration.supabase ??= structuredClone(LITE_STARTER_SUPABASE);
241
- declaration.supabase.redirectUrls = ['http://127.0.0.1:3000/auth/callback'];
242
- }
243
-
244
- /** Callback-specific source files are excluded from the browser-only starter. */
245
- const CALLBACK_SOURCE_FILES = Object.freeze(new Set([
246
- 'src/app/auth/callback/route.ts', 'src/modules/auth/server.ts', 'src/modules/auth/pkce-client.ts',
247
- ]));
248
- export const hasAuthCallback = repo => (repo.sides.fe.optionalSlots ?? []).includes('fe.route.callback');
249
-
250
- function requireAuthCallbackConnection(repo) {
251
- if (!(repo.sides.be?.connections ?? []).some(entry => entry.provider === 'supabase'))
252
- throw new ScaffoldError('HFS_ADD_CONNECTION_REQUIRED', 'the auth callback requires an existing Supabase connection; the full starter has none');
253
- }
254
-
255
- /** Apply the callback's cookie-backed auth owner and its config readers to product apps only. */
256
- export function callbackSourceFiles(files, repo) {
257
- const enabled = hasAuthCallback(repo);
258
- if (enabled) requireAuthCallbackConnection(repo);
259
- const selected = files.filter(file => {
260
- const [, , app, ...parts] = file.path.split('/');
261
- const relative = parts.join('/');
262
- if (relative === 'src/modules/auth/pkce-client.ts') return false;
263
- return MARKETING_FE_APPS.has(app) || enabled || !CALLBACK_SOURCE_FILES.has(relative);
264
- });
265
- if (!enabled) return selected;
266
- const config = fs.readFileSync(path.join(TEMPLATES_DIR, 'fe/skeleton-lite/apps/web/src/modules/config/index.ts'), 'utf8');
267
- const client = render(fs.readFileSync(path.join(TEMPLATES_DIR, 'fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts'), 'utf8').replace(/\r\n/g, '\n'), PARTIAL_VARIABLES);
268
- return selected.map(file => {
269
- const [, , app, ...parts] = file.path.split('/');
270
- if (MARKETING_FE_APPS.has(app)) return file;
271
- const relative = parts.join('/');
272
- if (relative === 'src/modules/config/index.ts') return { ...file, content: config };
273
- return relative === 'src/modules/auth/client.ts' ? { ...file, content: client } : file;
274
- });
275
- }
276
-
277
225
  /** A dependency section at the canon pins (a null range takes the pin, which must exist; every pinned name is null). */
278
226
  function pinnedSection(entries, pins) {
279
227
  const pinned = (dependency, range) => {
@@ -329,10 +277,9 @@ const workspaceSection = (entries, project, pins) => pinnedSection(Object.fromEn
329
277
  * The package.json of the fe app workspace `app` of the app `project`: @<project>/<app>, private, the workspace scripts, its own
330
278
  * dependencies. A product app (`core`, it carries the shared app core) also takes every core runtime package of the fe stack.
331
279
  */
332
- function feAppManifest(project, app, pins, edition, { core = true, authCallback = false, stack = coreFeStackPackages() } = {}) {
280
+ function feAppManifest(project, app, pins, edition, { core = true, stack = coreFeStackPackages() } = {}) {
333
281
  const base = edition === 'lite' ? LITE_FE_APP_DEPENDENCIES : FE_APP_DEPENDENCIES;
334
282
  const dependencies = { ...base, ...(core ? Object.fromEntries(stack.app.filter(name => !Object.hasOwn(base, name)).map(name => [name, null])) : {}) };
335
- if (core && authCallback) Object.assign(dependencies, { '@supabase/ssr': null, '@supabase/supabase-js': null, 'server-only': null });
336
283
  return { name: feAppPackageName(project, app), version: '0.0.0', private: true, scripts: { ...FE_APP_SCRIPTS }, dependencies: workspaceSection(dependencies, project, pins) };
337
284
  }
338
285
 
@@ -381,8 +328,8 @@ function nextAppTsconfig() {
381
328
  };
382
329
  }
383
330
 
384
- /** The names a skeleton file may fill: {{project}}, {{app}}, {{appPascal}} (the side's app), {{sonarGate}}, {{registryVisibility}} (the image visibility written once into deploy.registry) and the lite Supabase port block ({{supabasePort<Section>}}, supabase-ports.mjs). */
385
- const SKELETON_VARIABLES = Object.freeze(['project', 'app', 'appPascal', 'sonarGate', 'registryVisibility', ...SUPABASE_PORT_VARIABLES]);
331
+ /** The names a skeleton file may fill: {{project}}, {{app}}, {{appPascal}} (the side's app), {{sonarGate}} and {{registryVisibility}} (the image visibility written once into deploy.registry). */
332
+ const SKELETON_VARIABLES = Object.freeze(['project', 'app', 'appPascal', 'sonarGate', 'registryVisibility']);
386
333
 
387
334
  /**
388
335
  * A skeleton file with its variables filled. Only the skeleton names are variables: every other `{{name}}` is source the app keeps
@@ -411,7 +358,7 @@ const LITE_DEFERRED_GENERATOR_FILES = new Set([
411
358
  'src/modules/platform/database/seed-runner.service.ts',
412
359
  'src/modules/platform/primitives/sequence.policy.ts',
413
360
  ]);
414
- /** The TypeORM schema files of a platform capability: lite's schema authority is supabase/migrations (be.persistence lite overlay). */
361
+ /** The TypeORM schema files of a platform capability: lite's schema authority is db/migrations (be.persistence lite overlay). */
415
362
  const LITE_TYPEORM_SCHEMA = /^src\/modules\/platform\/[^/]+\/persistence\/(?:connection\.ts$|entities\/|migrations\/)/;
416
363
  const liteBaseSource = (rel) => !rel.startsWith('apps/cli/')
417
364
  && !rel.startsWith('src/features/cli/')
@@ -509,7 +456,7 @@ export function skeletonOf(scope, app, vars) {
509
456
  for (const entry of scope === 'fe' ? app.sides.fe.apps : []) {
510
457
  if (!unique.some(file => file.path.startsWith(`fe/apps/${entry.name}/`))) throw new ScaffoldError('HFS_SCAFFOLD_SKELETON_MISSING', `no skeleton templates for the fe app ${entry.name}`);
511
458
  }
512
- return scope === 'fe' ? [...new Map(callbackSourceFiles(unique, app).map(file => [file.path, file])).values()] : unique;
459
+ return unique;
513
460
  }
514
461
 
515
462
  /** The npm step that resolves the lockfile of a new app, exactly as the error names it. */
@@ -622,17 +569,6 @@ function scaffoldWrites(root, original) {
622
569
  };
623
570
  }
624
571
 
625
- /** Generate the lite database types and translate generation errors into the scaffold failure contract. */
626
- function emitScaffoldDatabaseTypes(root, name, writes, emitTypes) {
627
- try {
628
- const text = emitTypes({ root });
629
- if (typeof text !== 'string' || !text.trim()) throw new Error('the Supabase CLI returned no database types');
630
- writes.write(dbTypesPath, text);
631
- } catch (error) {
632
- throw new ScaffoldError('HFS_SCAFFOLD_TYPES_FAILED', `Supabase database types could not be generated for ${root} (${String(error?.message ?? error)}). Start the local Supabase stack and run starci app scaffold ${name} --edition lite again`);
633
- }
634
- }
635
-
636
572
  /**
637
573
  * `starci app scaffold <name>`: writes the new app under `into`, resolves its lockfile with npm (`lock`, npmLock), and returns
638
574
  * `{ root, files }` (app-relative paths, sorted). A new path is first made an empty repository with unborn main (`initGit`), so the
@@ -660,17 +596,15 @@ function bornRepository(root, initGit) {
660
596
  return fs.lstatSync(root);
661
597
  }
662
598
 
663
- export function scaffoldApp({ name, into, presets, harness, edition = 'full', authCallback = false, registryVisibility = 'public', manifest = loadSlotManifest(), pins = canonPins({ harness }), lock = npmLock, emitTypes = generateDbTypes, initGit = gitInit, now = () => new Date() }) {
599
+ export function scaffoldApp({ name, into, presets, harness, edition = 'full', registryVisibility = 'public', manifest = loadSlotManifest(), pins = canonPins({ harness }), lock = npmLock, initGit = gitInit, now = () => new Date() }) {
664
600
  if (!NAME.test(String(name))) throw new ScaffoldError('HFS_SCAFFOLD_NAME_INVALID', `the app name ${name} must be kebab-case (a project name: ${NAME})`);
665
601
  const root = path.resolve(into, name);
666
602
  const existing = fs.lstatSync(root, { throwIfNoEntry: false });
667
603
  if (existing && !emptyUnbornGit(root, existing)) throw new ScaffoldError('HFS_SCAFFOLD_EXISTS', `${root} already exists; only a plain directory containing solely .git with unborn main and an empty index can be scaffolded`);
668
604
  if (!['public', 'private'].includes(registryVisibility)) throw new ScaffoldError('HFS_SCAFFOLD_REGISTRY_VISIBILITY', `--registry-visibility accepts public or private (got ${registryVisibility})`);
669
- if (typeof authCallback !== 'boolean') throw new ScaffoldError('HFS_SCAFFOLD_NAME_INVALID', 'authCallback must be a boolean');
670
605
  if (!pins[HARNESS_PACKAGE]) throw new ScaffoldError('HFS_SCAFFOLD_PIN_MISSING', `${HARNESS_PACKAGE} has no pin: an app pins the CLI that scaffolds it, so run the scaffold through it (starci app scaffold)`);
671
606
  const original = existing ?? bornRepository(root, initGit);
672
607
  const declaration = starterDeclaration(name, manifest, edition);
673
- if (authCallback) enableAuthCallback(declaration);
674
608
  const app = resolveRepoDeclaration(manifest, declaration);
675
609
  const pkg = packageManifest(name, pins, app.edition, app.sides.fe.grammar);
676
610
  const timestamp = now().toISOString().replace(/\D/g, '').slice(0, 14);
@@ -683,14 +617,14 @@ export function scaffoldApp({ name, into, presets, harness, edition = 'full', au
683
617
  path: `fe/apps/${entry.name}/src/modules/types/ambient/styles.d.ts`,
684
618
  content: '/** Declares stylesheets loaded by the Next bundler; non-CSS side-effect imports remain checked. */\ndeclare module "*.css" {}\n',
685
619
  })),
686
- ...app.sides.fe.apps.map(entry => ({ path: `fe/apps/${entry.name}/package.json`, content: packageJsonText(feAppManifest(name, entry.name, pins, app.edition, { core: coreFeApps([entry]).length === 1, authCallback })) })),
620
+ ...app.sides.fe.apps.map(entry => ({ path: `fe/apps/${entry.name}/package.json`, content: packageJsonText(feAppManifest(name, entry.name, pins, app.edition, { core: coreFeApps([entry]).length === 1 })) })),
687
621
  ...imageFiles(app),
688
622
  ...(app.edition === 'lite' ? [] : Object.keys(FE_PACKAGES).flatMap(pkg => [
689
623
  { path: `fe/packages/${name}-${pkg}/package.json`, content: packageJsonText(fePackageManifest(name, pkg, pins)) },
690
624
  { path: `fe/packages/${name}-${pkg}/tsconfig.json`, content: jsonText(fePackageTsconfig()) },
691
625
  { path: `fe/packages/${name}-${pkg}/tsconfig.build.json`, content: jsonText(fePackageBuildTsconfig()) },
692
626
  ])),
693
- ...['app', 'be', 'fe'].flatMap(scope => skeletonOf(scope, app, { sonarGate: sonarGateName(parseYaml(fs.readFileSync(SONAR_GATE_FILE, 'utf8')), app.edition), timestamp, registryVisibility, ...supabasePortVars(name) })),
627
+ ...['app', 'be', 'fe'].flatMap(scope => skeletonOf(scope, app, { sonarGate: sonarGateName(parseYaml(fs.readFileSync(SONAR_GATE_FILE, 'utf8')), app.edition), timestamp, registryVisibility })),
694
628
  // A full app has a test world (integration, e2e and contract are rows of its release gate); lite has none (ruling:R212/case-3, hosting amended by owner:lite-production-swarm).
695
629
  ...(app.edition === 'lite' ? [] : testWorldFiles(app)),
696
630
  ];
@@ -700,12 +634,11 @@ export function scaffoldApp({ name, into, presets, harness, edition = 'full', au
700
634
  const targets = renderTargets(declaration, presets, { manifest, source: appSource(root) });
701
635
  for (const target of targets) writes.claim(target.path);
702
636
  writeTargets(root, targets);
703
- if (app.edition === 'lite') emitScaffoldDatabaseTypes(root, name, writes, emitTypes);
704
637
  const secretFiles = initializeScaffoldSecrets(root, writes);
705
638
  let locked;
706
639
  try { locked = lock(root); } finally { writes.lockfile(); }
707
640
  if (!locked.ok) throw new ScaffoldError('HFS_SCAFFOLD_LOCK_FAILED', `\`${LOCK_STEP}\` could not resolve the lockfile of ${root} (${locked.detail}). Check the network and the npm registry, then run starci app scaffold ${name} again`);
708
- return { root, files: [...new Set([...files.map(file => file.path), ...targets.map(target => target.path), ...secretFiles, ...(app.edition === 'lite' ? [dbTypesPath] : []), 'package-lock.json'])].sort(byCodeUnit) };
641
+ return { root, files: [...new Set([...files.map(file => file.path), ...targets.map(target => target.path), ...secretFiles, 'package-lock.json'])].sort(byCodeUnit) };
709
642
  } catch (error) {
710
643
  const held = writes.rollback();
711
644
  if (held.length) error.message += `; cleanup retained replaced or occupied entries: ${held.join(', ')}`;
@@ -26,7 +26,7 @@ const REGISTRATIONS = Object.freeze({
26
26
  {
27
27
  file: "be/src/modules/platform/database/index.ts",
28
28
  source: "./database.sql",
29
- names: ["requireOwnedRow", "sql"],
29
+ names: ["requireOwnedEntity"],
30
30
  beforeSource: "./errors/database.error",
31
31
  },
32
32
  {