@topogram/cli 0.3.53 → 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.
- package/package.json +1 -1
- package/src/adoption/review-groups.js +3 -3
- package/src/agent-ops/query-builders.js +34 -34
- package/src/archive/schema.js +1 -1
- package/src/cli.js +177 -22
- package/src/generator/adapters.js +2 -2
- package/src/generator/context/domain-coverage.js +2 -2
- package/src/generator/context/shared.js +6 -22
- package/src/generator/context/slice.js +10 -10
- package/src/generator/docs.js +1 -1
- package/src/generator/registry.js +1 -1
- package/src/generator/runtime/app-bundle.js +7 -5
- package/src/generator/runtime/compile-check.js +3 -1
- package/src/generator/runtime/deployment.js +3 -1
- package/src/generator/runtime/environment.js +22 -19
- package/src/generator/runtime/shared.js +7 -7
- package/src/generator/surfaces/contracts.js +1 -1
- package/src/generator/surfaces/databases/lifecycle-shared.js +1 -1
- package/src/generator/surfaces/databases/postgres/drizzle.js +1 -1
- package/src/generator/surfaces/databases/postgres/prisma.js +1 -1
- package/src/generator/surfaces/databases/shared.js +3 -3
- package/src/generator/surfaces/databases/sqlite/prisma.js +1 -1
- package/src/generator/surfaces/services/persistence-wiring.js +1 -1
- package/src/generator/surfaces/services/server-contract.js +1 -1
- package/src/generator/surfaces/shared.js +1 -1
- package/src/generator/surfaces/web/ui-surface-contract.js +1 -1
- package/src/generator/widget-conformance.js +6 -6
- package/src/generator/widgets.js +1 -1
- package/src/generator-policy.js +10 -10
- package/src/import/core/runner.js +60 -50
- package/src/project-config.js +5 -42
- package/src/proofs/contract-audit.js +1 -1
- package/src/realization/backend/build-backend-runtime-realization.js +3 -3
- package/src/realization/ui/build-ui-shared-realization.js +9 -9
- package/src/realization/ui/build-web-realization.js +3 -3
- package/src/reconcile/journeys.js +1 -1
- package/src/resolver/enrich/widget.js +2 -2
- package/src/resolver/index.js +4 -23
- package/src/validator/index.js +10 -10
- 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
|
-
|
|
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
|
-
|
|
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 ||
|
|
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?.
|
|
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.
|
|
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 ||
|
|
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.
|
|
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.
|
|
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.
|
|
382
|
-
if (usage.
|
|
383
|
-
const projectionType = projection.type || projection.
|
|
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 ||
|
|
410
|
-
regions: widget.widgetContract?.regions ||
|
|
411
|
-
behaviors: widget.widgetContract?.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?.
|
|
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.
|
|
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
|
package/src/generator/docs.js
CHANGED
|
@@ -229,7 +229,7 @@ export function generateDocs(graph) {
|
|
|
229
229
|
lines.push(projection.description);
|
|
230
230
|
lines.push("");
|
|
231
231
|
}
|
|
232
|
-
lines.push(`
|
|
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?.
|
|
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.
|
|
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.
|
|
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 (
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
75
|
+
type: apiProjection?.type || null
|
|
75
76
|
},
|
|
76
77
|
ui: {
|
|
77
78
|
id: uiProjection?.id || null,
|
|
78
|
-
|
|
79
|
+
type: uiProjection?.type || null
|
|
79
80
|
},
|
|
80
81
|
db: {
|
|
81
82
|
id: dbProjection?.id || null,
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
196
|
+
if (!plan.projections.db.type) {
|
|
194
197
|
return commonLines;
|
|
195
198
|
}
|
|
196
|
-
if (plan.projections.db.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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?.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 ||
|
|
429
|
+
const rawRuntimes = config.topology?.runtimes || [];
|
|
430
430
|
/** @type {RuntimeComponent[]} */
|
|
431
431
|
const runtimes = rawRuntimes.map((runtime) => ({
|
|
432
432
|
...runtime,
|
|
433
|
-
kind: runtime.kind ||
|
|
434
|
-
api: runtime.uses_api ??
|
|
435
|
-
database: runtime.uses_database ??
|
|
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(`
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
46
|
-
projection.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
502
|
+
const direct = uiProjectionCandidates(graph).filter((projection) => (projection.widgetBindings || []).length > 0);
|
|
503
503
|
const inherited = (graph.byKind.projection || []).filter((projection) => {
|
|
504
|
-
if ((projection.
|
|
505
|
-
return Boolean(sharedUiProjectionForWeb(graph, projection)?.
|
|
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
|
}
|
package/src/generator/widgets.js
CHANGED
|
@@ -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 ||
|
|
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
|
}
|