@topogram/cli 0.3.34

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 (257) hide show
  1. package/ARCHITECTURE.md +67 -0
  2. package/CHANGELOG.md +240 -0
  3. package/README.md +223 -0
  4. package/package.json +51 -0
  5. package/src/adoption/index.js +3 -0
  6. package/src/adoption/plan.js +702 -0
  7. package/src/adoption/reporting.js +464 -0
  8. package/src/adoption/review-groups.js +313 -0
  9. package/src/agent-ops/query-builders.js +5012 -0
  10. package/src/archive/archive.js +141 -0
  11. package/src/archive/compact.js +26 -0
  12. package/src/archive/jsonl.js +70 -0
  13. package/src/archive/resolver-bridge.js +82 -0
  14. package/src/archive/schema.js +87 -0
  15. package/src/archive/unarchive.js +108 -0
  16. package/src/catalog.js +752 -0
  17. package/src/cli/catalog-alias.js +166 -0
  18. package/src/cli.js +9738 -0
  19. package/src/component-behavior.js +173 -0
  20. package/src/example-implementation.js +91 -0
  21. package/src/format.js +19 -0
  22. package/src/generator/adapters.d.ts +4 -0
  23. package/src/generator/adapters.js +325 -0
  24. package/src/generator/api.d.ts +1 -0
  25. package/src/generator/api.js +1196 -0
  26. package/src/generator/check.js +355 -0
  27. package/src/generator/component-conformance.js +767 -0
  28. package/src/generator/components.js +39 -0
  29. package/src/generator/context/bundle.js +291 -0
  30. package/src/generator/context/diff.js +256 -0
  31. package/src/generator/context/digest.js +182 -0
  32. package/src/generator/context/domain-coverage.js +94 -0
  33. package/src/generator/context/domain-page.js +137 -0
  34. package/src/generator/context/index.js +42 -0
  35. package/src/generator/context/report.js +121 -0
  36. package/src/generator/context/shared.js +1397 -0
  37. package/src/generator/context/slice.js +703 -0
  38. package/src/generator/context/task-mode.js +466 -0
  39. package/src/generator/docs.js +327 -0
  40. package/src/generator/index.js +161 -0
  41. package/src/generator/native/parity-bundle.js +311 -0
  42. package/src/generator/output.js +300 -0
  43. package/src/generator/registry.js +482 -0
  44. package/src/generator/runtime/app-bundle.js +456 -0
  45. package/src/generator/runtime/bundle-shared.js +166 -0
  46. package/src/generator/runtime/compile-check.js +163 -0
  47. package/src/generator/runtime/deployment.js +287 -0
  48. package/src/generator/runtime/environment.js +635 -0
  49. package/src/generator/runtime/index.js +32 -0
  50. package/src/generator/runtime/runtime-check.js +554 -0
  51. package/src/generator/runtime/shared.js +515 -0
  52. package/src/generator/runtime/smoke.js +219 -0
  53. package/src/generator/schema.js +204 -0
  54. package/src/generator/sdlc/board.js +66 -0
  55. package/src/generator/sdlc/doc-page.js +53 -0
  56. package/src/generator/sdlc/index.js +23 -0
  57. package/src/generator/sdlc/release-notes.js +62 -0
  58. package/src/generator/sdlc/traceability-matrix.js +65 -0
  59. package/src/generator/shared.js +29 -0
  60. package/src/generator/surfaces/contracts.js +146 -0
  61. package/src/generator/surfaces/databases/contract.js +40 -0
  62. package/src/generator/surfaces/databases/index.js +84 -0
  63. package/src/generator/surfaces/databases/lifecycle-shared.d.ts +1 -0
  64. package/src/generator/surfaces/databases/lifecycle-shared.js +612 -0
  65. package/src/generator/surfaces/databases/migration-plan.js +281 -0
  66. package/src/generator/surfaces/databases/postgres/capabilities.js +14 -0
  67. package/src/generator/surfaces/databases/postgres/drizzle.js +99 -0
  68. package/src/generator/surfaces/databases/postgres/index.js +9 -0
  69. package/src/generator/surfaces/databases/postgres/lifecycle.js +16 -0
  70. package/src/generator/surfaces/databases/postgres/prisma.js +159 -0
  71. package/src/generator/surfaces/databases/postgres/sql-migration.js +102 -0
  72. package/src/generator/surfaces/databases/postgres/sql-schema.js +34 -0
  73. package/src/generator/surfaces/databases/shared.d.ts +1 -0
  74. package/src/generator/surfaces/databases/shared.js +350 -0
  75. package/src/generator/surfaces/databases/snapshot.js +96 -0
  76. package/src/generator/surfaces/databases/sqlite/capabilities.js +14 -0
  77. package/src/generator/surfaces/databases/sqlite/index.js +8 -0
  78. package/src/generator/surfaces/databases/sqlite/lifecycle.js +16 -0
  79. package/src/generator/surfaces/databases/sqlite/prisma.js +143 -0
  80. package/src/generator/surfaces/databases/sqlite/sql-migration.js +65 -0
  81. package/src/generator/surfaces/databases/sqlite/sql-schema.js +27 -0
  82. package/src/generator/surfaces/index.js +25 -0
  83. package/src/generator/surfaces/native/swiftui-app.js +38 -0
  84. package/src/generator/surfaces/native/swiftui-templates/Package.swift.txt +20 -0
  85. package/src/generator/surfaces/native/swiftui-templates/README.generated.md +26 -0
  86. package/src/generator/surfaces/native/swiftui-templates/runtime/DynamicScreens.swift +682 -0
  87. package/src/generator/surfaces/native/swiftui-templates/runtime/TodoAPIClient.swift +156 -0
  88. package/src/generator/surfaces/native/swiftui-templates/runtime/TodoSwiftUIApp.swift +44 -0
  89. package/src/generator/surfaces/native/swiftui-templates/runtime/Visibility.swift +183 -0
  90. package/src/generator/surfaces/services/express.d.ts +1 -0
  91. package/src/generator/surfaces/services/express.js +766 -0
  92. package/src/generator/surfaces/services/hono.d.ts +1 -0
  93. package/src/generator/surfaces/services/hono.js +204 -0
  94. package/src/generator/surfaces/services/index.js +42 -0
  95. package/src/generator/surfaces/services/persistence-wiring.js +240 -0
  96. package/src/generator/surfaces/services/runtime-helpers.js +631 -0
  97. package/src/generator/surfaces/services/server-contract.js +80 -0
  98. package/src/generator/surfaces/services/stateless.d.ts +1 -0
  99. package/src/generator/surfaces/services/stateless.js +97 -0
  100. package/src/generator/surfaces/shared.js +64 -0
  101. package/src/generator/surfaces/web/api-client.js +1 -0
  102. package/src/generator/surfaces/web/forms.js +1 -0
  103. package/src/generator/surfaces/web/index.d.ts +2 -0
  104. package/src/generator/surfaces/web/index.js +53 -0
  105. package/src/generator/surfaces/web/react-components.js +248 -0
  106. package/src/generator/surfaces/web/react.js +538 -0
  107. package/src/generator/surfaces/web/routes.js +1 -0
  108. package/src/generator/surfaces/web/screens.js +1 -0
  109. package/src/generator/surfaces/web/shared.js +369 -0
  110. package/src/generator/surfaces/web/sveltekit-actions.js +28 -0
  111. package/src/generator/surfaces/web/sveltekit-components.js +234 -0
  112. package/src/generator/surfaces/web/sveltekit.js +426 -0
  113. package/src/generator/surfaces/web/ui-web-contract.js +65 -0
  114. package/src/generator/surfaces/web/vanilla.js +239 -0
  115. package/src/generator/verification.js +84 -0
  116. package/src/generator.js +1 -0
  117. package/src/import/core/context.js +52 -0
  118. package/src/import/core/contracts.js +23 -0
  119. package/src/import/core/registry.js +81 -0
  120. package/src/import/core/runner.js +646 -0
  121. package/src/import/core/shared.js +910 -0
  122. package/src/import/enrichers/auth-session.js +18 -0
  123. package/src/import/enrichers/django-rest.js +226 -0
  124. package/src/import/enrichers/doc-linking.js +20 -0
  125. package/src/import/enrichers/rails-controllers.js +246 -0
  126. package/src/import/enrichers/rails-models.js +130 -0
  127. package/src/import/enrichers/workflow-target-state.js +10 -0
  128. package/src/import/extractors/api/aspnet-core.js +304 -0
  129. package/src/import/extractors/api/django-routes.js +318 -0
  130. package/src/import/extractors/api/express.js +154 -0
  131. package/src/import/extractors/api/fastify.js +371 -0
  132. package/src/import/extractors/api/flutter-dio.js +135 -0
  133. package/src/import/extractors/api/generic-route-fallback.js +90 -0
  134. package/src/import/extractors/api/graphql-code-first.js +565 -0
  135. package/src/import/extractors/api/graphql-sdl.js +309 -0
  136. package/src/import/extractors/api/jaxrs.js +303 -0
  137. package/src/import/extractors/api/micronaut.js +213 -0
  138. package/src/import/extractors/api/next-route.js +50 -0
  139. package/src/import/extractors/api/next-server-action.js +51 -0
  140. package/src/import/extractors/api/nextauth.js +52 -0
  141. package/src/import/extractors/api/openapi-code.js +242 -0
  142. package/src/import/extractors/api/openapi.js +232 -0
  143. package/src/import/extractors/api/rails-routes.js +230 -0
  144. package/src/import/extractors/api/react-native-repository.js +128 -0
  145. package/src/import/extractors/api/retrofit.js +103 -0
  146. package/src/import/extractors/api/spring-web.js +372 -0
  147. package/src/import/extractors/api/swift-webapi.js +116 -0
  148. package/src/import/extractors/api/trpc.js +212 -0
  149. package/src/import/extractors/db/django-models.js +232 -0
  150. package/src/import/extractors/db/dotnet-models.js +93 -0
  151. package/src/import/extractors/db/drizzle.js +242 -0
  152. package/src/import/extractors/db/ef-core.js +221 -0
  153. package/src/import/extractors/db/flutter-entities.js +120 -0
  154. package/src/import/extractors/db/jpa.js +120 -0
  155. package/src/import/extractors/db/liquibase.js +180 -0
  156. package/src/import/extractors/db/mybatis-xml.js +145 -0
  157. package/src/import/extractors/db/prisma.js +185 -0
  158. package/src/import/extractors/db/rails-schema.js +175 -0
  159. package/src/import/extractors/db/react-native-entities.js +95 -0
  160. package/src/import/extractors/db/room.js +193 -0
  161. package/src/import/extractors/db/snapshot.js +112 -0
  162. package/src/import/extractors/db/sql.js +180 -0
  163. package/src/import/extractors/db/swiftdata.js +137 -0
  164. package/src/import/extractors/ui/android-compose.js +230 -0
  165. package/src/import/extractors/ui/backend-only.js +70 -0
  166. package/src/import/extractors/ui/blazor.js +227 -0
  167. package/src/import/extractors/ui/flutter-screens.js +152 -0
  168. package/src/import/extractors/ui/maui-xaml.js +135 -0
  169. package/src/import/extractors/ui/next-app-router.js +83 -0
  170. package/src/import/extractors/ui/next-pages-router.js +141 -0
  171. package/src/import/extractors/ui/razor-pages.js +181 -0
  172. package/src/import/extractors/ui/react-native-screens.js +166 -0
  173. package/src/import/extractors/ui/react-router.js +139 -0
  174. package/src/import/extractors/ui/sveltekit.js +123 -0
  175. package/src/import/extractors/ui/swiftui.js +193 -0
  176. package/src/import/extractors/ui/uikit.js +175 -0
  177. package/src/import/extractors/verification/generic.js +290 -0
  178. package/src/import/extractors/workflows/generic.js +137 -0
  179. package/src/import/index.js +7 -0
  180. package/src/import/provenance.js +158 -0
  181. package/src/new-project.js +2107 -0
  182. package/src/parser.js +439 -0
  183. package/src/policy/review-boundaries.js +165 -0
  184. package/src/project-config.js +535 -0
  185. package/src/proofs/backend-parity.js +19 -0
  186. package/src/proofs/contract-audit.js +220 -0
  187. package/src/proofs/ios-parity.js +7 -0
  188. package/src/proofs/issues-parity.js +10 -0
  189. package/src/proofs/web-parity.js +50 -0
  190. package/src/realization/api/build-api-realization.js +5 -0
  191. package/src/realization/api/index.js +1 -0
  192. package/src/realization/backend/build-backend-runtime-realization.js +82 -0
  193. package/src/realization/backend/index.d.ts +1 -0
  194. package/src/realization/backend/index.js +4 -0
  195. package/src/realization/db/build-db-realization.js +17 -0
  196. package/src/realization/db/index.js +3 -0
  197. package/src/realization/db/migration-plan.js +5 -0
  198. package/src/realization/db/snapshot.js +5 -0
  199. package/src/realization/ui/build-ui-shared-realization.js +305 -0
  200. package/src/realization/ui/build-web-realization.js +189 -0
  201. package/src/realization/ui/index.js +2 -0
  202. package/src/reconcile/docs.js +280 -0
  203. package/src/reconcile/index.js +3 -0
  204. package/src/reconcile/journeys.js +441 -0
  205. package/src/resolver/docs.js +1 -0
  206. package/src/resolver/enrich/acceptance-criterion.js +14 -0
  207. package/src/resolver/enrich/bug.js +12 -0
  208. package/src/resolver/enrich/component.js +2 -0
  209. package/src/resolver/enrich/index.js +1 -0
  210. package/src/resolver/enrich/pitch.js +18 -0
  211. package/src/resolver/enrich/requirement.js +20 -0
  212. package/src/resolver/enrich/task.js +16 -0
  213. package/src/resolver/expressions.js +1 -0
  214. package/src/resolver/index.js +2422 -0
  215. package/src/resolver/normalize.js +1 -0
  216. package/src/resolver.js +1 -0
  217. package/src/sdlc/adopt.js +65 -0
  218. package/src/sdlc/check.js +86 -0
  219. package/src/sdlc/dod/acceptance-criterion.js +22 -0
  220. package/src/sdlc/dod/bug.js +26 -0
  221. package/src/sdlc/dod/document.js +23 -0
  222. package/src/sdlc/dod/index.js +25 -0
  223. package/src/sdlc/dod/pitch.js +23 -0
  224. package/src/sdlc/dod/requirement.js +34 -0
  225. package/src/sdlc/dod/task.js +39 -0
  226. package/src/sdlc/explain.js +116 -0
  227. package/src/sdlc/history.js +80 -0
  228. package/src/sdlc/paths.js +11 -0
  229. package/src/sdlc/release.js +106 -0
  230. package/src/sdlc/scaffold.js +89 -0
  231. package/src/sdlc/status-filter.js +54 -0
  232. package/src/sdlc/transition.js +112 -0
  233. package/src/sdlc/transitions/acceptance-criterion.js +28 -0
  234. package/src/sdlc/transitions/bug.js +31 -0
  235. package/src/sdlc/transitions/document.js +29 -0
  236. package/src/sdlc/transitions/index.js +56 -0
  237. package/src/sdlc/transitions/pitch.js +34 -0
  238. package/src/sdlc/transitions/requirement.js +31 -0
  239. package/src/sdlc/transitions/task.js +34 -0
  240. package/src/template-trust.js +597 -0
  241. package/src/validator/expressions.js +1 -0
  242. package/src/validator/index.js +3424 -0
  243. package/src/validator/kinds.js +346 -0
  244. package/src/validator/per-kind/acceptance-criterion.js +91 -0
  245. package/src/validator/per-kind/bug.js +77 -0
  246. package/src/validator/per-kind/component.js +274 -0
  247. package/src/validator/per-kind/domain.js +205 -0
  248. package/src/validator/per-kind/pitch.js +101 -0
  249. package/src/validator/per-kind/requirement.js +75 -0
  250. package/src/validator/per-kind/task.js +96 -0
  251. package/src/validator/registry.js +1 -0
  252. package/src/validator/utils.js +12 -0
  253. package/src/validator.js +1 -0
  254. package/src/workflows.js +7597 -0
  255. package/src/workspace-docs.js +265 -0
  256. package/template-helpers/react.js +5 -0
  257. package/template-helpers/sveltekit.js +5 -0
@@ -0,0 +1,173 @@
1
+ function asArray(value) {
2
+ if (Array.isArray(value)) {
3
+ return value.filter(Boolean);
4
+ }
5
+ return value == null ? [] : [value];
6
+ }
7
+
8
+ function propByName(contract) {
9
+ return new Map((contract?.props || []).map((prop) => [prop.name, prop]));
10
+ }
11
+
12
+ function bindingSourceForProp(usage, propName) {
13
+ return (usage?.dataBindings || []).find((binding) => binding.prop === propName)?.source || null;
14
+ }
15
+
16
+ function effectFromEventBinding(binding) {
17
+ if (binding.action === "navigate") {
18
+ return {
19
+ type: "navigation",
20
+ event: binding.event || null,
21
+ target: binding.target || null
22
+ };
23
+ }
24
+ if (binding.action === "action") {
25
+ return {
26
+ type: "command",
27
+ event: binding.event || null,
28
+ capability: binding.target || null
29
+ };
30
+ }
31
+ return {
32
+ type: "unknown",
33
+ event: binding.event || null,
34
+ action: binding.action || null,
35
+ target: binding.target || null
36
+ };
37
+ }
38
+
39
+ function uniqueEffects(effects) {
40
+ const seen = new Set();
41
+ const output = [];
42
+ for (const effect of effects) {
43
+ const key = JSON.stringify(effect);
44
+ if (seen.has(key)) {
45
+ continue;
46
+ }
47
+ seen.add(key);
48
+ output.push(effect);
49
+ }
50
+ return output;
51
+ }
52
+
53
+ function eventRealizations(usage, eventNames) {
54
+ const bindings = usage?.eventBindings || [];
55
+ return eventNames.map((eventName) => {
56
+ const matchingBindings = bindings.filter((binding) => binding.event === eventName);
57
+ return {
58
+ event: eventName,
59
+ bound: matchingBindings.length > 0,
60
+ bindings: matchingBindings.map((binding) => ({
61
+ event: binding.event || null,
62
+ action: binding.action || null,
63
+ target: binding.target || null
64
+ })),
65
+ effects: matchingBindings.map(effectFromEventBinding)
66
+ };
67
+ });
68
+ }
69
+
70
+ function capabilityActionRealization(usage, capabilityId) {
71
+ const bindings = (usage?.eventBindings || [])
72
+ .filter((binding) =>
73
+ binding.action === "action" &&
74
+ binding.target?.kind === "capability" &&
75
+ binding.target?.id === capabilityId
76
+ );
77
+ const capability = {
78
+ id: capabilityId,
79
+ kind: "capability"
80
+ };
81
+ return {
82
+ event: null,
83
+ capability,
84
+ bound: bindings.length > 0,
85
+ bindings: bindings.map((binding) => ({
86
+ event: binding.event || null,
87
+ action: binding.action || null,
88
+ target: binding.target || null
89
+ })),
90
+ effects: bindings.length > 0
91
+ ? bindings.map(effectFromEventBinding)
92
+ : [{
93
+ type: "command",
94
+ event: null,
95
+ capability,
96
+ source: "behavior"
97
+ }]
98
+ };
99
+ }
100
+
101
+ function actionRealizations(usage, actionNames, eventNames) {
102
+ return actionNames.map((actionName) => {
103
+ if (eventNames.has(actionName)) {
104
+ return eventRealizations(usage, [actionName])[0];
105
+ }
106
+ return capabilityActionRealization(usage, actionName);
107
+ });
108
+ }
109
+
110
+ function behaviorStatus({ hasDirectives, state, emits }) {
111
+ const hasMissingEventBinding = emits.some((entry) => !entry.bound);
112
+ if (hasMissingEventBinding) {
113
+ return "partial";
114
+ }
115
+ if (state?.requiredness === "required" && !state.bound) {
116
+ return "partial";
117
+ }
118
+ return hasDirectives ? "realized" : "declared";
119
+ }
120
+
121
+ /**
122
+ * Build projection-specific realizations for component behavior contracts.
123
+ *
124
+ * Components declare reusable behavior capabilities; projection ui_components
125
+ * bindings provide concrete data/event outcomes. This derived contract is the
126
+ * normalized bridge agents and generators can use without inferring behavior
127
+ * from stack code.
128
+ *
129
+ * @param {Record<string, any>|null} contract
130
+ * @param {Record<string, any>} usage
131
+ * @returns {Array<Record<string, any>>}
132
+ */
133
+ export function buildComponentBehaviorRealizations(contract, usage) {
134
+ const props = propByName(contract);
135
+ const eventNames = new Set((contract?.events || []).map((event) => event.id).filter(Boolean));
136
+ return (contract?.behaviors || []).map((behavior) => {
137
+ const directives = behavior.directives || {};
138
+ const statePropName = directives.state || null;
139
+ const stateProp = statePropName ? props.get(statePropName) || null : null;
140
+ const state = statePropName
141
+ ? {
142
+ prop: statePropName,
143
+ requiredness: stateProp?.requiredness || null,
144
+ bound: Boolean(bindingSourceForProp(usage, statePropName)),
145
+ source: bindingSourceForProp(usage, statePropName),
146
+ defaultValue: stateProp?.defaultValue ?? null
147
+ }
148
+ : null;
149
+ const emits = eventRealizations(usage, asArray(directives.emits));
150
+ const actions = actionRealizations(usage, [
151
+ ...asArray(directives.actions),
152
+ ...asArray(directives.submit)
153
+ ], eventNames);
154
+ const dataDependencies = (usage?.dataBindings || []).map((binding) => ({
155
+ prop: binding.prop || null,
156
+ source: binding.source || null
157
+ }));
158
+ const effects = uniqueEffects([...emits, ...actions].flatMap((entry) => entry.effects));
159
+ const hasDirectives = Object.keys(directives).length > 0;
160
+
161
+ return {
162
+ kind: behavior.kind || null,
163
+ source: behavior.source || null,
164
+ directives: { ...directives },
165
+ state,
166
+ emits,
167
+ actions,
168
+ dataDependencies,
169
+ effects,
170
+ status: behaviorStatus({ hasDirectives, state, emits: [...emits, ...actions] })
171
+ };
172
+ });
173
+ }
@@ -0,0 +1,91 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+
5
+ import {
6
+ findLegacyImplementationConfig,
7
+ findProjectConfig
8
+ } from "./project-config.js";
9
+ import { assertTrustedImplementation } from "./template-trust.js";
10
+
11
+ function normalizeRoot(root) {
12
+ return String(root || "").replace(/\\/g, "/");
13
+ }
14
+
15
+ function normalizeSearchRoot(root) {
16
+ if (!root) {
17
+ return null;
18
+ }
19
+ const absolute = path.resolve(root);
20
+ try {
21
+ return fs.realpathSync(absolute);
22
+ } catch {
23
+ return absolute;
24
+ }
25
+ }
26
+
27
+ function implementationProviderError(root) {
28
+ const suffix = root ? ` for ${normalizeRoot(root)}` : "";
29
+ return new Error(
30
+ `Topogram app/runtime generation requires an explicit implementation provider${suffix}. ` +
31
+ "Add topogram.project.json with implementation.module, or pass an implementation provider in generator options."
32
+ );
33
+ }
34
+
35
+ export function findImplementationConfig(root) {
36
+ const projectConfig = findProjectConfig(root);
37
+ if (projectConfig?.config?.implementation) {
38
+ return {
39
+ config: projectConfig.config.implementation,
40
+ configPath: projectConfig.configPath,
41
+ configDir: projectConfig.configDir
42
+ };
43
+ }
44
+ return findLegacyImplementationConfig(root);
45
+ }
46
+
47
+ export function getExampleImplementation(graph, options = {}) {
48
+ const implementation = options.implementation || options.implementationProvider || null;
49
+ if (implementation) {
50
+ return implementation;
51
+ }
52
+ throw implementationProviderError(graph?.root);
53
+ }
54
+
55
+ export async function loadImplementationProvider(root) {
56
+ const found = findImplementationConfig(root);
57
+ if (!found) {
58
+ throw implementationProviderError(root);
59
+ }
60
+
61
+ const { config, configPath, configDir } = found;
62
+ const projectConfig = findProjectConfig(root)?.config || null;
63
+ assertTrustedImplementation(found, projectConfig);
64
+ const implementationModule = config.implementation_module || config.module;
65
+ if (!implementationModule) {
66
+ throw new Error(
67
+ `Topogram implementation config ${normalizeRoot(configPath)} is missing implementation module.`
68
+ );
69
+ }
70
+
71
+ const modulePath = path.resolve(configDir, implementationModule);
72
+ const module = await import(pathToFileURL(modulePath).href);
73
+ const exportName = config.implementation_export || config.export || "default";
74
+ const implementation = module[exportName];
75
+ if (!implementation) {
76
+ throw new Error(
77
+ `Topogram implementation module ${normalizeRoot(modulePath)} does not export '${exportName}'.`
78
+ );
79
+ }
80
+ if (
81
+ (config.implementation_id || config.id) &&
82
+ implementation.exampleId &&
83
+ implementation.exampleId !== (config.implementation_id || config.id)
84
+ ) {
85
+ throw new Error(
86
+ `Topogram implementation config requested '${config.implementation_id || config.id}', ` +
87
+ `but provider exported '${implementation.exampleId}'.`
88
+ );
89
+ }
90
+ return implementation;
91
+ }
package/src/format.js ADDED
@@ -0,0 +1,19 @@
1
+ export function stableStringify(value) {
2
+ return JSON.stringify(sortValue(value), null, 2);
3
+ }
4
+
5
+ function sortValue(value) {
6
+ if (Array.isArray(value)) {
7
+ return value.map(sortValue);
8
+ }
9
+
10
+ if (value && typeof value === "object") {
11
+ const sorted = {};
12
+ for (const key of Object.keys(value).sort()) {
13
+ sorted[key] = sortValue(value[key]);
14
+ }
15
+ return sorted;
16
+ }
17
+
18
+ return value;
19
+ }
@@ -0,0 +1,4 @@
1
+ export const BUNDLED_GENERATOR_ADAPTERS: any[];
2
+ export function getBundledGeneratorAdapter(generatorId: string): any;
3
+ export function resolveGeneratorForComponent(component: any, options?: { rootDir?: string | null; configDir?: string | null }): { manifest: any; adapter: any };
4
+ export function generateWithComponentGenerator(context: any): { files: Record<string, string>; artifacts?: any; diagnostics?: any[] };
@@ -0,0 +1,325 @@
1
+ // @ts-check
2
+
3
+ import path from "node:path";
4
+ import { createRequire } from "node:module";
5
+
6
+ import { generateApiContractGraph } from "./api.js";
7
+ import {
8
+ getGeneratorManifest,
9
+ packageGeneratorInstallHint,
10
+ resolveGeneratorManifestForBinding,
11
+ validateGeneratorManifest
12
+ } from "./registry.js";
13
+ import { generateDbContractGraph } from "./surfaces/databases/contract.js";
14
+ import { generateDbLifecyclePlan } from "./surfaces/databases/lifecycle-shared.js";
15
+ import {
16
+ generatePostgresDbLifecycleBundle
17
+ } from "./surfaces/databases/postgres/index.js";
18
+ import {
19
+ generateSqliteDbLifecycleBundle
20
+ } from "./surfaces/databases/sqlite/index.js";
21
+ import { generateSwiftUiApp } from "./surfaces/native/swiftui-app.js";
22
+ import { generateExpressServer } from "./surfaces/services/express.js";
23
+ import { generateHonoServer } from "./surfaces/services/hono.js";
24
+ import { generateServerContract } from "./surfaces/services/server-contract.js";
25
+ import { generateStatelessServer } from "./surfaces/services/stateless.js";
26
+ import { generateReactApp } from "./surfaces/web/react.js";
27
+ import { generateSvelteKitApp } from "./surfaces/web/sveltekit.js";
28
+ import { generateUiWebContract } from "./surfaces/web/ui-web-contract.js";
29
+ import { generateVanillaWebApp } from "./surfaces/web/vanilla.js";
30
+
31
+ /**
32
+ * @typedef {import("./registry.js").GeneratorManifest} GeneratorManifest
33
+ */
34
+
35
+ /**
36
+ * @typedef {Object} GeneratorContext
37
+ * @property {Record<string, any>} graph
38
+ * @property {Record<string, any>} projection
39
+ * @property {Record<string, any>} component
40
+ * @property {Record<string, any>|null} [topology]
41
+ * @property {Record<string, any>} [contracts]
42
+ * @property {Record<string, any>|null} [implementation]
43
+ * @property {Record<string, any>} [options]
44
+ * @property {GeneratorManifest} [manifest]
45
+ */
46
+
47
+ /**
48
+ * @typedef {Object} GeneratorResult
49
+ * @property {Record<string, string>} files
50
+ * @property {Record<string, any>} [artifacts]
51
+ * @property {Array<Record<string, any>>} [diagnostics]
52
+ */
53
+
54
+ /**
55
+ * @typedef {Object} GeneratorAdapter
56
+ * @property {GeneratorManifest} manifest
57
+ * @property {(context: GeneratorContext) => GeneratorResult} generate
58
+ */
59
+
60
+ /**
61
+ * @param {Record<string, string>} files
62
+ * @param {Record<string, any>} [artifacts]
63
+ * @returns {GeneratorResult}
64
+ */
65
+ function fileResult(files, artifacts = {}) {
66
+ return { files, artifacts, diagnostics: [] };
67
+ }
68
+
69
+ /**
70
+ * @param {string} generatorId
71
+ * @returns {GeneratorManifest}
72
+ */
73
+ function requiredManifest(generatorId) {
74
+ const manifest = getGeneratorManifest(generatorId);
75
+ if (!manifest) {
76
+ throw new Error(`Bundled generator '${generatorId}' is missing its manifest.`);
77
+ }
78
+ return manifest;
79
+ }
80
+
81
+ /**
82
+ * @param {GeneratorContext} context
83
+ * @returns {string}
84
+ */
85
+ function projectionIdFor(context) {
86
+ return context.projection?.id || context.component?.projection?.id || context.options?.projectionId;
87
+ }
88
+
89
+ /**
90
+ * @param {GeneratorContext} context
91
+ * @param {string} profile
92
+ * @returns {Record<string, any>}
93
+ */
94
+ function serverOptions(context, profile) {
95
+ const projectionId = projectionIdFor(context);
96
+ const dbProjectionId = context.component?.databaseComponent?.projection?.id || context.options?.dbProjectionId;
97
+ return {
98
+ ...(context.options || {}),
99
+ projectionId,
100
+ dbProjectionId,
101
+ component: context.component,
102
+ topology: context.topology,
103
+ contracts: context.contracts,
104
+ implementation: context.implementation,
105
+ profile
106
+ };
107
+ }
108
+
109
+ /**
110
+ * @param {GeneratorContext} context
111
+ * @returns {Record<string, any>}
112
+ */
113
+ function commonOptions(context) {
114
+ return {
115
+ ...(context.options || {}),
116
+ projectionId: projectionIdFor(context),
117
+ component: context.component,
118
+ topology: context.topology,
119
+ contracts: context.contracts,
120
+ implementation: context.implementation
121
+ };
122
+ }
123
+
124
+ /**
125
+ * @param {GeneratorContext} context
126
+ * @returns {Record<string, any>}
127
+ */
128
+ function buildContractsForContext(context) {
129
+ const projectionId = projectionIdFor(context);
130
+ if (context.component.type === "web") {
131
+ return {
132
+ uiWeb: generateUiWebContract(context.graph, { ...(context.options || {}), projectionId })
133
+ };
134
+ }
135
+ if (context.component.type === "api") {
136
+ return {
137
+ server: generateServerContract(context.graph, { ...(context.options || {}), projectionId }),
138
+ api: generateApiContractGraph(context.graph, {})
139
+ };
140
+ }
141
+ if (context.component.type === "database") {
142
+ return {
143
+ db: generateDbContractGraph(context.graph, { ...(context.options || {}), projectionId }),
144
+ lifecyclePlan: generateDbLifecyclePlan(context.graph, { ...(context.options || {}), projectionId })
145
+ };
146
+ }
147
+ if (context.component.type === "native") {
148
+ return {
149
+ uiWeb: generateUiWebContract(context.graph, { ...(context.options || {}), projectionId })
150
+ };
151
+ }
152
+ return {};
153
+ }
154
+
155
+ /** @type {GeneratorAdapter[]} */
156
+ export const BUNDLED_GENERATOR_ADAPTERS = [
157
+ {
158
+ manifest: requiredManifest("topogram/hono"),
159
+ generate(context) {
160
+ if (context.component && !context.component.databaseComponent) {
161
+ return fileResult(generateStatelessServer(context.graph, serverOptions(context, "hono")));
162
+ }
163
+ return fileResult(generateHonoServer(context.graph, serverOptions(context, "hono")));
164
+ }
165
+ },
166
+ {
167
+ manifest: requiredManifest("topogram/express"),
168
+ generate(context) {
169
+ if (context.component && !context.component.databaseComponent) {
170
+ return fileResult(generateStatelessServer(context.graph, serverOptions(context, "express")));
171
+ }
172
+ return fileResult(generateExpressServer(context.graph, serverOptions(context, "express")));
173
+ }
174
+ },
175
+ {
176
+ manifest: requiredManifest("topogram/vanilla-web"),
177
+ generate(context) {
178
+ return fileResult(generateVanillaWebApp(context.graph, commonOptions(context)));
179
+ }
180
+ },
181
+ {
182
+ manifest: requiredManifest("topogram/sveltekit"),
183
+ generate(context) {
184
+ return fileResult(generateSvelteKitApp(context.graph, commonOptions(context)));
185
+ }
186
+ },
187
+ {
188
+ manifest: requiredManifest("topogram/react"),
189
+ generate(context) {
190
+ return fileResult(generateReactApp(context.graph, commonOptions(context)));
191
+ }
192
+ },
193
+ {
194
+ manifest: requiredManifest("topogram/swiftui"),
195
+ generate(context) {
196
+ return fileResult(generateSwiftUiApp(context.graph, commonOptions(context)));
197
+ }
198
+ },
199
+ {
200
+ manifest: requiredManifest("topogram/postgres"),
201
+ generate(context) {
202
+ return fileResult(generatePostgresDbLifecycleBundle(context.graph, commonOptions(context)));
203
+ }
204
+ },
205
+ {
206
+ manifest: requiredManifest("topogram/sqlite"),
207
+ generate(context) {
208
+ return fileResult(generateSqliteDbLifecycleBundle(context.graph, commonOptions(context)));
209
+ }
210
+ }
211
+ ];
212
+
213
+ const ADAPTER_BY_ID = new Map(BUNDLED_GENERATOR_ADAPTERS.map((adapter) => [adapter.manifest.id, adapter]));
214
+
215
+ /**
216
+ * @param {string} generatorId
217
+ * @returns {GeneratorAdapter|null}
218
+ */
219
+ export function getBundledGeneratorAdapter(generatorId) {
220
+ return ADAPTER_BY_ID.get(generatorId) || null;
221
+ }
222
+
223
+ /**
224
+ * @param {string|null|undefined} rootDir
225
+ * @returns {any}
226
+ */
227
+ function requireFromProject(rootDir) {
228
+ return createRequire(path.join(rootDir || process.cwd(), "package.json"));
229
+ }
230
+
231
+ /**
232
+ * @param {any} moduleValue
233
+ * @param {string|null|undefined} exportName
234
+ * @returns {any}
235
+ */
236
+ function selectPackageExport(moduleValue, exportName) {
237
+ if (exportName) {
238
+ return moduleValue?.[exportName] || moduleValue?.default?.[exportName] || null;
239
+ }
240
+ return moduleValue?.default || moduleValue;
241
+ }
242
+
243
+ /**
244
+ * @param {GeneratorManifest} manifest
245
+ * @param {Record<string, any>} component
246
+ * @param {{ rootDir?: string|null, configDir?: string|null }} [options]
247
+ * @returns {GeneratorAdapter}
248
+ */
249
+ function loadPackageGeneratorAdapter(manifest, component, options = {}) {
250
+ const packageName = manifest.package || component?.generator?.package;
251
+ if (!packageName) {
252
+ throw new Error(`Component '${component?.id || "unknown"}' generator '${manifest.id}@${manifest.version}' is package-backed but does not declare a package.`);
253
+ }
254
+ const rootDir = options.configDir || options.rootDir || process.cwd();
255
+ let moduleValue;
256
+ try {
257
+ moduleValue = requireFromProject(rootDir)(packageName);
258
+ } catch (error) {
259
+ const installHint = packageGeneratorInstallHint(packageName);
260
+ throw new Error(`Component '${component?.id || "unknown"}' generator package '${packageName}' could not be loaded from '${rootDir}': ${error instanceof Error ? error.message : String(error)}${installHint ? `. ${installHint}` : ""}`);
261
+ }
262
+ const adapter = selectPackageExport(moduleValue, manifest.export);
263
+ if (!adapter || typeof adapter.generate !== "function") {
264
+ throw new Error(`Component '${component?.id || "unknown"}' generator package '${packageName}' must export an adapter with a generate(context) function.`);
265
+ }
266
+ const adapterManifest = adapter.manifest || manifest;
267
+ if (adapterManifest.id !== manifest.id || adapterManifest.version !== manifest.version) {
268
+ throw new Error(`Component '${component?.id || "unknown"}' generator package '${packageName}' adapter manifest '${adapterManifest.id}@${adapterManifest.version}' does not match '${manifest.id}@${manifest.version}'.`);
269
+ }
270
+ return {
271
+ manifest,
272
+ generate(context) {
273
+ return adapter.generate(context);
274
+ }
275
+ };
276
+ }
277
+
278
+ /**
279
+ * @param {Record<string, any>} component
280
+ * @param {{ rootDir?: string|null, configDir?: string|null }} [options]
281
+ * @returns {{ manifest: GeneratorManifest, adapter: GeneratorAdapter }}
282
+ */
283
+ export function resolveGeneratorForComponent(component, options = {}) {
284
+ const generatorId = component?.generator?.id;
285
+ const resolved = resolveGeneratorManifestForBinding(component?.generator, options);
286
+ const manifest = resolved.manifest || getGeneratorManifest(generatorId);
287
+ if (!manifest) {
288
+ const detail = resolved.errors.length > 0 ? ` ${resolved.errors.join(" ")}` : "";
289
+ throw new Error(`Component '${component?.id || "unknown"}' uses unknown generator '${generatorId || "unknown"}'.${detail}`);
290
+ }
291
+ if (manifest.planned) {
292
+ throw new Error(`Component '${component?.id || "unknown"}' uses planned generator '${manifest.id}', which is not implemented yet.`);
293
+ }
294
+ if (component.generator?.version !== manifest.version) {
295
+ throw new Error(`Component '${component?.id || "unknown"}' generator '${manifest.id}' version '${component.generator?.version}' is unsupported; expected '${manifest.version}'.`);
296
+ }
297
+ const manifestValidation = validateGeneratorManifest(manifest);
298
+ if (!manifestValidation.ok) {
299
+ throw new Error(manifestValidation.errors.join("\n"));
300
+ }
301
+ if (manifest.source === "package") {
302
+ return { manifest, adapter: loadPackageGeneratorAdapter(manifest, component, options) };
303
+ }
304
+ const adapter = getBundledGeneratorAdapter(manifest.id);
305
+ if (!adapter) {
306
+ const installHint = packageGeneratorInstallHint(component?.generator?.package || manifest.package);
307
+ throw new Error(`Component '${component?.id || "unknown"}' generator '${manifest.id}@${manifest.version}' is not available. Package-backed generators must be installed before generation.${installHint ? ` ${installHint}` : ""}`);
308
+ }
309
+ return { manifest, adapter };
310
+ }
311
+
312
+ /**
313
+ * @param {Omit<GeneratorContext, "contracts"> & { contracts?: Record<string, any> }} context
314
+ * @returns {GeneratorResult}
315
+ */
316
+ export function generateWithComponentGenerator(context) {
317
+ const { manifest, adapter } = resolveGeneratorForComponent(context.component, context.options || {});
318
+ const contracts = context.contracts || buildContractsForContext(context);
319
+ return adapter.generate({
320
+ ...context,
321
+ projection: context.projection,
322
+ manifest,
323
+ contracts
324
+ });
325
+ }
@@ -0,0 +1 @@
1
+ export function generateApiContractGraph(graph: any, options?: any): any;