@topogram/cli 0.3.54 → 0.3.55

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 (40) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/review-groups.js +3 -3
  3. package/src/agent-ops/query-builders.js +34 -34
  4. package/src/archive/schema.js +1 -1
  5. package/src/cli.js +173 -20
  6. package/src/generator/adapters.js +2 -2
  7. package/src/generator/context/domain-coverage.js +2 -2
  8. package/src/generator/context/shared.js +6 -22
  9. package/src/generator/context/slice.js +10 -10
  10. package/src/generator/docs.js +1 -1
  11. package/src/generator/registry.js +1 -1
  12. package/src/generator/runtime/app-bundle.js +7 -5
  13. package/src/generator/runtime/compile-check.js +3 -1
  14. package/src/generator/runtime/deployment.js +3 -1
  15. package/src/generator/runtime/environment.js +22 -19
  16. package/src/generator/runtime/shared.js +7 -7
  17. package/src/generator/surfaces/contracts.js +1 -1
  18. package/src/generator/surfaces/databases/lifecycle-shared.js +1 -1
  19. package/src/generator/surfaces/databases/postgres/drizzle.js +1 -1
  20. package/src/generator/surfaces/databases/postgres/prisma.js +1 -1
  21. package/src/generator/surfaces/databases/shared.js +3 -3
  22. package/src/generator/surfaces/databases/sqlite/prisma.js +1 -1
  23. package/src/generator/surfaces/services/persistence-wiring.js +1 -1
  24. package/src/generator/surfaces/services/server-contract.js +1 -1
  25. package/src/generator/surfaces/shared.js +1 -1
  26. package/src/generator/surfaces/web/ui-surface-contract.js +1 -1
  27. package/src/generator/widget-conformance.js +6 -6
  28. package/src/generator/widgets.js +1 -1
  29. package/src/generator-policy.js +10 -10
  30. package/src/import/core/runner.js +60 -50
  31. package/src/project-config.js +5 -42
  32. package/src/proofs/contract-audit.js +1 -1
  33. package/src/realization/backend/build-backend-runtime-realization.js +3 -3
  34. package/src/realization/ui/build-ui-shared-realization.js +9 -9
  35. package/src/realization/ui/build-web-realization.js +3 -3
  36. package/src/reconcile/journeys.js +1 -1
  37. package/src/resolver/enrich/widget.js +2 -2
  38. package/src/resolver/index.js +4 -23
  39. package/src/validator/index.js +10 -10
  40. package/src/workflows.js +49 -49
@@ -58,11 +58,11 @@ function summarizeProjection(projection) {
58
58
  kind: projection.kind,
59
59
  name: projection.name || projection.id,
60
60
  description: projection.description || null,
61
- platform: projection.platform || null,
61
+ type: projection.type || null,
62
62
  realizes: refIds(projection.realizes),
63
63
  outputs: stableSortedStrings(projection.outputs || []),
64
64
  uiScreens: stableSortedStrings((projection.uiScreens || []).map((screen) => screen.id)),
65
- uiComponents: stableSortedStrings((projection.uiComponents || []).map((entry) => entry.component?.id).filter(Boolean)),
65
+ widgetBindings: stableSortedStrings((projection.widgetBindings || []).map((entry) => entry.widget?.id).filter(Boolean)),
66
66
  dbTables: stableSortedStrings((projection.dbTables || []).map((table) => table.table || table.entity?.id)),
67
67
  httpCapabilities: stableSortedStrings((projection.http || []).map((entry) => entry.capability?.id)),
68
68
  reviewBoundary: reviewBoundaryForProjectionPolicy(projection),
@@ -397,11 +397,7 @@ export function relatedProjectionsForShape(graph, shapeId) {
397
397
  }
398
398
 
399
399
  export function widgetById(graph, widgetId) {
400
- return (graph?.byKind?.widget || graph?.byKind?.component || []).find((widget) => widget.id === widgetId) || null;
401
- }
402
-
403
- export function componentById(graph, componentId) {
404
- return widgetById(graph, componentId);
400
+ return (graph?.byKind?.widget || []).find((widget) => widget.id === widgetId) || null;
405
401
  }
406
402
 
407
403
  function projectionById(graph, projectionId) {
@@ -435,7 +431,7 @@ function downstreamProjectionIds(graph, projectionIds) {
435
431
  }
436
432
 
437
433
  export function relatedWidgetsForProjection(graph, projection) {
438
- const directIds = (projection?.uiComponents || []).map((entry) => entry.component?.id).filter(Boolean);
434
+ const directIds = (projection?.widgetBindings || []).map((entry) => entry.widget?.id).filter(Boolean);
439
435
  const inheritedIds = realizedProjectionIds(projection)
440
436
  .map((projectionId) => projectionById(graph, projectionId))
441
437
  .filter(Boolean)
@@ -443,10 +439,6 @@ export function relatedWidgetsForProjection(graph, projection) {
443
439
  return stableSortedStrings([...directIds, ...inheritedIds]);
444
440
  }
445
441
 
446
- export function relatedComponentsForProjection(graph, projection) {
447
- return relatedWidgetsForProjection(graph, projection);
448
- }
449
-
450
442
  export function relatedShapesForWidget(widget) {
451
443
  if (!widget) return [];
452
444
  const ids = [
@@ -461,15 +453,11 @@ export function relatedShapesForWidget(widget) {
461
453
  return stableSortedStrings(ids);
462
454
  }
463
455
 
464
- export function relatedShapesForComponent(component) {
465
- return relatedShapesForWidget(component);
466
- }
467
-
468
456
  export function relatedProjectionsForWidget(graph, widgetId) {
469
457
  const widget = widgetById(graph, widgetId);
470
458
  if (!widget) return [];
471
459
  const explicitProjectionIds = stableSortedStrings((graph?.byKind?.projection || [])
472
- .filter((projection) => (projection.uiComponents || []).some((entry) => entry.component?.id === widgetId))
460
+ .filter((projection) => (projection.widgetBindings || []).some((entry) => entry.widget?.id === widgetId))
473
461
  .map((projection) => projection.id));
474
462
  const dependencyProjectionIds = stableSortedStrings((widget.dependencies || []).flatMap((dependency) => {
475
463
  const kind = referenceKind(dependency);
@@ -499,10 +487,6 @@ export function relatedProjectionsForWidget(graph, widgetId) {
499
487
  return downstreamProjectionIds(graph, stableSortedStrings([...explicitProjectionIds, ...dependencyProjectionIds, ...viaUiRegions]));
500
488
  }
501
489
 
502
- export function relatedProjectionsForComponent(graph, componentId) {
503
- return relatedProjectionsForWidget(graph, componentId);
504
- }
505
-
506
490
  function referenceKind(reference) {
507
491
  if (reference?.target?.kind) {
508
492
  return reference.target.kind;
@@ -555,7 +539,7 @@ export function workspaceInventory(graph) {
555
539
  journeys: stableSortedStrings((graph.docs || []).filter((doc) => doc.kind === "journey").map((doc) => doc.id)),
556
540
  entities: stableSortedStrings((graph.byKind.entity || []).map((item) => item.id)),
557
541
  projections: stableSortedStrings((graph.byKind.projection || []).map((item) => item.id)),
558
- widgets: stableSortedStrings((graph.byKind.widget || graph.byKind.component || []).map((item) => item.id)),
542
+ widgets: stableSortedStrings((graph.byKind.widget || []).map((item) => item.id)),
559
543
  verifications: stableSortedStrings((graph.byKind.verification || []).map((item) => item.id)),
560
544
  domains: stableSortedStrings((graph.byKind.domain || []).map((item) => item.id)),
561
545
  pitches: stableSortedStrings((graph.byKind.pitch || []).map((item) => item.id)),
@@ -337,7 +337,7 @@ function widgetDependencyKind(dependency) {
337
337
  }
338
338
 
339
339
  function uiAgentPacketForProjection(graph, projection) {
340
- const projectionType = projection.type || projection.platform || null;
340
+ const projectionType = projection.type || projection.type || null;
341
341
  if (projectionType !== "ui_contract" && !String(projectionType || "").endsWith("_surface")) {
342
342
  return null;
343
343
  }
@@ -368,7 +368,7 @@ function uiAgentPacketForProjection(graph, projection) {
368
368
  screenId: route.screenId,
369
369
  path: route.path
370
370
  })),
371
- widgets: (ownerProjection.uiComponents || []).map((usage) => widgetUsagePacket(usage)),
371
+ widgets: (ownerProjection.widgetBindings || []).map((usage) => widgetUsagePacket(usage)),
372
372
  designTokens: designIntentPacket(ownerProjection),
373
373
  requiredGates: uiRequiredGates(projection.id)
374
374
  };
@@ -378,9 +378,9 @@ function uiAgentPacketForWidget(graph, widget, projectionIds) {
378
378
  const projectionSet = new Set(projectionIds);
379
379
  const sourceUsages = [];
380
380
  for (const projection of graph.byKind.projection || []) {
381
- for (const usage of projection.uiComponents || []) {
382
- if (usage.component?.id !== widget.id) continue;
383
- const projectionType = projection.type || projection.platform || null;
381
+ for (const usage of projection.widgetBindings || []) {
382
+ if (usage.widget?.id !== widget.id) continue;
383
+ const projectionType = projection.type || projection.type || null;
384
384
  sourceUsages.push({
385
385
  projection: {
386
386
  id: projection.id,
@@ -406,9 +406,9 @@ function uiAgentPacketForWidget(graph, widget, projectionIds) {
406
406
  id: widget.id,
407
407
  name: widget.name || widget.id,
408
408
  category: widget.category || null,
409
- patterns: widget.widgetContract?.patterns || widget.componentContract?.patterns || [],
410
- regions: widget.widgetContract?.regions || widget.componentContract?.regions || [],
411
- behaviors: widget.widgetContract?.behaviors || widget.componentContract?.behaviors || []
409
+ patterns: widget.widgetContract?.patterns || [],
410
+ regions: widget.widgetContract?.regions || [],
411
+ behaviors: widget.widgetContract?.behaviors || []
412
412
  },
413
413
  sourceUsages,
414
414
  inheritedBy: [...projectionSet]
@@ -421,7 +421,7 @@ function uiAgentPacketForWidget(graph, widget, projectionIds) {
421
421
  function sharedUiProjectionFor(graph, projection) {
422
422
  for (const reference of projection.realizes || []) {
423
423
  const candidate = (graph.byKind.projection || []).find((entry) => entry.id === reference.id);
424
- if ((candidate?.type || candidate?.platform) === "ui_contract") {
424
+ if ((candidate?.type || candidate?.type) === "ui_contract") {
425
425
  return candidate;
426
426
  }
427
427
  }
@@ -432,7 +432,7 @@ function widgetUsagePacket(usage) {
432
432
  return {
433
433
  screenId: usage.screenId || null,
434
434
  region: usage.region || null,
435
- widgetId: usage.component?.id || null,
435
+ widgetId: usage.widget?.id || null,
436
436
  dataBindings: (usage.dataBindings || []).map((binding) => ({
437
437
  prop: binding.prop || null,
438
438
  source: binding.source?.id || binding.source || null
@@ -229,7 +229,7 @@ export function generateDocs(graph) {
229
229
  lines.push(projection.description);
230
230
  lines.push("");
231
231
  }
232
- lines.push(`Platform: \`${projection.platform}\``);
232
+ lines.push(`Projection type: \`${projection.type}\``);
233
233
  lines.push(`Realizes: ${refList(projection.realizes)}`);
234
234
  lines.push(`Outputs: ${symbolList(projection.outputs)}`);
235
235
  lines.push("");
@@ -490,7 +490,7 @@ export function projectionCompatibilityKey(projection) {
490
490
  if (isApiProjection(projection)) {
491
491
  return "api_contract";
492
492
  }
493
- return projection?.type || projection?.platform || "";
493
+ return projection?.type || projection?.type || "";
494
494
  }
495
495
 
496
496
  /**
@@ -23,6 +23,7 @@ import {
23
23
  } from "./shared.js";
24
24
  import { getExampleImplementation } from "../../example-implementation.js";
25
25
  import { mergeNamedBundles, renderLoadEnvScript, renderNestedBundleShellScript } from "./bundle-shared.js";
26
+ import { generateDbLifecyclePlan } from "../surfaces/databases/lifecycle-shared.js";
26
27
 
27
28
  function runtimeReferenceFor(graph, options = {}) {
28
29
  try {
@@ -47,6 +48,7 @@ function buildAppBundlePlan(graph, options = {}) {
47
48
  const runtimeReference = runtimeReferenceFor(graph, options);
48
49
  const topology = resolveRuntimeTopology(graph, options);
49
50
  const { apiProjection, uiProjection, dbProjection } = getDefaultEnvironmentProjections(graph, options);
51
+ const dbLifecycle = dbProjection ? generateDbLifecyclePlan(graph, { ...options, projectionId: dbProjection.id }) : null;
50
52
  const environmentProfile = options.profileId || "local_process";
51
53
  const deployProfile = options.deployProfileId || "fly_io";
52
54
  const smokeVerification = buildVerificationSummary(graph, ["smoke", "journey"]);
@@ -63,7 +65,9 @@ function buildAppBundlePlan(graph, options = {}) {
63
65
  projections: {
64
66
  api: apiProjection?.id || null,
65
67
  ui: uiProjection?.id || null,
66
- db: dbProjection?.id || null
68
+ db: dbProjection?.id || null,
69
+ dbType: dbProjection?.type || null,
70
+ dbEngine: dbLifecycle?.engine || null
67
71
  },
68
72
  topology: {
69
73
  runtimes: topology.runtimes.map((runtime) => ({
@@ -113,7 +117,7 @@ function renderAppBundleEnvExample(plan) {
113
117
  };
114
118
  const ports = runtimePorts(plan.runtimeReference, topology);
115
119
  const urls = runtimeUrls(plan.runtimeReference, topology);
116
- if (!plan.projections.dbPlatform) {
120
+ if (!plan.projections.dbType) {
117
121
  return `# App bundle defaults
118
122
  TOPOGRAM_ENVIRONMENT_PROFILE=${plan.profiles.environment}
119
123
  TOPOGRAM_DEPLOY_PROFILE=${plan.profiles.deployment}
@@ -126,7 +130,7 @@ ${plan.runtimeReference.environment.envExample || ""}
126
130
  # Smoke-test defaults
127
131
  ${plan.projections.api ? `TOPOGRAM_API_BASE_URL=${urls.api}\n` : ""}${plan.projections.ui ? `TOPOGRAM_WEB_BASE_URL=${urls.web}\n` : ""}`;
128
132
  }
129
- if (plan.projections.dbPlatform === "db_contract") {
133
+ if (plan.projections.dbEngine === "sqlite") {
130
134
  return `# App bundle defaults
131
135
  TOPOGRAM_ENVIRONMENT_PROFILE=${plan.profiles.environment}
132
136
  TOPOGRAM_DEPLOY_PROFILE=${plan.profiles.deployment}
@@ -409,8 +413,6 @@ function noopBundle(name, message) {
409
413
  export function generateAppBundle(graph, options = {}) {
410
414
  const plan = buildAppBundlePlan(graph, options);
411
415
  const topology = resolveRuntimeTopology(graph, options);
412
- const projections = getDefaultEnvironmentProjections(graph, options);
413
- plan.projections.dbPlatform = projections.dbProjection?.platform || null;
414
416
  const fullStack = topology.apiComponents.length > 0 && topology.webComponents.length > 0 && topology.dbComponents.length > 0;
415
417
  const envBundle = generateEnvironmentBundle(graph, { ...options, profileId: plan.profiles.environment });
416
418
  const deployBundle = fullStack
@@ -8,6 +8,7 @@ import {
8
8
  } from "./shared.js";
9
9
  import { getExampleImplementation } from "../../example-implementation.js";
10
10
  import { mergeBundleFiles } from "./bundle-shared.js";
11
+ import { generateDbLifecyclePlan } from "../surfaces/databases/lifecycle-shared.js";
11
12
 
12
13
  function compileCheckName(graph, options = {}) {
13
14
  try {
@@ -72,8 +73,9 @@ function renderCompileCheckEnvExample(graph, options = {}) {
72
73
  const runtimeReference = runtimeReferenceFor(graph, options);
73
74
  const topology = resolveRuntimeTopology(graph, options);
74
75
  const { dbProjection } = getDefaultEnvironmentProjections(graph, options);
76
+ const dbLifecycle = dbProjection ? generateDbLifecyclePlan(graph, { ...options, projectionId: dbProjection.id }) : null;
75
77
  const urls = runtimeUrls(runtimeReference, topology);
76
- if (dbProjection?.platform === "db_contract") {
78
+ if (dbLifecycle?.engine === "sqlite") {
77
79
  return `DATABASE_URL=./var/${runtimeReference.environment.databaseName || "topogram_app"}.sqlite
78
80
  PUBLIC_TOPOGRAM_API_BASE_URL=${urls.api}
79
81
  PUBLIC_TOPOGRAM_DEMO_USER_ID=${runtimeDemoUserId(runtimeReference)}
@@ -8,6 +8,7 @@ import {
8
8
  import { getExampleImplementation } from "../../example-implementation.js";
9
9
  import { mergeNamedBundles, renderRootEnvFileShellScript, renderRootShellScript } from "./bundle-shared.js";
10
10
  import { generatorProfile as manifestGeneratorProfile } from "../registry.js";
11
+ import { generateDbLifecyclePlan } from "../surfaces/databases/lifecycle-shared.js";
11
12
 
12
13
  function projectionHintProfile(projection, fallback) {
13
14
  for (const entry of projection.generatorDefaults || []) {
@@ -33,7 +34,8 @@ function buildDeploymentPlan(graph, options = {}) {
33
34
  const profile = options.profileId || "fly_io";
34
35
  const supportedProfiles = ["fly_io", "railway"];
35
36
  const webProfile = manifestGeneratorProfile(topology.primaryWeb?.generator?.id, null) || projectionHintProfile(uiProjection, "sveltekit");
36
- const databaseTarget = dbProjection.platform === "db_contract"
37
+ const dbLifecycle = dbProjection ? generateDbLifecyclePlan(graph, { ...options, projectionId: dbProjection.id }) : null;
38
+ const databaseTarget = dbLifecycle?.engine === "sqlite"
37
39
  ? "sqlite_file"
38
40
  : profile === "fly_io"
39
41
  ? "managed_postgres"
@@ -43,10 +43,11 @@ function buildEnvironmentPlan(graph, options = {}) {
43
43
  const topology = resolveRuntimeTopology(graph, options);
44
44
  const { apiProjection, uiProjection, dbProjection } = getDefaultEnvironmentProjections(graph, options);
45
45
  const dbLifecycle = dbProjection ? generateDbLifecyclePlan(graph, { ...options, projectionId: dbProjection.id }) : null;
46
- const profile = options.profileId || (dbProjection?.platform === "db_contract" || !dbProjection ? "local_process" : "local_docker");
46
+ const dbEngine = dbLifecycle?.engine || null;
47
+ const profile = options.profileId || (dbEngine === "sqlite" || !dbProjection ? "local_process" : "local_docker");
47
48
  const usesDocker = profile === "local_docker";
48
49
  const webProfile = manifestGeneratorProfile(topology.primaryWeb?.generator?.id, null) || projectionHintProfile(uiProjection, "sveltekit");
49
- const isSqlite = dbProjection?.platform === "db_contract";
50
+ const isSqlite = dbEngine === "sqlite";
50
51
  const ports = runtimePorts(runtimeReference, topology);
51
52
 
52
53
  return {
@@ -71,15 +72,16 @@ function buildEnvironmentPlan(graph, options = {}) {
71
72
  projections: {
72
73
  api: {
73
74
  id: apiProjection?.id || null,
74
- platform: apiProjection?.platform || null
75
+ type: apiProjection?.type || null
75
76
  },
76
77
  ui: {
77
78
  id: uiProjection?.id || null,
78
- platform: uiProjection?.platform || null
79
+ type: uiProjection?.type || null
79
80
  },
80
81
  db: {
81
82
  id: dbProjection?.id || null,
82
- platform: dbProjection?.platform || null,
83
+ type: dbProjection?.type || null,
84
+ engine: dbEngine,
83
85
  profile: dbProjection?.generatorDefaults?.profile || dbProjection?.profile || null
84
86
  }
85
87
  },
@@ -116,7 +118,7 @@ function buildEnvironmentPlan(graph, options = {}) {
116
118
  projection: component.projection.id,
117
119
  port: component.port || ports.server,
118
120
  dir: topology.serviceDir(component),
119
- database: component.database,
121
+ uses_database: component.database,
120
122
  databaseEnv: component.databaseComponent
121
123
  ? dbEnvVarsForComponent(component.databaseComponent, { primary: component.databaseComponent?.id === topology.primaryDb?.id })
122
124
  : null
@@ -126,12 +128,13 @@ function buildEnvironmentPlan(graph, options = {}) {
126
128
  projection: component.projection.id,
127
129
  port: component.port || ports.web,
128
130
  dir: topology.webDir(component),
129
- api: component.api
131
+ uses_api: component.api
130
132
  })),
131
133
  databases: topology.dbComponents.map((component) => ({
132
134
  id: component.id,
133
135
  projection: component.projection.id,
134
- platform: component.projection.platform,
136
+ type: component.projection.type,
137
+ engine: component.id === topology.primaryDb?.id ? dbEngine : null,
135
138
  port: component.port,
136
139
  dir: topology.dbDir(component),
137
140
  env: dbEnvVarsForComponent(component, { primary: component.id === topology.primaryDb?.id })
@@ -169,7 +172,7 @@ function renderEnvironmentEnvExample(plan) {
169
172
  .filter((component) => component.id !== plan.runtimes.databases[0]?.id)
170
173
  .map((component) => {
171
174
  const fallbackName = `${databaseName}_${component.id}`;
172
- if (component.platform === "db_contract") {
175
+ if (component.engine === "sqlite") {
173
176
  return `${component.env.databaseUrl}=file:./var/${component.id}.sqlite`;
174
177
  }
175
178
  return [
@@ -190,10 +193,10 @@ TOPOGRAM_DEMO_USER_ID=${demoUserId}
190
193
  ${plan.runtimeReference.environment.envExample || ""}
191
194
  TOPOGRAM_SEED_DEMO=true
192
195
  `;
193
- if (!plan.projections.db.platform) {
196
+ if (!plan.projections.db.type) {
194
197
  return commonLines;
195
198
  }
196
- if (plan.projections.db.platform === "db_contract") {
199
+ if (plan.projections.db.engine === "sqlite") {
197
200
  return `# Environment profile
198
201
  TOPOGRAM_ENVIRONMENT_PROFILE=${plan.environment.profile}
199
202
 
@@ -272,7 +275,7 @@ function renderEnvironmentReadme(plan) {
272
275
  const hasWeb = plan.runtimes.webs.length > 0;
273
276
  const localProcessNotes = !hasDb
274
277
  ? "- This bundle has no generated database surface."
275
- : plan.projections.db.platform === "db_contract"
278
+ : plan.projections.db.engine === "sqlite"
276
279
  ? "- SQLite is file-backed for this bundle; no separate DB server is required."
277
280
  : `- Make sure the Postgres server is already running before \`${plan.commands.bootstrapDb}\`.\n- \`DATABASE_URL\` and \`DATABASE_ADMIN_URL\` should point at your local or managed Postgres instance.`;
278
281
  const dockerSection = plan.orchestration.usesDocker
@@ -291,13 +294,13 @@ ${localProcessNotes}
291
294
 
292
295
  This bundle packages the generated runtime into one local environment:
293
296
 
294
- ${hasApi ? "- `services/<api-id>/`: generated API service scaffolds\n" : ""}${hasWeb ? `- \`web/<web-id>/\`: generated ${plan.runtimeProfiles.web === "react" ? "Vite + React Router" : plan.runtimeProfiles.web === "vanilla" ? "vanilla HTML/CSS/JS" : "SvelteKit"} web scaffolds\n` : ""}${hasDb ? "- `db/<db-id>/`: generated DB lifecycle bundles\n" : ""}${plan.files.dockerCompose ? `- \`${plan.files.dockerCompose}\`: local Postgres container` : hasDb ? (plan.projections.db.platform === "db_contract" ? "- local SQLite file orchestration (no Docker files generated)" : "- local-process Postgres orchestration (no Docker files generated)") : "- no DB orchestration is generated"}
297
+ ${hasApi ? "- `services/<api-id>/`: generated API service scaffolds\n" : ""}${hasWeb ? `- \`web/<web-id>/\`: generated ${plan.runtimeProfiles.web === "react" ? "Vite + React Router" : plan.runtimeProfiles.web === "vanilla" ? "vanilla HTML/CSS/JS" : "SvelteKit"} web scaffolds\n` : ""}${hasDb ? "- `db/<db-id>/`: generated DB lifecycle bundles\n" : ""}${plan.files.dockerCompose ? `- \`${plan.files.dockerCompose}\`: local Postgres container` : hasDb ? (plan.projections.db.engine === "sqlite" ? "- local SQLite file orchestration (no Docker files generated)" : "- local-process Postgres orchestration (no Docker files generated)") : "- no DB orchestration is generated"}
295
298
 
296
299
  ## Quick Start
297
300
 
298
301
  1. Copy \`.env.example\` to \`.env\` if you want to customize defaults
299
302
  2. Start the database:
300
- - ${!hasDb ? "not applicable" : plan.projections.db.platform === "db_contract" ? "no separate DB service is required" : plan.orchestration.usesDocker ? `\`${plan.commands.dockerDb}\`` : "use your local Postgres service"}
303
+ - ${!hasDb ? "not applicable" : plan.projections.db.engine === "sqlite" ? "no separate DB service is required" : plan.orchestration.usesDocker ? `\`${plan.commands.dockerDb}\`` : "use your local Postgres service"}
301
304
  3. Bootstrap or migrate the database:
302
305
  - \`${plan.commands.bootstrapDb}\`
303
306
  4. Start the stack:
@@ -313,7 +316,7 @@ ${dockerSection}
313
316
 
314
317
  ## Notes
315
318
 
316
- - ${hasApi && hasDb ? `The generated server expects ${plan.projections.db.platform === "db_contract" ? "SQLite plus Prisma." : "Postgres plus Prisma."}` : hasApi ? "The generated server is stateless." : "No server surface is generated."}
319
+ - ${hasApi && hasDb ? `The generated server expects ${plan.projections.db.engine === "sqlite" ? "SQLite plus Prisma." : "Postgres plus Prisma."}` : hasApi ? "The generated server is stateless." : "No server surface is generated."}
317
320
  - ${hasWeb && hasApi ? "The generated web app talks to `PUBLIC_TOPOGRAM_API_BASE_URL`." : hasWeb ? "The generated web app is standalone." : "No web surface is generated."}
318
321
  - If \`.env\` is missing, generated scripts fall back to \`.env.example\`.
319
322
  - The DB lifecycle scripts remain the source of truth for greenfield bootstrap and brownfield migration.
@@ -327,7 +330,7 @@ function renderEnvironmentLoadEnvScript() {
327
330
  function renderEnvironmentBootstrapDbScript(plan) {
328
331
  const dbBootstrapLines = plan.runtimes.databases.map((component) => {
329
332
  const env = component.env;
330
- const runtimeApi = plan.runtimes.apis.find((apiComponent) => apiComponent.database === component.id);
333
+ const runtimeApi = plan.runtimes.apis.find((apiComponent) => apiComponent.uses_database === component.id);
331
334
  const assignments = [
332
335
  `DATABASE_URL="\${${env.databaseUrl}:-}"`,
333
336
  `DATABASE_ADMIN_URL="\${${env.databaseAdminUrl}:-}"`,
@@ -353,7 +356,7 @@ function renderEnvironmentBootstrapDbScript(plan) {
353
356
  ...dbBootstrapLines,
354
357
  'if [[ "${TOPOGRAM_SEED_DEMO:-true}" != "false" ]]; then',
355
358
  ...apiDatabaseExportLines(primaryApi),
356
- ...(primaryApi?.database
359
+ ...(primaryApi?.uses_database
357
360
  ? [`(cd "$ROOT_DIR/${primaryApi.dir}" && npm install && npm exec -- prisma generate --schema prisma/schema.prisma && npm exec -- prisma db push --schema prisma/schema.prisma --skip-generate && npm run seed:demo)`]
358
361
  : ['echo "No DB-backed API component is configured; skipping demo seed."']),
359
362
  "fi"
@@ -381,7 +384,7 @@ function renderEnvironmentServerDevScript(plan, component = plan.runtimes.apis[0
381
384
  "",
382
385
  `cd "$ROOT_DIR/${component.dir}"`,
383
386
  "npm install",
384
- ...(component.database ? ["npm exec -- prisma generate --schema prisma/schema.prisma"] : []),
387
+ ...(component.uses_database ? ["npm exec -- prisma generate --schema prisma/schema.prisma"] : []),
385
388
  "npm run dev"
386
389
  ], options.componentScript ? componentScriptOptions() : {});
387
390
  }
@@ -390,7 +393,7 @@ function renderEnvironmentWebDevScript(plan, component = plan.runtimes.webs[0],
390
393
  if (!component) {
391
394
  return renderEnvAwareShellScript(['echo "No web runtimes are configured."']);
392
395
  }
393
- const apiComponent = plan.runtimes.apis.find((entry) => entry.id === component.api) || plan.runtimes.apis[0];
396
+ const apiComponent = plan.runtimes.apis.find((entry) => entry.id === component.uses_api) || plan.runtimes.apis[0];
394
397
  const guardPortsScript = options.componentScript ? '"$ROOT_DIR/scripts/guard-ports.mjs"' : '"$SCRIPT_DIR/guard-ports.mjs"';
395
398
  return renderEnvAwareShellScript([
396
399
  `node ${guardPortsScript} web`,
@@ -229,7 +229,7 @@ function apiProjectionCandidates(graph) {
229
229
  */
230
230
  function uiWebProjectionCandidates(graph) {
231
231
  return (graph.byKind.projection || []).filter(
232
- (projection) => projection.platform === "web_surface" && (projection.uiRoutes || []).length > 0
232
+ (projection) => projection.type === "web_surface" && (projection.uiRoutes || []).length > 0
233
233
  );
234
234
  }
235
235
 
@@ -247,7 +247,7 @@ const DEFAULT_NATIVE_UI_PLATFORM_ORDER = ["proj_ios_surface__swiftui"];
247
247
  */
248
248
  function uiIosProjectionCandidates(graph) {
249
249
  return (graph.byKind.projection || []).filter(
250
- (projection) => projection.platform === "ios_surface" && (projection.uiRoutes || []).length > 0
250
+ (projection) => projection.type === "ios_surface" && (projection.uiRoutes || []).length > 0
251
251
  );
252
252
  }
253
253
 
@@ -300,7 +300,7 @@ export function pickDefaultUiWebProjection(graph) {
300
300
  */
301
301
  export function getDefaultEnvironmentProjections(graph, options = {}) {
302
302
  const topology = resolveRuntimeTopology(graph, options);
303
- const dbCandidates = graph.byKind.projection?.filter((projection) => ["db_contract", "db_contract"].includes(projection.platform)) || [];
303
+ const dbCandidates = graph.byKind.projection?.filter((projection) => ["db_contract", "db_contract"].includes(projection.type)) || [];
304
304
  const apiProjection = /** @type {RuntimeStatement|null} */ (topology.primaryApi?.projection ||
305
305
  (options.projectionId ? getProjection(graph, options.projectionId) : null) ||
306
306
  apiProjectionCandidates(graph).find((projection) => projection.id === "proj_api") ||
@@ -426,13 +426,13 @@ export function dbEnvVarsForComponent(component, options = {}) {
426
426
  */
427
427
  function decorateRuntimes(graph, config) {
428
428
  const byProjectionId = new Map((graph.byKind.projection || []).map((projection) => [projection.id, projection]));
429
- const rawRuntimes = config.topology?.runtimes || config.topology?.components || [];
429
+ const rawRuntimes = config.topology?.runtimes || [];
430
430
  /** @type {RuntimeComponent[]} */
431
431
  const runtimes = rawRuntimes.map((runtime) => ({
432
432
  ...runtime,
433
- kind: runtime.kind || runtime.type || null,
434
- api: runtime.uses_api ?? runtime.api ?? null,
435
- database: runtime.uses_database ?? runtime.database ?? null,
433
+ kind: runtime.kind || null,
434
+ api: runtime.uses_api ?? null,
435
+ database: runtime.uses_database ?? null,
436
436
  projection: byProjectionId.get(runtime.projection) || {}
437
437
  }));
438
438
  const byId = new Map(runtimes.map((runtime) => [runtime.id, runtime]));
@@ -17,7 +17,7 @@ export function generateUiContractDebug(graph, options = {}) {
17
17
  for (const contract of contracts) {
18
18
  lines.push(`## \`${contract.projection.id}\` - ${contract.projection.name}`);
19
19
  lines.push("");
20
- lines.push(`Platform: \`${contract.projection.platform}\``);
20
+ lines.push(`Projection type: \`${contract.projection.type}\``);
21
21
  lines.push(`Realizes: ${refList(contract.realizes)}`);
22
22
  lines.push(`Outputs: ${symbolList(contract.outputs)}`);
23
23
  if (contract.appShell) {
@@ -87,7 +87,7 @@ function renderEmptySnapshotForProjection(projection) {
87
87
  projection: {
88
88
  id: projection.id,
89
89
  name: projection.name || projection.id,
90
- type: projection.type || projection.platform
90
+ type: projection.type || projection.type
91
91
  },
92
92
  profile,
93
93
  generatorDefaults: generatorDefaultsMap(projection),
@@ -50,7 +50,7 @@ function drizzleColumnBuilder(column, relation, targetTableVar) {
50
50
  export function generatePostgresDrizzleSchema(graph, options = {}) {
51
51
  resolvePostgresCapabilities(options.profileId);
52
52
  const projection = getProjection(graph, options.projectionId);
53
- const projectionType = projection.type || projection.platform;
53
+ const projectionType = projection.type || projection.type;
54
54
  if (projectionType !== "db_contract") {
55
55
  throw new Error(`Drizzle schema generation currently supports db_contract projections only, found '${projectionType}'`);
56
56
  }
@@ -40,7 +40,7 @@ function prismaDefaultForColumn(column, byId) {
40
40
  export function generatePostgresPrismaSchema(graph, options = {}) {
41
41
  resolvePostgresCapabilities(options.profileId);
42
42
  const projection = getProjection(graph, options.projectionId);
43
- const projectionType = projection.type || projection.platform;
43
+ const projectionType = projection.type || projection.type;
44
44
  if (projectionType !== "db_contract") {
45
45
  throw new Error(`Prisma schema generation currently supports db_contract projections only, found '${projectionType}'`);
46
46
  }
@@ -42,8 +42,8 @@ export function indexGraphStatements(graph) {
42
42
  export function dbProjectionCandidates(graph) {
43
43
  return (graph.byKind.projection || []).filter(
44
44
  (projection) =>
45
- (projection.type || projection.platform) === "db_contract" ||
46
- projection.platform?.startsWith("db_") ||
45
+ (projection.type || projection.type) === "db_contract" ||
46
+ projection.type?.startsWith("db_") ||
47
47
  (projection.dbTables || []).length > 0 ||
48
48
  (projection.dbColumns || []).length > 0 ||
49
49
  (projection.dbRelations || []).length > 0
@@ -278,7 +278,7 @@ export function buildDbProjectionContract(graph, projection) {
278
278
  projection: {
279
279
  id: projection.id,
280
280
  name: projection.name || projection.id,
281
- type: projection.type || projection.platform
281
+ type: projection.type || projection.type
282
282
  },
283
283
  profile: dbProfileForProjection(projection),
284
284
  generatorDefaults: generatorDefaultsMap(projection),
@@ -45,7 +45,7 @@ function prismaDefaultForColumn(column) {
45
45
  export function generateSqlitePrismaSchema(graph, options = {}) {
46
46
  resolveSqliteCapabilities(options.profileId);
47
47
  const projection = getProjection(graph, options.projectionId);
48
- const projectionType = projection.type || projection.platform;
48
+ const projectionType = projection.type || projection.type;
49
49
  if (projectionType !== "db_contract") {
50
50
  throw new Error(`Prisma schema generation currently supports db_contract projections only, found '${projectionType}'`);
51
51
  }
@@ -155,7 +155,7 @@ export function generatePersistenceScaffold(graph, options = {}) {
155
155
  const drizzleRepositoryClassName = repositoryReference.drizzleRepositoryClassName;
156
156
  const drizzleHint = repositoryReference.drizzleHint;
157
157
  const projection = getProjection(graph, options.projectionId);
158
- const projectionType = projection.type || projection.platform;
158
+ const projectionType = projection.type || projection.type;
159
159
  if (projectionType !== "db_contract") {
160
160
  throw new Error(`Persistence scaffold generation currently supports db_contract projections only, found '${projectionType}'`);
161
161
  }
@@ -30,7 +30,7 @@ function buildServerContract(graph, projection) {
30
30
  projection: {
31
31
  id: projection.id,
32
32
  name: projection.name || projection.id,
33
- type: projection.type || projection.platform
33
+ type: projection.type || projection.type
34
34
  },
35
35
  routes: realizedCapabilities.map((capability) => {
36
36
  const apiContract = generateApiContractGraph(graph, { capabilityId: capability.id });
@@ -38,7 +38,7 @@ export function uiProjectionCandidates(graph) {
38
38
  (projection.uiAppShell || []).length > 0 ||
39
39
  (projection.uiNavigation || []).length > 0 ||
40
40
  (projection.uiScreenRegions || []).length > 0 ||
41
- (projection.uiComponents || []).length > 0 ||
41
+ (projection.widgetBindings || []).length > 0 ||
42
42
  (projection.uiDesign || []).length > 0
43
43
  );
44
44
  }
@@ -3,7 +3,7 @@ import { buildWebRealization } from "../../../realization/ui/index.js";
3
3
  export function generateUiSurfaceContract(graph, options = {}) {
4
4
  if (!options.projectionId) {
5
5
  const output = {};
6
- for (const projection of (graph.byKind.projection || []).filter((entry) => (entry.type || entry.platform) === "web_surface")) {
6
+ for (const projection of (graph.byKind.projection || []).filter((entry) => (entry.type || entry.type) === "web_surface")) {
7
7
  output[projection.id] = buildWebRealization(graph, { ...options, projectionId: projection.id }).contract;
8
8
  }
9
9
  return output;
@@ -22,7 +22,7 @@ function summarizeProjection(projection) {
22
22
  ? {
23
23
  id: projection.id,
24
24
  name: projection.name || projection.id,
25
- type: projection.type || projection.platform || null,
25
+ type: projection.type || projection.type || null,
26
26
  status: projection.status || null,
27
27
  source_path: sourcePath(projection)
28
28
  }
@@ -479,14 +479,14 @@ function projectionUsageEntries(graph, projection) {
479
479
  const sharedProjection = sharedUiProjectionForWeb(graph, projection);
480
480
  const entries = [];
481
481
  if (sharedProjection) {
482
- entries.push(...(sharedProjection.uiComponents || []).map((usage, index) => ({
482
+ entries.push(...(sharedProjection.widgetBindings || []).map((usage, index) => ({
483
483
  projection,
484
484
  sourceProjection: sharedProjection,
485
485
  usage,
486
486
  index
487
487
  })));
488
488
  }
489
- entries.push(...(projection.uiComponents || []).map((usage, index) => ({
489
+ entries.push(...(projection.widgetBindings || []).map((usage, index) => ({
490
490
  projection,
491
491
  sourceProjection: projection,
492
492
  usage,
@@ -499,10 +499,10 @@ function candidateProjections(graph, projectionId) {
499
499
  if (projectionId) {
500
500
  return [getProjection(graph, projectionId)];
501
501
  }
502
- const direct = uiProjectionCandidates(graph).filter((projection) => (projection.uiComponents || []).length > 0);
502
+ const direct = uiProjectionCandidates(graph).filter((projection) => (projection.widgetBindings || []).length > 0);
503
503
  const inherited = (graph.byKind.projection || []).filter((projection) => {
504
- if ((projection.uiComponents || []).length > 0) return false;
505
- return Boolean(sharedUiProjectionForWeb(graph, projection)?.uiComponents?.length);
504
+ if ((projection.widgetBindings || []).length > 0) return false;
505
+ return Boolean(sharedUiProjectionForWeb(graph, projection)?.widgetBindings?.length);
506
506
  });
507
507
  return [...direct, ...inherited].sort((a, b) => a.id.localeCompare(b.id));
508
508
  }
@@ -27,7 +27,7 @@ function widgetContract(widget) {
27
27
  export function generateUiWidgetContract(graph, options = {}) {
28
28
  const widgetId = options.widgetId || options.componentId;
29
29
  if (widgetId) {
30
- const widget = (graph?.byKind?.widget || graph?.byKind?.component || []).find((entry) => entry.id === widgetId);
30
+ const widget = (graph?.byKind?.widget || []).find((entry) => entry.id === widgetId);
31
31
  if (!widget) {
32
32
  throw new Error(`No widget found with id '${widgetId}'`);
33
33
  }