@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,34 @@
1
+ import { generateDbContractGraph } from "../contract.js";
2
+ import { normalizeDbSchemaSnapshot } from "../snapshot.js";
3
+ import {
4
+ enumStatementsForSnapshot,
5
+ indexGraphStatements,
6
+ renderCreateEnumType,
7
+ renderCreateTable,
8
+ renderIndexes
9
+ } from "../shared.js";
10
+ import { resolvePostgresCapabilities } from "./capabilities.js";
11
+
12
+ export function generatePostgresSqlSchema(graph, options = {}) {
13
+ resolvePostgresCapabilities(options.profileId);
14
+ const contract = generateDbContractGraph(graph, options);
15
+ const byId = indexGraphStatements(graph);
16
+ const snapshot = normalizeDbSchemaSnapshot(contract, byId);
17
+ const statements = [];
18
+
19
+ for (const enumStatement of enumStatementsForSnapshot(snapshot, byId)) {
20
+ statements.push(renderCreateEnumType(enumStatement));
21
+ statements.push("");
22
+ }
23
+
24
+ for (const table of snapshot.tables) {
25
+ statements.push(renderCreateTable(table, "postgres", { byId }));
26
+ statements.push("");
27
+ for (const index of renderIndexes(table)) {
28
+ statements.push(index);
29
+ }
30
+ statements.push("");
31
+ }
32
+
33
+ return `${statements.join("\n").trimEnd()}\n`;
34
+ }
@@ -0,0 +1 @@
1
+ export function getProjection(graph: any, projectionId?: any): any;
@@ -0,0 +1,350 @@
1
+ export const DB_TARGETS = new Set([
2
+ "db-contract-graph",
3
+ "db-contract-debug",
4
+ "db-schema-snapshot",
5
+ "db-migration-plan",
6
+ "db-lifecycle-plan",
7
+ "db-lifecycle-bundle",
8
+ "sql-schema",
9
+ "sql-migration",
10
+ "prisma-schema",
11
+ "drizzle-schema"
12
+ ]);
13
+
14
+ export function getDbFamily(options = {}) {
15
+ if (options.projectionId?.includes("sqlite")) {
16
+ return "sqlite";
17
+ }
18
+ return "postgres";
19
+ }
20
+
21
+ function indexStatements(graph) {
22
+ const byId = new Map();
23
+ for (const statement of graph.statements) {
24
+ byId.set(statement.id, statement);
25
+ }
26
+ return byId;
27
+ }
28
+
29
+ export function getProjection(graph, projectionId) {
30
+ const byId = indexStatements(graph);
31
+ const projection = byId.get(projectionId);
32
+ if (!projection || projection.kind !== "projection") {
33
+ throw new Error(`No projection found with id '${projectionId}'`);
34
+ }
35
+ return projection;
36
+ }
37
+
38
+ export function indexGraphStatements(graph) {
39
+ return indexStatements(graph);
40
+ }
41
+
42
+ export function dbProjectionCandidates(graph) {
43
+ return (graph.byKind.projection || []).filter(
44
+ (projection) =>
45
+ projection.platform?.startsWith("db_") ||
46
+ (projection.dbTables || []).length > 0 ||
47
+ (projection.dbColumns || []).length > 0 ||
48
+ (projection.dbRelations || []).length > 0
49
+ );
50
+ }
51
+
52
+ export function defaultTableName(entityId) {
53
+ const base = entityId.replace(/^entity_/, "");
54
+ return base.endsWith("s") ? base : `${base}s`;
55
+ }
56
+
57
+ export function generatorDefaultsMap(projection) {
58
+ const defaults = {};
59
+ for (const entry of projection.generatorDefaults || []) {
60
+ if (entry.key && entry.value != null) {
61
+ defaults[entry.key] = entry.value;
62
+ }
63
+ }
64
+ return defaults;
65
+ }
66
+
67
+ export function dbProfileForProjection(projection) {
68
+ const defaults = generatorDefaultsMap(projection);
69
+ return defaults.profile || (projection.platform === "db_sqlite" ? "sqlite_sql" : "postgres_sql");
70
+ }
71
+
72
+ function mergeDbKeys(entity, projection) {
73
+ const projectionKeys = (projection.dbKeys || []).filter((entry) => entry.entity?.id === entity.id);
74
+ const baseKeys = entity.keys || [];
75
+ return [...baseKeys, ...projectionKeys.map((entry) => ({ type: entry.keyType, fields: entry.fields, raw: entry.raw }))];
76
+ }
77
+
78
+ function mergeDbIndexes(entity, projection) {
79
+ const projectionIndexes = (projection.dbIndexes || []).filter((entry) => entry.entity?.id === entity.id);
80
+ const baseIndexes = entity.keys
81
+ .filter((entry) => entry.type === "index" || entry.type === "unique")
82
+ .map((entry) => ({ type: entry.type, fields: entry.fields, raw: entry.raw }));
83
+ return [...baseIndexes, ...projectionIndexes.map((entry) => ({ type: entry.indexType, fields: entry.fields, raw: entry.raw }))];
84
+ }
85
+
86
+ function dedupeFieldSets(entries) {
87
+ const seen = new Set();
88
+ const output = [];
89
+ for (const entry of entries) {
90
+ const key = `${entry.type}:${entry.fields.join(",")}`;
91
+ if (seen.has(key)) {
92
+ continue;
93
+ }
94
+ seen.add(key);
95
+ output.push(entry);
96
+ }
97
+ return output;
98
+ }
99
+
100
+ export function sortFieldSets(fieldSets = []) {
101
+ return [...fieldSets]
102
+ .map((fields) => [...fields])
103
+ .sort((a, b) => a.join("|").localeCompare(b.join("|")));
104
+ }
105
+
106
+ export function findEnumStatement(byId, typeName) {
107
+ const candidate = byId.get(typeName);
108
+ return candidate?.kind === "enum" ? candidate : null;
109
+ }
110
+
111
+ export function toPascalCase(value) {
112
+ return value
113
+ .split(/[_\-\s]+/)
114
+ .filter(Boolean)
115
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
116
+ .join("");
117
+ }
118
+
119
+ export function sqlTypeForField(fieldType, engine, byId = null) {
120
+ const enumStatement = byId ? findEnumStatement(byId, fieldType) : null;
121
+ if (enumStatement && engine === "postgres") {
122
+ return `"${toPascalCase(enumStatement.id.replace(/^enum_/, ""))}"`;
123
+ }
124
+ if (engine === "sqlite") {
125
+ switch (fieldType) {
126
+ case "integer":
127
+ case "boolean":
128
+ return "integer";
129
+ case "number":
130
+ return "real";
131
+ default:
132
+ return "text";
133
+ }
134
+ }
135
+
136
+ switch (fieldType) {
137
+ case "uuid":
138
+ return "uuid";
139
+ case "integer":
140
+ return "integer";
141
+ case "number":
142
+ return "double precision";
143
+ case "boolean":
144
+ return "boolean";
145
+ case "datetime":
146
+ return "timestamptz";
147
+ default:
148
+ return "text";
149
+ }
150
+ }
151
+
152
+ export function sqlDefaultLiteral(value, fieldType, engine) {
153
+ if (value == null) {
154
+ return null;
155
+ }
156
+ if (fieldType === "boolean") {
157
+ return engine === "sqlite" ? (value === "true" ? "1" : "0") : value.toUpperCase();
158
+ }
159
+ if (fieldType === "integer" || fieldType === "number") {
160
+ return value;
161
+ }
162
+ return `'${String(value).replace(/'/g, "''")}'`;
163
+ }
164
+
165
+ export function enumStatementsForSnapshot(snapshot, byId) {
166
+ if ((snapshot.enums || []).length > 0) {
167
+ return [...snapshot.enums].sort((a, b) => a.id.localeCompare(b.id));
168
+ }
169
+ const enums = new Map();
170
+ for (const table of snapshot.tables || []) {
171
+ for (const column of table.columns || []) {
172
+ const enumStatement = findEnumStatement(byId, column.fieldType);
173
+ if (enumStatement) {
174
+ enums.set(enumStatement.id, enumStatement);
175
+ }
176
+ }
177
+ }
178
+ return [...enums.values()].sort((a, b) => a.id.localeCompare(b.id));
179
+ }
180
+
181
+ export function renderCreateEnumType(enumStatement) {
182
+ const typeName = enumStatement.typeName || toPascalCase(enumStatement.id.replace(/^enum_/, ""));
183
+ const values = enumStatement.values.map((value) => `'${String(value).replace(/'/g, "''")}'`).join(", ");
184
+ return `DO $$ BEGIN\n CREATE TYPE "${typeName}" AS ENUM (${values});\nEXCEPTION\n WHEN duplicate_object THEN null;\nEND $$;`;
185
+ }
186
+
187
+ export function renderAddEnumValue(enumStatement, operation) {
188
+ const typeName = enumStatement.typeName || toPascalCase(enumStatement.id.replace(/^enum_/, ""));
189
+ const escapedValue = `'${String(operation.value).replace(/'/g, "''")}'`;
190
+ const position =
191
+ operation.before != null
192
+ ? ` BEFORE '${String(operation.before).replace(/'/g, "''")}'`
193
+ : operation.after != null
194
+ ? ` AFTER '${String(operation.after).replace(/'/g, "''")}'`
195
+ : "";
196
+ return `ALTER TYPE "${typeName}" ADD VALUE IF NOT EXISTS ${escapedValue}${position};`;
197
+ }
198
+
199
+ export function renderCreateTable(table, engine, options = {}) {
200
+ const includeRelations = options.includeRelations !== false;
201
+ const byId = options.byId || null;
202
+ const lines = [];
203
+ for (const column of table.columns) {
204
+ const parts = [`${column.name} ${sqlTypeForField(column.fieldType, engine, byId)}`];
205
+ if (column.requiredness === "required") {
206
+ parts.push("not null");
207
+ }
208
+ const defaultLiteral = sqlDefaultLiteral(column.defaultValue, column.fieldType, engine);
209
+ if (defaultLiteral != null) {
210
+ parts.push(`default ${defaultLiteral}`);
211
+ }
212
+ lines.push(` ${parts.join(" ")}`);
213
+ }
214
+
215
+ if (table.primaryKey.length > 0) {
216
+ lines.push(` primary key (${table.primaryKey.join(", ")})`);
217
+ }
218
+ for (const fields of table.uniques) {
219
+ lines.push(` unique (${fields.join(", ")})`);
220
+ }
221
+ if (includeRelations) {
222
+ for (const relation of table.relations) {
223
+ if (relation.target?.id && relation.target?.field) {
224
+ const targetTable = defaultTableName(relation.target.id);
225
+ lines.push(` foreign key (${relation.field}) references ${targetTable}(${relation.target.field})${relation.onDelete ? ` on delete ${relation.onDelete.replace("_", " ")}` : ""}`);
226
+ }
227
+ }
228
+ }
229
+
230
+ return `create table ${table.table} (\n${lines.join(",\n")}\n);`;
231
+ }
232
+
233
+ export function renderIndexes(table) {
234
+ return table.indexes
235
+ .filter((entry) => entry.type === "index")
236
+ .map((entry) => {
237
+ const name = `${table.table}_${entry.fields.join("_")}_${entry.type}`;
238
+ return `create index ${name} on ${table.table} (${entry.fields.join(", ")});`;
239
+ });
240
+ }
241
+
242
+ export function renderAddColumn(table, column) {
243
+ const parts = [`alter table ${table} add column ${column.name} ${column.dbType}`];
244
+ if (column.required) {
245
+ parts.push("not null");
246
+ }
247
+ if (column.defaultSql != null) {
248
+ parts.push(`default ${column.defaultSql}`);
249
+ }
250
+ return `${parts.join(" ")};`;
251
+ }
252
+
253
+ export function buildDbProjectionContract(graph, projection) {
254
+ const byId = indexStatements(graph);
255
+ const realizedEntities = (projection.realizes || [])
256
+ .map((ref) => byId.get(ref.id))
257
+ .filter((statement) => statement?.kind === "entity");
258
+
259
+ const tableMappings = new Map((projection.dbTables || []).map((entry) => [entry.entity?.id, entry.table]));
260
+ const columnMappings = new Map((projection.dbColumns || []).map((entry) => [`${entry.entity?.id}:${entry.field}`, entry.column]));
261
+ const relationMappings = new Map();
262
+ for (const entry of projection.dbRelations || []) {
263
+ if (!relationMappings.has(entry.entity?.id)) {
264
+ relationMappings.set(entry.entity?.id, []);
265
+ }
266
+ relationMappings.get(entry.entity?.id).push(entry);
267
+ }
268
+ const lifecycleMappings = new Map();
269
+ for (const entry of projection.dbLifecycle || []) {
270
+ if (!lifecycleMappings.has(entry.entity?.id)) {
271
+ lifecycleMappings.set(entry.entity?.id, []);
272
+ }
273
+ lifecycleMappings.get(entry.entity?.id).push(entry);
274
+ }
275
+
276
+ return {
277
+ projection: {
278
+ id: projection.id,
279
+ name: projection.name || projection.id,
280
+ platform: projection.platform
281
+ },
282
+ profile: dbProfileForProjection(projection),
283
+ generatorDefaults: generatorDefaultsMap(projection),
284
+ tables: realizedEntities.map((entity) => {
285
+ const tableName = tableMappings.get(entity.id) || defaultTableName(entity.id);
286
+ const mergedKeys = mergeDbKeys(entity, projection);
287
+ const mergedIndexes = dedupeFieldSets(mergeDbIndexes(entity, projection));
288
+ const primaryKey = mergedKeys.find((entry) => entry.type === "primary")?.fields || [];
289
+ const uniques = mergedKeys
290
+ .filter((entry) => entry.type === "unique")
291
+ .map((entry) => entry.fields);
292
+ const indexes = mergedIndexes
293
+ .filter((entry) => entry.type === "index" || entry.type === "unique")
294
+ .map((entry) => ({
295
+ type: entry.type,
296
+ fields: entry.fields
297
+ }));
298
+ const relationEntries = relationMappings.get(entity.id) || entity.relations.map((relation) => ({
299
+ field: relation.sourceField,
300
+ target: relation.target,
301
+ onDelete: null
302
+ }));
303
+ const lifecycleEntries = lifecycleMappings.get(entity.id) || [];
304
+
305
+ return {
306
+ type: "db_table_contract",
307
+ entity: {
308
+ id: entity.id,
309
+ name: entity.name || entity.id
310
+ },
311
+ table: tableName,
312
+ columns: entity.fields.map((field) => ({
313
+ name: columnMappings.get(`${entity.id}:${field.name}`) || field.name,
314
+ sourceField: field.name,
315
+ fieldType: field.fieldType,
316
+ requiredness: field.requiredness,
317
+ defaultValue: field.defaultValue
318
+ })),
319
+ primaryKey,
320
+ uniques,
321
+ indexes,
322
+ relations: relationEntries.map((entry) => ({
323
+ field: entry.field || entry.sourceField,
324
+ target: entry.target,
325
+ onDelete: entry.onDelete || null
326
+ })),
327
+ lifecycle: {
328
+ softDelete: (() => {
329
+ const entry = lifecycleEntries.find((item) => item.lifecycleType === "soft_delete");
330
+ return entry
331
+ ? {
332
+ field: entry.field,
333
+ value: entry.value
334
+ }
335
+ : null;
336
+ })(),
337
+ timestamps: (() => {
338
+ const entry = lifecycleEntries.find((item) => item.lifecycleType === "timestamps");
339
+ return entry
340
+ ? {
341
+ createdAt: entry.createdAt,
342
+ updatedAt: entry.updatedAt
343
+ }
344
+ : null;
345
+ })()
346
+ }
347
+ };
348
+ })
349
+ };
350
+ }
@@ -0,0 +1,96 @@
1
+ import {
2
+ buildDbProjectionContract,
3
+ dbProjectionCandidates,
4
+ findEnumStatement,
5
+ getProjection,
6
+ indexGraphStatements,
7
+ sortFieldSets,
8
+ sqlDefaultLiteral,
9
+ sqlTypeForField,
10
+ toPascalCase
11
+ } from "./shared.js";
12
+
13
+ export function normalizeDbSchemaSnapshot(contract, byId = null) {
14
+ const engine = contract.projection.platform === "db_sqlite" ? "sqlite" : "postgres";
15
+ const tables = [...contract.tables]
16
+ .map((table) => ({
17
+ table: table.table,
18
+ entity: table.entity,
19
+ columns: [...table.columns]
20
+ .map((column) => ({
21
+ name: column.name,
22
+ sourceField: column.sourceField,
23
+ fieldType: column.fieldType,
24
+ dbType: sqlTypeForField(column.fieldType, engine, byId),
25
+ required: column.requiredness === "required",
26
+ requiredness: column.requiredness,
27
+ defaultValue: column.defaultValue ?? null,
28
+ defaultSql: sqlDefaultLiteral(column.defaultValue, column.fieldType, engine)
29
+ }))
30
+ .sort((a, b) => a.name.localeCompare(b.name)),
31
+ primaryKey: [...table.primaryKey],
32
+ uniques: sortFieldSets(table.uniques || []),
33
+ indexes: [...(table.indexes || [])]
34
+ .map((entry) => ({
35
+ type: entry.type,
36
+ fields: [...entry.fields]
37
+ }))
38
+ .sort((a, b) => `${a.type}:${a.fields.join("|")}`.localeCompare(`${b.type}:${b.fields.join("|")}`)),
39
+ relations: [...(table.relations || [])]
40
+ .map((relation) => ({
41
+ field: relation.field,
42
+ target: relation.target,
43
+ onDelete: relation.onDelete || null
44
+ }))
45
+ .sort((a, b) => `${a.field}:${a.target?.id || ""}:${a.target?.field || ""}`.localeCompare(`${b.field}:${b.target?.id || ""}:${b.target?.field || ""}`)),
46
+ lifecycle: table.lifecycle
47
+ }))
48
+ .sort((a, b) => a.table.localeCompare(b.table));
49
+ const enums = byId
50
+ ? [...new Map(
51
+ tables.flatMap((table) =>
52
+ table.columns.flatMap((column) => {
53
+ const enumStatement = findEnumStatement(byId, column.fieldType);
54
+ if (!enumStatement) {
55
+ return [];
56
+ }
57
+ return [[
58
+ enumStatement.id,
59
+ {
60
+ id: enumStatement.id,
61
+ typeName: toPascalCase(enumStatement.id.replace(/^enum_/, "")),
62
+ values: [...enumStatement.values]
63
+ }
64
+ ]];
65
+ })
66
+ )
67
+ ).values()].sort((a, b) => a.id.localeCompare(b.id))
68
+ : [];
69
+
70
+ return {
71
+ type: "db_schema_snapshot",
72
+ projection: contract.projection,
73
+ profile: contract.profile,
74
+ generatorDefaults: contract.generatorDefaults,
75
+ engine,
76
+ enums,
77
+ tables
78
+ };
79
+ }
80
+
81
+ export function getTableFromSnapshot(snapshot, tableName) {
82
+ return (snapshot.tables || []).find((table) => table.table === tableName);
83
+ }
84
+
85
+ export function generateDbSchemaSnapshot(graph, options = {}) {
86
+ const byId = indexGraphStatements(graph);
87
+ if (options.projectionId) {
88
+ return normalizeDbSchemaSnapshot(buildDbProjectionContract(graph, getProjection(graph, options.projectionId)), byId);
89
+ }
90
+
91
+ const output = {};
92
+ for (const projection of dbProjectionCandidates(graph)) {
93
+ output[projection.id] = normalizeDbSchemaSnapshot(buildDbProjectionContract(graph, projection), byId);
94
+ }
95
+ return output;
96
+ }
@@ -0,0 +1,14 @@
1
+ export const SQLITE_CAPABILITIES = {
2
+ default: {
3
+ family: "sqlite",
4
+ profiles: ["default"],
5
+ supportsEnums: false,
6
+ supportsPrisma: true,
7
+ supportsDrizzle: false,
8
+ supportsGeneratedBundles: true
9
+ }
10
+ };
11
+
12
+ export function resolveSqliteCapabilities(profileId = "default") {
13
+ return SQLITE_CAPABILITIES[profileId] || SQLITE_CAPABILITIES.default;
14
+ }
@@ -0,0 +1,8 @@
1
+ export { resolveSqliteCapabilities } from "./capabilities.js";
2
+ export { generateSqlitePrismaSchema } from "./prisma.js";
3
+ export { generateSqliteSqlSchema } from "./sql-schema.js";
4
+ export { generateSqliteSqlMigration } from "./sql-migration.js";
5
+ export {
6
+ generateSqliteDbLifecycleBundle,
7
+ generateSqliteDbLifecyclePlan
8
+ } from "./lifecycle.js";
@@ -0,0 +1,16 @@
1
+ import {
2
+ generateDbLifecycleBundleForProjection,
3
+ generateDbLifecyclePlanForProjection
4
+ } from "../lifecycle-shared.js";
5
+ import { getProjection } from "../shared.js";
6
+ import { resolveSqliteCapabilities } from "./capabilities.js";
7
+
8
+ export function generateSqliteDbLifecyclePlan(graph, options = {}) {
9
+ resolveSqliteCapabilities(options.profileId);
10
+ return generateDbLifecyclePlanForProjection(graph, getProjection(graph, options.projectionId), options);
11
+ }
12
+
13
+ export function generateSqliteDbLifecycleBundle(graph, options = {}) {
14
+ resolveSqliteCapabilities(options.profileId);
15
+ return generateDbLifecycleBundleForProjection(graph, getProjection(graph, options.projectionId), options);
16
+ }
@@ -0,0 +1,143 @@
1
+ import {
2
+ buildDbProjectionContract,
3
+ defaultTableName,
4
+ findEnumStatement,
5
+ getProjection,
6
+ indexGraphStatements,
7
+ toPascalCase
8
+ } from "../shared.js";
9
+ import { normalizeDbSchemaSnapshot } from "../snapshot.js";
10
+ import { resolveSqliteCapabilities } from "./capabilities.js";
11
+
12
+ function prismaScalarForColumn(column, byId) {
13
+ const enumStatement = findEnumStatement(byId, column.fieldType);
14
+ if (enumStatement) {
15
+ return { type: "String" };
16
+ }
17
+
18
+ switch (column.fieldType) {
19
+ case "integer":
20
+ return { type: "Int" };
21
+ case "number":
22
+ return { type: "Float" };
23
+ case "boolean":
24
+ return { type: "Boolean" };
25
+ case "datetime":
26
+ return { type: "DateTime" };
27
+ default:
28
+ return { type: "String" };
29
+ }
30
+ }
31
+
32
+ function prismaDefaultForColumn(column) {
33
+ if (column.defaultValue == null) {
34
+ return null;
35
+ }
36
+ if (column.fieldType === "boolean") {
37
+ return `@default(${String(column.defaultValue) === "true" ? "true" : "false"})`;
38
+ }
39
+ if (column.fieldType === "integer" || column.fieldType === "number") {
40
+ return `@default(${column.defaultValue})`;
41
+ }
42
+ return `@default("${String(column.defaultValue).replace(/"/g, '\\"')}")`;
43
+ }
44
+
45
+ export function generateSqlitePrismaSchema(graph, options = {}) {
46
+ resolveSqliteCapabilities(options.profileId);
47
+ const projection = getProjection(graph, options.projectionId);
48
+ if (projection.platform !== "db_sqlite") {
49
+ throw new Error(`Prisma schema generation currently supports db_sqlite projections only, found '${projection.platform}'`);
50
+ }
51
+
52
+ const byId = indexGraphStatements(graph);
53
+ const snapshot = normalizeDbSchemaSnapshot(buildDbProjectionContract(graph, projection));
54
+
55
+ const relationBackrefs = new Map();
56
+ for (const table of snapshot.tables) {
57
+ for (const relation of table.relations || []) {
58
+ const targetTable = defaultTableName(relation.target.id);
59
+ if (!relationBackrefs.has(targetTable)) {
60
+ relationBackrefs.set(targetTable, []);
61
+ }
62
+ relationBackrefs.get(targetTable).push({
63
+ fromTable: table.table,
64
+ fromModel: toPascalCase(table.entity.id.replace(/^entity_/, "")),
65
+ field: relation.field,
66
+ relationName: `${toPascalCase(table.entity.id.replace(/^entity_/, ""))}_${relation.field}_to_${toPascalCase(relation.target.id.replace(/^entity_/, ""))}`
67
+ });
68
+ }
69
+ }
70
+
71
+ const lines = [];
72
+ lines.push('generator client {');
73
+ lines.push(' provider = "prisma-client-js"');
74
+ lines.push("}");
75
+ lines.push("");
76
+ lines.push("datasource db {");
77
+ lines.push(' provider = "sqlite"');
78
+ lines.push(' url = env("DATABASE_URL")');
79
+ lines.push("}");
80
+ lines.push("");
81
+
82
+ for (const table of snapshot.tables) {
83
+ const modelName = toPascalCase(table.entity.id.replace(/^entity_/, ""));
84
+ const pk = table.primaryKey || [];
85
+ const relationFields = new Map((table.relations || []).map((entry) => [entry.field, entry]));
86
+
87
+ lines.push(`model ${modelName} {`);
88
+ for (const column of table.columns) {
89
+ const scalar = prismaScalarForColumn(column, byId);
90
+ const optional = column.required ? "" : "?";
91
+ const attrs = [];
92
+ if (pk.length === 1 && pk[0] === column.name) {
93
+ attrs.push("@id");
94
+ }
95
+ const uniqueMatch = (table.uniques || []).find((fields) => fields.length === 1 && fields[0] === column.name);
96
+ if (uniqueMatch) {
97
+ attrs.push("@unique");
98
+ }
99
+ const defaultAttr = prismaDefaultForColumn(column);
100
+ if (defaultAttr) {
101
+ attrs.push(defaultAttr);
102
+ }
103
+ if (column.name !== column.sourceField) {
104
+ attrs.push(`@map("${column.name}")`);
105
+ }
106
+ lines.push(` ${column.sourceField} ${scalar.type}${optional}${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""}`);
107
+
108
+ const relation = relationFields.get(column.name);
109
+ if (relation) {
110
+ const targetModel = toPascalCase(relation.target.id.replace(/^entity_/, ""));
111
+ const relationName = `${modelName}_${column.sourceField}_to_${targetModel}`;
112
+ const optionalRelation = column.required ? "" : "?";
113
+ lines.push(` ${column.sourceField.replace(/_id$/, "")} ${targetModel}${optionalRelation} @relation("${relationName}", fields: [${column.sourceField}], references: [${relation.target.field}])`);
114
+ }
115
+ }
116
+
117
+ for (const backref of relationBackrefs.get(table.table) || []) {
118
+ const relationFieldName = `${backref.fromTable}`;
119
+ lines.push(` ${relationFieldName} ${backref.fromModel}[] @relation("${backref.relationName}")`);
120
+ }
121
+
122
+ if (pk.length > 1) {
123
+ lines.push(` @@id([${pk.join(", ")}])`);
124
+ }
125
+ for (const fields of table.uniques || []) {
126
+ if (fields.length > 1) {
127
+ lines.push(` @@unique([${fields.join(", ")}])`);
128
+ }
129
+ }
130
+ for (const index of table.indexes || []) {
131
+ if (index.type === "index") {
132
+ lines.push(` @@index([${index.fields.join(", ")}])`);
133
+ }
134
+ }
135
+ if (table.table !== modelName) {
136
+ lines.push(` @@map("${table.table}")`);
137
+ }
138
+ lines.push("}");
139
+ lines.push("");
140
+ }
141
+
142
+ return `${lines.join("\n").trimEnd()}\n`;
143
+ }