@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 @@
1
+ export function generateHonoServer(graph: any, options?: any): any;
@@ -0,0 +1,204 @@
1
+ import { generateDbTarget } from "../databases/index.js";
2
+ import { buildBackendRuntimeRealization } from "../../../realization/backend/index.js";
3
+ import { getProjection } from "../shared.js";
4
+ import { generatePersistenceScaffold } from "./persistence-wiring.js";
5
+ import {
6
+ renderServerContextTs,
7
+ renderServerHelpers,
8
+ renderServerIndexTs,
9
+ renderServerPackageJson,
10
+ renderServerSeedScript,
11
+ renderServerTsconfig,
12
+ routeTypeNames,
13
+ } from "./runtime-helpers.js";
14
+ import { generateServerContract, renderServerContractModule } from "./server-contract.js";
15
+ import { toPascalCase } from "../databases/shared.js";
16
+
17
+ function renderServerAppTs(realization) {
18
+ const { contract, lookupRoutes } = realization;
19
+ const lines = [];
20
+ const typeImportNames = routeTypeNames(contract);
21
+ const serviceName = realization.backendReference.serviceName;
22
+ const defaultWebPort = realization.runtimeReference?.ports?.web || 5173;
23
+ const repositoryReference = realization.repositoryReference;
24
+ const dependencyName = repositoryReference.dependencyName;
25
+ const preconditionCapabilityIds = repositoryReference.preconditionCapabilityIds;
26
+ const preconditionResource = repositoryReference.preconditionResource;
27
+ const preconditionVariableName = preconditionResource.variableName || "currentResource";
28
+ const downloadCapabilityId = repositoryReference.downloadCapabilityId;
29
+
30
+ lines.push('import { Hono } from "hono";');
31
+ lines.push('import { cors } from "hono/cors";');
32
+ lines.push('import type { Context } from "hono";');
33
+ lines.push('import { serverContract } from "../topogram/server-contract";');
34
+ lines.push('import { HttpError, coerceValue, contentDisposition, jsonError, requireHeaders, requireRequestFields } from "./helpers";');
35
+ lines.push('import type { ServerDependencies } from "./context";');
36
+ lines.push(`import type { ${typeImportNames.join(", ")} } from "../persistence/types";`);
37
+ lines.push("");
38
+ lines.push("function buildInput(c: Context, route: any, body: Record<string, unknown>) {");
39
+ lines.push(" const input: Record<string, unknown> = {};");
40
+ lines.push(" for (const field of (route.requestContract?.transport.path || []) as any[]) {");
41
+ lines.push(" input[field.name] = coerceValue(c.req.param(field.transport.wireName), field.schema);");
42
+ lines.push(" }");
43
+ lines.push(" for (const field of (route.requestContract?.transport.query || []) as any[]) {");
44
+ lines.push(" input[field.name] = coerceValue(c.req.query(field.transport.wireName), field.schema);");
45
+ lines.push(" }");
46
+ lines.push(" for (const field of (route.requestContract?.transport.header || []) as any[]) {");
47
+ lines.push(" input[field.name] = coerceValue(c.req.header(field.transport.wireName), field.schema);");
48
+ lines.push(" }");
49
+ lines.push(" for (const field of (route.requestContract?.transport.body || []) as any[]) {");
50
+ lines.push(' const defaultValue = field.schema && typeof field.schema === "object" && "default" in field.schema ? field.schema.default : undefined;');
51
+ lines.push(" input[field.name] = body[field.transport.wireName] ?? defaultValue;");
52
+ lines.push(" }");
53
+ lines.push(" return input;");
54
+ lines.push("}");
55
+ lines.push("");
56
+ lines.push("function corsOrigin(origin: string) {");
57
+ lines.push(` const configured = process.env.TOPOGRAM_CORS_ORIGINS || "http://localhost:${defaultWebPort},http://127.0.0.1:${defaultWebPort}";`);
58
+ lines.push(" const allowed = new Set(configured.split(\",\").map((entry) => entry.trim()).filter(Boolean));");
59
+ lines.push(" return allowed.has(origin) ? origin : \"\";");
60
+ lines.push("}");
61
+ lines.push("");
62
+ lines.push("export function createApp(deps: ServerDependencies) {");
63
+ lines.push(" const app = new Hono();");
64
+ lines.push(' app.use("*", cors({');
65
+ lines.push(" origin: corsOrigin,");
66
+ lines.push(' allowMethods: ["GET", "POST", "PATCH", "DELETE", "OPTIONS"],');
67
+ lines.push(' allowHeaders: ["Content-Type", "If-Match", "If-None-Match", "Idempotency-Key", "Authorization"],');
68
+ lines.push(' exposeHeaders: ["ETag", "Location", "Retry-After", "Content-Disposition"]');
69
+ lines.push(" }));");
70
+ lines.push("");
71
+ lines.push(` app.get("/health", (c) => c.json({ ok: true, service: "${serviceName}" }, 200 as any));`);
72
+ lines.push("");
73
+ lines.push(' app.get("/ready", async (c) => {');
74
+ lines.push(" try {");
75
+ lines.push(" await deps.ready?.();");
76
+ lines.push(` return c.json({ ok: true, ready: true, service: "${serviceName}" }, 200 as any);`);
77
+ lines.push(" } catch (error) {");
78
+ lines.push(' const message = error instanceof Error ? error.message : "Readiness check failed";');
79
+ lines.push(` return c.json({ ok: false, ready: false, service: "${serviceName}", message }, 503 as any);`);
80
+ lines.push(" }");
81
+ lines.push(" });");
82
+ lines.push("");
83
+
84
+ for (const lookup of lookupRoutes) {
85
+ lines.push(` app.get("${lookup.route}", async (c) => {`);
86
+ lines.push(" try {");
87
+ lines.push(` const result = await deps.${dependencyName}.${lookup.repositoryMethod}();`);
88
+ lines.push(" return c.json(result, 200 as any);");
89
+ lines.push(" } catch (error) {");
90
+ lines.push(" const failure = jsonError(error);");
91
+ lines.push(" return c.json(failure.body, failure.status as any);");
92
+ lines.push(" }");
93
+ lines.push(" });");
94
+ lines.push("");
95
+ }
96
+
97
+ contract.routes.forEach((route, routeIndex) => {
98
+ const method = route.method.toLowerCase();
99
+ const routeVar = `route${routeIndex}`;
100
+ const responseMode = route.responseContract?.mode || "item";
101
+ const methodName = route.repositoryMethod;
102
+ const hasOwnershipAuthz = (route.endpoint.authz || []).some((rule) => rule.ownership && rule.ownership !== "none");
103
+ const authLoaderVar = `loadAuthorizationResource${routeIndex}`;
104
+ lines.push(` const ${routeVar} = serverContract.routes[${routeIndex}]!;`);
105
+ lines.push(` app.${method}(${routeVar}.path, async (c) => {`);
106
+ lines.push(" try {");
107
+ const needsBody = (route.requestContract?.transport.body || []).length > 0;
108
+ if (needsBody) {
109
+ lines.push(" const body = await c.req.json().catch(() => ({}));");
110
+ } else {
111
+ lines.push(" const body = {};");
112
+ }
113
+ lines.push(` const input = buildInput(c, ${routeVar}, body);`);
114
+ if ((route.endpoint.authz || []).length > 0) {
115
+ if (hasOwnershipAuthz) {
116
+ if (preconditionCapabilityIds.includes(route.capabilityId)) {
117
+ lines.push(` const ${authLoaderVar} = async () => await deps.${dependencyName}.${preconditionResource.repositoryMethod}({ ${preconditionResource.inputField}: String(input.${preconditionResource.inputField} || "") } as unknown as ${toPascalCase(preconditionResource.repositoryMethod)}Input) as unknown as Record<string, unknown>;`);
118
+ } else if (route.method === "GET" && responseMode === "item") {
119
+ lines.push(` const ${authLoaderVar} = async () => await deps.${dependencyName}.${methodName}(input as unknown as ${toPascalCase(methodName)}Input) as unknown as Record<string, unknown>;`);
120
+ } else {
121
+ lines.push(` const ${authLoaderVar} = undefined;`);
122
+ }
123
+ }
124
+ lines.push(' if (!deps.authorize) throw new HttpError(500, "authorization_handler_missing", "Missing authorization handler for protected route");');
125
+ lines.push(` await deps.authorize(c, ${routeVar}.endpoint.authz, { capabilityId: ${routeVar}.capabilityId, input, ${hasOwnershipAuthz ? `loadResource: typeof ${authLoaderVar} === "function" ? ${authLoaderVar} : undefined` : "loadResource: undefined"} });`);
126
+ }
127
+ if ((route.endpoint.preconditions || []).length > 0 || (route.endpoint.idempotency || []).length > 0) {
128
+ lines.push(` requireHeaders(c, [...${routeVar}.endpoint.preconditions, ...${routeVar}.endpoint.idempotency]);`);
129
+ }
130
+ lines.push(` requireRequestFields(${routeVar}, input);`);
131
+ if (preconditionCapabilityIds.includes(route.capabilityId)) {
132
+ lines.push(' const ifMatch = c.req.header("If-Match");');
133
+ lines.push(' if (ifMatch) {');
134
+ lines.push(` const ${preconditionVariableName} = await deps.${dependencyName}.${preconditionResource.repositoryMethod}({ ${preconditionResource.inputField}: String(input.${preconditionResource.inputField} || "") } as unknown as ${toPascalCase(preconditionResource.repositoryMethod)}Input);`);
135
+ lines.push(` if (${preconditionVariableName}.${preconditionResource.versionField} !== ifMatch) {`);
136
+ lines.push(' throw new HttpError(412, "stale_precondition", "If-Match does not match the current resource version");');
137
+ lines.push(" }");
138
+ lines.push(" }");
139
+ }
140
+
141
+ if (route.capabilityId === downloadCapabilityId) {
142
+ lines.push(` const artifact = await deps.${dependencyName}.${methodName}(input as unknown as ${toPascalCase(methodName)}Input);`);
143
+ lines.push(" const responseHeaders = new Headers();");
144
+ lines.push(` responseHeaders.set("Content-Type", artifact.contentType || "${route.endpoint.download?.[0]?.media || "application/octet-stream"}");`);
145
+ lines.push(` responseHeaders.set("Content-Disposition", contentDisposition("${route.endpoint.download?.[0]?.disposition || "attachment"}", artifact.filename || "${route.endpoint.download?.[0]?.filename || "download.bin"}"));`);
146
+ lines.push(` return new Response(artifact.body as BodyInit | null, { status: ${route.successStatus}, headers: responseHeaders });`);
147
+ } else {
148
+ lines.push(` const result = await deps.${dependencyName}.${methodName}(input as unknown as ${toPascalCase(methodName)}Input);`);
149
+ if ((route.endpoint.cache || []).length > 0) {
150
+ const cacheRule = route.endpoint.cache[0];
151
+ lines.push(` const etag = (result as unknown as Record<string, unknown>)["${cacheRule.source}"];`);
152
+ lines.push(` if (etag && c.req.header("${cacheRule.requestHeader}") === String(etag)) {`);
153
+ lines.push(` return c.body(null, ${cacheRule.notModified} as any);`);
154
+ lines.push(" }");
155
+ lines.push(` if (etag) c.header("${cacheRule.responseHeader}", String(etag));`);
156
+ }
157
+ if ((route.endpoint.async || []).length > 0) {
158
+ const asyncRule = route.endpoint.async[0];
159
+ lines.push(` c.header("${asyncRule.locationHeader}", (result as unknown as Record<string, unknown>).status_url ? String((result as unknown as Record<string, unknown>).status_url) : "${asyncRule.statusPath}".replace(":job_id", String((result as unknown as Record<string, unknown>).job_id ?? "")));`);
160
+ lines.push(` c.header("${asyncRule.retryAfterHeader}", "5");`);
161
+ }
162
+ if (responseMode === "item" || responseMode === "cursor" || responseMode === "paged" || responseMode === "collection") {
163
+ lines.push(` return c.json(result as ${toPascalCase(methodName)}Result, ${route.successStatus} as any);`);
164
+ } else {
165
+ lines.push(` return c.json(result, ${route.successStatus} as any);`);
166
+ }
167
+ }
168
+ lines.push(" } catch (error) {");
169
+ lines.push(" const failure = jsonError(error);");
170
+ lines.push(" return c.json(failure.body, failure.status as any);");
171
+ lines.push(" }");
172
+ lines.push(" });");
173
+ lines.push("");
174
+ });
175
+
176
+ lines.push(" return app;");
177
+ lines.push("}");
178
+ lines.push("");
179
+ lines.push("export type AppType = ReturnType<typeof createApp>;");
180
+ return `${lines.join("\n").trimEnd()}\n`;
181
+ }
182
+
183
+ export function generateHonoServer(graph, options = {}) {
184
+ const projection = getProjection(graph, options.projectionId);
185
+ const realization = buildBackendRuntimeRealization(graph, options);
186
+ const contract = realization.contract;
187
+ const persistenceScaffold = generatePersistenceScaffold(graph, { ...options, projectionId: realization.dbProjection.id });
188
+ const prismaSchema = generateDbTarget("prisma-schema", graph, { projectionId: realization.dbProjection.id });
189
+
190
+ return {
191
+ "package.json": renderServerPackageJson(),
192
+ "tsconfig.json": renderServerTsconfig(),
193
+ "scripts/seed-demo.mjs": renderServerSeedScript(graph, options),
194
+ "src/index.ts": renderServerIndexTs(graph, options),
195
+ "src/lib/topogram/server-contract.ts": renderServerContractModule(graph, projection.id),
196
+ "src/lib/server/helpers.ts": renderServerHelpers(),
197
+ "src/lib/server/context.ts": renderServerContextTs(contract, graph, options),
198
+ "src/lib/server/app.ts": renderServerAppTs(realization),
199
+ "src/lib/persistence/types.ts": persistenceScaffold["types.ts"],
200
+ "src/lib/persistence/repositories.ts": persistenceScaffold["repositories.ts"],
201
+ "src/lib/persistence/prisma/repositories.ts": persistenceScaffold["prisma/repositories.ts"],
202
+ "prisma/schema.prisma": prismaSchema
203
+ };
204
+ }
@@ -0,0 +1,42 @@
1
+ import { generateWithComponentGenerator } from "../../adapters.js";
2
+ import { generateExpressServer } from "./express.js";
3
+ import { generateHonoServer } from "./hono.js";
4
+ import { generatePersistenceScaffold } from "./persistence-wiring.js";
5
+ import { generateServerContract } from "./server-contract.js";
6
+
7
+ export function generateBackendTarget(target, graph, options = {}) {
8
+ if (target === "server-contract") {
9
+ return generateServerContract(graph, options);
10
+ }
11
+ if (target === "persistence-scaffold") {
12
+ return generatePersistenceScaffold(graph, options);
13
+ }
14
+ if (target === "hono-server") {
15
+ if (options.component?.generator?.id) {
16
+ return generateWithComponentGenerator({
17
+ graph,
18
+ projection: options.component.projection,
19
+ component: options.component,
20
+ topology: options.topology || null,
21
+ implementation: options.implementation || null,
22
+ options
23
+ }).files;
24
+ }
25
+ return generateHonoServer(graph, options);
26
+ }
27
+ if (target === "express-server") {
28
+ if (options.component?.generator?.id) {
29
+ return generateWithComponentGenerator({
30
+ graph,
31
+ projection: options.component.projection,
32
+ component: options.component,
33
+ topology: options.topology || null,
34
+ implementation: options.implementation || null,
35
+ options
36
+ }).files;
37
+ }
38
+ return generateExpressServer(graph, options);
39
+ }
40
+
41
+ throw new Error(`Unsupported backend generator target '${target}'`);
42
+ }
@@ -0,0 +1,240 @@
1
+ import { generateApiContractGraph } from "../../api.js";
2
+ import { findEnumStatement, toPascalCase } from "../databases/shared.js";
3
+ import { getProjection } from "../shared.js";
4
+ import { repositoryMethodName } from "./runtime-helpers.js";
5
+ import { getExampleImplementation } from "../../../example-implementation.js";
6
+
7
+ function indexStatements(graph) {
8
+ const byId = new Map();
9
+ for (const statement of graph.statements) {
10
+ byId.set(statement.id, statement);
11
+ }
12
+ return byId;
13
+ }
14
+
15
+ function repositoryOperations(graph, repositoryReference) {
16
+ const byId = indexStatements(graph);
17
+ const capabilityIds = repositoryReference.capabilityIds;
18
+
19
+ return capabilityIds
20
+ .map((id) => byId.get(id))
21
+ .filter(Boolean)
22
+ .map((capability) => ({
23
+ capability,
24
+ input: capability.input[0] ? byId.get(capability.input[0].id) : null,
25
+ output: capability.output[0] ? byId.get(capability.output[0].id) : null
26
+ }));
27
+ }
28
+
29
+ function tsTypeForField(field, byId) {
30
+ const enumStatement = findEnumStatement(byId, field.fieldType);
31
+ if (enumStatement) {
32
+ return enumStatement.values.map((value) => `"${value}"`).join(" | ");
33
+ }
34
+ switch (field.fieldType) {
35
+ case "integer":
36
+ case "number":
37
+ return "number";
38
+ case "boolean":
39
+ return "boolean";
40
+ default:
41
+ return "string";
42
+ }
43
+ }
44
+
45
+ function renderShapeType(name, shape, byId) {
46
+ if (!shape) {
47
+ return `export type ${name} = Record<string, unknown>;\n`;
48
+ }
49
+ const fields = shape.projectedFields || shape.fields || [];
50
+ const lines = [`export interface ${name} {`];
51
+ for (const field of fields) {
52
+ lines.push(` ${field.name}${field.requiredness === "required" ? "" : "?"}: ${tsTypeForField(field, byId)};`);
53
+ }
54
+ lines.push("}");
55
+ return `${lines.join("\n")}\n`;
56
+ }
57
+
58
+ function renderResponseTypeDeclarations(baseName, contract, byId) {
59
+ if (!contract) {
60
+ return [`export type ${baseName} = Record<string, unknown>;\n`];
61
+ }
62
+
63
+ if (contract.mode === "item") {
64
+ const fields = contract.fields || [];
65
+ const lines = [`export interface ${baseName} {`];
66
+ for (const field of fields) {
67
+ const shapeField = {
68
+ fieldType:
69
+ field.schema?.["x-topogram-type"] || field.schema?.format === "uuid"
70
+ ? field.schema.format === "uuid"
71
+ ? "uuid"
72
+ : field.schema["x-topogram-type"]
73
+ : field.schema?.format === "date-time"
74
+ ? "datetime"
75
+ : field.schema?.type === "integer"
76
+ ? "integer"
77
+ : field.schema?.type === "number"
78
+ ? "number"
79
+ : field.schema?.type === "boolean"
80
+ ? "boolean"
81
+ : field.schema?.enum
82
+ ? field.schema.enum.map((value) => `"${value}"`).join(" | ")
83
+ : "text",
84
+ name: field.name,
85
+ requiredness: field.required ? "required" : "optional"
86
+ };
87
+ const type = field.schema?.enum
88
+ ? field.schema.enum.map((value) => `"${value}"`).join(" | ")
89
+ : tsTypeForField(shapeField, byId);
90
+ lines.push(` ${field.name}${field.required ? "" : "?"}: ${type};`);
91
+ }
92
+ lines.push("}");
93
+ return [`${lines.join("\n")}\n`];
94
+ }
95
+
96
+ const itemName = `${baseName}Item`;
97
+ const itemFields = contract.fields || [];
98
+ const itemLines = [`export interface ${itemName} {`];
99
+ for (const field of itemFields) {
100
+ const shapeField = {
101
+ fieldType: field.schema?.format === "uuid"
102
+ ? "uuid"
103
+ : field.schema?.format === "date-time"
104
+ ? "datetime"
105
+ : field.schema?.type === "integer"
106
+ ? "integer"
107
+ : field.schema?.type === "number"
108
+ ? "number"
109
+ : field.schema?.type === "boolean"
110
+ ? "boolean"
111
+ : field.schema?.enum
112
+ ? field.schema.enum.map((value) => `"${value}"`).join(" | ")
113
+ : "text",
114
+ name: field.name,
115
+ requiredness: field.required ? "required" : "optional"
116
+ };
117
+ const type = field.schema?.enum
118
+ ? field.schema.enum.map((value) => `"${value}"`).join(" | ")
119
+ : tsTypeForField(shapeField, byId);
120
+ itemLines.push(` ${field.name}${field.required ? "" : "?"}: ${type};`);
121
+ }
122
+ itemLines.push("}");
123
+
124
+ const envelopeLines = [`export interface ${baseName} {`];
125
+ if (contract.mode === "collection") {
126
+ envelopeLines.push(` items: ${itemName}[];`);
127
+ }
128
+ if (contract.mode === "paged") {
129
+ envelopeLines.push(` items: ${itemName}[];`);
130
+ envelopeLines.push(" page: number;");
131
+ envelopeLines.push(" page_size: number;");
132
+ envelopeLines.push(" total: number;");
133
+ }
134
+ if (contract.mode === "cursor") {
135
+ envelopeLines.push(` items: ${itemName}[];`);
136
+ envelopeLines.push(` ${contract.cursor?.responseNext || "next_cursor"}: string;`);
137
+ if (contract.cursor?.responsePrev) {
138
+ envelopeLines.push(` ${contract.cursor.responsePrev}?: string;`);
139
+ }
140
+ if (contract.total?.included) {
141
+ envelopeLines.push(" total?: number;");
142
+ }
143
+ }
144
+ envelopeLines.push("}");
145
+
146
+ return [`${itemLines.join("\n")}\n`, `${envelopeLines.join("\n")}\n`];
147
+ }
148
+
149
+ export function generatePersistenceScaffold(graph, options = {}) {
150
+ const implementation = getExampleImplementation(graph, options);
151
+ const repositoryReference = implementation.backend.repositoryReference;
152
+ const repositoryRenderers = implementation.backend.repositoryRenderers;
153
+ const repositoryInterfaceName = repositoryReference.repositoryInterfaceName;
154
+ const prismaRepositoryClassName = repositoryReference.prismaRepositoryClassName;
155
+ const drizzleRepositoryClassName = repositoryReference.drizzleRepositoryClassName;
156
+ const drizzleHint = repositoryReference.drizzleHint;
157
+ const projection = getProjection(graph, options.projectionId);
158
+ if (!["db_postgres", "db_sqlite"].includes(projection.platform)) {
159
+ throw new Error(`Persistence scaffold generation currently supports db_postgres and db_sqlite projections only, found '${projection.platform}'`);
160
+ }
161
+
162
+ const byId = indexStatements(graph);
163
+ const operations = repositoryOperations(graph, repositoryReference);
164
+ const interfaceLines = [];
165
+ const prismaLines = [];
166
+ const drizzleLines = [];
167
+ const typeLines = [];
168
+
169
+ prismaLines.push('import { PrismaClient } from "@prisma/client";');
170
+ prismaLines.push(`import type { ${repositoryInterfaceName} } from "../repositories";`);
171
+ prismaLines.push('import type {');
172
+ drizzleLines.push('import type { NodePgDatabase } from "drizzle-orm/node-postgres";');
173
+ drizzleLines.push(`import type { ${repositoryInterfaceName} } from "../repositories";`);
174
+ drizzleLines.push(`import { ${repositoryReference.drizzleSchemaImports.join(", ")} } from "../schema";`);
175
+ drizzleLines.push('import type {');
176
+
177
+ const usedTypes = new Set();
178
+ for (const operation of operations) {
179
+ const apiContract = generateApiContractGraph(graph, { capabilityId: operation.capability.id });
180
+ const method = repositoryMethodName(operation.capability.id);
181
+ const inputType = `${toPascalCase(method)}Input`;
182
+ const outputType = `${toPascalCase(method)}Result`;
183
+ usedTypes.add(inputType);
184
+ usedTypes.add(outputType);
185
+ prismaLines.push(` ${inputType},`);
186
+ prismaLines.push(` ${outputType},`);
187
+ drizzleLines.push(` ${inputType},`);
188
+ drizzleLines.push(` ${outputType},`);
189
+ typeLines.push(renderShapeType(inputType, operation.input, byId));
190
+ typeLines.push(...renderResponseTypeDeclarations(outputType, apiContract.responseContract, byId));
191
+ }
192
+ for (const declaration of repositoryReference.additionalTypeDeclarations || []) {
193
+ typeLines.push(`${declaration.trimEnd()}\n`);
194
+ }
195
+ for (const typeName of repositoryReference.additionalTypeNames || []) {
196
+ usedTypes.add(typeName);
197
+ prismaLines.push(` ${typeName},`);
198
+ drizzleLines.push(` ${typeName},`);
199
+ }
200
+ interfaceLines.push('import type {');
201
+ for (const typeName of [...usedTypes].sort()) {
202
+ interfaceLines.push(` ${typeName},`);
203
+ }
204
+ interfaceLines.push('} from "./types";');
205
+ interfaceLines.push("");
206
+ interfaceLines.push(`export interface ${repositoryInterfaceName} {`);
207
+ for (const operation of operations) {
208
+ const method = repositoryMethodName(operation.capability.id);
209
+ const inputType = `${toPascalCase(method)}Input`;
210
+ const outputType = `${toPascalCase(method)}Result`;
211
+ interfaceLines.push(` ${method}(input: ${inputType}): Promise<${outputType}>;`);
212
+ }
213
+ for (const method of repositoryReference.additionalInterfaceMethods || []) {
214
+ interfaceLines.push(` ${method}`);
215
+ }
216
+ interfaceLines.push("}");
217
+ prismaLines.push('} from "../types";');
218
+ drizzleLines.push('} from "../types";');
219
+ prismaLines.push("");
220
+ prismaLines.push('import { HttpError } from "../../server/helpers";');
221
+ drizzleLines.push("");
222
+ prismaLines.push(repositoryRenderers.renderPrismaRepositoryBody({
223
+ repositoryInterfaceName,
224
+ prismaRepositoryClassName,
225
+ repositoryReference
226
+ }).trimEnd());
227
+ drizzleLines.push(repositoryRenderers.renderDrizzleRepositoryBody({
228
+ repositoryInterfaceName,
229
+ drizzleRepositoryClassName,
230
+ repositoryReference,
231
+ drizzleHint
232
+ }).trimEnd());
233
+
234
+ return {
235
+ "types.ts": `${typeLines.join("\n").trimEnd()}\n`,
236
+ "repositories.ts": `${interfaceLines.join("\n").trimEnd()}\n`,
237
+ "prisma/repositories.ts": `${prismaLines.join("\n").trimEnd()}\n`,
238
+ "drizzle/repositories.ts": `${drizzleLines.join("\n").trimEnd()}\n`
239
+ };
240
+ }