@topogram/cli 0.3.54 → 0.3.56

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 (45) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/package.json +1 -1
  3. package/src/adoption/review-groups.js +3 -3
  4. package/src/agent-ops/query-builders.js +34 -34
  5. package/src/archive/schema.js +1 -1
  6. package/src/cli.js +173 -20
  7. package/src/generator/adapters.js +23 -7
  8. package/src/generator/context/domain-coverage.js +2 -2
  9. package/src/generator/context/shared.js +6 -22
  10. package/src/generator/context/slice.js +10 -10
  11. package/src/generator/docs.js +1 -1
  12. package/src/generator/registry.js +1 -1
  13. package/src/generator/runtime/app-bundle.js +8 -6
  14. package/src/generator/runtime/compile-check.js +7 -5
  15. package/src/generator/runtime/deployment.js +6 -4
  16. package/src/generator/runtime/environment.js +31 -28
  17. package/src/generator/runtime/shared.js +53 -39
  18. package/src/generator/surfaces/contracts.js +1 -1
  19. package/src/generator/surfaces/databases/index.js +5 -3
  20. package/src/generator/surfaces/databases/lifecycle-shared.js +1 -1
  21. package/src/generator/surfaces/databases/postgres/drizzle.js +1 -1
  22. package/src/generator/surfaces/databases/postgres/prisma.js +1 -1
  23. package/src/generator/surfaces/databases/shared.js +3 -3
  24. package/src/generator/surfaces/databases/sqlite/prisma.js +1 -1
  25. package/src/generator/surfaces/index.js +5 -3
  26. package/src/generator/surfaces/services/index.js +10 -6
  27. package/src/generator/surfaces/services/persistence-wiring.js +1 -1
  28. package/src/generator/surfaces/services/server-contract.js +1 -1
  29. package/src/generator/surfaces/shared.js +1 -1
  30. package/src/generator/surfaces/web/index.js +5 -3
  31. package/src/generator/surfaces/web/ui-surface-contract.js +1 -1
  32. package/src/generator/widget-conformance.js +6 -6
  33. package/src/generator/widgets.js +1 -1
  34. package/src/generator-policy.js +10 -10
  35. package/src/import/core/runner.js +60 -50
  36. package/src/project-config.js +5 -42
  37. package/src/proofs/contract-audit.js +1 -1
  38. package/src/realization/backend/build-backend-runtime-realization.js +3 -3
  39. package/src/realization/ui/build-ui-shared-realization.js +9 -9
  40. package/src/realization/ui/build-web-realization.js +3 -3
  41. package/src/reconcile/journeys.js +1 -1
  42. package/src/resolver/enrich/widget.js +2 -2
  43. package/src/resolver/index.js +4 -23
  44. package/src/validator/index.js +10 -10
  45. package/src/workflows.js +49 -49
@@ -12,7 +12,7 @@ import {
12
12
  function projectionTypesFromProjections(projections) {
13
13
  const projectionTypes = new Set();
14
14
  for (const projection of projections) {
15
- const projectionType = projection?.type || projection?.platform;
15
+ const projectionType = projection?.type || projection?.type;
16
16
  if (projectionType) {
17
17
  projectionTypes.add(projectionType);
18
18
  }
@@ -53,7 +53,7 @@ export function generateDomainCoverage(graph, options = {}) {
53
53
  for (const projectionType of projectionTypes) {
54
54
  const realized = projectionStatements.some(
55
55
  (projection) =>
56
- (projection.type || projection.platform) === projectionType &&
56
+ (projection.type || projection.type) === projectionType &&
57
57
  (projection.realizes || []).some((entry) => entry.id === capabilityId)
58
58
  );
59
59
  matrix[capabilityId][projectionType] = realized;
@@ -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,9 +413,7 @@ 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
- const fullStack = topology.apiComponents.length > 0 && topology.webComponents.length > 0 && topology.dbComponents.length > 0;
416
+ const fullStack = topology.apiRuntimes.length > 0 && topology.webRuntimes.length > 0 && topology.dbRuntimes.length > 0;
415
417
  const envBundle = generateEnvironmentBundle(graph, { ...options, profileId: plan.profiles.environment });
416
418
  const deployBundle = fullStack
417
419
  ? generateDeploymentBundle(graph, { ...options, profileId: plan.profiles.deployment })
@@ -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 {
@@ -28,13 +29,13 @@ function runtimeReferenceFor(graph, options = {}) {
28
29
  function buildCompileCheckPlan(graph, options = {}) {
29
30
  const topology = resolveRuntimeTopology(graph, options);
30
31
  const { apiProjection, uiProjection, dbProjection } = getDefaultEnvironmentProjections(graph, options);
31
- const apiChecks = topology.apiComponents.map((component, index) => ({
32
+ const apiChecks = topology.apiRuntimes.map((component, index) => ({
32
33
  id: index === 0 ? "server_typecheck" : `server_typecheck_${component.id}`,
33
34
  cwd: topology.serviceDir(component),
34
35
  install: "npm install --no-audit --no-fund",
35
36
  command: "npm run check"
36
37
  }));
37
- const webChecks = topology.webComponents.flatMap((component, index) => [
38
+ const webChecks = topology.webRuntimes.flatMap((component, index) => [
38
39
  {
39
40
  id: index === 0 ? "web_typecheck" : `web_typecheck_${component.id}`,
40
41
  cwd: topology.webDir(component),
@@ -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)}
@@ -148,11 +150,11 @@ export function generateCompileCheckBundle(graph, options = {}) {
148
150
  "compile-check-plan.json": `${JSON.stringify(plan, null, 2)}\n`,
149
151
  "scripts/check.sh": renderCompileCheckScript(plan)
150
152
  };
151
- for (const component of topology.apiComponents) {
153
+ for (const component of topology.apiRuntimes) {
152
154
  const serverBundle = generateServerBundle(graph, component.projection.id, { ...options, component });
153
155
  mergeBundleFiles(files, topology.serviceDir(component), serverBundle);
154
156
  }
155
- for (const component of topology.webComponents) {
157
+ for (const component of topology.webRuntimes) {
156
158
  const webBundle = generateWebBundle(graph, component.projection.id, { ...options, component });
157
159
  mergeBundleFiles(files, topology.webDir(component), webBundle);
158
160
  }
@@ -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"
@@ -260,19 +262,19 @@ export function generateDeploymentBundle(graph, options = {}) {
260
262
  files["railway.json"] = renderRailwayJson(plan);
261
263
  }
262
264
 
263
- for (const component of topology.apiComponents) {
265
+ for (const component of topology.apiRuntimes) {
264
266
  const serverBundle = generateServerBundle(graph, component.projection.id, { ...options, component });
265
267
  mergeNamedBundles(files, {
266
268
  [topology.serviceDir(component)]: serverBundle
267
269
  });
268
270
  }
269
- for (const component of topology.webComponents) {
271
+ for (const component of topology.webRuntimes) {
270
272
  const webBundle = generateWebBundle(graph, component.projection.id, { ...options, component });
271
273
  mergeNamedBundles(files, {
272
274
  [topology.webDir(component)]: webBundle
273
275
  });
274
276
  }
275
- for (const component of topology.dbComponents) {
277
+ for (const component of topology.dbRuntimes) {
276
278
  const dbBundle = generateDbBundle(graph, component.projection.id, { ...options, component });
277
279
  mergeNamedBundles(files, {
278
280
  [topology.dbDir(component)]: dbBundle
@@ -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
  },
@@ -111,27 +113,28 @@ function buildEnvironmentPlan(graph, options = {}) {
111
113
  scripts: "scripts"
112
114
  },
113
115
  runtimes: {
114
- apis: topology.apiComponents.map((component) => ({
116
+ apis: topology.apiRuntimes.map((component) => ({
115
117
  id: component.id,
116
118
  projection: component.projection.id,
117
119
  port: component.port || ports.server,
118
120
  dir: topology.serviceDir(component),
119
- database: component.database,
120
- databaseEnv: component.databaseComponent
121
- ? dbEnvVarsForComponent(component.databaseComponent, { primary: component.databaseComponent?.id === topology.primaryDb?.id })
121
+ uses_database: component.database,
122
+ databaseEnv: component.databaseRuntime
123
+ ? dbEnvVarsForComponent(component.databaseRuntime, { primary: component.databaseRuntime?.id === topology.primaryDb?.id })
122
124
  : null
123
125
  })),
124
- webs: topology.webComponents.map((component) => ({
126
+ webs: topology.webRuntimes.map((component) => ({
125
127
  id: component.id,
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
- databases: topology.dbComponents.map((component) => ({
133
+ databases: topology.dbRuntimes.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((apiRuntime) => apiRuntime.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,12 +393,12 @@ 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 apiRuntime = 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`,
397
400
  "",
398
- ...(apiComponent ? [`export PUBLIC_TOPOGRAM_API_BASE_URL="\${PUBLIC_TOPOGRAM_API_BASE_URL:-http://localhost:\${${apiComponent.id.toUpperCase()}_PORT:-\${SERVER_PORT:-${apiComponent.port}}}}"`] : []),
401
+ ...(apiRuntime ? [`export PUBLIC_TOPOGRAM_API_BASE_URL="\${PUBLIC_TOPOGRAM_API_BASE_URL:-http://localhost:\${${apiRuntime.id.toUpperCase()}_PORT:-\${SERVER_PORT:-${apiRuntime.port}}}}"`] : []),
399
402
  `export TOPOGRAM_CORS_ORIGINS="\${TOPOGRAM_CORS_ORIGINS:-http://localhost:\${${component.id.toUpperCase()}_PORT:-\${WEB_PORT:-${component.port}}},http://127.0.0.1:\${${component.id.toUpperCase()}_PORT:-\${WEB_PORT:-${component.port}}}}"`,
400
403
  "",
401
404
  `cd "$ROOT_DIR/${component.dir}"`,
@@ -609,19 +612,19 @@ export function generateEnvironmentBundle(graph, options = {}) {
609
612
  files["scripts/docker-stack.sh"] = renderEnvironmentDockerStackScript();
610
613
  }
611
614
 
612
- for (const component of topology.apiComponents) {
615
+ for (const component of topology.apiRuntimes) {
613
616
  const serverBundle = generateServerBundle(graph, component.projection.id, { ...options, component });
614
617
  mergeNamedBundles(files, {
615
618
  [topology.serviceDir(component)]: serverBundle
616
619
  });
617
620
  }
618
- for (const component of topology.webComponents) {
621
+ for (const component of topology.webRuntimes) {
619
622
  const webBundle = generateWebBundle(graph, component.projection.id, { ...options, component });
620
623
  mergeNamedBundles(files, {
621
624
  [topology.webDir(component)]: webBundle
622
625
  });
623
626
  }
624
- for (const component of topology.dbComponents) {
627
+ for (const component of topology.dbRuntimes) {
625
628
  const dbBundle = generateDbBundle(graph, component.projection.id, { ...options, component });
626
629
  mergeNamedBundles(files, {
627
630
  [topology.dbDir(component)]: dbBundle