@topogram/cli 0.3.64 → 0.3.66

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 (278) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +716 -0
  3. package/src/adoption/plan.js +12 -703
  4. package/src/adoption/reporting.js +1 -1
  5. package/src/agent-brief.js +7 -21
  6. package/src/agent-ops/query-builders/auth.js +375 -0
  7. package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
  8. package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
  9. package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
  10. package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
  11. package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
  12. package/src/agent-ops/query-builders/change-risk.js +25 -0
  13. package/src/agent-ops/query-builders/common.js +149 -0
  14. package/src/agent-ops/query-builders/maintained-risk.js +539 -0
  15. package/src/agent-ops/query-builders/maintained-shared.js +120 -0
  16. package/src/agent-ops/query-builders/multi-agent.js +547 -0
  17. package/src/agent-ops/query-builders/projection-impacts.js +514 -0
  18. package/src/agent-ops/query-builders/work-packets.js +417 -0
  19. package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
  20. package/src/agent-ops/query-builders/workflow-context.js +398 -0
  21. package/src/agent-ops/query-builders/workflow-presets-core.js +677 -0
  22. package/src/agent-ops/query-builders/workflow-presets.js +341 -0
  23. package/src/agent-ops/query-builders.d.ts +26 -26
  24. package/src/agent-ops/query-builders.js +42 -5021
  25. package/src/archive/jsonl.js +2 -2
  26. package/src/archive/resolver-bridge.js +1 -1
  27. package/src/archive/unarchive.js +2 -1
  28. package/src/catalog/constants.js +10 -0
  29. package/src/catalog/copy.js +65 -0
  30. package/src/catalog/diagnostics.js +15 -0
  31. package/src/catalog/entries.js +42 -0
  32. package/src/catalog/files.js +67 -0
  33. package/src/catalog/provenance.js +123 -0
  34. package/src/catalog/source.js +150 -0
  35. package/src/catalog/validation.js +252 -0
  36. package/src/catalog.d.ts +2 -0
  37. package/src/catalog.js +18 -746
  38. package/src/cli/command-parsers/project.js +3 -0
  39. package/src/cli/command-parsers/shared.js +1 -1
  40. package/src/cli/commands/agent.js +2 -2
  41. package/src/cli/commands/catalog/check.js +31 -0
  42. package/src/cli/commands/catalog/copy.js +59 -0
  43. package/src/cli/commands/catalog/doctor.js +248 -0
  44. package/src/cli/commands/catalog/help.js +21 -0
  45. package/src/cli/commands/catalog/list.js +52 -0
  46. package/src/cli/commands/catalog/runner.js +92 -0
  47. package/src/cli/commands/catalog/shared.js +17 -0
  48. package/src/cli/commands/catalog/show.js +134 -0
  49. package/src/cli/commands/catalog.js +30 -615
  50. package/src/cli/commands/check.js +3 -3
  51. package/src/cli/commands/doctor.js +2 -9
  52. package/src/cli/commands/generator-policy/package-info.js +162 -0
  53. package/src/cli/commands/generator-policy/payloads.js +372 -0
  54. package/src/cli/commands/generator-policy/printers.js +159 -0
  55. package/src/cli/commands/generator-policy/runner.js +81 -0
  56. package/src/cli/commands/generator-policy/shared.js +39 -0
  57. package/src/cli/commands/generator-policy.js +15 -783
  58. package/src/cli/commands/import/adopt.js +170 -0
  59. package/src/cli/commands/import/check.js +91 -0
  60. package/src/cli/commands/import/diff.js +84 -0
  61. package/src/cli/commands/import/help.js +47 -0
  62. package/src/cli/commands/import/paths.js +269 -0
  63. package/src/cli/commands/import/plan.js +292 -0
  64. package/src/cli/commands/import/refresh.js +471 -0
  65. package/src/cli/commands/import/status-history.js +196 -0
  66. package/src/cli/commands/import/workspace.js +233 -0
  67. package/src/cli/commands/import.js +33 -1732
  68. package/src/cli/commands/migrate.js +153 -0
  69. package/src/cli/commands/package/constants.js +17 -0
  70. package/src/cli/commands/package/doctor.js +240 -0
  71. package/src/cli/commands/package/help.js +27 -0
  72. package/src/cli/commands/package/lockfile.js +135 -0
  73. package/src/cli/commands/package/npm.js +97 -0
  74. package/src/cli/commands/package/reporting.js +35 -0
  75. package/src/cli/commands/package/runner.js +33 -0
  76. package/src/cli/commands/package/shared.js +9 -0
  77. package/src/cli/commands/package/update-cli.js +252 -0
  78. package/src/cli/commands/package/versions.js +35 -0
  79. package/src/cli/commands/package.js +29 -813
  80. package/src/cli/commands/query/change-plan.js +68 -0
  81. package/src/cli/commands/query/definitions.js +202 -0
  82. package/src/cli/commands/query/import-adopt.js +121 -0
  83. package/src/cli/commands/query/runner/artifacts.js +102 -0
  84. package/src/cli/commands/query/runner/boundaries.js +211 -0
  85. package/src/cli/commands/query/runner/change.js +182 -0
  86. package/src/cli/commands/query/runner/import-adopt.js +111 -0
  87. package/src/cli/commands/query/runner/index.js +31 -0
  88. package/src/cli/commands/query/runner/output.js +12 -0
  89. package/src/cli/commands/query/runner/workflow.js +241 -0
  90. package/src/cli/commands/query/runner.js +3 -0
  91. package/src/cli/commands/query/workflow-context.js +5 -0
  92. package/src/cli/commands/query/workspace.js +270 -0
  93. package/src/cli/commands/query.js +9 -1300
  94. package/src/cli/commands/source.js +3 -12
  95. package/src/cli/commands/template/baseline.js +100 -0
  96. package/src/cli/commands/template/check.js +467 -0
  97. package/src/cli/commands/template/constants.js +8 -0
  98. package/src/cli/commands/template/diagnostics.js +26 -0
  99. package/src/cli/commands/template/help.js +28 -0
  100. package/src/cli/commands/template/lifecycle.js +404 -0
  101. package/src/cli/commands/template/list-show.js +287 -0
  102. package/src/cli/commands/template/policy.js +422 -0
  103. package/src/cli/commands/template/shared.js +127 -0
  104. package/src/cli/commands/template/updates.js +352 -0
  105. package/src/cli/commands/template-runner.js +6 -6
  106. package/src/cli/commands/template.js +41 -2143
  107. package/src/cli/commands/trust.js +1 -1
  108. package/src/cli/commands/workflow.js +6 -1
  109. package/src/cli/dispatcher.js +6 -1
  110. package/src/cli/help.js +15 -14
  111. package/src/cli/migration-guidance.js +1 -1
  112. package/src/cli/output-safety.js +2 -1
  113. package/src/cli/path-normalization.js +3 -13
  114. package/src/generator/api/contracts.js +497 -0
  115. package/src/generator/api/metadata.js +221 -0
  116. package/src/generator/api/openapi.js +559 -0
  117. package/src/generator/api/schema.js +124 -0
  118. package/src/generator/api/types.d.ts +98 -0
  119. package/src/generator/api.js +3 -1195
  120. package/src/generator/context/domain-page.js +1 -1
  121. package/src/generator/context/shared/domain-sdlc.js +282 -0
  122. package/src/generator/context/shared/maintained-boundary.js +665 -0
  123. package/src/generator/context/shared/metrics.js +85 -0
  124. package/src/generator/context/shared/primitives.js +64 -0
  125. package/src/generator/context/shared/relationships.js +453 -0
  126. package/src/generator/context/shared/summaries.js +263 -0
  127. package/src/generator/context/shared/types.d.ts +207 -0
  128. package/src/generator/context/shared.d.ts +42 -0
  129. package/src/generator/context/shared.js +80 -1390
  130. package/src/generator/context/slice/core.js +397 -0
  131. package/src/generator/context/slice/sdlc.js +417 -0
  132. package/src/generator/context/slice/ui-packets.js +183 -0
  133. package/src/generator/context/slice.js +2 -859
  134. package/src/generator/context/task-mode.js +2 -2
  135. package/src/generator/registry/index.js +507 -0
  136. package/src/generator/registry.js +18 -504
  137. package/src/generator/runtime/environment/index.js +666 -0
  138. package/src/generator/runtime/environment.js +4 -666
  139. package/src/generator/runtime/runtime-check/index.js +554 -0
  140. package/src/generator/runtime/runtime-check.js +4 -554
  141. package/src/generator/runtime/shared/index.js +572 -0
  142. package/src/generator/runtime/shared.js +19 -570
  143. package/src/generator/sdlc/doc-page.js +1 -1
  144. package/src/generator/shared.d.ts +2 -0
  145. package/src/generator/surfaces/databases/lifecycle-shared.js +1 -1
  146. package/src/generator/surfaces/native/swiftui-templates/README.generated.md +1 -1
  147. package/src/generator/surfaces/shared.d.ts +3 -0
  148. package/src/generator/widget-conformance/behavior-report.js +258 -0
  149. package/src/generator/widget-conformance/checks.js +371 -0
  150. package/src/generator/widget-conformance/projection-context.js +200 -0
  151. package/src/generator/widget-conformance/report.js +166 -0
  152. package/src/generator/widget-conformance/types.d.ts +121 -0
  153. package/src/generator/widget-conformance.js +3 -824
  154. package/src/import/core/context.d.ts +3 -0
  155. package/src/import/core/context.js +5 -7
  156. package/src/import/core/contracts.d.ts +1 -0
  157. package/src/import/core/registry.d.ts +4 -0
  158. package/src/import/core/runner/candidates.js +337 -0
  159. package/src/import/core/runner/options.js +22 -0
  160. package/src/import/core/runner/reports.js +51 -0
  161. package/src/import/core/runner/run.js +79 -0
  162. package/src/import/core/runner/tracks.js +150 -0
  163. package/src/import/core/runner/ui-drafts.js +393 -0
  164. package/src/import/core/runner.js +3 -698
  165. package/src/import/core/shared/api-routes.js +221 -0
  166. package/src/import/core/shared/candidates.js +97 -0
  167. package/src/import/core/shared/files.js +177 -0
  168. package/src/import/core/shared/next-app.js +389 -0
  169. package/src/import/core/shared/types.d.ts +51 -0
  170. package/src/import/core/shared/ui-routes.js +230 -0
  171. package/src/import/core/shared.js +60 -861
  172. package/src/new-project/constants.js +128 -0
  173. package/src/new-project/create.js +90 -0
  174. package/src/new-project/json.js +28 -0
  175. package/src/new-project/metadata.js +96 -0
  176. package/src/new-project/package-spec.js +161 -0
  177. package/src/new-project/project-files.js +351 -0
  178. package/src/new-project/template-policy.js +269 -0
  179. package/src/new-project/template-resolution.js +370 -0
  180. package/src/new-project/template-snapshots.js +442 -0
  181. package/src/new-project/template-updates.js +512 -0
  182. package/src/new-project/types.d.ts +83 -0
  183. package/src/new-project.js +6 -2277
  184. package/src/parser.d.ts +87 -1
  185. package/src/parser.js +118 -0
  186. package/src/policy/review-boundaries.d.ts +15 -0
  187. package/src/project-config/index.js +591 -0
  188. package/src/project-config.js +19 -561
  189. package/src/resolver/enrich/acceptance-criterion.js +2 -0
  190. package/src/resolver/enrich/bug.js +2 -0
  191. package/src/resolver/enrich/pitch.js +2 -0
  192. package/src/resolver/enrich/requirement.js +2 -0
  193. package/src/resolver/enrich/task.js +2 -0
  194. package/src/resolver/index.js +19 -2089
  195. package/src/resolver/normalize.js +384 -1
  196. package/src/resolver/plans.js +168 -0
  197. package/src/resolver/projections-api.js +494 -0
  198. package/src/resolver/projections-db.js +133 -0
  199. package/src/resolver/projections-ui.js +317 -0
  200. package/src/resolver/shapes.js +251 -0
  201. package/src/resolver/shared.js +278 -0
  202. package/src/resolver/widgets.js +132 -0
  203. package/src/sdlc/adopt.js +6 -5
  204. package/src/sdlc/paths.js +3 -5
  205. package/src/sdlc/scaffold.js +2 -1
  206. package/src/template-trust/constants.js +62 -0
  207. package/src/template-trust/content.js +258 -0
  208. package/src/template-trust/diff.js +92 -0
  209. package/src/template-trust/policy.js +61 -0
  210. package/src/template-trust/record.js +90 -0
  211. package/src/template-trust/status.js +182 -0
  212. package/src/template-trust.js +24 -687
  213. package/src/text-helpers.d.ts +1 -0
  214. package/src/topogram-types.d.ts +69 -0
  215. package/src/validator/common.js +488 -0
  216. package/src/validator/data-model.js +237 -0
  217. package/src/validator/docs.js +167 -0
  218. package/src/validator/expressions.js +146 -1
  219. package/src/validator/index.d.ts +23 -0
  220. package/src/validator/index.js +32 -3585
  221. package/src/validator/kinds.d.ts +41 -0
  222. package/src/validator/kinds.js +2 -0
  223. package/src/validator/model-helpers.js +46 -0
  224. package/src/validator/per-kind/acceptance-criterion.js +5 -0
  225. package/src/validator/per-kind/bug.js +6 -0
  226. package/src/validator/per-kind/domain.js +15 -2
  227. package/src/validator/per-kind/pitch.js +7 -0
  228. package/src/validator/per-kind/requirement.js +5 -0
  229. package/src/validator/per-kind/task.js +7 -0
  230. package/src/validator/per-kind/widget.js +14 -0
  231. package/src/validator/projections/api-http-async.js +410 -0
  232. package/src/validator/projections/api-http-authz.js +88 -0
  233. package/src/validator/projections/api-http-core.js +205 -0
  234. package/src/validator/projections/api-http-policies.js +339 -0
  235. package/src/validator/projections/api-http-responses.js +233 -0
  236. package/src/validator/projections/api-http.js +44 -0
  237. package/src/validator/projections/db.js +353 -0
  238. package/src/validator/projections/generator-defaults.js +45 -0
  239. package/src/validator/projections/helpers.js +87 -0
  240. package/src/validator/projections/ui-helpers.js +214 -0
  241. package/src/validator/projections/ui-navigation.js +344 -0
  242. package/src/validator/projections/ui-structure.js +364 -0
  243. package/src/validator/projections/ui-widgets.js +493 -0
  244. package/src/validator/projections/ui.js +46 -0
  245. package/src/validator/registry.js +48 -1
  246. package/src/validator/utils.d.ts +20 -0
  247. package/src/validator/utils.js +115 -12
  248. package/src/widget-behavior.d.ts +1 -0
  249. package/src/workflows/import-app/api/collect.js +221 -0
  250. package/src/workflows/import-app/api/openapi.js +257 -0
  251. package/src/workflows/import-app/api/routes.js +327 -0
  252. package/src/workflows/import-app/api/sources.js +22 -0
  253. package/src/workflows/import-app/api.js +2 -797
  254. package/src/workflows/reconcile/adoption-plan/build.js +212 -0
  255. package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
  256. package/src/workflows/reconcile/adoption-plan/outputs.js +153 -0
  257. package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
  258. package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
  259. package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
  260. package/src/workflows/reconcile/adoption-plan.js +30 -740
  261. package/src/workflows/reconcile/auth/closures.js +115 -0
  262. package/src/workflows/reconcile/auth/formatters.js +142 -0
  263. package/src/workflows/reconcile/auth/inference.js +330 -0
  264. package/src/workflows/reconcile/auth/roles.js +122 -0
  265. package/src/workflows/reconcile/auth.js +35 -690
  266. package/src/workflows/reconcile/bundle-core/index.js +600 -0
  267. package/src/workflows/reconcile/bundle-core.js +12 -598
  268. package/src/workflows/reconcile/candidate-model.js +18 -2
  269. package/src/workflows/reconcile/canonical-surface.js +1 -1
  270. package/src/workflows/reconcile/impacts/adoption-plan.js +196 -0
  271. package/src/workflows/reconcile/impacts/indexes.js +105 -0
  272. package/src/workflows/reconcile/impacts/patches.js +252 -0
  273. package/src/workflows/reconcile/impacts/reports.js +80 -0
  274. package/src/workflows/reconcile/impacts.js +14 -623
  275. package/src/workflows/reconcile/renderers.js +41 -6
  276. package/src/workflows/shared.js +5 -11
  277. package/src/workspace-docs.d.ts +29 -0
  278. package/src/workspace-paths.js +328 -0
@@ -0,0 +1,233 @@
1
+ // @ts-check
2
+
3
+ import {
4
+ blockSymbolItems,
5
+ getFieldValue,
6
+ pushError,
7
+ symbolValues
8
+ } from "../utils.js";
9
+ import { resolveCapabilityContractFields } from "./helpers.js";
10
+
11
+ /**
12
+ * @param {ValidationErrors} errors
13
+ * @param {TopogramStatement} statement
14
+ * @param {TopogramFieldMap} fieldMap
15
+ * @param {TopogramRegistry} registry
16
+ * @returns {void}
17
+ */
18
+ export function validateProjectionHttpResponses(errors, statement, fieldMap, registry) {
19
+ if (statement.kind !== "projection") {
20
+ return;
21
+ }
22
+
23
+ const httpResponsesField = fieldMap.get("responses")?.[0];
24
+ if (!httpResponsesField || httpResponsesField.value.type !== "block") {
25
+ return;
26
+ }
27
+
28
+ const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
29
+ for (const entry of httpResponsesField.value.entries) {
30
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
31
+ const capabilityId = tokens[0];
32
+ const capability = registry.get(capabilityId);
33
+
34
+ if (!capability) {
35
+ pushError(errors, `Projection ${statement.id} responses references missing capability '${capabilityId}'`, entry.loc);
36
+ continue;
37
+ }
38
+ if (capability.kind !== "capability") {
39
+ pushError(errors, `Projection ${statement.id} responses must target a capability, found ${capability.kind} '${capability.id}'`, entry.loc);
40
+ }
41
+ if (!realized.has(capabilityId)) {
42
+ pushError(errors, `Projection ${statement.id} responses for '${capabilityId}' must also appear in 'realizes'`, entry.loc);
43
+ }
44
+
45
+ const directives = parseProjectionHttpResponsesDirectives(tokens.slice(1));
46
+ for (const message of directives.errors) {
47
+ pushError(errors, `Projection ${statement.id} responses for '${capabilityId}' ${message}`, entry.loc);
48
+ }
49
+
50
+ if (!directives.mode) {
51
+ pushError(errors, `Projection ${statement.id} responses for '${capabilityId}' must include 'mode'`, entry.loc);
52
+ }
53
+
54
+ const mode = directives.mode;
55
+ if (mode && !["item", "collection", "paged", "cursor"].includes(mode)) {
56
+ pushError(errors, `Projection ${statement.id} responses for '${capabilityId}' has invalid mode '${mode}'`, entry.loc);
57
+ }
58
+
59
+ const itemShapeId = directives.item;
60
+ if (mode && mode !== "item" && !itemShapeId) {
61
+ pushError(errors, `Projection ${statement.id} responses for '${capabilityId}' must include 'item' for mode '${mode}'`, entry.loc);
62
+ }
63
+ if (itemShapeId) {
64
+ const itemShape = registry.get(itemShapeId);
65
+ if (!itemShape) {
66
+ pushError(errors, `Projection ${statement.id} responses for '${capabilityId}' references missing shape '${itemShapeId}'`, entry.loc);
67
+ } else if (itemShape.kind !== "shape") {
68
+ pushError(errors, `Projection ${statement.id} responses for '${capabilityId}' must reference a shape for 'item', found ${itemShape.kind} '${itemShape.id}'`, entry.loc);
69
+ }
70
+ }
71
+
72
+ if (mode === "cursor") {
73
+ if (!directives.cursor?.requestAfter) {
74
+ pushError(errors, `Projection ${statement.id} responses for '${capabilityId}' must include 'cursor request_after <field>'`, entry.loc);
75
+ }
76
+ if (!directives.cursor?.responseNext) {
77
+ pushError(errors, `Projection ${statement.id} responses for '${capabilityId}' must include 'cursor response_next <wire_name>'`, entry.loc);
78
+ }
79
+ if (!directives.limit) {
80
+ pushError(errors, `Projection ${statement.id} responses for '${capabilityId}' must include 'limit field <field> default <n> max <n>'`, entry.loc);
81
+ }
82
+ if (!directives.sort) {
83
+ pushError(errors, `Projection ${statement.id} responses for '${capabilityId}' must include 'sort by <field> direction <asc|desc>'`, entry.loc);
84
+ }
85
+ }
86
+
87
+ if (directives.sort && !["asc", "desc"].includes(directives.sort.direction || "")) {
88
+ pushError(errors, `Projection ${statement.id} responses for '${capabilityId}' has invalid sort direction '${directives.sort.direction}'`, entry.loc);
89
+ }
90
+
91
+ if (directives.total && !["true", "false"].includes(directives.total.included || "")) {
92
+ pushError(errors, `Projection ${statement.id} responses for '${capabilityId}' has invalid total included value '${directives.total.included}'`, entry.loc);
93
+ }
94
+
95
+ if (directives.limit) {
96
+ const defaultValue = Number.parseInt(directives.limit.defaultValue || "", 10);
97
+ const maxValue = Number.parseInt(directives.limit.maxValue || "", 10);
98
+ if (!Number.isInteger(defaultValue) || !Number.isInteger(maxValue)) {
99
+ pushError(errors, `Projection ${statement.id} responses for '${capabilityId}' must use integer default/max values for 'limit'`, entry.loc);
100
+ } else if (defaultValue > maxValue) {
101
+ pushError(errors, `Projection ${statement.id} responses for '${capabilityId}' must use default <= max for 'limit'`, entry.loc);
102
+ }
103
+ }
104
+
105
+ const inputFields = resolveCapabilityContractFields(registry, capabilityId, "input");
106
+ const outputFields = resolveCapabilityContractFields(registry, capabilityId, "output");
107
+ if (directives.cursor?.requestAfter && inputFields.size > 0 && !inputFields.has(directives.cursor.requestAfter)) {
108
+ pushError(errors, `Projection ${statement.id} responses references unknown input field '${directives.cursor.requestAfter}' for cursor request_after on ${capabilityId}`, entry.loc);
109
+ }
110
+ if (directives.limit?.field && inputFields.size > 0 && !inputFields.has(directives.limit.field)) {
111
+ pushError(errors, `Projection ${statement.id} responses references unknown input field '${directives.limit.field}' for limit on ${capabilityId}`, entry.loc);
112
+ }
113
+ if (directives.sort?.field && outputFields.size > 0 && !outputFields.has(directives.sort.field)) {
114
+ pushError(errors, `Projection ${statement.id} responses references unknown output field '${directives.sort.field}' for sort on ${capabilityId}`, entry.loc);
115
+ }
116
+ }
117
+ }
118
+
119
+ /**
120
+ * @param {string[]} tokens
121
+ * @returns {{
122
+ * mode: string | null,
123
+ * item: string | null,
124
+ * cursor: { requestAfter: string | null, responseNext: string | null, responsePrev: string | null } | null,
125
+ * limit: { field: string | null, defaultValue: string | null, maxValue: string | null } | null,
126
+ * sort: { field: string | null, direction: string | null } | null,
127
+ * total: { included: string | null } | null,
128
+ * errors: string[]
129
+ * }}
130
+ */
131
+ export function parseProjectionHttpResponsesDirectives(tokens) {
132
+ /** @type {{
133
+ * mode: string | null,
134
+ * item: string | null,
135
+ * cursor: { requestAfter: string | null, responseNext: string | null, responsePrev: string | null } | null,
136
+ * limit: { field: string | null, defaultValue: string | null, maxValue: string | null } | null,
137
+ * sort: { field: string | null, direction: string | null } | null,
138
+ * total: { included: string | null } | null,
139
+ * errors: string[]
140
+ * }}
141
+ */
142
+ const result = {
143
+ mode: null,
144
+ item: null,
145
+ cursor: null,
146
+ limit: null,
147
+ sort: null,
148
+ total: null,
149
+ errors: []
150
+ };
151
+
152
+ for (let i = 0; i < tokens.length; i += 1) {
153
+ const token = tokens[i];
154
+ if (token === "mode") {
155
+ result.mode = tokens[i + 1] || null;
156
+ if (!tokens[i + 1]) {
157
+ result.errors.push("is missing a value for 'mode'");
158
+ }
159
+ i += 1;
160
+ continue;
161
+ }
162
+ if (token === "item") {
163
+ result.item = tokens[i + 1] || null;
164
+ if (!tokens[i + 1]) {
165
+ result.errors.push("is missing a value for 'item'");
166
+ }
167
+ i += 1;
168
+ continue;
169
+ }
170
+ if (token === "cursor") {
171
+ const requestKeyword = tokens[i + 1];
172
+ const requestField = tokens[i + 2];
173
+ const responseKeyword = tokens[i + 3];
174
+ const responseNext = tokens[i + 4];
175
+ let responsePrev = null;
176
+ let consumed = 4;
177
+ if (tokens[i + 5] === "response_prev") {
178
+ responsePrev = tokens[i + 6] || null;
179
+ consumed = 6;
180
+ }
181
+ result.cursor = {
182
+ requestAfter: requestKeyword === "request_after" ? requestField : null,
183
+ responseNext: responseKeyword === "response_next" ? responseNext : null,
184
+ responsePrev
185
+ };
186
+ if (requestKeyword !== "request_after") {
187
+ result.errors.push("must use 'cursor request_after <field>'");
188
+ }
189
+ if (responseKeyword !== "response_next") {
190
+ result.errors.push("must use 'cursor response_next <wire_name>'");
191
+ }
192
+ i += consumed;
193
+ continue;
194
+ }
195
+ if (token === "limit") {
196
+ result.limit = {
197
+ field: tokens[i + 1] === "field" ? tokens[i + 2] || null : null,
198
+ defaultValue: tokens[i + 3] === "default" ? tokens[i + 4] || null : null,
199
+ maxValue: tokens[i + 5] === "max" ? tokens[i + 6] || null : null
200
+ };
201
+ if (tokens[i + 1] !== "field" || tokens[i + 3] !== "default" || tokens[i + 5] !== "max") {
202
+ result.errors.push("must use 'limit field <field> default <n> max <n>'");
203
+ }
204
+ i += 6;
205
+ continue;
206
+ }
207
+ if (token === "sort") {
208
+ result.sort = {
209
+ field: tokens[i + 1] === "by" ? tokens[i + 2] || null : null,
210
+ direction: tokens[i + 3] === "direction" ? tokens[i + 4] || null : null
211
+ };
212
+ if (tokens[i + 1] !== "by" || tokens[i + 3] !== "direction") {
213
+ result.errors.push("must use 'sort by <field> direction <asc|desc>'");
214
+ }
215
+ i += 4;
216
+ continue;
217
+ }
218
+ if (token === "total") {
219
+ result.total = {
220
+ included: tokens[i + 1] === "included" ? tokens[i + 2] || null : null
221
+ };
222
+ if (tokens[i + 1] !== "included") {
223
+ result.errors.push("must use 'total included <true|false>'");
224
+ }
225
+ i += 2;
226
+ continue;
227
+ }
228
+
229
+ result.errors.push(`has unknown directive '${token}'`);
230
+ }
231
+
232
+ return result;
233
+ }
@@ -0,0 +1,44 @@
1
+ // @ts-check
2
+
3
+ import {
4
+ validateProjectionHttp,
5
+ validateProjectionHttpErrors,
6
+ validateProjectionHttpFields
7
+ } from "./api-http-core.js";
8
+ import { validateProjectionHttpResponses } from "./api-http-responses.js";
9
+ import {
10
+ validateProjectionHttpPreconditions,
11
+ validateProjectionHttpIdempotency,
12
+ validateProjectionHttpCache,
13
+ validateProjectionHttpDelete
14
+ } from "./api-http-policies.js";
15
+ import {
16
+ validateProjectionHttpAsync,
17
+ validateProjectionHttpStatus,
18
+ validateProjectionHttpDownload,
19
+ validateProjectionHttpCallbacks
20
+ } from "./api-http-async.js";
21
+ import { validateProjectionHttpAuthz } from "./api-http-authz.js";
22
+
23
+ /**
24
+ * @param {ValidationErrors} errors
25
+ * @param {TopogramStatement} statement
26
+ * @param {TopogramFieldMap} fieldMap
27
+ * @param {TopogramRegistry} registry
28
+ * @returns {void}
29
+ */
30
+ export function validateApiHttpProjection(errors, statement, fieldMap, registry) {
31
+ validateProjectionHttp(errors, statement, fieldMap, registry);
32
+ validateProjectionHttpErrors(errors, statement, fieldMap, registry);
33
+ validateProjectionHttpFields(errors, statement, fieldMap, registry);
34
+ validateProjectionHttpResponses(errors, statement, fieldMap, registry);
35
+ validateProjectionHttpPreconditions(errors, statement, fieldMap, registry);
36
+ validateProjectionHttpIdempotency(errors, statement, fieldMap, registry);
37
+ validateProjectionHttpCache(errors, statement, fieldMap, registry);
38
+ validateProjectionHttpDelete(errors, statement, fieldMap, registry);
39
+ validateProjectionHttpAsync(errors, statement, fieldMap, registry);
40
+ validateProjectionHttpStatus(errors, statement, fieldMap, registry);
41
+ validateProjectionHttpDownload(errors, statement, fieldMap, registry);
42
+ validateProjectionHttpAuthz(errors, statement, fieldMap, registry);
43
+ validateProjectionHttpCallbacks(errors, statement, fieldMap, registry);
44
+ }
@@ -0,0 +1,353 @@
1
+ // @ts-check
2
+
3
+ import {
4
+ blockSymbolItems,
5
+ getFieldValue,
6
+ pushError,
7
+ symbolValues
8
+ } from "../utils.js";
9
+ import { statementFieldNames } from "../model-helpers.js";
10
+ import { parseUiDirectiveMap } from "./helpers.js";
11
+
12
+ /**
13
+ * @param {ValidationErrors} errors
14
+ * @param {TopogramStatement} statement
15
+ * @param {TopogramFieldMap} fieldMap
16
+ * @param {TopogramRegistry} registry
17
+ * @returns {void}
18
+ */
19
+ function validateProjectionDbTables(errors, statement, fieldMap, registry) {
20
+ if (statement.kind !== "projection") {
21
+ return;
22
+ }
23
+
24
+ const dbTablesField = fieldMap.get("tables")?.[0];
25
+ if (!dbTablesField || dbTablesField.value.type !== "block") {
26
+ return;
27
+ }
28
+
29
+ const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
30
+ const seenTables = new Set();
31
+ for (const entry of dbTablesField.value.entries) {
32
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
33
+ const [entityId, tableKeyword, tableName] = tokens;
34
+ const entity = registry.get(entityId);
35
+
36
+ if (!entity) {
37
+ pushError(errors, `Projection ${statement.id} tables references missing entity '${entityId}'`, entry.loc);
38
+ continue;
39
+ }
40
+ if (entity.kind !== "entity") {
41
+ pushError(errors, `Projection ${statement.id} tables must target an entity, found ${entity.kind} '${entity.id}'`, entry.loc);
42
+ }
43
+ if (!realized.has(entityId)) {
44
+ pushError(errors, `Projection ${statement.id} tables entity '${entityId}' must also appear in 'realizes'`, entry.loc);
45
+ }
46
+ if (tableKeyword !== "table") {
47
+ pushError(errors, `Projection ${statement.id} tables for '${entityId}' must use 'table'`, entry.loc);
48
+ }
49
+ if (!tableName) {
50
+ pushError(errors, `Projection ${statement.id} tables for '${entityId}' must include a table name`, entry.loc);
51
+ } else if (seenTables.has(tableName)) {
52
+ pushError(errors, `Projection ${statement.id} tables has duplicate table name '${tableName}'`, entry.loc);
53
+ }
54
+ seenTables.add(tableName);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * @param {ValidationErrors} errors
60
+ * @param {TopogramStatement} statement
61
+ * @param {TopogramFieldMap} fieldMap
62
+ * @param {TopogramRegistry} registry
63
+ * @returns {void}
64
+ */
65
+ function validateProjectionDbColumns(errors, statement, fieldMap, registry) {
66
+ if (statement.kind !== "projection") {
67
+ return;
68
+ }
69
+
70
+ const dbColumnsField = fieldMap.get("columns")?.[0];
71
+ if (!dbColumnsField || dbColumnsField.value.type !== "block") {
72
+ return;
73
+ }
74
+
75
+ const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
76
+ for (const entry of dbColumnsField.value.entries) {
77
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
78
+ const [entityId, fieldKeyword, fieldName, columnKeyword, columnName] = tokens;
79
+ const entity = registry.get(entityId);
80
+
81
+ if (!entity) {
82
+ pushError(errors, `Projection ${statement.id} columns references missing entity '${entityId}'`, entry.loc);
83
+ continue;
84
+ }
85
+ if (entity.kind !== "entity") {
86
+ pushError(errors, `Projection ${statement.id} columns must target an entity, found ${entity.kind} '${entity.id}'`, entry.loc);
87
+ }
88
+ if (!realized.has(entityId)) {
89
+ pushError(errors, `Projection ${statement.id} columns entity '${entityId}' must also appear in 'realizes'`, entry.loc);
90
+ }
91
+ if (fieldKeyword !== "field") {
92
+ pushError(errors, `Projection ${statement.id} columns for '${entityId}' must use 'field'`, entry.loc);
93
+ }
94
+ if (columnKeyword !== "column") {
95
+ pushError(errors, `Projection ${statement.id} columns for '${entityId}' must use 'column'`, entry.loc);
96
+ }
97
+ const entityFieldNames = new Set(statementFieldNames(entity));
98
+ if (fieldName && entityFieldNames.size > 0 && !entityFieldNames.has(fieldName)) {
99
+ pushError(errors, `Projection ${statement.id} columns references unknown field '${fieldName}' on ${entityId}`, entry.loc);
100
+ }
101
+ if (!columnName) {
102
+ pushError(errors, `Projection ${statement.id} columns for '${entityId}.${fieldName}' must include a column name`, entry.loc);
103
+ }
104
+ }
105
+ }
106
+
107
+ /**
108
+ * @param {ValidationErrors} errors
109
+ * @param {TopogramStatement} statement
110
+ * @param {TopogramFieldMap} fieldMap
111
+ * @param {TopogramRegistry} registry
112
+ * @returns {void}
113
+ */
114
+ function validateProjectionDbKeys(errors, statement, fieldMap, registry) {
115
+ if (statement.kind !== "projection") {
116
+ return;
117
+ }
118
+
119
+ const dbKeysField = fieldMap.get("keys")?.[0];
120
+ if (!dbKeysField || dbKeysField.value.type !== "block") {
121
+ return;
122
+ }
123
+
124
+ const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
125
+ for (const entry of dbKeysField.value.entries) {
126
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
127
+ const [entityId, keyType] = tokens;
128
+ const entity = registry.get(entityId);
129
+
130
+ if (!entity) {
131
+ pushError(errors, `Projection ${statement.id} keys references missing entity '${entityId}'`, entry.loc);
132
+ continue;
133
+ }
134
+ if (entity.kind !== "entity") {
135
+ pushError(errors, `Projection ${statement.id} keys must target an entity, found ${entity.kind} '${entity.id}'`, entry.loc);
136
+ }
137
+ if (!realized.has(entityId)) {
138
+ pushError(errors, `Projection ${statement.id} keys entity '${entityId}' must also appear in 'realizes'`, entry.loc);
139
+ }
140
+ if (!["primary", "unique"].includes(keyType || "")) {
141
+ pushError(errors, `Projection ${statement.id} keys for '${entityId}' has invalid key type '${keyType}'`, entry.loc);
142
+ }
143
+ const fieldList = entry.items[2];
144
+ if (!fieldList || fieldList.type !== "list" || fieldList.items.length === 0) {
145
+ pushError(errors, `Projection ${statement.id} keys for '${entityId}' must include a non-empty field list`, entry.loc);
146
+ continue;
147
+ }
148
+ const entityFieldNames = new Set(statementFieldNames(entity));
149
+ for (const item of fieldList.items) {
150
+ if (item.type === "symbol" && entityFieldNames.size > 0 && !entityFieldNames.has(item.value)) {
151
+ pushError(errors, `Projection ${statement.id} keys references unknown field '${item.value}' on ${entityId}`, item.loc);
152
+ }
153
+ }
154
+ }
155
+ }
156
+
157
+ /**
158
+ * @param {ValidationErrors} errors
159
+ * @param {TopogramStatement} statement
160
+ * @param {TopogramFieldMap} fieldMap
161
+ * @param {TopogramRegistry} registry
162
+ * @returns {void}
163
+ */
164
+ function validateProjectionDbIndexes(errors, statement, fieldMap, registry) {
165
+ if (statement.kind !== "projection") {
166
+ return;
167
+ }
168
+
169
+ const dbIndexesField = fieldMap.get("indexes")?.[0];
170
+ if (!dbIndexesField || dbIndexesField.value.type !== "block") {
171
+ return;
172
+ }
173
+
174
+ const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
175
+ for (const entry of dbIndexesField.value.entries) {
176
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
177
+ const [entityId, indexType] = tokens;
178
+ const entity = registry.get(entityId);
179
+
180
+ if (!entity) {
181
+ pushError(errors, `Projection ${statement.id} indexes references missing entity '${entityId}'`, entry.loc);
182
+ continue;
183
+ }
184
+ if (entity.kind !== "entity") {
185
+ pushError(errors, `Projection ${statement.id} indexes must target an entity, found ${entity.kind} '${entity.id}'`, entry.loc);
186
+ }
187
+ if (!realized.has(entityId)) {
188
+ pushError(errors, `Projection ${statement.id} indexes entity '${entityId}' must also appear in 'realizes'`, entry.loc);
189
+ }
190
+ if (!["index", "unique"].includes(indexType || "")) {
191
+ pushError(errors, `Projection ${statement.id} indexes for '${entityId}' has invalid index type '${indexType}'`, entry.loc);
192
+ }
193
+ const fieldList = entry.items[2];
194
+ if (!fieldList || fieldList.type !== "list" || fieldList.items.length === 0) {
195
+ pushError(errors, `Projection ${statement.id} indexes for '${entityId}' must include a non-empty field list`, entry.loc);
196
+ continue;
197
+ }
198
+ const entityFieldNames = new Set(statementFieldNames(entity));
199
+ for (const item of fieldList.items) {
200
+ if (item.type === "symbol" && entityFieldNames.size > 0 && !entityFieldNames.has(item.value)) {
201
+ pushError(errors, `Projection ${statement.id} indexes references unknown field '${item.value}' on ${entityId}`, item.loc);
202
+ }
203
+ }
204
+ }
205
+ }
206
+
207
+ /**
208
+ * @param {ValidationErrors} errors
209
+ * @param {TopogramStatement} statement
210
+ * @param {TopogramFieldMap} fieldMap
211
+ * @param {TopogramRegistry} registry
212
+ * @returns {void}
213
+ */
214
+ function validateProjectionDbRelations(errors, statement, fieldMap, registry) {
215
+ if (statement.kind !== "projection") {
216
+ return;
217
+ }
218
+
219
+ const dbRelationsField = fieldMap.get("relations")?.[0];
220
+ if (!dbRelationsField || dbRelationsField.value.type !== "block") {
221
+ return;
222
+ }
223
+
224
+ const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
225
+ for (const entry of dbRelationsField.value.entries) {
226
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
227
+ const [entityId, relationType, fieldName, referencesKeyword, targetRef, onDeleteKeyword, onDeleteValue] = tokens;
228
+ const entity = registry.get(entityId);
229
+
230
+ if (!entity) {
231
+ pushError(errors, `Projection ${statement.id} relations references missing entity '${entityId}'`, entry.loc);
232
+ continue;
233
+ }
234
+ if (entity.kind !== "entity") {
235
+ pushError(errors, `Projection ${statement.id} relations must target an entity, found ${entity.kind} '${entity.id}'`, entry.loc);
236
+ }
237
+ if (!realized.has(entityId)) {
238
+ pushError(errors, `Projection ${statement.id} relations entity '${entityId}' must also appear in 'realizes'`, entry.loc);
239
+ }
240
+ if (relationType !== "foreign_key") {
241
+ pushError(errors, `Projection ${statement.id} relations for '${entityId}' must use 'foreign_key'`, entry.loc);
242
+ }
243
+ if (referencesKeyword !== "references") {
244
+ pushError(errors, `Projection ${statement.id} relations for '${entityId}' must use 'references'`, entry.loc);
245
+ }
246
+ if (onDeleteKeyword && onDeleteKeyword !== "on_delete") {
247
+ pushError(errors, `Projection ${statement.id} relations for '${entityId}' has unexpected token '${onDeleteKeyword}'`, entry.loc);
248
+ }
249
+ if (onDeleteValue && !["cascade", "restrict", "set_null", "no_action"].includes(onDeleteValue)) {
250
+ pushError(errors, `Projection ${statement.id} relations for '${entityId}' has invalid on_delete '${onDeleteValue}'`, entry.loc);
251
+ }
252
+ const entityFieldNames = new Set(statementFieldNames(entity));
253
+ if (fieldName && entityFieldNames.size > 0 && !entityFieldNames.has(fieldName)) {
254
+ pushError(errors, `Projection ${statement.id} relations references unknown field '${fieldName}' on ${entityId}`, entry.loc);
255
+ }
256
+ const [targetEntityId, targetFieldName] = (targetRef || "").split(".");
257
+ const targetEntity = registry.get(targetEntityId);
258
+ if (!targetEntity) {
259
+ pushError(errors, `Projection ${statement.id} relations references missing target entity '${targetEntityId}'`, entry.loc);
260
+ continue;
261
+ }
262
+ if (targetEntity.kind !== "entity") {
263
+ pushError(errors, `Projection ${statement.id} relations must reference an entity target, found ${targetEntity.kind} '${targetEntity.id}'`, entry.loc);
264
+ }
265
+ const targetFieldNames = new Set(statementFieldNames(targetEntity));
266
+ if (targetFieldName && targetFieldNames.size > 0 && !targetFieldNames.has(targetFieldName)) {
267
+ pushError(errors, `Projection ${statement.id} relations references unknown target field '${targetFieldName}' on ${targetEntityId}`, entry.loc);
268
+ }
269
+ }
270
+ }
271
+
272
+ /**
273
+ * @param {ValidationErrors} errors
274
+ * @param {TopogramStatement} statement
275
+ * @param {TopogramFieldMap} fieldMap
276
+ * @param {TopogramRegistry} registry
277
+ * @returns {void}
278
+ */
279
+ function validateProjectionDbLifecycle(errors, statement, fieldMap, registry) {
280
+ if (statement.kind !== "projection") {
281
+ return;
282
+ }
283
+
284
+ const dbLifecycleField = fieldMap.get("lifecycle")?.[0];
285
+ if (!dbLifecycleField || dbLifecycleField.value.type !== "block") {
286
+ return;
287
+ }
288
+
289
+ const realized = new Set(symbolValues(getFieldValue(statement, "realizes")));
290
+ for (const entry of dbLifecycleField.value.entries) {
291
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
292
+ const [entityId, lifecycleType] = tokens;
293
+ const entity = registry.get(entityId);
294
+
295
+ if (!entity) {
296
+ pushError(errors, `Projection ${statement.id} lifecycle references missing entity '${entityId}'`, entry.loc);
297
+ continue;
298
+ }
299
+ if (entity.kind !== "entity") {
300
+ pushError(errors, `Projection ${statement.id} lifecycle must target an entity, found ${entity.kind} '${entity.id}'`, entry.loc);
301
+ }
302
+ if (!realized.has(entityId)) {
303
+ pushError(errors, `Projection ${statement.id} lifecycle entity '${entityId}' must also appear in 'realizes'`, entry.loc);
304
+ }
305
+
306
+ const directives = parseUiDirectiveMap(tokens, 2, errors, statement, entry, `lifecycle for '${entityId}'`);
307
+ if (!["soft_delete", "timestamps"].includes(lifecycleType || "")) {
308
+ pushError(errors, `Projection ${statement.id} lifecycle for '${entityId}' has invalid lifecycle '${lifecycleType}'`, entry.loc);
309
+ continue;
310
+ }
311
+
312
+ const entityFieldNames = new Set(statementFieldNames(entity));
313
+ if (lifecycleType === "soft_delete") {
314
+ for (const requiredKey of ["field", "value"]) {
315
+ if (!directives.has(requiredKey)) {
316
+ pushError(errors, `Projection ${statement.id} lifecycle for '${entityId}' must include '${requiredKey}' for soft_delete`, entry.loc);
317
+ }
318
+ }
319
+ const fieldName = directives.get("field");
320
+ if (fieldName && entityFieldNames.size > 0 && !entityFieldNames.has(fieldName)) {
321
+ pushError(errors, `Projection ${statement.id} lifecycle references unknown field '${fieldName}' on ${entityId}`, entry.loc);
322
+ }
323
+ }
324
+
325
+ if (lifecycleType === "timestamps") {
326
+ for (const requiredKey of ["created_at", "updated_at"]) {
327
+ if (!directives.has(requiredKey)) {
328
+ pushError(errors, `Projection ${statement.id} lifecycle for '${entityId}' must include '${requiredKey}' for timestamps`, entry.loc);
329
+ }
330
+ const fieldName = directives.get(requiredKey);
331
+ if (fieldName && entityFieldNames.size > 0 && !entityFieldNames.has(fieldName)) {
332
+ pushError(errors, `Projection ${statement.id} lifecycle references unknown field '${fieldName}' on ${entityId}`, entry.loc);
333
+ }
334
+ }
335
+ }
336
+ }
337
+ }
338
+
339
+ /**
340
+ * @param {ValidationErrors} errors
341
+ * @param {TopogramStatement} statement
342
+ * @param {TopogramFieldMap} fieldMap
343
+ * @param {TopogramRegistry} registry
344
+ * @returns {void}
345
+ */
346
+ export function validateDbProjection(errors, statement, fieldMap, registry) {
347
+ validateProjectionDbTables(errors, statement, fieldMap, registry);
348
+ validateProjectionDbColumns(errors, statement, fieldMap, registry);
349
+ validateProjectionDbKeys(errors, statement, fieldMap, registry);
350
+ validateProjectionDbIndexes(errors, statement, fieldMap, registry);
351
+ validateProjectionDbRelations(errors, statement, fieldMap, registry);
352
+ validateProjectionDbLifecycle(errors, statement, fieldMap, registry);
353
+ }
@@ -0,0 +1,45 @@
1
+ // @ts-check
2
+
3
+ import {
4
+ blockSymbolItems,
5
+ pushError
6
+ } from "../utils.js";
7
+
8
+ /**
9
+ * @param {ValidationErrors} errors
10
+ * @param {TopogramStatement} statement
11
+ * @param {TopogramFieldMap} fieldMap
12
+ * @returns {void}
13
+ */
14
+ export function validateProjectionGeneratorDefaults(errors, statement, fieldMap) {
15
+ if (statement.kind !== "projection") {
16
+ return;
17
+ }
18
+
19
+ const generatorField = fieldMap.get("generator_defaults")?.[0];
20
+ if (!generatorField || generatorField.value.type !== "block") {
21
+ return;
22
+ }
23
+
24
+ for (const entry of generatorField.value.entries) {
25
+ const tokens = blockSymbolItems(entry).map((item) => item.value);
26
+ const [key, value] = tokens;
27
+ if (!["profile", "language", "styling"].includes(key || "")) {
28
+ pushError(errors, `Projection ${statement.id} generator_defaults has unknown key '${key}'`, entry.loc);
29
+ continue;
30
+ }
31
+ if (!value) {
32
+ pushError(errors, `Projection ${statement.id} generator_defaults is missing a value for '${key}'`, entry.loc);
33
+ continue;
34
+ }
35
+ if (key === "profile" && !["vanilla", "sveltekit", "react", "swiftui", "postgres_sql", "sqlite_sql", "prisma", "drizzle"].includes(value)) {
36
+ pushError(errors, `Projection ${statement.id} generator_defaults has unsupported profile '${value}'`, entry.loc);
37
+ }
38
+ if (key === "language" && !["typescript", "javascript", "swift", "sql"].includes(value)) {
39
+ pushError(errors, `Projection ${statement.id} generator_defaults has unsupported language '${value}'`, entry.loc);
40
+ }
41
+ if (key === "styling" && !["tailwind", "css"].includes(value)) {
42
+ pushError(errors, `Projection ${statement.id} generator_defaults has unsupported styling '${value}'`, entry.loc);
43
+ }
44
+ }
45
+ }