@topogram/cli 0.3.64 → 0.3.65

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 (245) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +703 -0
  3. package/src/adoption/plan.js +12 -703
  4. package/src/agent-ops/query-builders/auth.js +375 -0
  5. package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
  6. package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
  7. package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
  8. package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
  9. package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
  10. package/src/agent-ops/query-builders/change-risk.js +25 -0
  11. package/src/agent-ops/query-builders/common.js +149 -0
  12. package/src/agent-ops/query-builders/maintained-risk.js +539 -0
  13. package/src/agent-ops/query-builders/maintained-shared.js +120 -0
  14. package/src/agent-ops/query-builders/multi-agent.js +547 -0
  15. package/src/agent-ops/query-builders/projection-impacts.js +514 -0
  16. package/src/agent-ops/query-builders/work-packets.js +417 -0
  17. package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
  18. package/src/agent-ops/query-builders/workflow-context.js +398 -0
  19. package/src/agent-ops/query-builders/workflow-presets-core.js +676 -0
  20. package/src/agent-ops/query-builders/workflow-presets.js +341 -0
  21. package/src/agent-ops/query-builders.d.ts +26 -26
  22. package/src/agent-ops/query-builders.js +42 -5021
  23. package/src/catalog/constants.js +10 -0
  24. package/src/catalog/copy.js +60 -0
  25. package/src/catalog/diagnostics.js +15 -0
  26. package/src/catalog/entries.js +42 -0
  27. package/src/catalog/files.js +67 -0
  28. package/src/catalog/provenance.js +122 -0
  29. package/src/catalog/source.js +150 -0
  30. package/src/catalog/validation.js +252 -0
  31. package/src/catalog.d.ts +2 -0
  32. package/src/catalog.js +18 -746
  33. package/src/cli/commands/catalog/check.js +31 -0
  34. package/src/cli/commands/catalog/copy.js +59 -0
  35. package/src/cli/commands/catalog/doctor.js +248 -0
  36. package/src/cli/commands/catalog/help.js +21 -0
  37. package/src/cli/commands/catalog/list.js +52 -0
  38. package/src/cli/commands/catalog/runner.js +92 -0
  39. package/src/cli/commands/catalog/shared.js +17 -0
  40. package/src/cli/commands/catalog/show.js +134 -0
  41. package/src/cli/commands/catalog.js +30 -615
  42. package/src/cli/commands/generator-policy/package-info.js +162 -0
  43. package/src/cli/commands/generator-policy/payloads.js +372 -0
  44. package/src/cli/commands/generator-policy/printers.js +159 -0
  45. package/src/cli/commands/generator-policy/runner.js +81 -0
  46. package/src/cli/commands/generator-policy/shared.js +39 -0
  47. package/src/cli/commands/generator-policy.js +15 -783
  48. package/src/cli/commands/import/adopt.js +170 -0
  49. package/src/cli/commands/import/check.js +91 -0
  50. package/src/cli/commands/import/diff.js +84 -0
  51. package/src/cli/commands/import/help.js +47 -0
  52. package/src/cli/commands/import/paths.js +277 -0
  53. package/src/cli/commands/import/plan.js +284 -0
  54. package/src/cli/commands/import/refresh.js +470 -0
  55. package/src/cli/commands/import/status-history.js +196 -0
  56. package/src/cli/commands/import/workspace.js +230 -0
  57. package/src/cli/commands/import.js +33 -1732
  58. package/src/cli/commands/package/constants.js +17 -0
  59. package/src/cli/commands/package/doctor.js +240 -0
  60. package/src/cli/commands/package/help.js +27 -0
  61. package/src/cli/commands/package/lockfile.js +135 -0
  62. package/src/cli/commands/package/npm.js +97 -0
  63. package/src/cli/commands/package/reporting.js +35 -0
  64. package/src/cli/commands/package/runner.js +33 -0
  65. package/src/cli/commands/package/shared.js +9 -0
  66. package/src/cli/commands/package/update-cli.js +252 -0
  67. package/src/cli/commands/package/versions.js +35 -0
  68. package/src/cli/commands/package.js +29 -813
  69. package/src/cli/commands/query/change-plan.js +68 -0
  70. package/src/cli/commands/query/definitions.js +202 -0
  71. package/src/cli/commands/query/import-adopt.js +121 -0
  72. package/src/cli/commands/query/runner/artifacts.js +102 -0
  73. package/src/cli/commands/query/runner/boundaries.js +211 -0
  74. package/src/cli/commands/query/runner/change.js +182 -0
  75. package/src/cli/commands/query/runner/import-adopt.js +111 -0
  76. package/src/cli/commands/query/runner/index.js +31 -0
  77. package/src/cli/commands/query/runner/output.js +12 -0
  78. package/src/cli/commands/query/runner/workflow.js +241 -0
  79. package/src/cli/commands/query/runner.js +3 -0
  80. package/src/cli/commands/query/workflow-context.js +5 -0
  81. package/src/cli/commands/query/workspace.js +274 -0
  82. package/src/cli/commands/query.js +9 -1300
  83. package/src/cli/commands/template/baseline.js +100 -0
  84. package/src/cli/commands/template/check.js +466 -0
  85. package/src/cli/commands/template/constants.js +8 -0
  86. package/src/cli/commands/template/diagnostics.js +26 -0
  87. package/src/cli/commands/template/help.js +28 -0
  88. package/src/cli/commands/template/lifecycle.js +404 -0
  89. package/src/cli/commands/template/list-show.js +287 -0
  90. package/src/cli/commands/template/policy.js +422 -0
  91. package/src/cli/commands/template/shared.js +127 -0
  92. package/src/cli/commands/template/updates.js +352 -0
  93. package/src/cli/commands/template.js +41 -2143
  94. package/src/generator/api/contracts.js +497 -0
  95. package/src/generator/api/metadata.js +221 -0
  96. package/src/generator/api/openapi.js +559 -0
  97. package/src/generator/api/schema.js +124 -0
  98. package/src/generator/api/types.d.ts +98 -0
  99. package/src/generator/api.js +3 -1195
  100. package/src/generator/context/shared/domain-sdlc.js +282 -0
  101. package/src/generator/context/shared/maintained-boundary.js +665 -0
  102. package/src/generator/context/shared/metrics.js +85 -0
  103. package/src/generator/context/shared/primitives.js +64 -0
  104. package/src/generator/context/shared/relationships.js +453 -0
  105. package/src/generator/context/shared/summaries.js +263 -0
  106. package/src/generator/context/shared/types.d.ts +207 -0
  107. package/src/generator/context/shared.d.ts +42 -0
  108. package/src/generator/context/shared.js +80 -1390
  109. package/src/generator/context/slice/core.js +397 -0
  110. package/src/generator/context/slice/sdlc.js +417 -0
  111. package/src/generator/context/slice/ui-packets.js +183 -0
  112. package/src/generator/context/slice.js +2 -859
  113. package/src/generator/registry/index.js +507 -0
  114. package/src/generator/registry.js +18 -504
  115. package/src/generator/runtime/environment/index.js +666 -0
  116. package/src/generator/runtime/environment.js +4 -666
  117. package/src/generator/runtime/runtime-check/index.js +554 -0
  118. package/src/generator/runtime/runtime-check.js +4 -554
  119. package/src/generator/runtime/shared/index.js +572 -0
  120. package/src/generator/runtime/shared.js +19 -570
  121. package/src/generator/shared.d.ts +2 -0
  122. package/src/generator/surfaces/shared.d.ts +3 -0
  123. package/src/generator/widget-conformance/behavior-report.js +258 -0
  124. package/src/generator/widget-conformance/checks.js +371 -0
  125. package/src/generator/widget-conformance/projection-context.js +200 -0
  126. package/src/generator/widget-conformance/report.js +166 -0
  127. package/src/generator/widget-conformance/types.d.ts +121 -0
  128. package/src/generator/widget-conformance.js +3 -824
  129. package/src/import/core/context.d.ts +3 -0
  130. package/src/import/core/contracts.d.ts +1 -0
  131. package/src/import/core/registry.d.ts +4 -0
  132. package/src/import/core/runner/candidates.js +217 -0
  133. package/src/import/core/runner/options.js +22 -0
  134. package/src/import/core/runner/reports.js +50 -0
  135. package/src/import/core/runner/run.js +79 -0
  136. package/src/import/core/runner/tracks.js +150 -0
  137. package/src/import/core/runner/ui-drafts.js +337 -0
  138. package/src/import/core/runner.js +3 -698
  139. package/src/import/core/shared/api-routes.js +221 -0
  140. package/src/import/core/shared/candidates.js +97 -0
  141. package/src/import/core/shared/files.js +177 -0
  142. package/src/import/core/shared/next-app.js +389 -0
  143. package/src/import/core/shared/types.d.ts +51 -0
  144. package/src/import/core/shared/ui-routes.js +230 -0
  145. package/src/import/core/shared.js +60 -861
  146. package/src/new-project/constants.js +128 -0
  147. package/src/new-project/create.js +83 -0
  148. package/src/new-project/json.js +28 -0
  149. package/src/new-project/metadata.js +96 -0
  150. package/src/new-project/package-spec.js +161 -0
  151. package/src/new-project/project-files.js +348 -0
  152. package/src/new-project/template-policy.js +269 -0
  153. package/src/new-project/template-resolution.js +368 -0
  154. package/src/new-project/template-snapshots.js +430 -0
  155. package/src/new-project/template-updates.js +512 -0
  156. package/src/new-project/types.d.ts +83 -0
  157. package/src/new-project.js +6 -2277
  158. package/src/parser.d.ts +87 -1
  159. package/src/parser.js +118 -0
  160. package/src/policy/review-boundaries.d.ts +15 -0
  161. package/src/project-config/index.js +564 -0
  162. package/src/project-config.js +19 -561
  163. package/src/resolver/enrich/acceptance-criterion.js +2 -0
  164. package/src/resolver/enrich/bug.js +2 -0
  165. package/src/resolver/enrich/pitch.js +2 -0
  166. package/src/resolver/enrich/requirement.js +2 -0
  167. package/src/resolver/enrich/task.js +2 -0
  168. package/src/resolver/index.js +19 -2089
  169. package/src/resolver/normalize.js +384 -1
  170. package/src/resolver/plans.js +168 -0
  171. package/src/resolver/projections-api.js +494 -0
  172. package/src/resolver/projections-db.js +133 -0
  173. package/src/resolver/projections-ui.js +317 -0
  174. package/src/resolver/shapes.js +251 -0
  175. package/src/resolver/shared.js +278 -0
  176. package/src/resolver/widgets.js +132 -0
  177. package/src/template-trust/constants.js +62 -0
  178. package/src/template-trust/content.js +258 -0
  179. package/src/template-trust/diff.js +92 -0
  180. package/src/template-trust/policy.js +61 -0
  181. package/src/template-trust/record.js +90 -0
  182. package/src/template-trust/status.js +182 -0
  183. package/src/template-trust.js +24 -687
  184. package/src/text-helpers.d.ts +1 -0
  185. package/src/topogram-types.d.ts +69 -0
  186. package/src/validator/common.js +488 -0
  187. package/src/validator/data-model.js +237 -0
  188. package/src/validator/docs.js +167 -0
  189. package/src/validator/expressions.js +146 -1
  190. package/src/validator/index.d.ts +23 -0
  191. package/src/validator/index.js +32 -3585
  192. package/src/validator/kinds.d.ts +41 -0
  193. package/src/validator/kinds.js +2 -0
  194. package/src/validator/model-helpers.js +46 -0
  195. package/src/validator/per-kind/acceptance-criterion.js +5 -0
  196. package/src/validator/per-kind/bug.js +6 -0
  197. package/src/validator/per-kind/domain.js +15 -2
  198. package/src/validator/per-kind/pitch.js +7 -0
  199. package/src/validator/per-kind/requirement.js +5 -0
  200. package/src/validator/per-kind/task.js +7 -0
  201. package/src/validator/per-kind/widget.js +14 -0
  202. package/src/validator/projections/api-http-async.js +410 -0
  203. package/src/validator/projections/api-http-authz.js +88 -0
  204. package/src/validator/projections/api-http-core.js +205 -0
  205. package/src/validator/projections/api-http-policies.js +339 -0
  206. package/src/validator/projections/api-http-responses.js +233 -0
  207. package/src/validator/projections/api-http.js +44 -0
  208. package/src/validator/projections/db.js +353 -0
  209. package/src/validator/projections/generator-defaults.js +45 -0
  210. package/src/validator/projections/helpers.js +87 -0
  211. package/src/validator/projections/ui-helpers.js +214 -0
  212. package/src/validator/projections/ui-navigation.js +344 -0
  213. package/src/validator/projections/ui-structure.js +364 -0
  214. package/src/validator/projections/ui-widgets.js +493 -0
  215. package/src/validator/projections/ui.js +46 -0
  216. package/src/validator/registry.js +48 -1
  217. package/src/validator/utils.d.ts +20 -0
  218. package/src/validator/utils.js +115 -12
  219. package/src/widget-behavior.d.ts +1 -0
  220. package/src/workflows/import-app/api/collect.js +221 -0
  221. package/src/workflows/import-app/api/openapi.js +257 -0
  222. package/src/workflows/import-app/api/routes.js +327 -0
  223. package/src/workflows/import-app/api/sources.js +22 -0
  224. package/src/workflows/import-app/api.js +2 -797
  225. package/src/workflows/reconcile/adoption-plan/build.js +208 -0
  226. package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
  227. package/src/workflows/reconcile/adoption-plan/outputs.js +143 -0
  228. package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
  229. package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
  230. package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
  231. package/src/workflows/reconcile/adoption-plan.js +30 -740
  232. package/src/workflows/reconcile/auth/closures.js +115 -0
  233. package/src/workflows/reconcile/auth/formatters.js +142 -0
  234. package/src/workflows/reconcile/auth/inference.js +330 -0
  235. package/src/workflows/reconcile/auth/roles.js +122 -0
  236. package/src/workflows/reconcile/auth.js +35 -690
  237. package/src/workflows/reconcile/bundle-core/index.js +600 -0
  238. package/src/workflows/reconcile/bundle-core.js +12 -598
  239. package/src/workflows/reconcile/canonical-surface.js +1 -1
  240. package/src/workflows/reconcile/impacts/adoption-plan.js +192 -0
  241. package/src/workflows/reconcile/impacts/indexes.js +101 -0
  242. package/src/workflows/reconcile/impacts/patches.js +252 -0
  243. package/src/workflows/reconcile/impacts/reports.js +80 -0
  244. package/src/workflows/reconcile/impacts.js +14 -623
  245. package/src/workspace-docs.d.ts +29 -0
@@ -0,0 +1,497 @@
1
+ import { fieldSignature, symbolList } from "../shared.js";
2
+ import { apiMetadataForCapability } from "./metadata.js";
3
+ import { cloneSchema, generateShapeJsonSchema, indexStatements, schemaForField } from "./schema.js";
4
+
5
+ /**
6
+ * @param {import("./types.d.ts").ApiGraph} graph
7
+ * @param {string} capabilityId
8
+ * @returns {any}
9
+ */
10
+ export function getCapability(graph, capabilityId) {
11
+ const byId = indexStatements(graph);
12
+ const capability = byId.get(capabilityId);
13
+ if (!capability || capability.kind !== "capability") {
14
+ throw new Error(`No capability found with id '${capabilityId}'`);
15
+ }
16
+ return capability;
17
+ }
18
+
19
+ /**
20
+ * @param {import("./types.d.ts").ApiContract} contract
21
+ * @param {import("./types.d.ts").ApiMetadata} apiMetadata
22
+ * @param {any} direction
23
+ * @returns {any}
24
+ */
25
+ export function fieldTransportBindings(contract, apiMetadata, direction) {
26
+ const bindings = (apiMetadata.fieldBindings || []).filter(/** @param {any} binding */ (binding) => binding.direction === direction);
27
+ const byField = new Map(bindings.map(/** @param {any} binding */ (binding) => [binding.field, binding]));
28
+ const inferredBindings = new Map();
29
+
30
+ if (direction === "input" && apiMetadata.response?.cursor?.requestAfter) {
31
+ inferredBindings.set(apiMetadata.response.cursor.requestAfter, { location: "query", wireName: "after" });
32
+ }
33
+ if (direction === "input" && apiMetadata.response?.limit?.field) {
34
+ inferredBindings.set(apiMetadata.response.limit.field, { location: "query", wireName: "limit" });
35
+ }
36
+
37
+ return contract.fields.map(/** @param {import("./types.d.ts").ApiField} field */ (field) => {
38
+ const binding = byField.get(field.name) || byField.get(field.sourceName) || inferredBindings.get(field.name);
39
+ return {
40
+ ...field,
41
+ transport: {
42
+ location: binding?.location || (direction === "input" ? apiMetadata.request : "body"),
43
+ wireName: binding?.wireName || field.name
44
+ }
45
+ };
46
+ });
47
+ }
48
+
49
+ /**
50
+ * @param {import("./types.d.ts").ApiField[]} fields
51
+ * @returns {any}
52
+ */
53
+ export function splitFieldsByLocation(fields) {
54
+ return {
55
+ path: fields.filter(/** @param {import("./types.d.ts").ApiField} field */ (field) => field.transport.location === "path"),
56
+ query: fields.filter(/** @param {import("./types.d.ts").ApiField} field */ (field) => field.transport.location === "query"),
57
+ header: fields.filter(/** @param {import("./types.d.ts").ApiField} field */ (field) => field.transport.location === "header"),
58
+ body: fields.filter(/** @param {import("./types.d.ts").ApiField} field */ (field) => field.transport.location === "body")
59
+ };
60
+ }
61
+
62
+ /**
63
+ * @param {import("./types.d.ts").ApiField} field
64
+ * @param {any} byId
65
+ * @returns {any}
66
+ */
67
+ export function fieldContract(field, byId) {
68
+ return {
69
+ name: field.name,
70
+ sourceName: field.sourceName ?? field.name,
71
+ required: field.requiredness === "required",
72
+ schema: schemaForField(field, byId)
73
+ };
74
+ }
75
+
76
+ /**
77
+ * @param {import("./types.d.ts").ApiShape} shape
78
+ * @param {any} byId
79
+ * @param {any} direction
80
+ * @returns {any}
81
+ */
82
+ export function contractFromShape(shape, byId, direction) {
83
+ const fields = (shape.projectedFields || shape.fields || []).map(/** @param {import("./types.d.ts").ApiField} field */ (field) => fieldContract(field, byId));
84
+ return {
85
+ type: direction === "request" ? "api_request_contract" : "api_response_contract",
86
+ shape: {
87
+ id: shape.id,
88
+ name: shape.name || shape.id
89
+ },
90
+ fields,
91
+ required: fields.filter(/** @param {import("./types.d.ts").ApiField} field */ (field) => field.required).map(/** @param {import("./types.d.ts").ApiField} field */ (field) => field.name),
92
+ jsonSchema: generateShapeJsonSchema(shape, byId)
93
+ };
94
+ }
95
+
96
+ /**
97
+ * @param {import("./types.d.ts").ApiCapability} capability
98
+ * @param {import("./types.d.ts").ApiMetadata} apiMetadata
99
+ * @returns {any}
100
+ */
101
+ export function isCollectionCapability(capability, apiMetadata) {
102
+ if (["collection", "paged", "cursor"].includes(apiMetadata?.response?.mode)) return true;
103
+ if (apiMetadata?.response?.mode === "item") return false;
104
+ if ((apiMetadata?.method || "").toUpperCase() !== "GET") return false;
105
+ return capability.id.startsWith("cap_list_");
106
+ }
107
+
108
+ /**
109
+ * @param {import("./types.d.ts").ApiGraph} graph
110
+ * @param {import("./types.d.ts").ApiCapability} capability
111
+ * @returns {any}
112
+ */
113
+ export function policyConstraintsForCapability(graph, capability) {
114
+ const rules = graph.byKind.rule || [];
115
+ return rules
116
+ .filter(/** @param {any} rule */ (rule) => rule.appliesTo.some(/** @param {any} target */ (target) => target.id === capability.id))
117
+ .map(/** @param {any} rule */ (rule) => ({
118
+ type: "api_policy_constraint",
119
+ rule: {
120
+ id: rule.id,
121
+ name: rule.name || rule.id
122
+ },
123
+ requirement: rule.requirementNode || null,
124
+ condition: rule.conditionNode || null,
125
+ severity: rule.severity
126
+ }));
127
+ }
128
+
129
+ /**
130
+ * @param {import("./types.d.ts").ApiCapability} capability
131
+ * @param {any} policyConstraints
132
+ * @param {import("./types.d.ts").ApiMetadata} apiMetadata
133
+ * @returns {any}
134
+ */
135
+ export function apiErrorCasesForCapability(capability, policyConstraints, apiMetadata) {
136
+ const errors = [];
137
+ const overrideMap = new Map((apiMetadata.errorMappings || []).map(/** @param {any} mapping */ (mapping) => [mapping.code, mapping.status]));
138
+
139
+ for (const policy of policyConstraints) {
140
+ errors.push({
141
+ type: "api_error_case",
142
+ code: policy.rule.id,
143
+ status: overrideMap.get(policy.rule.id) || (policy.severity === "error" ? 400 : 422),
144
+ source: "policy"
145
+ });
146
+ }
147
+
148
+ if (capability.input.length > 0) {
149
+ errors.push({
150
+ type: "api_error_case",
151
+ code: `${capability.id}_invalid_request`,
152
+ status: overrideMap.get(`${capability.id}_invalid_request`) || 400,
153
+ source: "request_contract"
154
+ });
155
+ }
156
+
157
+ if (apiMetadata.response?.mode === "cursor") {
158
+ errors.push({
159
+ type: "api_error_case",
160
+ code: `${capability.id}_invalid_cursor`,
161
+ status: overrideMap.get(`${capability.id}_invalid_cursor`) || 400,
162
+ source: "cursor_contract"
163
+ });
164
+ errors.push({
165
+ type: "api_error_case",
166
+ code: `${capability.id}_invalid_limit`,
167
+ status: overrideMap.get(`${capability.id}_invalid_limit`) || 400,
168
+ source: "cursor_contract"
169
+ });
170
+ }
171
+
172
+ for (const precondition of apiMetadata.preconditions || []) {
173
+ errors.push({
174
+ type: "api_error_case",
175
+ code: precondition.code,
176
+ status: precondition.error || 412,
177
+ source: "precondition"
178
+ });
179
+ }
180
+
181
+ for (const idempotency of apiMetadata.idempotency || []) {
182
+ errors.push({
183
+ type: "api_error_case",
184
+ code: idempotency.code,
185
+ status: idempotency.error || 409,
186
+ source: "idempotency"
187
+ });
188
+ }
189
+
190
+ const seenCodes = new Set(errors.map(/** @param {any} error */ (error) => error.code));
191
+ for (const mapping of apiMetadata.errorMappings || []) {
192
+ if (seenCodes.has(mapping.code)) continue;
193
+ errors.push({
194
+ type: "api_error_case",
195
+ code: mapping.code,
196
+ status: mapping.status,
197
+ source: "projection_mapping"
198
+ });
199
+ }
200
+
201
+ return errors;
202
+ }
203
+
204
+ /**
205
+ * @param {import("./types.d.ts").ApiShape} shape
206
+ * @param {any} byId
207
+ * @param {import("./types.d.ts").ApiCapability} capability
208
+ * @param {import("./types.d.ts").ApiMetadata} apiMetadata
209
+ * @returns {any}
210
+ */
211
+ export function responseContractForCapability(shape, byId, capability, apiMetadata) {
212
+ const baseContract = contractFromShape(shape, byId, "response");
213
+ const responseMode = apiMetadata?.response?.mode || (isCollectionCapability(capability, apiMetadata) ? "collection" : "item");
214
+
215
+ if (responseMode === "item") {
216
+ return { ...baseContract, mode: "item", collection: false, itemJsonSchema: null, pagination: null };
217
+ }
218
+
219
+ if (responseMode === "paged") {
220
+ return {
221
+ ...baseContract,
222
+ mode: "paged",
223
+ collection: true,
224
+ itemJsonSchema: baseContract.jsonSchema,
225
+ pagination: {
226
+ itemsProperty: "items",
227
+ pageProperty: "page",
228
+ pageSizeProperty: "page_size",
229
+ totalProperty: "total"
230
+ },
231
+ jsonSchema: {
232
+ type: "object",
233
+ additionalProperties: false,
234
+ required: ["items", "page", "page_size", "total"],
235
+ properties: {
236
+ items: { type: "array", items: cloneSchema(baseContract.jsonSchema) },
237
+ page: { type: "integer" },
238
+ page_size: { type: "integer" },
239
+ total: { type: "integer" }
240
+ }
241
+ }
242
+ };
243
+ }
244
+
245
+ if (responseMode === "cursor") {
246
+ const totalIncluded = apiMetadata.response.total?.included === true;
247
+ return {
248
+ ...baseContract,
249
+ mode: "cursor",
250
+ collection: true,
251
+ itemJsonSchema: baseContract.jsonSchema,
252
+ pagination: null,
253
+ itemShape: { id: shape.id, name: shape.name || shape.id },
254
+ ordering: apiMetadata.response.ordering,
255
+ cursor: apiMetadata.response.cursor,
256
+ limit: apiMetadata.response.limit,
257
+ total: apiMetadata.response.total,
258
+ jsonSchema: {
259
+ type: "object",
260
+ additionalProperties: false,
261
+ required: ["items", apiMetadata.response.cursor?.responseNext || "next_cursor"],
262
+ properties: {
263
+ items: { type: "array", items: cloneSchema(baseContract.jsonSchema) },
264
+ [apiMetadata.response.cursor?.responseNext || "next_cursor"]: { type: "string" },
265
+ ...(apiMetadata.response.cursor?.responsePrev
266
+ ? { [apiMetadata.response.cursor.responsePrev]: { type: "string" } }
267
+ : {}),
268
+ ...(totalIncluded ? { total: { type: "integer" } } : {})
269
+ }
270
+ }
271
+ };
272
+ }
273
+
274
+ return {
275
+ ...baseContract,
276
+ mode: "collection",
277
+ collection: true,
278
+ itemJsonSchema: baseContract.jsonSchema,
279
+ pagination: null,
280
+ jsonSchema: { type: "array", items: cloneSchema(baseContract.jsonSchema) }
281
+ };
282
+ }
283
+
284
+ /**
285
+ * @param {import("./types.d.ts").ApiGraph} graph
286
+ * @param {import("./types.d.ts").ApiCapability} capability
287
+ * @param {any} byId
288
+ * @returns {any}
289
+ */
290
+ export function buildApiContractForCapability(graph, capability, byId) {
291
+ const inputShape = capability.input[0]?.id ? byId.get(capability.input[0].id) : null;
292
+ const policyConstraints = policyConstraintsForCapability(graph, capability);
293
+ const apiMetadata = apiMetadataForCapability(graph, capability);
294
+ const outputShapeId = apiMetadata.response.itemShapeId || capability.output[0]?.id || null;
295
+ const outputShape = outputShapeId ? byId.get(outputShapeId) : null;
296
+ const requestContract = inputShape ? contractFromShape(inputShape, byId, "request") : null;
297
+ const responseContract = outputShape ? responseContractForCapability(outputShape, byId, capability, apiMetadata) : null;
298
+ const requestFields = requestContract ? fieldTransportBindings(requestContract, apiMetadata, "input") : [];
299
+ const responseFields = responseContract ? fieldTransportBindings(responseContract, apiMetadata, "output") : [];
300
+
301
+ return {
302
+ type: "api_contract_graph",
303
+ capability: {
304
+ id: capability.id,
305
+ name: capability.name || capability.id,
306
+ description: capability.description || null
307
+ },
308
+ endpoint: {
309
+ type: "api_endpoint",
310
+ operationId: capability.id,
311
+ method: apiMetadata.method,
312
+ path: apiMetadata.path,
313
+ successStatus: apiMetadata.success,
314
+ auth: apiMetadata.auth,
315
+ requestPlacement: apiMetadata.request,
316
+ projection: apiMetadata.projection,
317
+ preconditions: apiMetadata.preconditions || [],
318
+ idempotency: apiMetadata.idempotency || [],
319
+ cache: apiMetadata.cache || [],
320
+ delete: apiMetadata.delete || [],
321
+ async: apiMetadata.async || [],
322
+ status: apiMetadata.status || [],
323
+ download: apiMetadata.download || [],
324
+ authz: apiMetadata.authz || [],
325
+ callbacks: apiMetadata.callbacks || [],
326
+ actors: capability.actors.map(/** @param {any} actor @param {any} index */ (actor, index) => ({
327
+ type: "api_actor",
328
+ id: actor.id,
329
+ kind: actor.target?.kind || null,
330
+ order: index
331
+ }))
332
+ },
333
+ requestContract: requestContract
334
+ ? { ...requestContract, fields: requestFields, transport: splitFieldsByLocation(requestFields) }
335
+ : null,
336
+ responseContract: responseContract
337
+ ? { ...responseContract, fields: responseFields, transport: splitFieldsByLocation(responseFields) }
338
+ : null,
339
+ policy: policyConstraints,
340
+ errors: apiErrorCasesForCapability(capability, policyConstraints, apiMetadata)
341
+ };
342
+ }
343
+
344
+ /**
345
+ * @param {import("./types.d.ts").ApiGraph} graph
346
+ * @param {import("./types.d.ts").ApiOptions} options
347
+ * @returns {any}
348
+ */
349
+ export function generateApiContractGraph(graph, options = {}) {
350
+ const byId = indexStatements(graph);
351
+ const capabilities = graph.byKind.capability || [];
352
+
353
+ if (options.capabilityId) {
354
+ return buildApiContractForCapability(graph, getCapability(graph, options.capabilityId), byId);
355
+ }
356
+
357
+ const output = /** @type {Record<string, any>} */ ({});
358
+ for (const capability of capabilities) {
359
+ output[capability.id] = buildApiContractForCapability(graph, capability, byId);
360
+ }
361
+ return output;
362
+ }
363
+
364
+ /**
365
+ * @param {import("./types.d.ts").ApiGraph} graph
366
+ * @param {import("./types.d.ts").ApiOptions} options
367
+ * @returns {any}
368
+ */
369
+ export function generateApiContractDebug(graph, options = {}) {
370
+ const capabilities = options.capabilityId ? [getCapability(graph, options.capabilityId)] : graph.byKind.capability || [];
371
+ const byId = indexStatements(graph);
372
+ const lines = [];
373
+
374
+ lines.push("# API Contract Debug");
375
+ lines.push("");
376
+ lines.push(`Generated from \`${graph.root}\``);
377
+ lines.push("");
378
+
379
+ for (const capability of capabilities) {
380
+ const contract = buildApiContractForCapability(graph, capability, byId);
381
+ lines.push(`## \`${capability.id}\` - ${capability.name || capability.id}`);
382
+ lines.push("");
383
+ if (capability.description) {
384
+ lines.push(capability.description);
385
+ lines.push("");
386
+ }
387
+ lines.push(`Endpoint: \`${contract.endpoint.method} ${contract.endpoint.path}\``);
388
+ lines.push(`Success: \`${contract.endpoint.successStatus}\``);
389
+ lines.push(`Auth: \`${contract.endpoint.auth}\``);
390
+ lines.push(`Request placement: \`${contract.endpoint.requestPlacement}\``);
391
+ if (contract.endpoint.projection?.id) {
392
+ lines.push(`Projection: \`${contract.endpoint.projection.id}\``);
393
+ }
394
+ if (contract.endpoint.preconditions?.length > 0) {
395
+ lines.push(`Preconditions: ${contract.endpoint.preconditions.map(/** @param {any} precondition */ (precondition) => `\`${precondition.header}\``).join(", ")}`);
396
+ }
397
+ if (contract.endpoint.idempotency?.length > 0) {
398
+ lines.push(`Idempotency: ${contract.endpoint.idempotency.map(/** @param {any} rule */ (rule) => `\`${rule.header}\``).join(", ")}`);
399
+ }
400
+ if (contract.endpoint.cache?.length > 0) {
401
+ lines.push(`Cache: ${contract.endpoint.cache.map(/** @param {any} rule */ (rule) => `\`${rule.responseHeader}\` via \`${rule.requestHeader}\` -> ${rule.notModified}`).join(", ")}`);
402
+ }
403
+ if (contract.endpoint.delete?.length > 0) {
404
+ lines.push(`Delete: ${contract.endpoint.delete.map(/** @param {any} rule */ (rule) => `\`${rule.mode}\`${rule.field ? ` via \`${rule.field}=${rule.value}\`` : ""} response \`${rule.response}\``).join(", ")}`);
405
+ }
406
+ if (contract.endpoint.async?.length > 0) {
407
+ lines.push(`Async: ${contract.endpoint.async.map(/** @param {any} rule */ (rule) => `\`${rule.mode}\` accepted ${rule.accepted} status \`${rule.statusPath}\`${rule.statusCapability?.id ? ` via \`${rule.statusCapability.id}\`` : ""}`).join(", ")}`);
408
+ }
409
+ if (contract.endpoint.status?.length > 0) {
410
+ lines.push(`Status: ${contract.endpoint.status.map(/** @param {any} rule */ (rule) => `state \`${rule.stateField}\` complete \`${rule.completed}\` fail \`${rule.failed}\`${rule.downloadCapability?.id ? ` download \`${rule.downloadCapability.id}\`` : ""}`).join(", ")}`);
411
+ }
412
+ if (contract.endpoint.download?.length > 0) {
413
+ lines.push(`Download: ${contract.endpoint.download.map(/** @param {any} rule */ (rule) => `\`${rule.media}\` ${rule.disposition}${rule.filename ? ` filename \`${rule.filename}\`` : ""}`).join(", ")}`);
414
+ }
415
+ if (contract.endpoint.authz?.length > 0) {
416
+ lines.push(`Authorization: ${contract.endpoint.authz.map(/** @param {any} rule */ (rule) => [
417
+ rule.role ? `role \`${rule.role}\`` : null,
418
+ rule.permission ? `permission \`${rule.permission}\`` : null,
419
+ rule.claim ? `claim \`${rule.claim}\`${rule.claimValue ? ` = \`${rule.claimValue}\`` : ""}` : null,
420
+ rule.ownership ? `ownership \`${rule.ownership}\`` : null
421
+ ].filter(Boolean).join(", ")).join(" | ")}`);
422
+ }
423
+ if (contract.endpoint.callbacks?.length > 0) {
424
+ lines.push(`Callbacks: ${contract.endpoint.callbacks.map(/** @param {any} rule */ (rule) => `\`${rule.event}\` -> \`${rule.method}\` via \`${rule.targetField}\``).join(", ")}`);
425
+ }
426
+ lines.push(`Actors: ${symbolList(capability.actors)}`);
427
+ lines.push("");
428
+
429
+ lines.push("Request contract:");
430
+ if (!contract.requestContract) {
431
+ lines.push("- _none_");
432
+ } else {
433
+ lines.push(`- shape: \`${contract.requestContract.shape.id}\``);
434
+ for (const field of contract.requestContract.fields) {
435
+ lines.push(`- ${fieldSignature({
436
+ name: field.transport?.wireName || field.name,
437
+ sourceName: field.sourceName,
438
+ fieldType: field.schema["x-topogram-type"] || field.schema.type || "unknown",
439
+ requiredness: field.required ? "required" : "optional",
440
+ defaultValue: field.schema.default ?? null
441
+ })} in \`${field.transport?.location}\``);
442
+ }
443
+ }
444
+ lines.push("");
445
+
446
+ lines.push("Response contract:");
447
+ if (!contract.responseContract) {
448
+ lines.push("- _none_");
449
+ } else {
450
+ lines.push(`- shape: \`${contract.responseContract.shape.id}\``);
451
+ lines.push(`- mode: \`${contract.responseContract.mode}\``);
452
+ if (contract.responseContract.pagination) {
453
+ lines.push(`- envelope: \`${contract.responseContract.pagination.itemsProperty}\`, \`${contract.responseContract.pagination.pageProperty}\`, \`${contract.responseContract.pagination.pageSizeProperty}\`, \`${contract.responseContract.pagination.totalProperty}\``);
454
+ }
455
+ if (contract.responseContract.cursor) {
456
+ lines.push(`- cursor: request \`${contract.responseContract.cursor.requestAfter}\`, next \`${contract.responseContract.cursor.responseNext}\`${contract.responseContract.cursor.responsePrev ? `, prev \`${contract.responseContract.cursor.responsePrev}\`` : ""}`);
457
+ }
458
+ if (contract.responseContract.limit) {
459
+ lines.push(`- limit: field \`${contract.responseContract.limit.field}\`, default ${contract.responseContract.limit.defaultValue}, max ${contract.responseContract.limit.maxValue}`);
460
+ }
461
+ if (contract.responseContract.ordering) {
462
+ lines.push(`- sort: \`${contract.responseContract.ordering.field} ${contract.responseContract.ordering.direction}\``);
463
+ }
464
+ if (contract.responseContract.total) {
465
+ lines.push(`- total included: \`${contract.responseContract.total.included}\``);
466
+ }
467
+ for (const field of contract.responseContract.fields) {
468
+ lines.push(`- ${fieldSignature({
469
+ name: field.transport?.wireName || field.name,
470
+ sourceName: field.sourceName,
471
+ fieldType: field.schema["x-topogram-type"] || field.schema.type || "unknown",
472
+ requiredness: field.required ? "required" : "optional",
473
+ defaultValue: field.schema.default ?? null
474
+ })} in \`${field.transport?.location}\``);
475
+ }
476
+ }
477
+ lines.push("");
478
+
479
+ lines.push("Policy constraints:");
480
+ if (contract.policy.length === 0) {
481
+ lines.push("- _none_");
482
+ } else {
483
+ for (const policy of contract.policy) {
484
+ lines.push(`- \`${policy.rule.id}\` (${policy.severity})`);
485
+ }
486
+ }
487
+ lines.push("");
488
+
489
+ lines.push("Error cases:");
490
+ for (const error of contract.errors) {
491
+ lines.push(`- \`${error.code}\` -> ${error.status}`);
492
+ }
493
+ lines.push("");
494
+ }
495
+
496
+ return `${lines.join("\n").trimEnd()}\n`;
497
+ }