@starci/hfs 4.9.11 → 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 (219) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +7 -9
  3. package/db/backup-receipt.mjs +10 -47
  4. package/db/drift.mjs +1 -1
  5. package/db/dump.mjs +11 -10
  6. package/db/entity-list.mjs +1 -1
  7. package/db/migrate.mjs +83 -259
  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/package.json +1 -1
  13. package/release/gate.mjs +3 -15
  14. package/runtime/knowledge/hfs/canon-pins.yaml +44 -24
  15. package/runtime/knowledge/hfs/design-truths.yaml +1 -1
  16. package/runtime/knowledge/hfs/fe-stack.yaml +4 -11
  17. package/runtime/knowledge/hfs/rules.yaml +177 -155
  18. package/runtime/knowledge/hfs/slots.yaml +164 -172
  19. package/runtime/knowledge/patterns/be/event-bus.yaml +3 -3
  20. package/runtime/knowledge/patterns/be/idempotency.yaml +3 -3
  21. package/runtime/knowledge/patterns/be/jobs.yaml +4 -4
  22. package/runtime/knowledge/patterns/be/projections.yaml +7 -5
  23. package/runtime/knowledge/patterns/be/queues.yaml +8 -8
  24. package/runtime/knowledge/patterns/be/reactors.yaml +2 -2
  25. package/runtime/knowledge/patterns/be/realtime.yaml +6 -6
  26. package/runtime/knowledge/patterns/be/saga.yaml +3 -3
  27. package/runtime/knowledge/patterns/be/webhooks.yaml +1 -1
  28. package/runtime/modules/kernel/command-policy.yaml +1 -1
  29. package/runtime/modules/kernel/failure-codes.yaml +65 -115
  30. package/runtime/modules/schemas/application-stacks.schema.yaml +5 -5
  31. package/runtime/scripts/hfs/architecture/canon-clones.mjs +1 -1
  32. package/runtime/scripts/hfs/architecture/{supabase-be.mjs → identity.mjs} +45 -15
  33. package/runtime/scripts/hfs/architecture/index.mjs +2 -3
  34. package/runtime/scripts/hfs/architecture/{supabase-ast.mjs → jwt-ast.mjs} +2 -103
  35. package/runtime/scripts/hfs/architecture/register-once.mjs +1 -1
  36. package/runtime/scripts/hfs/architecture/schema-owner.mjs +29 -4
  37. package/runtime/scripts/hfs/architecture/server-action.mjs +3 -4
  38. package/runtime/scripts/hfs/architecture/side-boundary.mjs +1 -1
  39. package/runtime/scripts/hfs/architecture/{supabase-keys.mjs → sql-keys.mjs} +1 -1
  40. package/runtime/scripts/hfs/architecture/sql-owner.mjs +25 -27
  41. package/runtime/scripts/hfs/architecture/transport-owner.mjs +7 -3
  42. package/runtime/scripts/hfs/check.mjs +6 -6
  43. package/runtime/scripts/hfs/coverage-scope.mjs +1 -1
  44. package/runtime/scripts/hfs/db-baseline-verb.mjs +48 -30
  45. package/runtime/scripts/hfs/db-plan-verb.mjs +17 -106
  46. package/runtime/scripts/hfs/db-restamp-verb.mjs +11 -10
  47. package/runtime/scripts/hfs/declaration-shape.mjs +15 -18
  48. package/runtime/scripts/hfs/manifest-shape.mjs +4 -4
  49. package/runtime/scripts/hfs/rule-params-shape.mjs +4 -1
  50. package/runtime/scripts/hfs/rules/a15-findings.mjs +1 -1
  51. package/runtime/scripts/hfs/rules/cli.mjs +1 -1
  52. package/runtime/scripts/hfs/rules/database-access.mjs +8 -74
  53. package/runtime/scripts/hfs/rules/database-baseline.mjs +3 -3
  54. package/runtime/scripts/hfs/rules/database-config.mjs +6 -214
  55. package/runtime/scripts/hfs/rules/database-constants.mjs +8 -10
  56. package/runtime/scripts/hfs/rules/database-migrations.mjs +3 -3
  57. package/runtime/scripts/hfs/rules/database-queues.mjs +4 -5
  58. package/runtime/scripts/hfs/rules/database-sql.mjs +1 -1
  59. package/runtime/scripts/hfs/rules/database.mjs +36 -39
  60. package/runtime/scripts/hfs/rules/edition.mjs +1 -1
  61. package/runtime/scripts/hfs/rules/{supabase-secrets.mjs → lite-secrets.mjs} +6 -6
  62. package/runtime/scripts/hfs/rules/script-hygiene.mjs +1 -10
  63. package/runtime/scripts/hfs/rules/stacks-lite-backing.mjs +5 -4
  64. package/runtime/scripts/hfs/rules/stacks-release.mjs +3 -2
  65. package/runtime/scripts/hfs/rules/stacks-runtime.mjs +2 -2
  66. package/runtime/scripts/hfs/rules/stacks.mjs +1 -1
  67. package/runtime/scripts/hfs/slot-app-view.mjs +1 -1
  68. package/runtime/scripts/hfs/slots.mjs +8 -2
  69. package/runtime/scripts/hfs/sql/pg-parse.mjs +2 -2
  70. package/runtime/scripts/lib/secret-patterns.mjs +2 -2
  71. package/runtime/scripts/lib/swarm-stack.mjs +7 -4
  72. package/scaffold/add-app.mjs +2 -10
  73. package/scaffold/add-cli-lite.mjs +6 -6
  74. package/scaffold/add-compose.mjs +1 -1
  75. package/scaffold/add-inbox-lite.mjs +4 -8
  76. package/scaffold/add-plan.mjs +1 -1
  77. package/scaffold/add-queue-lite.mjs +16 -24
  78. package/scaffold/add-table.mjs +34 -26
  79. package/scaffold/add.mjs +2 -2
  80. package/scaffold/app.mjs +19 -86
  81. package/scaffold/lite-exports.mjs +1 -1
  82. package/src/main.mjs +19 -20
  83. package/stack/target-binding.mjs +0 -60
  84. package/stack/urgent-floor.mjs +2 -2
  85. package/sync/index.mjs +32 -8
  86. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +1 -17
  87. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +6 -8
  88. package/templates/app/codegen-lite/scripts/codegen.mjs +2 -39
  89. package/templates/app/db-bootstrap/db/bootstrap.sql +139 -0
  90. package/templates/app/gitignore-lite +0 -3
  91. package/templates/app/package-scripts/db.json +9 -0
  92. package/templates/app/package-scripts/package.json +0 -1
  93. package/templates/app/package-scripts-lite/package.json +1 -1
  94. package/templates/app/skeleton/.starcistacks/dev/README.md +1 -1
  95. package/templates/app/skeleton/.starcistacks/production/README.md +1 -1
  96. package/templates/app/skeleton/README.md +1 -1
  97. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +84 -19
  98. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +5 -5
  99. package/templates/app/skeleton-lite/.starcistacks/dev/environment.json +1 -1
  100. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/recovery/restore-proof.md +1 -1
  101. package/templates/app/skeleton-lite/.starcistacks/dev/infra/swarm/stack.yaml +69 -6
  102. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +18 -7
  103. package/templates/app/skeleton-lite/.starcistacks/production/README.md +6 -6
  104. package/templates/app/skeleton-lite/.starcistacks/production/environment.json +1 -1
  105. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/recovery/restore-proof.md +2 -2
  106. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +69 -6
  107. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +17 -9
  108. package/templates/app/skeleton-lite/README.md +11 -11
  109. package/templates/app/skeleton-lite/{supabase → db}/migrations/__timestamp___baseline.sql +21 -1
  110. package/templates/app/tool-config-lite/prettierignore +0 -1
  111. package/templates/be/inbox/database-client.spec.ts.tpl +9 -5
  112. package/templates/be/inbox/database-client.ts.tpl +13 -4
  113. package/templates/be/inbox/platform/client-operation-outcomes.sql.tpl +47 -0
  114. package/templates/be/inbox/platform/event-bus/event-bus.service.spec.ts.tpl +3 -1
  115. package/templates/be/inbox/platform/event-bus/event-bus.service.ts.tpl +2 -2
  116. package/templates/be/inbox/platform/inbox/inbox.service.spec.ts.tpl +6 -1
  117. package/templates/be/inbox/platform/inbox/inbox.service.ts.tpl +39 -7
  118. package/templates/be/inbox/platform/migration.sql.tpl +22 -0
  119. package/templates/be/inbox/service.spec.ts.tpl +2 -2
  120. package/templates/be/inbox/service.ts.tpl +1 -2
  121. package/templates/be/patterns/jobs/platform-lite/job-claim.service.spec.ts.tpl +156 -0
  122. package/templates/be/patterns/jobs/platform-lite/job-claim.service.ts.tpl +114 -0
  123. package/templates/be/patterns/jobs/platform-lite/jobs.sql.tpl +51 -0
  124. package/templates/be/patterns/queues/platform-lite/queue-outbox.service.spec.ts.tpl +62 -0
  125. package/templates/be/patterns/queues/platform-lite/queue-outbox.service.ts.tpl +20 -0
  126. package/templates/be/patterns/queues/platform-lite/queue-outbox.sql.tpl +35 -0
  127. package/templates/be/patterns/queues/platform-lite/queue-relay.service.spec.ts.tpl +4 -1
  128. package/templates/be/patterns/queues/platform-lite/queue-relay.service.ts.tpl +16 -5
  129. package/templates/be/patterns/realtime/channel.gateway.spec.ts.tpl +8 -8
  130. package/templates/be/patterns/realtime/channel.websocket.module.ts.tpl +3 -3
  131. package/templates/be/patterns/realtime/channel.websocket.options.ts.tpl +2 -2
  132. package/templates/be/skeleton-lite/apps/api/Dockerfile +0 -1
  133. package/templates/be/skeleton-lite/apps/api/src/api.env.ts +9 -5
  134. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +7 -4
  135. package/templates/be/skeleton-lite/apps/api/src/app.module.ts +4 -4
  136. package/templates/be/skeleton-lite/apps/api/src/compose/domain.ts +3 -3
  137. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +9 -13
  138. package/templates/be/skeleton-lite/apps/api/src/compose/platform.ts +4 -2
  139. package/templates/be/skeleton-lite/apps/cli/src/cli.env.ts +2 -2
  140. package/templates/be/skeleton-lite/apps/cli/src/compose/platform.ts +3 -2
  141. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.spec.ts +4 -4
  142. package/templates/be/skeleton-lite/src/modules/domain/identity/auth.guard.ts +3 -3
  143. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.contracts.ts +1 -1
  144. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +6 -6
  145. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.options.ts +3 -3
  146. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/index.ts +4 -0
  147. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.spec.ts +22 -0
  148. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.client.ts +25 -0
  149. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.config.ts +12 -0
  150. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.decorators.ts +9 -0
  151. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.options.ts +35 -0
  152. package/templates/be/skeleton-lite/src/modules/integrations/passport/errors/passport.error.ts +22 -0
  153. package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +12 -0
  154. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.config.ts +23 -0
  155. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.cookie.ts +29 -0
  156. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.decorators.ts +22 -0
  157. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.options.ts +63 -0
  158. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.password.ts +11 -0
  159. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.spec.ts +32 -0
  160. package/templates/be/skeleton-lite/src/modules/integrations/passport/passport.tokens.ts +69 -0
  161. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/google.strategy.ts +37 -0
  162. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +36 -0
  163. package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/oauth-state.store.ts +59 -0
  164. package/templates/be/skeleton-lite/src/modules/platform/database/database.functions.ts +24 -0
  165. package/templates/be/skeleton-lite/src/modules/platform/database/database.module.ts +2 -1
  166. package/templates/be/skeleton-lite/src/modules/platform/database/database.options.ts +4 -2
  167. package/templates/be/skeleton-lite/src/modules/platform/database/database.sql.ts +19 -6
  168. package/templates/be/skeleton-lite/src/modules/platform/database/entities.ts +6 -0
  169. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
  170. package/templates/be/skeleton-lite/src/modules/platform/database/migration-runner.service.ts +1 -1
  171. package/templates/be/skeleton-lite/src/modules/platform/database/primary.config.ts +2 -2
  172. package/templates/be/skeleton-lite/src/modules/platform/database/seed-runner.service.ts +2 -2
  173. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +18 -5
  174. package/templates/be/skeleton-lite/src/tests/fixtures/builders/cli.builder.ts +1 -1
  175. package/templates/be/table/entities.index.ts.tpl +4 -0
  176. package/templates/be/table/entity.ts.tpl +21 -0
  177. package/templates/be/table/index.ts.tpl +1 -0
  178. package/templates/be/table/module.ts.tpl +1 -1
  179. package/templates/be/table/service.api.ts.tpl +10 -11
  180. package/templates/be/table/service.ts.tpl +5 -6
  181. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/auth-door.ts +30 -0
  182. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/client.ts +46 -14
  183. package/templates/fe/skeleton-lite/apps/__app__/src/modules/auth/token-store.ts +31 -0
  184. package/templates/fe/skeleton-lite/apps/web/src/modules/config/index.ts +2 -5
  185. package/db/local-migrate.mjs +0 -194
  186. package/db/reset.mjs +0 -125
  187. package/emit/db-types.mjs +0 -271
  188. package/runtime/scripts/api/supabase/database-dump.mjs +0 -5
  189. package/runtime/scripts/api/supabase/database-push.mjs +0 -5
  190. package/runtime/scripts/api/supabase/database-query.mjs +0 -5
  191. package/runtime/scripts/api/supabase/lib.mjs +0 -55
  192. package/runtime/scripts/api/supabase/migration-repair.mjs +0 -5
  193. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +0 -50
  194. package/runtime/scripts/hfs/architecture/supabase.mjs +0 -226
  195. package/scaffold/supabase-ports.mjs +0 -26
  196. package/templates/app/package-scripts/supabase.json +0 -7
  197. package/templates/app/skeleton-lite/supabase/config.toml +0 -48
  198. package/templates/be/inbox/platform/event-bus/persistence/event-bus.sql.ts.tpl +0 -5
  199. package/templates/be/inbox/platform/inbox/persistence/inbox.rows.ts.tpl +0 -33
  200. package/templates/be/inbox/platform/inbox/persistence/inbox.sql.ts.tpl +0 -33
  201. package/templates/be/inbox/sql.ts.tpl +0 -16
  202. package/templates/be/patterns/jobs/platform-lite/persistence.jobs.sql.ts.tpl +0 -33
  203. package/templates/be/patterns/queues/platform-lite/persistence.queue.sql.ts.tpl +0 -12
  204. package/templates/be/skeleton-lite/src/modules/integrations/supabase/errors/supabase.error.ts +0 -16
  205. package/templates/be/skeleton-lite/src/modules/integrations/supabase/index.ts +0 -6
  206. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.spec.ts +0 -27
  207. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.client.ts +0 -24
  208. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.config.ts +0 -8
  209. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.decorators.ts +0 -17
  210. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.jwks.ts +0 -32
  211. package/templates/be/skeleton-lite/src/modules/integrations/supabase/supabase.options.ts +0 -24
  212. package/templates/be/table/rows.ts.tpl +0 -4
  213. package/templates/be/table/sql.api.ts.tpl +0 -7
  214. package/templates/be/table/sql.ts.tpl +0 -4
  215. package/templates/fe/auth/supabase-client.ts.tpl +0 -38
  216. package/templates/fe/skeleton/apps/__app__/src/app/auth/callback/route.ts +0 -11
  217. package/templates/fe/skeleton/apps/__app__/src/modules/auth/pkce-client.ts +0 -18
  218. package/templates/fe/skeleton/apps/__app__/src/modules/auth/server.ts +0 -19
  219. /package/templates/app/skeleton-lite/{supabase → db}/seed.sql +0 -0
@@ -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
  {
package/src/main.mjs CHANGED
@@ -49,7 +49,7 @@
49
49
  // starci app ci secret set <NAME> [--env E | --dependabot] [--repo owner/name] reads the value from stdin only.
50
50
  // starci app ci var set <NAME> [--env E] [--value v] | var sync [--execute] | verify [--json]
51
51
  // declared GitHub CI custody; verify compares names, scopes and public values.
52
- // starci app db baseline | restamp --map <restamp.yaml> | plan
52
+ // starci app db new <name> | baseline | restamp --map <restamp.yaml> | plan | migrate | post-cutover | dump
53
53
  // preview the database cutover or compare its state with the repo; baseline and restamp mutate only with --apply.
54
54
  // starci app add <api|webhook|realtime|saga|job|reactor|queue|projection> <name> [--event <event> --from <service> --service <Class>=<module>] [--owner <service> --failed <event> --done <event>] [--connection <name>] [--cwd <dir>]
55
55
  // exactly that kind's file tree, generated FROM the files: tree of its pattern topic (knowledge/patterns/be) with the
@@ -79,7 +79,6 @@ import { main as syncMain } from '../sync/cli.mjs';
79
79
  import { SyncError, bundledPresets } from '../sync/index.mjs';
80
80
  import { managedFindings } from '../sync/managed.mjs';
81
81
  import { emitContracts } from '../emit/contracts.mjs';
82
- import { dbTypesEmitter } from '../emit/db-types.mjs';
83
82
  import { ScaffoldError, newService, newSpec } from '../scaffold/service.mjs';
84
83
  import { EditionRefusal } from '../scaffold/edition-gate.mjs';
85
84
  import { addKind } from '../scaffold/add.mjs';
@@ -95,26 +94,26 @@ import { releaseMain } from '../release/index.mjs';
95
94
  import { dbMigrateMain } from '../db/migrate.mjs';
96
95
  import { dbPostCutoverMain } from '../db/post-cutover.mjs';
97
96
  import { dbDumpMain } from '../db/dump.mjs';
98
- import { dbResetMain } from '../db/reset.mjs';
97
+ import { dbNewMain } from '../db/new.mjs';
99
98
  import { dbDriftMain } from '../db/drift.mjs';
100
99
  import { codemodMain } from '../codemod/button-intent.mjs';
101
100
 
102
101
  export const VERBS = Object.freeze(['scaffold', 'add', 'lint', 'sync', 'check', 'upgrade', 'stack', 'release', 'explain', 'emit', 'new', 'secret', 'ci', 'db', 'hygiene', 'coverage', 'codemod']);
103
- const DATABASE_VERBS = new Map([['baseline', dbBaselineMain], ['restamp', dbRestampMain], ['plan', dbPlanMain], ['migrate', dbMigrateMain], ['post-cutover', dbPostCutoverMain], ['dump', dbDumpMain], ['reset', dbResetMain], ['drift', dbDriftMain]]);
102
+ const DATABASE_VERBS = new Map([['baseline', dbBaselineMain], ['restamp', dbRestampMain], ['plan', dbPlanMain], ['migrate', dbMigrateMain], ['post-cutover', dbPostCutoverMain], ['dump', dbDumpMain], ['new', dbNewMain], ['drift', dbDriftMain]]);
104
103
 
105
104
  const USAGE = `starci app scaffold <name> [--into <dir>] [--edition full|lite] [--registry-visibility public|private] [--cwd <dir>]
106
105
  starci app add <api|webhook|realtime|saga|job|reactor|queue|projection|cli|table|app|block|composite|branch|leaf|page|overlay|layout> <name> [options] [--cwd <dir>]
107
106
  starci app lint [--cwd <dir>] [--changed <file>...] [--workspace <dir>] [--side be|fe] [--fix] [--format text|json] [--sonar <file>]
108
107
  starci app codemod button-intent [--write] [--format json|markdown] [--report <file.json|file.md>] [--cwd <dir>]
109
108
  starci app sync (--check | --write) [--cwd <dir>]
110
- starci app check [--cwd <dir>] [--json] [--fast] [--base <ref>] [--db-types] [--wiring] [--coverage-from <lcov>]
111
- starci app db baseline [--stamp <version>] [--local] [--apply] [--cwd <dir>]
112
- starci app db restamp --map <restamp.yaml> [--local] [--apply] [--cwd <dir>]
113
- starci app db plan [--local] [--json] [--cwd <dir>]
114
- starci app db dump (--linked | --local | --url-env <NAME>) --out <dir> [--schema-only] [--json] [--cwd <dir>]
115
- starci app db reset --linked --auth keep|drop --storage keep|drop --backup <receipt.json> [--execute] [--json] [--cwd <dir>]
116
- starci app db migrate [--execute] [--local | --linked | --url-env <NAME>] [--json] [--cwd <dir>]
117
- starci app db post-cutover [--execute] [--green-at <ISO> | --owner-confirms-old-app-gone] [--linked | --url-env <NAME>] [--json] [--cwd <dir>]
109
+ starci app check [--cwd <dir>] [--json] [--fast] [--base <ref>] [--wiring] [--coverage-from <lcov>]
110
+ starci app db new <kebab-name> [--json] [--cwd <dir>]
111
+ starci app db baseline [--stamp <version>] [--url-env <NAME>] [--apply] [--cwd <dir>]
112
+ starci app db restamp --map <restamp.yaml> [--url-env <NAME>] [--apply] [--cwd <dir>]
113
+ starci app db plan [--url-env <NAME>] [--json] [--cwd <dir>]
114
+ starci app db dump [--url-env <NAME>] --out <dir> [--schema-only] [--json] [--cwd <dir>]
115
+ starci app db migrate [--execute] [--url-env <NAME>] [--json] [--cwd <dir>]
116
+ starci app db post-cutover [--execute] [--green-at <ISO> | --owner-confirms-old-app-gone] [--url-env <NAME>] [--json] [--cwd <dir>]
118
117
  starci app db drift [--url-env <NAME>] [--json] [--cwd <dir>]
119
118
  starci app release gate [--execute] [--source <ref>] [--tag-prefix <prefix>] [--backfill <tag>] [--json] [--cwd <dir>]
120
119
  starci app release push [--execute] [--remote <name>] [--urgent --reason <text>] [--json] [--cwd <dir>]
@@ -147,7 +146,7 @@ const PER_CODE_LIMIT = 25;
147
146
  const VALUE_FLAGS = new Set(['--base', '--inject', '--into', '--event', '--from', '--service', '--connection', '--owner', '--failed', '--done', '--coverage-from', '--registry-visibility', '--shapes', '--read', '--app', '--kind', '--blocks', '--tabs']);
148
147
  /** Flags that may repeat: their values are collected in order. */
149
148
  const LIST_FLAGS = new Set(['--inject']);
150
- const BOOL_FLAGS = new Set(['--json', '--fast', '--fe', '--no-types', '--db-types', '--wiring', '--form']);
149
+ const BOOL_FLAGS = new Set(['--json', '--fast', '--fe', '--wiring', '--form']);
151
150
 
152
151
  function coverageArgument(arg, args, opts) {
153
152
  const inline = arg.startsWith('--coverage-from=');
@@ -250,11 +249,11 @@ function printExplain(e, out) {
250
249
  * The app pass: slots, managed files, formatter, contract snapshots and the architecture machine's check surface, root and sides.
251
250
  * `only` limits the formatter to those (app-relative) files.
252
251
  */
253
- async function runCheck({ repoRoot, harness, fast = false, base, only, presets, prettier, dbTypes = false }) {
252
+ async function runCheck({ repoRoot, harness, fast = false, base, only, presets, prettier }) {
254
253
  const tracked = trackedFiles(repoRoot);
255
254
  // Managed files of the root and both sides, the .gitignore block and sonar against their render (R04, R05, R11, ...), and prettier
256
255
  // through the app's own install (R19, never under --fast).
257
- const extraFindings = [...await managedFindings({ repoRoot, tracked, presets }), ...await checkDatabase({ repoRoot, files: tracked, base, ...(dbTypes ? { emitTypes: dbTypesEmitter() } : {}) }), ...(fast ? [] : await formatFindings({ repoRoot, files: only ?? tracked, prettier }))];
256
+ const extraFindings = [...await managedFindings({ repoRoot, tracked, presets }), ...await checkDatabase({ repoRoot, files: tracked, base }), ...(fast ? [] : await formatFindings({ repoRoot, files: only ?? tracked, prettier }))];
258
257
  // R23 against the be side itself (full pass only): committed snapshots equal what `starci app emit` writes now.
259
258
  let contracts = null;
260
259
  let be = null;
@@ -345,7 +344,7 @@ async function installedStackMain(cwd) {
345
344
  function databaseMain(argv, io) {
346
345
  const [verb, ...rest] = argv;
347
346
  const dispatch = DATABASE_VERBS.get(verb);
348
- if (!dispatch) throw new Error('starci app db takes baseline, restamp, plan, migrate, post-cutover, dump, reset or drift');
347
+ if (!dispatch) throw new Error('starci app db takes new, baseline, restamp, plan, migrate, post-cutover, dump or drift');
349
348
  return dispatch(rest, io);
350
349
  }
351
350
 
@@ -357,7 +356,7 @@ function printCheckProofs(result, out) {
357
356
  async function checkMain(opts, { cwd: repoRoot, harness, edition, stdout: out, presets, prettier, checkApp = runCheck, runLinter }) {
358
357
  if (opts.positional.length) throw new Error('starci app check takes no path');
359
358
  if (opts.base !== undefined && opts.fast !== true) throw new Error('--base names the ref --fast compares with; it needs --fast');
360
- const checked = await checkEdition({ repoRoot, edition, normal: () => checkApp({ repoRoot, harness, fast: opts.fast === true, base: opts.base, presets, prettier, dbTypes: opts['db-types'] === true }), presets, prettier });
359
+ const checked = await checkEdition({ repoRoot, edition, normal: () => checkApp({ repoRoot, harness, fast: opts.fast === true, base: opts.base, presets, prettier }), presets, prettier });
361
360
  const proof = withCheckProofs(checked, { repoRoot, edition, wiring: opts.wiring === true, coverageFrom: opts['coverage-from'] });
362
361
  const changed = opts.fast === true ? changedSince(repoRoot, proof.fast?.base ?? opts.base).files : null;
363
362
  const existing = changed ? changed.filter(file => fs.existsSync(path.join(repoRoot, file))) : [];
@@ -398,7 +397,7 @@ function newMain(opts, { cwd: repoRoot, stdout: out }) {
398
397
  function addMain(opts, { cwd: repoRoot, stdout: out }) {
399
398
  const [noun, name, ...extra] = opts.positional;
400
399
  if (!noun || !name || extra.length) throw new Error('starci app add takes `<noun> <name>` and the options of the noun');
401
- const { created, registered } = addKind({ repoRoot, noun, name, options: { event: opts.event, from: opts.from, service: opts.service, connection: opts.connection, owner: opts.owner, failed: opts.failed, done: opts.done, fe: opts.fe, noTypes: opts['no-types'], shapes: opts.shapes, read: opts.read, app: opts.app, kind: opts.kind, form: opts.form, blocks: opts.blocks, tabs: opts.tabs } });
400
+ const { created, registered } = addKind({ repoRoot, noun, name, options: { event: opts.event, from: opts.from, service: opts.service, connection: opts.connection, owner: opts.owner, failed: opts.failed, done: opts.done, fe: opts.fe, shapes: opts.shapes, read: opts.read, app: opts.app, kind: opts.kind, form: opts.form, blocks: opts.blocks, tabs: opts.tabs } });
402
401
  for (const file of created) out(`created ${file}\n`);
403
402
  const kinds = registered.kinds.length ? `; kinds ${registered.kinds.join(', ')}` : '';
404
403
  // A noun that registers nothing in hfs.json (a table, an app, a front-end unit) ends with what it wrote.
@@ -437,7 +436,7 @@ function declaredEdition(cwd) {
437
436
  */
438
437
  async function stackAppMain(argv, { cwd, edition, stackMain, stdout: out, stderr: err, env, sops, stackIo }) {
439
438
  if (STACK_ACTIONS.includes(argv[0])) return deployStackMain(argv, { ...stackIo, cwd, stdout: out, stderr: err, env, ...(sops ? { sops } : {}) });
440
- if ((edition ?? declaredEdition(cwd)) === 'lite') throw new Error('starci app stack up|down|status manage the local test world of the full edition; a lite app starts its local infrastructure with the Supabase CLI');
439
+ if ((edition ?? declaredEdition(cwd)) === 'lite') throw new Error('starci app stack up|down|status manage the local test world of the full edition; a lite app starts its local database with npm run db:start');
441
440
  const run = stackMain ?? await installedStackMain(cwd);
442
441
  return run(argv, { cwd, out, err });
443
442
  }
@@ -460,7 +459,7 @@ function errorMessage(error) {
460
459
 
461
460
  /**
462
461
  * `harness` ({ version }) is the CLI that runs this package in-process: its version is the starci-harness an app is judged and
463
- * scaffolded at (runtime/scripts/hfs/canon-pins.mjs). `run` injects the Supabase runner, `runLinter` the linter transport, `checkApp`
462
+ * scaffolded at (runtime/scripts/hfs/canon-pins.mjs). `run` injects the database runner, `runLinter` the linter transport, `checkApp`
464
463
  * the HFS check and `stackIo` Swarm tools; production defaults execute every check.
465
464
  */
466
465
  export async function main(argv, {