@contractspec/lib.contracts-transformers 1.57.0 → 1.58.0

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 (135) hide show
  1. package/dist/browser/common/index.js +86 -0
  2. package/dist/browser/index.js +2414 -0
  3. package/dist/browser/openapi/index.js +2404 -0
  4. package/dist/common/index.d.ts +6 -3
  5. package/dist/common/index.d.ts.map +1 -0
  6. package/dist/common/index.js +87 -3
  7. package/dist/common/types.d.ts +119 -120
  8. package/dist/common/types.d.ts.map +1 -1
  9. package/dist/common/utils.d.ts +11 -14
  10. package/dist/common/utils.d.ts.map +1 -1
  11. package/dist/index.d.ts +18 -18
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +2415 -18
  14. package/dist/node/common/index.js +86 -0
  15. package/dist/node/index.js +2414 -0
  16. package/dist/node/openapi/index.js +2404 -0
  17. package/dist/openapi/differ.d.ts +21 -21
  18. package/dist/openapi/differ.d.ts.map +1 -1
  19. package/dist/openapi/exporter/data-views.d.ts +24 -25
  20. package/dist/openapi/exporter/data-views.d.ts.map +1 -1
  21. package/dist/openapi/exporter/events.d.ts +15 -15
  22. package/dist/openapi/exporter/events.d.ts.map +1 -1
  23. package/dist/openapi/exporter/features.d.ts +23 -24
  24. package/dist/openapi/exporter/features.d.ts.map +1 -1
  25. package/dist/openapi/exporter/forms.d.ts +17 -17
  26. package/dist/openapi/exporter/forms.d.ts.map +1 -1
  27. package/dist/openapi/exporter/index.d.ts +12 -0
  28. package/dist/openapi/exporter/index.d.ts.map +1 -0
  29. package/dist/openapi/exporter/operations.d.ts +29 -29
  30. package/dist/openapi/exporter/operations.d.ts.map +1 -1
  31. package/dist/openapi/exporter/presentations.d.ts +16 -17
  32. package/dist/openapi/exporter/presentations.d.ts.map +1 -1
  33. package/dist/openapi/exporter/registries.d.ts +13 -14
  34. package/dist/openapi/exporter/registries.d.ts.map +1 -1
  35. package/dist/openapi/exporter/workflows.d.ts +23 -24
  36. package/dist/openapi/exporter/workflows.d.ts.map +1 -1
  37. package/dist/openapi/exporter.d.ts +24 -23
  38. package/dist/openapi/exporter.d.ts.map +1 -1
  39. package/dist/openapi/exporter.test.d.ts +2 -0
  40. package/dist/openapi/exporter.test.d.ts.map +1 -0
  41. package/dist/openapi/importer/analyzer.d.ts +14 -0
  42. package/dist/openapi/importer/analyzer.d.ts.map +1 -0
  43. package/dist/openapi/importer/events.d.ts +7 -0
  44. package/dist/openapi/importer/events.d.ts.map +1 -0
  45. package/dist/openapi/importer/generator.d.ts +8 -0
  46. package/dist/openapi/importer/generator.d.ts.map +1 -0
  47. package/dist/openapi/importer/grouping.d.ts +27 -0
  48. package/dist/openapi/importer/grouping.d.ts.map +1 -0
  49. package/dist/openapi/importer/index.d.ts +11 -9
  50. package/dist/openapi/importer/index.d.ts.map +1 -1
  51. package/dist/openapi/importer/models.d.ts +7 -0
  52. package/dist/openapi/importer/models.d.ts.map +1 -0
  53. package/dist/openapi/importer/schemas.d.ts +15 -0
  54. package/dist/openapi/importer/schemas.d.ts.map +1 -0
  55. package/dist/openapi/importer.d.ts +6 -0
  56. package/dist/openapi/importer.d.ts.map +1 -0
  57. package/dist/openapi/index.d.ts +12 -16
  58. package/dist/openapi/index.d.ts.map +1 -0
  59. package/dist/openapi/index.js +2405 -18
  60. package/dist/openapi/parser/document.d.ts +5 -9
  61. package/dist/openapi/parser/document.d.ts.map +1 -1
  62. package/dist/openapi/parser/index.d.ts +6 -0
  63. package/dist/openapi/parser/index.d.ts.map +1 -0
  64. package/dist/openapi/parser/operation.d.ts +7 -0
  65. package/dist/openapi/parser/operation.d.ts.map +1 -0
  66. package/dist/openapi/parser/parameters.d.ts +11 -0
  67. package/dist/openapi/parser/parameters.d.ts.map +1 -0
  68. package/dist/openapi/parser/resolvers.d.ts +21 -0
  69. package/dist/openapi/parser/resolvers.d.ts.map +1 -0
  70. package/dist/openapi/parser/utils.d.ts +9 -8
  71. package/dist/openapi/parser/utils.d.ts.map +1 -1
  72. package/dist/openapi/parser.d.ts +6 -0
  73. package/dist/openapi/parser.d.ts.map +1 -0
  74. package/dist/openapi/schema-converter.d.ts +45 -45
  75. package/dist/openapi/schema-converter.d.ts.map +1 -1
  76. package/dist/openapi/schema-generators/index.d.ts +115 -0
  77. package/dist/openapi/schema-generators/index.d.ts.map +1 -0
  78. package/dist/openapi/schema-generators.test.d.ts +2 -0
  79. package/dist/openapi/schema-generators.test.d.ts.map +1 -0
  80. package/dist/openapi/types.d.ts +198 -198
  81. package/dist/openapi/types.d.ts.map +1 -1
  82. package/package.json +53 -20
  83. package/dist/common/utils.js +0 -103
  84. package/dist/common/utils.js.map +0 -1
  85. package/dist/openapi/differ.js +0 -222
  86. package/dist/openapi/differ.js.map +0 -1
  87. package/dist/openapi/exporter/data-views.js +0 -47
  88. package/dist/openapi/exporter/data-views.js.map +0 -1
  89. package/dist/openapi/exporter/events.js +0 -39
  90. package/dist/openapi/exporter/events.js.map +0 -1
  91. package/dist/openapi/exporter/features.js +0 -46
  92. package/dist/openapi/exporter/features.js.map +0 -1
  93. package/dist/openapi/exporter/forms.js +0 -49
  94. package/dist/openapi/exporter/forms.js.map +0 -1
  95. package/dist/openapi/exporter/index.js +0 -8
  96. package/dist/openapi/exporter/operations.js +0 -143
  97. package/dist/openapi/exporter/operations.js.map +0 -1
  98. package/dist/openapi/exporter/presentations.js +0 -60
  99. package/dist/openapi/exporter/presentations.js.map +0 -1
  100. package/dist/openapi/exporter/registries.js +0 -29
  101. package/dist/openapi/exporter/registries.js.map +0 -1
  102. package/dist/openapi/exporter/workflows.js +0 -54
  103. package/dist/openapi/exporter/workflows.js.map +0 -1
  104. package/dist/openapi/exporter.js +0 -122
  105. package/dist/openapi/exporter.js.map +0 -1
  106. package/dist/openapi/importer/analyzer.js +0 -28
  107. package/dist/openapi/importer/analyzer.js.map +0 -1
  108. package/dist/openapi/importer/events.js +0 -40
  109. package/dist/openapi/importer/events.js.map +0 -1
  110. package/dist/openapi/importer/generator.js +0 -105
  111. package/dist/openapi/importer/generator.js.map +0 -1
  112. package/dist/openapi/importer/grouping.js +0 -72
  113. package/dist/openapi/importer/grouping.js.map +0 -1
  114. package/dist/openapi/importer/index.js +0 -175
  115. package/dist/openapi/importer/index.js.map +0 -1
  116. package/dist/openapi/importer/models.js +0 -22
  117. package/dist/openapi/importer/models.js.map +0 -1
  118. package/dist/openapi/importer/schemas.js +0 -60
  119. package/dist/openapi/importer/schemas.js.map +0 -1
  120. package/dist/openapi/parser/document.js +0 -95
  121. package/dist/openapi/parser/document.js.map +0 -1
  122. package/dist/openapi/parser/index.js +0 -5
  123. package/dist/openapi/parser/operation.js +0 -59
  124. package/dist/openapi/parser/operation.js.map +0 -1
  125. package/dist/openapi/parser/parameters.js +0 -37
  126. package/dist/openapi/parser/parameters.js.map +0 -1
  127. package/dist/openapi/parser/resolvers.js +0 -63
  128. package/dist/openapi/parser/resolvers.js.map +0 -1
  129. package/dist/openapi/parser/utils.js +0 -48
  130. package/dist/openapi/parser/utils.js.map +0 -1
  131. package/dist/openapi/parser.js +0 -6
  132. package/dist/openapi/schema-converter.js +0 -161
  133. package/dist/openapi/schema-converter.js.map +0 -1
  134. package/dist/openapi/schema-generators/index.js +0 -461
  135. package/dist/openapi/schema-generators/index.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,18 +1,2415 @@
1
- import { detectFormat, detectVersion, parseOpenApiString } from "./openapi/parser/utils.js";
2
- import { parseOpenApi, parseOpenApiDocument } from "./openapi/parser/document.js";
3
- import { defaultRestPath, exportOperations, generateOperationsRegistry, jsonSchemaForSpec, schemaModelToJsonSchema, toHttpMethod, toOperationId, toRestPath, toSchemaName } from "./openapi/exporter/operations.js";
4
- import { exportEvents, generateEventsExports } from "./openapi/exporter/events.js";
5
- import { exportFeatures, generateFeaturesRegistry } from "./openapi/exporter/features.js";
6
- import { exportPresentations, exportPresentationsFromArray, generatePresentationsRegistry } from "./openapi/exporter/presentations.js";
7
- import { exportForms, generateFormsRegistry } from "./openapi/exporter/forms.js";
8
- import { exportDataViews, generateDataViewsRegistry } from "./openapi/exporter/data-views.js";
9
- import { exportWorkflows, generateWorkflowsRegistry } from "./openapi/exporter/workflows.js";
10
- import { generateRegistryIndex } from "./openapi/exporter/registries.js";
11
- import { contractSpecToJson, contractSpecToYaml, exportContractSpec, openApiForRegistry, openApiToJson, openApiToYaml } from "./openapi/exporter.js";
12
- import { deepEqual, extractPathParams, getByPath, normalizePath, toCamelCase, toFileName, toKebabCase, toPascalCase, toSnakeCase, toSpecKey, toValidIdentifier } from "./common/utils.js";
13
- import { generateImports, generateSchemaModelCode, getScalarType, jsonSchemaToType } from "./openapi/schema-converter.js";
14
- import { importFromOpenApi, importOperation } from "./openapi/importer/index.js";
15
- import { createSpecDiff, diffAll, diffSpecVsOperation, diffSpecs, formatDiffChanges } from "./openapi/differ.js";
16
- import "./openapi/index.js";
17
-
18
- export { contractSpecToJson, contractSpecToYaml, createSpecDiff, deepEqual, defaultRestPath, detectFormat, detectVersion, diffAll, diffSpecVsOperation, diffSpecs, exportContractSpec, exportDataViews, exportEvents, exportFeatures, exportForms, exportOperations, exportPresentations, exportPresentationsFromArray, exportWorkflows, extractPathParams, formatDiffChanges, generateDataViewsRegistry, generateEventsExports, generateFeaturesRegistry, generateFormsRegistry, generateImports, generateOperationsRegistry, generatePresentationsRegistry, generateRegistryIndex, generateSchemaModelCode, generateWorkflowsRegistry, getByPath, getScalarType, importFromOpenApi, importOperation, jsonSchemaForSpec, jsonSchemaToType, normalizePath, openApiForRegistry, openApiToJson, openApiToYaml, parseOpenApi, parseOpenApiDocument, parseOpenApiString, schemaModelToJsonSchema, toCamelCase, toFileName, toHttpMethod, toKebabCase, toOperationId, toPascalCase, toRestPath, toSchemaName, toSnakeCase, toSpecKey, toValidIdentifier };
1
+ // @bun
2
+ // src/common/utils.ts
3
+ function toPascalCase(str) {
4
+ return str.replace(/[-_./\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^./, (c) => c.toUpperCase());
5
+ }
6
+ function toCamelCase(str) {
7
+ const pascal = toPascalCase(str);
8
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
9
+ }
10
+ function toKebabCase(str) {
11
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_./]+/g, "-").toLowerCase();
12
+ }
13
+ function toSnakeCase(str) {
14
+ return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s\-./]+/g, "_").toLowerCase();
15
+ }
16
+ function toValidIdentifier(str) {
17
+ let result = str.replace(/[^a-zA-Z0-9_$]/g, "_");
18
+ if (/^[0-9]/.test(result)) {
19
+ result = "_" + result;
20
+ }
21
+ return result;
22
+ }
23
+ function toSpecKey(operationId, prefix) {
24
+ const key = toCamelCase(operationId);
25
+ return prefix ? `${prefix}.${key}` : key;
26
+ }
27
+ function toFileName(specName) {
28
+ return toKebabCase(specName.replace(/\./g, "-")) + ".ts";
29
+ }
30
+ function deepEqual(a, b) {
31
+ if (a === b)
32
+ return true;
33
+ if (a === null || b === null)
34
+ return false;
35
+ if (typeof a !== typeof b)
36
+ return false;
37
+ if (typeof a === "object") {
38
+ const aObj = a;
39
+ const bObj = b;
40
+ const aKeys = Object.keys(aObj);
41
+ const bKeys = Object.keys(bObj);
42
+ if (aKeys.length !== bKeys.length)
43
+ return false;
44
+ for (const key of aKeys) {
45
+ if (!bKeys.includes(key))
46
+ return false;
47
+ if (!deepEqual(aObj[key], bObj[key]))
48
+ return false;
49
+ }
50
+ return true;
51
+ }
52
+ return false;
53
+ }
54
+ function getByPath(obj, path) {
55
+ const parts = path.split(".").filter(Boolean);
56
+ let current = obj;
57
+ for (const part of parts) {
58
+ if (current === null || current === undefined)
59
+ return;
60
+ if (typeof current !== "object")
61
+ return;
62
+ current = current[part];
63
+ }
64
+ return current;
65
+ }
66
+ function extractPathParams(path) {
67
+ const matches = path.match(/\{([^}]+)\}/g) || [];
68
+ return matches.map((m) => m.slice(1, -1));
69
+ }
70
+ function normalizePath(path) {
71
+ let normalized = path.replace(/^\/+|\/+$/g, "");
72
+ normalized = normalized.replace(/\/+/g, "/");
73
+ return "/" + normalized;
74
+ }
75
+ // src/openapi/parser/utils.ts
76
+ import { parse as parseYaml } from "yaml";
77
+ var HTTP_METHODS = [
78
+ "get",
79
+ "post",
80
+ "put",
81
+ "delete",
82
+ "patch",
83
+ "head",
84
+ "options",
85
+ "trace"
86
+ ];
87
+ function parseOpenApiString(content, format = "json") {
88
+ if (format === "yaml") {
89
+ return parseYaml(content);
90
+ }
91
+ return JSON.parse(content);
92
+ }
93
+ function detectFormat(content) {
94
+ const trimmed = content.trim();
95
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
96
+ return "json";
97
+ }
98
+ return "yaml";
99
+ }
100
+ function detectVersion(doc) {
101
+ const version = doc.openapi;
102
+ if (version.startsWith("3.1")) {
103
+ return "3.1";
104
+ }
105
+ return "3.0";
106
+ }
107
+ function generateOperationId(method, path) {
108
+ const pathParts = path.split("/").filter(Boolean).map((part) => {
109
+ if (part.startsWith("{") && part.endsWith("}")) {
110
+ return "By" + part.slice(1, -1).charAt(0).toUpperCase() + part.slice(2, -1);
111
+ }
112
+ return part.charAt(0).toUpperCase() + part.slice(1);
113
+ });
114
+ return method + pathParts.join("");
115
+ }
116
+ // src/openapi/parser/resolvers.ts
117
+ function isReference(obj) {
118
+ return typeof obj === "object" && obj !== null && "$ref" in obj;
119
+ }
120
+ function resolveRef(doc, ref) {
121
+ if (!ref.startsWith("#/")) {
122
+ return;
123
+ }
124
+ const path = ref.slice(2).split("/");
125
+ let current = doc;
126
+ for (const part of path) {
127
+ if (current === null || current === undefined)
128
+ return;
129
+ if (typeof current !== "object")
130
+ return;
131
+ current = current[part];
132
+ }
133
+ return current;
134
+ }
135
+ function dereferenceSchema(doc, schema, seen = new Set) {
136
+ if (!schema)
137
+ return;
138
+ if (isReference(schema)) {
139
+ if (seen.has(schema.$ref)) {
140
+ return schema;
141
+ }
142
+ const newSeen = new Set(seen);
143
+ newSeen.add(schema.$ref);
144
+ const resolved = resolveRef(doc, schema.$ref);
145
+ if (!resolved)
146
+ return schema;
147
+ const dereferenced = dereferenceSchema(doc, resolved, newSeen);
148
+ if (!dereferenced)
149
+ return schema;
150
+ const refParts = schema.$ref.split("/");
151
+ const typeName = refParts[refParts.length - 1];
152
+ return {
153
+ ...dereferenced,
154
+ _originalRef: schema.$ref,
155
+ _originalTypeName: typeName
156
+ };
157
+ }
158
+ const schemaObj = { ...schema };
159
+ if (schemaObj.properties) {
160
+ const props = schemaObj.properties;
161
+ const newProps = {};
162
+ for (const [key, prop] of Object.entries(props)) {
163
+ newProps[key] = dereferenceSchema(doc, prop, seen) ?? prop;
164
+ }
165
+ schemaObj.properties = newProps;
166
+ }
167
+ if (schemaObj.items) {
168
+ schemaObj.items = dereferenceSchema(doc, schemaObj.items, seen);
169
+ }
170
+ const combinators = ["allOf", "anyOf", "oneOf"];
171
+ for (const comb of combinators) {
172
+ if (Array.isArray(schemaObj[comb])) {
173
+ schemaObj[comb] = schemaObj[comb].map((s) => dereferenceSchema(doc, s, seen) ?? s);
174
+ }
175
+ }
176
+ return schemaObj;
177
+ }
178
+ // src/openapi/parser/parameters.ts
179
+ function parseParameters(doc, params) {
180
+ const result = {
181
+ path: [],
182
+ query: [],
183
+ header: [],
184
+ cookie: []
185
+ };
186
+ if (!params)
187
+ return result;
188
+ for (const param of params) {
189
+ let resolved;
190
+ if (isReference(param)) {
191
+ const ref = resolveRef(doc, param.$ref);
192
+ if (!ref)
193
+ continue;
194
+ resolved = ref;
195
+ } else {
196
+ resolved = param;
197
+ }
198
+ const parsed = {
199
+ name: resolved.name,
200
+ in: resolved.in,
201
+ required: resolved.required ?? resolved.in === "path",
202
+ description: resolved.description,
203
+ schema: dereferenceSchema(doc, resolved.schema),
204
+ deprecated: resolved.deprecated ?? false
205
+ };
206
+ result[resolved.in]?.push(parsed);
207
+ }
208
+ return result;
209
+ }
210
+ // src/openapi/parser/operation.ts
211
+ function parseOperation(doc, method, path, operation, pathParams) {
212
+ const allParams = [...pathParams ?? [], ...operation.parameters ?? []];
213
+ const params = parseParameters(doc, allParams);
214
+ let requestBody;
215
+ if (operation.requestBody) {
216
+ const body = isReference(operation.requestBody) ? resolveRef(doc, operation.requestBody.$ref) : operation.requestBody;
217
+ if (body) {
218
+ const contentType = Object.keys(body.content ?? {})[0] ?? "application/json";
219
+ const content = body.content?.[contentType];
220
+ if (content?.schema) {
221
+ requestBody = {
222
+ required: body.required ?? false,
223
+ schema: dereferenceSchema(doc, content.schema) ?? {},
224
+ contentType
225
+ };
226
+ }
227
+ }
228
+ }
229
+ const responses = {};
230
+ for (const [status, response] of Object.entries(operation.responses ?? {})) {
231
+ const resolved = isReference(response) ? resolveRef(doc, response.$ref) : response;
232
+ if (resolved) {
233
+ const contentType = Object.keys(resolved.content ?? {})[0];
234
+ const content = contentType ? resolved.content?.[contentType] : undefined;
235
+ responses[status] = {
236
+ description: resolved.description,
237
+ schema: content?.schema ? dereferenceSchema(doc, content.schema) : undefined,
238
+ contentType
239
+ };
240
+ }
241
+ }
242
+ const contractSpecMeta = operation?.["x-contractspec"];
243
+ return {
244
+ operationId: operation.operationId ?? generateOperationId(method, path),
245
+ method,
246
+ path,
247
+ summary: operation.summary,
248
+ description: operation.description,
249
+ tags: operation.tags ?? [],
250
+ pathParams: params.path,
251
+ queryParams: params.query,
252
+ headerParams: params.header,
253
+ cookieParams: params.cookie,
254
+ requestBody,
255
+ responses,
256
+ deprecated: operation.deprecated ?? false,
257
+ security: operation.security,
258
+ contractSpecMeta
259
+ };
260
+ }
261
+ // src/openapi/parser/document.ts
262
+ function parseOpenApiDocument(doc, _options = {}) {
263
+ const version = detectVersion(doc);
264
+ const warnings = [];
265
+ const operations = [];
266
+ for (const [path, pathItem] of Object.entries(doc.paths ?? {})) {
267
+ if (!pathItem)
268
+ continue;
269
+ const pathParams = pathItem.parameters;
270
+ for (const method of HTTP_METHODS) {
271
+ const operation = pathItem[method];
272
+ if (operation) {
273
+ try {
274
+ operations.push(parseOperation(doc, method, path, operation, pathParams));
275
+ } catch (error) {
276
+ warnings.push(`Failed to parse ${method.toUpperCase()} ${path}: ${error}`);
277
+ }
278
+ }
279
+ }
280
+ }
281
+ const schemas = {};
282
+ const components = doc.components;
283
+ if (components?.schemas) {
284
+ for (const [name, schema] of Object.entries(components.schemas)) {
285
+ schemas[name] = schema;
286
+ }
287
+ }
288
+ const servers = (doc.servers ?? []).map((s) => ({
289
+ url: s.url,
290
+ description: s.description,
291
+ variables: s.variables
292
+ }));
293
+ const events = [];
294
+ if ("webhooks" in doc && doc.webhooks) {
295
+ for (const [name, pathItem] of Object.entries(doc.webhooks)) {
296
+ if (typeof pathItem !== "object" || !pathItem)
297
+ continue;
298
+ const operation = pathItem["post"];
299
+ if (operation && operation.requestBody) {
300
+ if ("$ref" in operation.requestBody) {
301
+ throw new Error(`'$ref' isn't supported`);
302
+ }
303
+ const content = operation.requestBody.content?.["application/json"];
304
+ if (content?.schema) {
305
+ events.push({
306
+ name,
307
+ description: operation.summary || operation.description,
308
+ payload: content.schema
309
+ });
310
+ }
311
+ }
312
+ }
313
+ }
314
+ return {
315
+ document: doc,
316
+ version,
317
+ info: {
318
+ title: doc.info.title,
319
+ version: doc.info.version,
320
+ description: doc.info.description
321
+ },
322
+ operations,
323
+ schemas,
324
+ servers,
325
+ warnings,
326
+ events
327
+ };
328
+ }
329
+ async function parseOpenApi(source, options = {}) {
330
+ const {
331
+ fetch: fetchFn = globalThis.fetch,
332
+ readFile,
333
+ timeout = 30000
334
+ } = options;
335
+ let content;
336
+ let format;
337
+ if (source.startsWith("http://") || source.startsWith("https://")) {
338
+ const controller = new AbortController;
339
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
340
+ try {
341
+ const response = await fetchFn(source, { signal: controller.signal });
342
+ if (!response.ok) {
343
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
344
+ }
345
+ content = await response.text();
346
+ } finally {
347
+ clearTimeout(timeoutId);
348
+ }
349
+ if (source.endsWith(".yaml") || source.endsWith(".yml")) {
350
+ format = "yaml";
351
+ } else if (source.endsWith(".json")) {
352
+ format = "json";
353
+ } else {
354
+ format = detectFormat(content);
355
+ }
356
+ } else {
357
+ if (!readFile) {
358
+ throw new Error("readFile adapter required for file paths");
359
+ }
360
+ content = await readFile(source);
361
+ if (source.endsWith(".yaml") || source.endsWith(".yml")) {
362
+ format = "yaml";
363
+ } else if (source.endsWith(".json")) {
364
+ format = "json";
365
+ } else {
366
+ format = detectFormat(content);
367
+ }
368
+ }
369
+ const doc = parseOpenApiString(content, format);
370
+ return parseOpenApiDocument(doc, options);
371
+ }
372
+ // src/openapi/exporter/operations.ts
373
+ import { z } from "zod";
374
+ import { compareVersions } from "compare-versions";
375
+ function toOperationId(name, version) {
376
+ return `${name.replace(/\./g, "_")}_v${version.replace(/\./g, "_")}`;
377
+ }
378
+ function toSchemaName(prefix, name, version) {
379
+ return `${prefix}_${toOperationId(name, version)}`;
380
+ }
381
+ function toHttpMethod(kind, override) {
382
+ const method = override ?? (kind === "query" ? "GET" : "POST");
383
+ return method.toLowerCase();
384
+ }
385
+ function defaultRestPath(name, version) {
386
+ return `/${name.replace(/\./g, "/")}/v${version}`;
387
+ }
388
+ function toRestPath(spec) {
389
+ const path = spec.transport?.rest?.path ?? defaultRestPath(spec.meta.key, spec.meta.version);
390
+ return path.startsWith("/") ? path : `/${path}`;
391
+ }
392
+ function schemaModelToJsonSchema(schema) {
393
+ if (!schema)
394
+ return null;
395
+ return z.toJSONSchema(schema.getZod());
396
+ }
397
+ function jsonSchemaForSpec(spec) {
398
+ return {
399
+ input: schemaModelToJsonSchema(spec.io.input),
400
+ output: schemaModelToJsonSchema(spec.io.output),
401
+ meta: {
402
+ key: spec.meta.key,
403
+ version: spec.meta.version,
404
+ kind: spec.meta.kind,
405
+ description: spec.meta.description,
406
+ tags: spec.meta.tags ?? [],
407
+ stability: spec.meta.stability ?? "stable"
408
+ }
409
+ };
410
+ }
411
+ function exportOperations(registry) {
412
+ const specs = Array.from(registry.list().values()).filter((s) => s.meta.kind === "command" || s.meta.kind === "query").sort((a, b) => {
413
+ const byName = a.meta.key.localeCompare(b.meta.key);
414
+ return byName !== 0 ? byName : compareVersions(a.meta.version, b.meta.version);
415
+ });
416
+ const paths = {};
417
+ const schemas = {};
418
+ for (const spec of specs) {
419
+ const schema = jsonSchemaForSpec(spec);
420
+ const method = toHttpMethod(spec.meta.kind, spec.transport?.rest?.method);
421
+ const path = toRestPath(spec);
422
+ const operationId = toOperationId(spec.meta.key, spec.meta.version);
423
+ const pathItem = paths[path] ??= {};
424
+ const op = {
425
+ operationId,
426
+ summary: spec.meta.description ?? spec.meta.key,
427
+ description: spec.meta.description,
428
+ tags: spec.meta.tags ?? [],
429
+ "x-contractspec": {
430
+ name: spec.meta.key,
431
+ version: spec.meta.version,
432
+ kind: spec.meta.kind
433
+ },
434
+ responses: {}
435
+ };
436
+ if (schema.input) {
437
+ const inputName = toSchemaName("Input", spec.meta.key, spec.meta.version);
438
+ schemas[inputName] = schema.input;
439
+ op["requestBody"] = {
440
+ required: true,
441
+ content: {
442
+ "application/json": {
443
+ schema: { $ref: `#/components/schemas/${inputName}` }
444
+ }
445
+ }
446
+ };
447
+ }
448
+ const responses = {};
449
+ if (schema.output) {
450
+ const outputName = toSchemaName("Output", spec.meta.key, spec.meta.version);
451
+ schemas[outputName] = schema.output;
452
+ responses["200"] = {
453
+ description: "OK",
454
+ content: {
455
+ "application/json": {
456
+ schema: { $ref: `#/components/schemas/${outputName}` }
457
+ }
458
+ }
459
+ };
460
+ } else {
461
+ responses["200"] = { description: "OK" };
462
+ }
463
+ op["responses"] = responses;
464
+ pathItem[method] = op;
465
+ }
466
+ return { paths, schemas };
467
+ }
468
+ function generateOperationsRegistry(registry) {
469
+ const specs = Array.from(registry.list().values());
470
+ const imports = new Set;
471
+ const registrations = [];
472
+ for (const spec of specs) {
473
+ const specVarName = spec.meta.key.replace(/\./g, "_") + `_v${spec.meta.version.replace(/\./g, "_")}`;
474
+ imports.add(`import { ${specVarName} } from './${spec.meta.key.split(".")[0]}';`);
475
+ registrations.push(` .register(${specVarName})`);
476
+ }
477
+ const code = `/**
478
+ * Auto-generated operations registry.
479
+ * DO NOT EDIT - This file is generated by ContractSpec exporter.
480
+ */
481
+ import { OperationSpecRegistry } from '@contractspec/lib.contracts';
482
+
483
+ ${Array.from(imports).join(`
484
+ `)}
485
+
486
+ export const operationsRegistry = new OperationSpecRegistry()
487
+ ${registrations.join(`
488
+ `)};
489
+ `;
490
+ return {
491
+ code,
492
+ fileName: "operations-registry.ts"
493
+ };
494
+ }
495
+
496
+ // src/openapi/exporter/events.ts
497
+ import { z as z2 } from "zod";
498
+ function exportEvents(events) {
499
+ return events.map((event) => ({
500
+ name: event.meta.key,
501
+ version: event.meta.version,
502
+ description: event.meta.description,
503
+ payload: event.payload ? z2.toJSONSchema(event.payload.getZod()) : null,
504
+ pii: event.pii
505
+ }));
506
+ }
507
+ function generateEventsExports(events) {
508
+ const eventExports = [];
509
+ for (const event of events) {
510
+ const eventVarName = event.meta.key.replace(/\./g, "_") + `_v${event.meta.version}`;
511
+ eventExports.push(`export { ${eventVarName} } from './${event.meta.key.split(".")[0]}';`);
512
+ }
513
+ const code = `/**
514
+ * Auto-generated events exports.
515
+ * DO NOT EDIT - This file is generated by ContractSpec exporter.
516
+ */
517
+
518
+ ${eventExports.join(`
519
+ `)}
520
+ `;
521
+ return {
522
+ code,
523
+ fileName: "events-exports.ts"
524
+ };
525
+ }
526
+
527
+ // src/openapi/exporter/features.ts
528
+ function exportFeatures(registry) {
529
+ return registry.list().map((feature) => ({
530
+ key: feature.meta.key,
531
+ description: feature.meta.description,
532
+ owners: feature.meta.owners,
533
+ stability: feature.meta.stability,
534
+ operations: feature.operations,
535
+ events: feature.events,
536
+ presentations: feature.presentations
537
+ }));
538
+ }
539
+ function generateFeaturesRegistry(registry) {
540
+ const features = registry.list();
541
+ const imports = new Set;
542
+ const registrations = [];
543
+ for (const feature of features) {
544
+ const featureVarName = feature.meta.key.replace(/-/g, "_");
545
+ imports.add(`import { ${featureVarName} } from './${feature.meta.key}';`);
546
+ registrations.push(` .register(${featureVarName})`);
547
+ }
548
+ const code = `/**
549
+ * Auto-generated features registry.
550
+ * DO NOT EDIT - This file is generated by ContractSpec exporter.
551
+ */
552
+ import { FeatureRegistry } from '@contractspec/lib.contracts';
553
+
554
+ ${Array.from(imports).join(`
555
+ `)}
556
+
557
+ export const featuresRegistry = new FeatureRegistry()
558
+ ${registrations.join(`
559
+ `)};
560
+ `;
561
+ return {
562
+ code,
563
+ fileName: "features-registry.ts"
564
+ };
565
+ }
566
+
567
+ // src/openapi/exporter/presentations.ts
568
+ function exportPresentations(registry) {
569
+ return registry.list().map((pres) => ({
570
+ name: pres.meta.key,
571
+ version: pres.meta.version,
572
+ description: pres.meta.description,
573
+ stability: pres.meta.stability,
574
+ sourceType: pres.source.type,
575
+ targets: pres.targets,
576
+ tags: pres.meta.tags
577
+ }));
578
+ }
579
+ function exportPresentationsFromArray(descriptors) {
580
+ return descriptors.map((desc) => ({
581
+ name: desc.meta.key,
582
+ version: desc.meta.version,
583
+ description: desc.meta.description,
584
+ stability: desc.meta.stability,
585
+ sourceType: desc.source.type,
586
+ targets: desc.targets,
587
+ tags: desc.meta.tags
588
+ }));
589
+ }
590
+ function generatePresentationsRegistry(registry) {
591
+ const presentations = registry.list();
592
+ const imports = new Set;
593
+ const registrations = [];
594
+ for (const pres of presentations) {
595
+ const presVarName = pres.meta.key.replace(/\./g, "_") + `_v${pres.meta.version}`;
596
+ imports.add(`import { ${presVarName} } from './${pres.meta.key.split(".")[0]}';`);
597
+ registrations.push(` .register(${presVarName})`);
598
+ }
599
+ const code = `/**
600
+ * Auto-generated presentations registry.
601
+ * DO NOT EDIT - This file is generated by ContractSpec exporter.
602
+ */
603
+ import { PresentationRegistry } from '@contractspec/lib.contracts';
604
+
605
+ ${Array.from(imports).join(`
606
+ `)}
607
+
608
+ export const presentationsRegistry = new PresentationRegistry()
609
+ ${registrations.join(`
610
+ `)};
611
+ `;
612
+ return {
613
+ code,
614
+ fileName: "presentations-registry.ts"
615
+ };
616
+ }
617
+
618
+ // src/openapi/exporter/forms.ts
619
+ import { z as z3 } from "zod";
620
+ function exportForms(registry) {
621
+ return registry.list().map((form) => ({
622
+ key: form.meta.key,
623
+ version: form.meta.version,
624
+ description: form.meta.description,
625
+ stability: form.meta.stability,
626
+ owners: form.meta.owners,
627
+ fields: form.fields,
628
+ model: form.model ? z3.toJSONSchema(form.model.getZod()) : null,
629
+ actions: form.actions
630
+ }));
631
+ }
632
+ function generateFormsRegistry(registry) {
633
+ const forms = registry.list();
634
+ const imports = new Set;
635
+ const registrations = [];
636
+ for (const form of forms) {
637
+ const formVarName = form.meta.key.replace(/-/g, "_") + `_v${form.meta.version}`;
638
+ imports.add(`import { ${formVarName} } from './${form.meta.key}';`);
639
+ registrations.push(` .register(${formVarName})`);
640
+ }
641
+ const code = `/**
642
+ * Auto-generated forms registry.
643
+ * DO NOT EDIT - This file is generated by ContractSpec exporter.
644
+ */
645
+ import { FormRegistry } from '@contractspec/lib.contracts';
646
+
647
+ ${Array.from(imports).join(`
648
+ `)}
649
+
650
+ export const formsRegistry = new FormRegistry()
651
+ ${registrations.join(`
652
+ `)};
653
+ `;
654
+ return {
655
+ code,
656
+ fileName: "forms-registry.ts"
657
+ };
658
+ }
659
+
660
+ // src/openapi/exporter/data-views.ts
661
+ function exportDataViews(registry) {
662
+ return registry.list().map((dv) => ({
663
+ name: dv.meta.key,
664
+ version: dv.meta.version,
665
+ description: dv.meta.description,
666
+ stability: dv.meta.stability,
667
+ entity: dv.meta.entity,
668
+ kind: dv.view.kind,
669
+ source: dv.source,
670
+ fields: dv.view.fields
671
+ }));
672
+ }
673
+ function generateDataViewsRegistry(registry) {
674
+ const dataViews = registry.list();
675
+ const imports = new Set;
676
+ const registrations = [];
677
+ for (const dv of dataViews) {
678
+ const dvVarName = dv.meta.key.replace(/\./g, "_") + `_v${dv.meta.version}`;
679
+ imports.add(`import { ${dvVarName} } from './${dv.meta.key.split(".")[0]}';`);
680
+ registrations.push(` .register(${dvVarName})`);
681
+ }
682
+ const code = `/**
683
+ * Auto-generated data views registry.
684
+ * DO NOT EDIT - This file is generated by ContractSpec exporter.
685
+ */
686
+ import { DataViewRegistry } from '@contractspec/lib.contracts';
687
+
688
+ ${Array.from(imports).join(`
689
+ `)}
690
+
691
+ export const dataViewsRegistry = new DataViewRegistry()
692
+ ${registrations.join(`
693
+ `)};
694
+ `;
695
+ return {
696
+ code,
697
+ fileName: "dataviews-registry.ts"
698
+ };
699
+ }
700
+
701
+ // src/openapi/exporter/workflows.ts
702
+ function exportWorkflows(registry) {
703
+ return registry.list().map((wf) => ({
704
+ name: wf.meta.key,
705
+ version: wf.meta.version,
706
+ description: wf.meta.description,
707
+ stability: wf.meta.stability,
708
+ owners: wf.meta.owners,
709
+ steps: wf.definition.steps.map((s) => ({
710
+ id: s.id,
711
+ type: s.type,
712
+ label: s.label
713
+ })),
714
+ transitions: wf.definition.transitions.map((t) => ({
715
+ from: t.from,
716
+ to: t.to,
717
+ label: t.label
718
+ }))
719
+ }));
720
+ }
721
+ function generateWorkflowsRegistry(registry) {
722
+ const workflows = registry.list();
723
+ const imports = new Set;
724
+ const registrations = [];
725
+ for (const wf of workflows) {
726
+ const wfVarName = wf.meta.key.replace(/\./g, "_") + `_v${wf.meta.version}`;
727
+ imports.add(`import { ${wfVarName} } from './${wf.meta.key.split(".")[0]}';`);
728
+ registrations.push(` .register(${wfVarName})`);
729
+ }
730
+ const code = `/**
731
+ * Auto-generated workflows registry.
732
+ * DO NOT EDIT - This file is generated by ContractSpec exporter.
733
+ */
734
+ import { WorkflowRegistry } from '@contractspec/lib.contracts';
735
+
736
+ ${Array.from(imports).join(`
737
+ `)}
738
+
739
+ export const workflowsRegistry = new WorkflowRegistry()
740
+ ${registrations.join(`
741
+ `)};
742
+ `;
743
+ return {
744
+ code,
745
+ fileName: "workflows-registry.ts"
746
+ };
747
+ }
748
+
749
+ // src/openapi/exporter/registries.ts
750
+ function generateRegistryIndex(options = {}) {
751
+ const {
752
+ operations = true,
753
+ events = true,
754
+ features = true,
755
+ presentations = true,
756
+ forms = true,
757
+ dataViews = true,
758
+ workflows = true
759
+ } = options;
760
+ const exports = [];
761
+ if (operations) {
762
+ exports.push("export * from './operations-registry';");
763
+ }
764
+ if (events) {
765
+ exports.push("export * from './events-exports';");
766
+ }
767
+ if (features) {
768
+ exports.push("export * from './features-registry';");
769
+ }
770
+ if (presentations) {
771
+ exports.push("export * from './presentations-registry';");
772
+ }
773
+ if (forms) {
774
+ exports.push("export * from './forms-registry';");
775
+ }
776
+ if (dataViews) {
777
+ exports.push("export * from './dataviews-registry';");
778
+ }
779
+ if (workflows) {
780
+ exports.push("export * from './workflows-registry';");
781
+ }
782
+ const code = `/**
783
+ * Auto-generated registry index.
784
+ * DO NOT EDIT - This file is generated by ContractSpec exporter.
785
+ */
786
+
787
+ ${exports.join(`
788
+ `)}
789
+ `;
790
+ return {
791
+ code,
792
+ fileName: "index.ts"
793
+ };
794
+ }
795
+
796
+ // src/openapi/exporter.ts
797
+ function openApiForRegistry(registry, options = {}) {
798
+ const { paths, schemas } = exportOperations(registry);
799
+ return {
800
+ openapi: "3.1.0",
801
+ info: {
802
+ title: options.title ?? "ContractSpec API",
803
+ version: options.version ?? "0.0.0",
804
+ ...options.description ? { description: options.description } : {}
805
+ },
806
+ ...options.servers ? { servers: options.servers } : {},
807
+ paths,
808
+ components: { schemas }
809
+ };
810
+ }
811
+ function exportContractSpec(registries, options = {}) {
812
+ const {
813
+ operations: includeOps = true,
814
+ events: includeEvents = true,
815
+ features: includeFeatures = true,
816
+ presentations: includePresentations = true,
817
+ forms: includeForms = true,
818
+ dataViews: includeDataViews = true,
819
+ workflows: includeWorkflows = true,
820
+ generateRegistries = true
821
+ } = options;
822
+ let paths = {};
823
+ let schemas = {};
824
+ if (includeOps && registries.operations) {
825
+ const opResult = exportOperations(registries.operations);
826
+ paths = opResult.paths;
827
+ schemas = opResult.schemas;
828
+ }
829
+ const doc = {
830
+ openapi: "3.1.0",
831
+ info: {
832
+ title: options.title ?? "ContractSpec API",
833
+ version: options.version ?? "0.0.0",
834
+ ...options.description ? { description: options.description } : {}
835
+ },
836
+ ...options.servers ? { servers: options.servers } : {},
837
+ paths,
838
+ components: { schemas }
839
+ };
840
+ if (includeEvents && registries.events?.length) {
841
+ doc["x-contractspec-events"] = exportEvents(registries.events);
842
+ }
843
+ if (includeFeatures && registries.features) {
844
+ doc["x-contractspec-features"] = exportFeatures(registries.features);
845
+ }
846
+ if (includePresentations && registries.presentations) {
847
+ doc["x-contractspec-presentations"] = exportPresentations(registries.presentations);
848
+ }
849
+ if (includeForms && registries.forms) {
850
+ doc["x-contractspec-forms"] = exportForms(registries.forms);
851
+ }
852
+ if (includeDataViews && registries.dataViews) {
853
+ doc["x-contractspec-dataviews"] = exportDataViews(registries.dataViews);
854
+ }
855
+ if (includeWorkflows && registries.workflows) {
856
+ doc["x-contractspec-workflows"] = exportWorkflows(registries.workflows);
857
+ }
858
+ const result = {
859
+ openApi: doc
860
+ };
861
+ if (generateRegistries) {
862
+ result.registries = {};
863
+ if (includeOps && registries.operations) {
864
+ result.registries.operations = generateOperationsRegistry(registries.operations);
865
+ }
866
+ if (includeEvents && registries.events?.length) {
867
+ result.registries.events = generateEventsExports(registries.events);
868
+ }
869
+ if (includeFeatures && registries.features) {
870
+ result.registries.features = generateFeaturesRegistry(registries.features);
871
+ }
872
+ if (includePresentations && registries.presentations) {
873
+ result.registries.presentations = generatePresentationsRegistry(registries.presentations);
874
+ }
875
+ if (includeForms && registries.forms) {
876
+ result.registries.forms = generateFormsRegistry(registries.forms);
877
+ }
878
+ if (includeDataViews && registries.dataViews) {
879
+ result.registries.dataViews = generateDataViewsRegistry(registries.dataViews);
880
+ }
881
+ if (includeWorkflows && registries.workflows) {
882
+ result.registries.workflows = generateWorkflowsRegistry(registries.workflows);
883
+ }
884
+ result.registries.index = generateRegistryIndex({
885
+ operations: includeOps && !!registries.operations,
886
+ events: includeEvents && !!registries.events?.length,
887
+ features: includeFeatures && !!registries.features,
888
+ presentations: includePresentations && !!registries.presentations,
889
+ forms: includeForms && !!registries.forms,
890
+ dataViews: includeDataViews && !!registries.dataViews,
891
+ workflows: includeWorkflows && !!registries.workflows
892
+ });
893
+ }
894
+ return result;
895
+ }
896
+ function openApiToJson(registry, options = {}) {
897
+ const doc = openApiForRegistry(registry, options);
898
+ return JSON.stringify(doc, null, 2);
899
+ }
900
+ function openApiToYaml(registry, options = {}) {
901
+ const doc = openApiForRegistry(registry, options);
902
+ return jsonToYaml(doc);
903
+ }
904
+ function contractSpecToJson(registries, options = {}) {
905
+ const result = exportContractSpec(registries, options);
906
+ return JSON.stringify(result.openApi, null, 2);
907
+ }
908
+ function contractSpecToYaml(registries, options = {}) {
909
+ const result = exportContractSpec(registries, options);
910
+ return jsonToYaml(result.openApi);
911
+ }
912
+ function jsonToYaml(obj, indent = 0) {
913
+ const spaces = " ".repeat(indent);
914
+ let yaml = "";
915
+ if (Array.isArray(obj)) {
916
+ for (const item of obj) {
917
+ if (typeof item === "object" && item !== null) {
918
+ yaml += `${spaces}-
919
+ ${jsonToYaml(item, indent + 1)}`;
920
+ } else {
921
+ yaml += `${spaces}- ${JSON.stringify(item)}
922
+ `;
923
+ }
924
+ }
925
+ } else if (typeof obj === "object" && obj !== null) {
926
+ for (const [key, value] of Object.entries(obj)) {
927
+ if (Array.isArray(value)) {
928
+ yaml += `${spaces}${key}:
929
+ ${jsonToYaml(value, indent + 1)}`;
930
+ } else if (typeof value === "object" && value !== null) {
931
+ yaml += `${spaces}${key}:
932
+ ${jsonToYaml(value, indent + 1)}`;
933
+ } else {
934
+ yaml += `${spaces}${key}: ${JSON.stringify(value)}
935
+ `;
936
+ }
937
+ }
938
+ }
939
+ return yaml;
940
+ }
941
+ // src/openapi/schema-generators/index.ts
942
+ var JSON_SCHEMA_TO_SCALAR = {
943
+ string: "ScalarTypeEnum.String_unsecure",
944
+ integer: "ScalarTypeEnum.Int_unsecure",
945
+ number: "ScalarTypeEnum.Float_unsecure",
946
+ boolean: "ScalarTypeEnum.Boolean",
947
+ "string:date": "ScalarTypeEnum.Date",
948
+ "string:date-time": "ScalarTypeEnum.DateTime",
949
+ "string:email": "ScalarTypeEnum.EmailAddress",
950
+ "string:uri": "ScalarTypeEnum.URL",
951
+ "string:uuid": "ScalarTypeEnum.ID"
952
+ };
953
+ function isReference2(schema) {
954
+ return typeof schema === "object" && schema !== null && "$ref" in schema;
955
+ }
956
+ function typeNameFromRef(ref) {
957
+ const parts = ref.split("/");
958
+ return parts[parts.length - 1] ?? "Unknown";
959
+ }
960
+ function createSchemaGenerator(format, config) {
961
+ switch (format) {
962
+ case "zod":
963
+ return new ZodSchemaGenerator(config);
964
+ case "json-schema":
965
+ return new JsonSchemaGenerator(config);
966
+ case "graphql":
967
+ return new GraphQLSchemaGenerator(config);
968
+ case "contractspec":
969
+ default:
970
+ return new ContractSpecSchemaGenerator(config);
971
+ }
972
+ }
973
+
974
+ class ContractSpecSchemaGenerator {
975
+ format = "contractspec";
976
+ config;
977
+ constructor(config) {
978
+ this.config = config;
979
+ }
980
+ generateModel(schema, name) {
981
+ const model = this.generateContractSpecSchema(schema, name);
982
+ const dependencyImports = this.config ? generateImports(model.fields, this.config, false).split(`
983
+ `).filter(Boolean) : [];
984
+ return {
985
+ code: model.code,
986
+ fileName: toKebabCase(name) + ".ts",
987
+ imports: [...this.getBaseImports(), ...dependencyImports],
988
+ name: model.name
989
+ };
990
+ }
991
+ generateField(schema, fieldName, required) {
992
+ const field = this.convertField(schema, fieldName, required);
993
+ return {
994
+ code: field.scalarType ? `${field.scalarType}()` : "ScalarTypeEnum.String_unsecure()",
995
+ typeRef: field.type.type,
996
+ isOptional: field.type.optional,
997
+ isArray: field.type.array
998
+ };
999
+ }
1000
+ getBaseImports() {
1001
+ return [
1002
+ "import { defineSchemaModel, ScalarTypeEnum, EnumType } from '@contractspec/lib.schema';"
1003
+ ];
1004
+ }
1005
+ generateContractSpecSchema(schema, modelName, indent = 0) {
1006
+ const spaces = " ".repeat(indent);
1007
+ const fields = [];
1008
+ if (isReference2(schema)) {
1009
+ return {
1010
+ name: toPascalCase(typeNameFromRef(schema.$ref)),
1011
+ fields: [],
1012
+ code: `// Reference to ${schema.$ref}`
1013
+ };
1014
+ }
1015
+ const schemaObj = schema;
1016
+ const description = schemaObj["description"];
1017
+ const properties = schemaObj["properties"];
1018
+ const required = schemaObj["required"] ?? [];
1019
+ const enumValues = schemaObj["enum"];
1020
+ if (enumValues && enumValues.length > 0) {
1021
+ const safeModelName2 = toPascalCase(toValidIdentifier(modelName));
1022
+ const enumCode = [
1023
+ `${spaces}/**`,
1024
+ `${spaces} * Enum type: ${safeModelName2}`,
1025
+ description ? `${spaces} * ${description}` : null,
1026
+ `${spaces} */`,
1027
+ `${spaces}export const ${safeModelName2} = new EnumType('${safeModelName2}', [${enumValues.map((v) => `'${String(v)}'`).join(", ")}]);`
1028
+ ].filter((line) => line !== null).join(`
1029
+ `);
1030
+ return {
1031
+ name: safeModelName2,
1032
+ description,
1033
+ fields: [],
1034
+ code: enumCode
1035
+ };
1036
+ }
1037
+ const schemaType = schemaObj["type"];
1038
+ if (schemaType && !properties && !enumValues) {
1039
+ const safeModelName2 = toPascalCase(toValidIdentifier(modelName));
1040
+ const format = schemaObj["format"];
1041
+ const scalarKey = format ? `${schemaType}:${format}` : schemaType;
1042
+ const scalarType = JSON_SCHEMA_TO_SCALAR[scalarKey] ?? JSON_SCHEMA_TO_SCALAR[schemaType];
1043
+ if (scalarType) {
1044
+ const aliasCode = [
1045
+ `${spaces}/**`,
1046
+ `${spaces} * Type alias: ${safeModelName2}`,
1047
+ description ? `${spaces} * ${description}` : null,
1048
+ `${spaces} * Underlying type: ${scalarType}`,
1049
+ `${spaces} */`,
1050
+ `${spaces}export const ${safeModelName2} = defineSchemaModel({`,
1051
+ `${spaces} name: '${safeModelName2}',`,
1052
+ description ? `${spaces} description: ${JSON.stringify(description)},` : null,
1053
+ `${spaces} fields: {`,
1054
+ `${spaces} value: {`,
1055
+ `${spaces} type: ${scalarType}(),`,
1056
+ `${spaces} isOptional: false,`,
1057
+ `${spaces} },`,
1058
+ `${spaces} },`,
1059
+ `${spaces}});`
1060
+ ].filter((line) => line !== null).join(`
1061
+ `);
1062
+ return {
1063
+ name: safeModelName2,
1064
+ description,
1065
+ fields: [],
1066
+ code: aliasCode
1067
+ };
1068
+ }
1069
+ }
1070
+ const additionalProperties = schemaObj["additionalProperties"];
1071
+ if (additionalProperties && !properties) {
1072
+ const safeModelName2 = toPascalCase(toValidIdentifier(modelName));
1073
+ const dictCode = [
1074
+ `${spaces}/**`,
1075
+ `${spaces} * Dictionary/Record type: ${safeModelName2}`,
1076
+ description ? `${spaces} * ${description}` : null,
1077
+ `${spaces} * Use as: Record<string, unknown> - access via record[key]`,
1078
+ `${spaces} */`,
1079
+ `${spaces}export const ${safeModelName2} = ScalarTypeEnum.JSONObject();`
1080
+ ].filter((line) => line !== null).join(`
1081
+ `);
1082
+ return {
1083
+ name: safeModelName2,
1084
+ description,
1085
+ fields: [],
1086
+ code: dictCode
1087
+ };
1088
+ }
1089
+ if (!properties) {
1090
+ const safeModelName2 = toPascalCase(toValidIdentifier(modelName));
1091
+ const emptyModelCode = [
1092
+ `${spaces}export const ${safeModelName2} = defineSchemaModel({`,
1093
+ `${spaces} name: '${safeModelName2}',`,
1094
+ description ? `${spaces} description: ${JSON.stringify(description)},` : null,
1095
+ `${spaces} fields: {},`,
1096
+ `${spaces}});`
1097
+ ].filter((line) => line !== null).join(`
1098
+ `);
1099
+ return {
1100
+ name: safeModelName2,
1101
+ description,
1102
+ fields: [],
1103
+ code: emptyModelCode
1104
+ };
1105
+ }
1106
+ const safeModelName = toPascalCase(toValidIdentifier(modelName));
1107
+ for (const [propName, propSchema] of Object.entries(properties)) {
1108
+ const isRequired = required.includes(propName);
1109
+ fields.push(this.convertField(propSchema, propName, isRequired, safeModelName));
1110
+ }
1111
+ const lines = [];
1112
+ for (const field of fields) {
1113
+ if (field.nestedModel) {
1114
+ lines.push(field.nestedModel.code);
1115
+ lines.push("");
1116
+ }
1117
+ }
1118
+ lines.push(`${spaces}export const ${safeModelName} = defineSchemaModel({`);
1119
+ lines.push(`${spaces} name: '${safeModelName}',`);
1120
+ if (description) {
1121
+ lines.push(`${spaces} description: ${JSON.stringify(description)},`);
1122
+ }
1123
+ lines.push(`${spaces} fields: {`);
1124
+ for (const field of fields) {
1125
+ const fieldLines = this.generateFieldCodeHelper(field, indent + 2);
1126
+ lines.push(fieldLines);
1127
+ }
1128
+ lines.push(`${spaces} },`);
1129
+ lines.push(`${spaces}});`);
1130
+ return {
1131
+ name: safeModelName,
1132
+ description,
1133
+ fields,
1134
+ code: lines.join(`
1135
+ `),
1136
+ imports: []
1137
+ };
1138
+ }
1139
+ convertField(schema, fieldName, required, parentName) {
1140
+ const type = jsonSchemaToType(schema, fieldName);
1141
+ const scalarType = getScalarType(schema);
1142
+ let enumValues;
1143
+ let nestedModel;
1144
+ if (!isReference2(schema)) {
1145
+ const schemaObj = schema;
1146
+ const enumArr = schemaObj["enum"];
1147
+ if (enumArr) {
1148
+ enumValues = enumArr.map(String);
1149
+ }
1150
+ if (schemaObj["type"] === "object" && !scalarType && schemaObj["properties"] && !enumValues) {
1151
+ const nestedName = (parentName ? parentName : "") + toPascalCase(fieldName);
1152
+ nestedModel = this.generateContractSpecSchema(schema, nestedName);
1153
+ type.type = nestedModel.name;
1154
+ type.isReference = true;
1155
+ }
1156
+ }
1157
+ return {
1158
+ name: fieldName,
1159
+ type: {
1160
+ ...type,
1161
+ optional: !required || type.optional,
1162
+ description: !isReference2(schema) ? schema["description"] : undefined
1163
+ },
1164
+ scalarType,
1165
+ enumValues,
1166
+ nestedModel
1167
+ };
1168
+ }
1169
+ generateFieldCodeHelper(field, indent) {
1170
+ const spaces = " ".repeat(indent);
1171
+ const lines = [];
1172
+ const isIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(field.name);
1173
+ const safeKey = isIdentifier ? field.name : `'${field.name}'`;
1174
+ lines.push(`${spaces}${safeKey}: {`);
1175
+ if (field.enumValues) {
1176
+ const enumName = toPascalCase(field.name) + "Enum";
1177
+ lines.push(`${spaces} type: new EnumType('${enumName}', [${field.enumValues.map((v) => `'${v}'`).join(", ")}]),`);
1178
+ } else if (field.scalarType) {
1179
+ lines.push(`${spaces} type: ${field.scalarType}(),`);
1180
+ } else if (field.nestedModel) {
1181
+ lines.push(`${spaces} type: ${field.nestedModel.name},`);
1182
+ } else if (field.type.primitive) {
1183
+ const fallbackScalar = field.type.type === "number" ? "ScalarTypeEnum.Float_unsecure" : field.type.type === "boolean" ? "ScalarTypeEnum.Boolean_unsecure" : "ScalarTypeEnum.String_unsecure";
1184
+ lines.push(`${spaces} type: ${fallbackScalar}(),`);
1185
+ } else if (field.type.isReference) {
1186
+ lines.push(`${spaces} type: ${field.type.type},`);
1187
+ } else {
1188
+ lines.push(`${spaces} type: ScalarTypeEnum.JSONObject(), // TODO: Define nested model for ${field.type.type}`);
1189
+ }
1190
+ lines.push(`${spaces} isOptional: ${field.type.optional},`);
1191
+ if (field.type.array) {
1192
+ lines.push(`${spaces} isArray: true,`);
1193
+ }
1194
+ lines.push(`${spaces}},`);
1195
+ return lines.join(`
1196
+ `);
1197
+ }
1198
+ }
1199
+
1200
+ class ZodSchemaGenerator {
1201
+ format = "zod";
1202
+ config;
1203
+ constructor(config) {
1204
+ this.config = config;
1205
+ }
1206
+ generateModel(schema, name, options) {
1207
+ const schemaObj = schema;
1208
+ const properties = schemaObj["properties"];
1209
+ const _required = schemaObj["required"] ?? [];
1210
+ const description = options?.description ?? schemaObj["description"];
1211
+ const lines = [];
1212
+ if (description) {
1213
+ lines.push(`/**`);
1214
+ lines.push(` * ${description}`);
1215
+ lines.push(` */`);
1216
+ }
1217
+ const schemaName = `${name}Schema`;
1218
+ let schemaCode;
1219
+ if (properties) {
1220
+ schemaCode = this.generateZodObject(schemaObj);
1221
+ } else {
1222
+ schemaCode = "z.object({})";
1223
+ }
1224
+ lines.push(`export const ${schemaName} = ${schemaCode};`);
1225
+ lines.push(``);
1226
+ lines.push(`export const ${name} = new ZodSchemaType(${schemaName}, { name: '${name}', description: ${JSON.stringify(description)} });`);
1227
+ lines.push(``);
1228
+ lines.push(`export type ${name} = z.infer<typeof ${schemaName}>;`);
1229
+ return {
1230
+ code: lines.join(`
1231
+ `),
1232
+ fileName: toKebabCase(name) + ".ts",
1233
+ imports: this.getBaseImports(),
1234
+ name
1235
+ };
1236
+ }
1237
+ generateField(schema, _fieldName, required) {
1238
+ const schemaObj = schema;
1239
+ const type = schemaObj["type"];
1240
+ const format = schemaObj["format"];
1241
+ const nullable = schemaObj["nullable"];
1242
+ let zodType;
1243
+ if (type === "object" && schemaObj["properties"]) {
1244
+ zodType = this.generateZodObject(schemaObj);
1245
+ } else {
1246
+ zodType = this.mapTypeToZod(type, format);
1247
+ if (schemaObj["enum"]) {
1248
+ const enumValues = schemaObj["enum"];
1249
+ zodType = `z.enum([${enumValues.map((v) => `'${v}'`).join(", ")}])`;
1250
+ }
1251
+ if (type === "array") {
1252
+ const items = schemaObj["items"];
1253
+ if (items) {
1254
+ const itemField = this.generateField(items, "item", true);
1255
+ zodType = `z.array(${itemField.code.replace(".optional()", "")})`;
1256
+ } else {
1257
+ zodType = "z.array(z.unknown())";
1258
+ }
1259
+ }
1260
+ }
1261
+ if (type === "string") {
1262
+ if (schemaObj["minLength"] !== undefined)
1263
+ zodType += `.min(${schemaObj["minLength"]})`;
1264
+ if (schemaObj["maxLength"] !== undefined)
1265
+ zodType += `.max(${schemaObj["maxLength"]})`;
1266
+ if (schemaObj["pattern"] !== undefined)
1267
+ zodType += `.regex(/${schemaObj["pattern"]}/)`;
1268
+ } else if (type === "integer" || type === "number") {
1269
+ if (schemaObj["minimum"] !== undefined) {
1270
+ zodType += schemaObj["exclusiveMinimum"] === true ? `.gt(${schemaObj["minimum"]})` : `.min(${schemaObj["minimum"]})`;
1271
+ } else if (typeof schemaObj["exclusiveMinimum"] === "number") {
1272
+ zodType += `.gt(${schemaObj["exclusiveMinimum"]})`;
1273
+ }
1274
+ if (schemaObj["maximum"] !== undefined) {
1275
+ zodType += schemaObj["exclusiveMaximum"] === true ? `.lt(${schemaObj["maximum"]})` : `.max(${schemaObj["maximum"]})`;
1276
+ } else if (typeof schemaObj["exclusiveMaximum"] === "number") {
1277
+ zodType += `.lt(${schemaObj["exclusiveMaximum"]})`;
1278
+ }
1279
+ if (schemaObj["multipleOf"] !== undefined) {
1280
+ zodType += `.step(${schemaObj["multipleOf"]})`;
1281
+ }
1282
+ } else if (type === "array") {
1283
+ if (schemaObj["minItems"] !== undefined)
1284
+ zodType += `.min(${schemaObj["minItems"]})`;
1285
+ if (schemaObj["maxItems"] !== undefined)
1286
+ zodType += `.max(${schemaObj["maxItems"]})`;
1287
+ }
1288
+ if (schemaObj["default"] !== undefined) {
1289
+ zodType += `.default(${JSON.stringify(schemaObj["default"])})`;
1290
+ }
1291
+ if (!required || nullable) {
1292
+ zodType = `${zodType}.optional()`;
1293
+ }
1294
+ return {
1295
+ code: zodType,
1296
+ typeRef: type ?? "unknown",
1297
+ isOptional: !required || Boolean(nullable),
1298
+ isArray: type === "array"
1299
+ };
1300
+ }
1301
+ getBaseImports() {
1302
+ return [
1303
+ "import * as z from 'zod';",
1304
+ "import { ZodSchemaType } from '@contractspec/lib.schema';"
1305
+ ];
1306
+ }
1307
+ generateZodObject(schemaObj) {
1308
+ const required = schemaObj["required"] ?? [];
1309
+ const properties = schemaObj["properties"];
1310
+ const lines = ["z.object({"];
1311
+ for (const [propName, propSchema] of Object.entries(properties)) {
1312
+ const isRequired = required.includes(propName);
1313
+ const field = this.generateField(propSchema, propName, isRequired);
1314
+ const safeName = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(propName) ? propName : `'${propName}'`;
1315
+ lines.push(` ${safeName}: ${field.code},`);
1316
+ }
1317
+ lines.push("})");
1318
+ return lines.join(`
1319
+ `);
1320
+ }
1321
+ mapTypeToZod(type, format) {
1322
+ if (format === "date-time")
1323
+ return "z.string().datetime()";
1324
+ if (format === "date")
1325
+ return "z.string().date()";
1326
+ if (format === "email")
1327
+ return "z.string().email()";
1328
+ if (format === "uri" || format === "url")
1329
+ return "z.string().url()";
1330
+ if (format === "uuid")
1331
+ return "z.string().uuid()";
1332
+ switch (type) {
1333
+ case "string":
1334
+ return "z.string()";
1335
+ case "integer":
1336
+ return "z.number().int()";
1337
+ case "number":
1338
+ return "z.number()";
1339
+ case "boolean":
1340
+ return "z.boolean()";
1341
+ case "object":
1342
+ return "z.record(z.string(), z.unknown())";
1343
+ case "null":
1344
+ return "z.null()";
1345
+ default:
1346
+ return "z.unknown()";
1347
+ }
1348
+ }
1349
+ }
1350
+
1351
+ class JsonSchemaGenerator {
1352
+ format = "json-schema";
1353
+ config;
1354
+ constructor(config) {
1355
+ this.config = config;
1356
+ }
1357
+ generateModel(schema, name, options) {
1358
+ const schemaObj = schema;
1359
+ const description = options?.description ?? schemaObj["description"];
1360
+ const jsonSchema = {
1361
+ $schema: "https://json-schema.org/draft/2020-12/schema",
1362
+ title: name,
1363
+ ...schemaObj
1364
+ };
1365
+ if (description) {
1366
+ jsonSchema["description"] = description;
1367
+ }
1368
+ const lines = [];
1369
+ lines.push(`/**`);
1370
+ lines.push(` * JSON Schema: ${name}`);
1371
+ if (description) {
1372
+ lines.push(` * ${description}`);
1373
+ }
1374
+ lines.push(` */`);
1375
+ const schemaName = `${name}Schema`;
1376
+ lines.push(`export const ${schemaName} = ${JSON.stringify(jsonSchema, null, 2)} as const;`);
1377
+ lines.push(``);
1378
+ lines.push(`export const ${name} = new JsonSchemaType(${schemaName});`);
1379
+ lines.push(``);
1380
+ lines.push(`export type ${name} = unknown; // JSON Schema type inference not fully supported`);
1381
+ return {
1382
+ code: lines.join(`
1383
+ `),
1384
+ fileName: toKebabCase(name) + ".ts",
1385
+ imports: this.getBaseImports(),
1386
+ name
1387
+ };
1388
+ }
1389
+ generateField(schema, _fieldName, required) {
1390
+ const schemaObj = schema;
1391
+ const type = schemaObj["type"];
1392
+ const nullable = schemaObj["nullable"];
1393
+ return {
1394
+ code: JSON.stringify(schemaObj),
1395
+ typeRef: type ?? "unknown",
1396
+ isOptional: !required || Boolean(nullable),
1397
+ isArray: type === "array"
1398
+ };
1399
+ }
1400
+ getBaseImports() {
1401
+ return ["import { JsonSchemaType } from '@contractspec/lib.schema';"];
1402
+ }
1403
+ }
1404
+
1405
+ class GraphQLSchemaGenerator {
1406
+ format = "graphql";
1407
+ config;
1408
+ constructor(config) {
1409
+ this.config = config;
1410
+ }
1411
+ generateModel(schema, name, options) {
1412
+ const schemaObj = schema;
1413
+ const properties = schemaObj["properties"];
1414
+ const required = schemaObj["required"] ?? [];
1415
+ const description = options?.description ?? schemaObj["description"];
1416
+ const lines = [];
1417
+ if (description) {
1418
+ lines.push(`"""${description}"""`);
1419
+ }
1420
+ lines.push(`type ${name} {`);
1421
+ if (properties) {
1422
+ for (const [propName, propSchema] of Object.entries(properties)) {
1423
+ const isRequired = required.includes(propName);
1424
+ const field = this.generateField(propSchema, propName, isRequired);
1425
+ const nullMarker = isRequired ? "!" : "";
1426
+ lines.push(` ${propName}: ${field.typeRef}${nullMarker}`);
1427
+ }
1428
+ }
1429
+ lines.push(`}`);
1430
+ const sdl = lines.join(`
1431
+ `);
1432
+ const tsLines = [];
1433
+ tsLines.push(`/**`);
1434
+ tsLines.push(` * GraphQL type definition: ${name}`);
1435
+ tsLines.push(` */`);
1436
+ tsLines.push(`export const ${name}TypeDef = \`${sdl}\`;`);
1437
+ tsLines.push(``);
1438
+ tsLines.push(`export const ${name} = new GraphQLSchemaType(${name}TypeDef, '${name}');`);
1439
+ return {
1440
+ code: tsLines.join(`
1441
+ `),
1442
+ fileName: toKebabCase(name) + ".ts",
1443
+ imports: this.getBaseImports(),
1444
+ name
1445
+ };
1446
+ }
1447
+ generateField(schema, _fieldName, required) {
1448
+ const schemaObj = schema;
1449
+ const type = schemaObj["type"];
1450
+ const format = schemaObj["format"];
1451
+ const nullable = schemaObj["nullable"];
1452
+ const gqlType = this.mapTypeToGraphQL(type, format);
1453
+ return {
1454
+ code: gqlType,
1455
+ typeRef: gqlType,
1456
+ isOptional: !required || Boolean(nullable),
1457
+ isArray: type === "array"
1458
+ };
1459
+ }
1460
+ getBaseImports() {
1461
+ return ["import { GraphQLSchemaType } from '@contractspec/lib.schema';"];
1462
+ }
1463
+ mapTypeToGraphQL(type, format) {
1464
+ if (format === "date-time")
1465
+ return "DateTime";
1466
+ if (format === "date")
1467
+ return "Date";
1468
+ if (format === "email")
1469
+ return "String";
1470
+ if (format === "uri" || format === "url")
1471
+ return "String";
1472
+ if (format === "uuid")
1473
+ return "ID";
1474
+ switch (type) {
1475
+ case "string":
1476
+ return "String";
1477
+ case "integer":
1478
+ return "Int";
1479
+ case "number":
1480
+ return "Float";
1481
+ case "boolean":
1482
+ return "Boolean";
1483
+ case "object":
1484
+ return "JSON";
1485
+ case "array":
1486
+ return "[JSON]";
1487
+ default:
1488
+ return "JSON";
1489
+ }
1490
+ }
1491
+ }
1492
+
1493
+ // src/openapi/schema-converter.ts
1494
+ var JSON_SCHEMA_TO_SCALAR2 = {
1495
+ string: "ScalarTypeEnum.String_unsecure",
1496
+ integer: "ScalarTypeEnum.Int_unsecure",
1497
+ number: "ScalarTypeEnum.Float_unsecure",
1498
+ boolean: "ScalarTypeEnum.Boolean",
1499
+ "string:date": "ScalarTypeEnum.Date",
1500
+ "string:date-time": "ScalarTypeEnum.DateTime",
1501
+ "string:email": "ScalarTypeEnum.EmailAddress",
1502
+ "string:uri": "ScalarTypeEnum.URL",
1503
+ "string:uuid": "ScalarTypeEnum.ID"
1504
+ };
1505
+ function isReference3(schema) {
1506
+ return typeof schema === "object" && schema !== null && "$ref" in schema;
1507
+ }
1508
+ function typeNameFromRef2(ref) {
1509
+ const parts = ref.split("/");
1510
+ return parts[parts.length - 1] ?? "Unknown";
1511
+ }
1512
+ function jsonSchemaToType(schema, name) {
1513
+ if (isReference3(schema)) {
1514
+ return {
1515
+ type: toPascalCase(typeNameFromRef2(schema.$ref)),
1516
+ optional: false,
1517
+ array: false,
1518
+ primitive: false,
1519
+ isReference: true
1520
+ };
1521
+ }
1522
+ const schemaObj = schema;
1523
+ const type = schemaObj["type"];
1524
+ const format = schemaObj["format"];
1525
+ const nullable = schemaObj["nullable"];
1526
+ const originalTypeName = schemaObj["_originalTypeName"];
1527
+ if (originalTypeName) {
1528
+ return {
1529
+ type: toPascalCase(originalTypeName),
1530
+ optional: nullable ?? false,
1531
+ array: false,
1532
+ primitive: false,
1533
+ isReference: true
1534
+ };
1535
+ }
1536
+ if (type === "array") {
1537
+ const items = schemaObj["items"];
1538
+ if (items) {
1539
+ const itemType = jsonSchemaToType(items, name);
1540
+ return {
1541
+ ...itemType,
1542
+ array: true,
1543
+ optional: nullable ?? false
1544
+ };
1545
+ }
1546
+ return {
1547
+ type: "unknown",
1548
+ optional: nullable ?? false,
1549
+ array: true,
1550
+ primitive: false,
1551
+ isReference: true
1552
+ };
1553
+ }
1554
+ if (type === "object" || schemaObj["properties"]) {
1555
+ return {
1556
+ type: name ? toPascalCase(name) : "Record<string, unknown>",
1557
+ optional: nullable ?? false,
1558
+ array: false,
1559
+ primitive: false,
1560
+ isReference: true
1561
+ };
1562
+ }
1563
+ if (schemaObj["enum"]) {
1564
+ return {
1565
+ type: name ? toPascalCase(name) : "string",
1566
+ optional: nullable ?? false,
1567
+ array: false,
1568
+ primitive: false,
1569
+ isReference: true
1570
+ };
1571
+ }
1572
+ const scalarKey = format ? `${type}:${format}` : type;
1573
+ if (scalarKey === "string") {
1574
+ return {
1575
+ type: "string",
1576
+ optional: nullable ?? false,
1577
+ array: false,
1578
+ primitive: true
1579
+ };
1580
+ }
1581
+ if (scalarKey === "integer" || type === "number") {
1582
+ return {
1583
+ type: "number",
1584
+ optional: nullable ?? false,
1585
+ array: false,
1586
+ primitive: true
1587
+ };
1588
+ }
1589
+ if (scalarKey === "boolean") {
1590
+ return {
1591
+ type: "boolean",
1592
+ optional: nullable ?? false,
1593
+ array: false,
1594
+ primitive: true
1595
+ };
1596
+ }
1597
+ return {
1598
+ type: "unknown",
1599
+ optional: nullable ?? false,
1600
+ array: false,
1601
+ primitive: false,
1602
+ isReference: true
1603
+ };
1604
+ }
1605
+ function getScalarType(schema) {
1606
+ if (isReference3(schema)) {
1607
+ return;
1608
+ }
1609
+ const schemaObj = schema;
1610
+ const type = schemaObj["type"];
1611
+ const format = schemaObj["format"];
1612
+ if (!type)
1613
+ return;
1614
+ if (type === "array") {
1615
+ const items = schemaObj["items"];
1616
+ if (items) {
1617
+ return getScalarType(items);
1618
+ }
1619
+ return;
1620
+ }
1621
+ const key = format ? `${type}:${format}` : type;
1622
+ return JSON_SCHEMA_TO_SCALAR2[key] ?? JSON_SCHEMA_TO_SCALAR2[type] ?? undefined;
1623
+ }
1624
+ function generateSchemaModelCode(schema, modelName, schemaFormat = "contractspec", config) {
1625
+ const generator = createSchemaGenerator(schemaFormat, config);
1626
+ const result = generator.generateModel(schema, modelName, {
1627
+ description: schema["description"]
1628
+ });
1629
+ return {
1630
+ name: result.name,
1631
+ description: schema["description"],
1632
+ fields: [],
1633
+ code: result.code,
1634
+ imports: result.imports
1635
+ };
1636
+ }
1637
+ function generateImports(fields, options, sameDirectory = true) {
1638
+ const imports = new Set;
1639
+ const modelsDir = sameDirectory ? "." : `../${options.conventions.models}`;
1640
+ imports.add("import { defineSchemaModel, ScalarTypeEnum, EnumType } from '@contractspec/lib.schema';");
1641
+ for (const field of fields) {
1642
+ if (field.type.isReference && !field.type.primitive && !field.enumValues && !field.scalarType && !field.nestedModel) {
1643
+ const modelName = field.type.type;
1644
+ const kebabName = modelName.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
1645
+ imports.add(`import { ${modelName} } from '${modelsDir}/${kebabName}';`);
1646
+ }
1647
+ }
1648
+ return Array.from(imports).join(`
1649
+ `);
1650
+ }
1651
+
1652
+ // src/openapi/importer/schemas.ts
1653
+ function buildInputSchemas(operation2) {
1654
+ const result = {};
1655
+ if (operation2.pathParams.length > 0) {
1656
+ result.params = {
1657
+ type: "object",
1658
+ properties: operation2.pathParams.reduce((acc, p) => {
1659
+ acc[p.name] = p.schema;
1660
+ return acc;
1661
+ }, {}),
1662
+ required: operation2.pathParams.map((p) => p.name)
1663
+ };
1664
+ }
1665
+ if (operation2.queryParams.length > 0) {
1666
+ result.query = {
1667
+ type: "object",
1668
+ properties: operation2.queryParams.reduce((acc, p) => {
1669
+ acc[p.name] = p.schema;
1670
+ return acc;
1671
+ }, {}),
1672
+ required: operation2.queryParams.filter((p) => p.required).map((p) => p.name)
1673
+ };
1674
+ }
1675
+ const excludedHeaders = [
1676
+ "authorization",
1677
+ "content-type",
1678
+ "accept",
1679
+ "user-agent"
1680
+ ];
1681
+ const actualHeaders = operation2.headerParams.filter((p) => !excludedHeaders.includes(p.name.toLowerCase()));
1682
+ if (actualHeaders.length > 0) {
1683
+ result.headers = {
1684
+ type: "object",
1685
+ properties: actualHeaders.reduce((acc, p) => {
1686
+ acc[p.name] = p.schema;
1687
+ return acc;
1688
+ }, {}),
1689
+ required: actualHeaders.filter((p) => p.required).map((p) => p.name)
1690
+ };
1691
+ }
1692
+ if (operation2.requestBody?.schema) {
1693
+ result.body = operation2.requestBody.schema;
1694
+ }
1695
+ return result;
1696
+ }
1697
+ function getOutputSchema(operation2) {
1698
+ const successCodes = ["200", "201", "202", "204"];
1699
+ for (const code of successCodes) {
1700
+ const response = operation2.responses[code];
1701
+ if (response?.schema) {
1702
+ return response.schema;
1703
+ }
1704
+ }
1705
+ for (const [code, response] of Object.entries(operation2.responses)) {
1706
+ if (code.startsWith("2") && response.schema) {
1707
+ return response.schema;
1708
+ }
1709
+ }
1710
+ return null;
1711
+ }
1712
+
1713
+ // src/openapi/importer/analyzer.ts
1714
+ var COMMAND_METHODS = ["post", "put", "delete", "patch"];
1715
+ function inferOpKind(method) {
1716
+ return COMMAND_METHODS.includes(method.toLowerCase()) ? "command" : "query";
1717
+ }
1718
+ function inferAuthLevel(operation2, defaultAuth) {
1719
+ if (!operation2.security || operation2.security.length === 0) {
1720
+ return defaultAuth;
1721
+ }
1722
+ for (const sec of operation2.security) {
1723
+ if (Object.keys(sec).length === 0) {
1724
+ return "anonymous";
1725
+ }
1726
+ }
1727
+ return "user";
1728
+ }
1729
+
1730
+ // src/openapi/importer/generator.ts
1731
+ function generateSpecCode(operation2, contractspecConfig, options = {}, inputModel, outputModel, queryModel = null, paramsModel = null, headersModel = null) {
1732
+ const specKey = toSpecKey(operation2.operationId, options.prefix);
1733
+ const kind = inferOpKind(operation2.method);
1734
+ const auth = inferAuthLevel(operation2, options.defaultAuth ?? "user");
1735
+ const lines = [];
1736
+ lines.push("import { defineCommand, defineQuery } from '@contractspec/lib.contracts';");
1737
+ if (inputModel || outputModel || queryModel || paramsModel || headersModel) {
1738
+ const collectedImports = new Set;
1739
+ const models = [
1740
+ inputModel,
1741
+ outputModel,
1742
+ queryModel,
1743
+ paramsModel,
1744
+ headersModel
1745
+ ].filter((m) => !!m);
1746
+ models.forEach((m) => {
1747
+ if (m.imports && m.imports.length > 0) {
1748
+ m.imports.forEach((i) => collectedImports.add(i));
1749
+ }
1750
+ });
1751
+ const legacyModels = models.filter((m) => !m.imports || m.imports.length === 0);
1752
+ const legacyFields = legacyModels.flatMap((m) => m.fields);
1753
+ if (legacyFields.length > 0) {
1754
+ const legacyImportStr = generateImports(legacyFields, contractspecConfig, false);
1755
+ legacyImportStr.split(`
1756
+ `).filter(Boolean).forEach((i) => collectedImports.add(i));
1757
+ }
1758
+ if (collectedImports.size > 0) {
1759
+ lines.push(Array.from(collectedImports).sort().join(`
1760
+ `));
1761
+ }
1762
+ }
1763
+ lines.push("");
1764
+ const schemaSections = [
1765
+ { label: "Input schema", model: inputModel },
1766
+ { label: "Query schema", model: queryModel },
1767
+ { label: "Path schema", model: paramsModel },
1768
+ { label: "Header schema", model: headersModel },
1769
+ { label: "Output schema", model: outputModel }
1770
+ ];
1771
+ for (const section of schemaSections) {
1772
+ if (section.model && section.model.code) {
1773
+ lines.push(`// ${section.label}`);
1774
+ lines.push(section.model.code);
1775
+ lines.push("");
1776
+ }
1777
+ }
1778
+ const defineFunc = kind === "command" ? "defineCommand" : "defineQuery";
1779
+ const safeName = toValidIdentifier(toPascalCase(operation2.operationId));
1780
+ lines.push(`/**`);
1781
+ lines.push(` * ${operation2.summary ?? operation2.operationId}`);
1782
+ if (operation2.description) {
1783
+ lines.push(` *`);
1784
+ lines.push(` * ${operation2.description}`);
1785
+ }
1786
+ lines.push(` *`);
1787
+ lines.push(` * @source OpenAPI: ${operation2.method.toUpperCase()} ${operation2.path}`);
1788
+ lines.push(` */`);
1789
+ lines.push(`export const ${safeName}Spec = ${defineFunc}({`);
1790
+ lines.push(" meta: {");
1791
+ lines.push(` key: '${specKey}',`);
1792
+ lines.push(" version: '1.0.0',");
1793
+ lines.push(` stability: '${options.defaultStability ?? "stable"}',`);
1794
+ lines.push(` owners: [${(options.defaultOwners ?? []).map((o) => `'${o}'`).join(", ")}],`);
1795
+ lines.push(` tags: [${operation2.tags.map((t) => `'${t}'`).join(", ")}],`);
1796
+ lines.push(` description: ${JSON.stringify(operation2.summary ?? operation2.operationId)},`);
1797
+ lines.push(` goal: ${JSON.stringify(operation2.description ?? "Imported from OpenAPI")},`);
1798
+ lines.push(` context: 'Imported from OpenAPI: ${operation2.method.toUpperCase()} ${operation2.path}',`);
1799
+ lines.push(" },");
1800
+ lines.push(" io: {");
1801
+ lines.push(` input: ${inputModel?.name ?? "null"},`);
1802
+ if (queryModel)
1803
+ lines.push(` query: ${queryModel.name},`);
1804
+ if (paramsModel)
1805
+ lines.push(` params: ${paramsModel.name},`);
1806
+ if (headersModel)
1807
+ lines.push(` headers: ${headersModel.name},`);
1808
+ if (outputModel) {
1809
+ lines.push(` output: ${outputModel.name},`);
1810
+ } else {
1811
+ lines.push(" output: null, // TODO: Define output schema");
1812
+ }
1813
+ lines.push(" },");
1814
+ lines.push(" policy: {");
1815
+ lines.push(` auth: '${auth}',`);
1816
+ lines.push(" },");
1817
+ const httpMethod = operation2.method.toUpperCase();
1818
+ const restMethod = httpMethod === "GET" ? "GET" : "POST";
1819
+ lines.push(" transport: {");
1820
+ lines.push(" rest: {");
1821
+ lines.push(` method: '${restMethod}',`);
1822
+ lines.push(` path: '${operation2.path}',`);
1823
+ lines.push(" },");
1824
+ lines.push(" },");
1825
+ lines.push("});");
1826
+ return lines.join(`
1827
+ `);
1828
+ }
1829
+
1830
+ // src/openapi/importer/models.ts
1831
+ function generateModelCode(name, schema, options) {
1832
+ const modelName = toPascalCase(toValidIdentifier(name));
1833
+ const schemaFormat = options.schemaFormat || "contractspec";
1834
+ const model = generateSchemaModelCode(schema, modelName, schemaFormat, options);
1835
+ let imports = "";
1836
+ if (model.imports && model.imports.length > 0) {
1837
+ imports = model.imports.join(`
1838
+ `);
1839
+ } else if (model.fields.length > 0) {
1840
+ imports = generateImports(model.fields, options);
1841
+ }
1842
+ return `
1843
+ ${imports}
1844
+
1845
+ ${model.code}
1846
+ `.trim();
1847
+ }
1848
+
1849
+ // src/openapi/importer/events.ts
1850
+ function generateEventCode(event, options) {
1851
+ const eventName = toValidIdentifier(event.name);
1852
+ const modelName = toPascalCase(eventName) + "Payload";
1853
+ const schemaFormat = options.schemaFormat || "contractspec";
1854
+ const payloadModel = generateSchemaModelCode(event.payload, modelName, schemaFormat, options);
1855
+ const imports = new Set;
1856
+ imports.add("import { defineEvent, type EventSpec } from '@contractspec/lib.contracts';");
1857
+ if (payloadModel.imports && payloadModel.imports.length > 0) {
1858
+ payloadModel.imports.forEach((i) => imports.add(i));
1859
+ } else if (payloadModel.fields && payloadModel.fields.length > 0) {
1860
+ const modelImports = generateImports(payloadModel.fields, options);
1861
+ modelImports.split(`
1862
+ `).filter(Boolean).forEach((i) => imports.add(i));
1863
+ }
1864
+ if (payloadModel.name !== modelName) {
1865
+ const modelsDir = `../${options.conventions.models}`;
1866
+ const kebabName = payloadModel.name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
1867
+ imports.add(`import { ${payloadModel.name} } from '${modelsDir}/${kebabName}';`);
1868
+ }
1869
+ const allImports = Array.from(imports).join(`
1870
+ `);
1871
+ return `
1872
+ ${allImports}
1873
+
1874
+ ${payloadModel.code}
1875
+
1876
+ export const ${eventName} = defineEvent({
1877
+ meta: {
1878
+ key: '${event.name}',
1879
+ version: '1.0.0',
1880
+ description: ${JSON.stringify(event.description ?? "")},
1881
+ },
1882
+ payload: ${payloadModel.name},
1883
+ });
1884
+ `.trim();
1885
+ }
1886
+
1887
+ // src/openapi/importer/grouping.ts
1888
+ function resolveOperationGroupFolder(operation2, conventions) {
1889
+ const groupingRule = conventions.operationsGrouping;
1890
+ if (!groupingRule || groupingRule.strategy === "none") {
1891
+ return "";
1892
+ }
1893
+ return applyGroupingStrategy(groupingRule, {
1894
+ name: operation2.operationId,
1895
+ tags: operation2.tags,
1896
+ path: operation2.path
1897
+ });
1898
+ }
1899
+ function resolveModelGroupFolder(modelName, conventions, relatedPath, relatedTags) {
1900
+ const groupingRule = conventions.modelsGrouping;
1901
+ if (!groupingRule || groupingRule.strategy === "none") {
1902
+ return "";
1903
+ }
1904
+ return applyGroupingStrategy(groupingRule, {
1905
+ name: modelName,
1906
+ tags: relatedTags ?? [],
1907
+ path: relatedPath
1908
+ });
1909
+ }
1910
+ function resolveEventGroupFolder(eventName, conventions, relatedTags) {
1911
+ const groupingRule = conventions.eventsGrouping;
1912
+ if (!groupingRule || groupingRule.strategy === "none") {
1913
+ return "";
1914
+ }
1915
+ return applyGroupingStrategy(groupingRule, {
1916
+ name: eventName,
1917
+ tags: relatedTags ?? []
1918
+ });
1919
+ }
1920
+ function applyGroupingStrategy(rule, context) {
1921
+ switch (rule.strategy) {
1922
+ case "by-tag":
1923
+ return context.tags?.[0] ?? "untagged";
1924
+ case "by-owner":
1925
+ return context.owners?.[0] ?? "unowned";
1926
+ case "by-domain":
1927
+ return extractDomain(context.name);
1928
+ case "by-url-path-single":
1929
+ return extractUrlPathLevel(context.path, 1);
1930
+ case "by-url-path-multi":
1931
+ return extractUrlPathLevel(context.path, rule.urlPathLevel ?? 2);
1932
+ case "by-feature":
1933
+ return extractDomain(context.name);
1934
+ case "none":
1935
+ default:
1936
+ return "";
1937
+ }
1938
+ }
1939
+ function extractDomain(name) {
1940
+ if (name.includes(".")) {
1941
+ return name.split(".")[0] ?? "default";
1942
+ }
1943
+ if (name.includes("_")) {
1944
+ return name.split("_")[0] ?? "default";
1945
+ }
1946
+ const match = name.match(/^([a-z]+)/i);
1947
+ return match?.[1]?.toLowerCase() ?? "default";
1948
+ }
1949
+ function extractUrlPathLevel(path, level) {
1950
+ if (!path)
1951
+ return "root";
1952
+ const segments = path.split("/").filter(Boolean);
1953
+ const nonParamSegments = segments.filter((s) => !s.startsWith("{"));
1954
+ if (nonParamSegments.length === 0)
1955
+ return "root";
1956
+ return nonParamSegments.slice(0, level).join("/");
1957
+ }
1958
+
1959
+ // src/openapi/importer/index.ts
1960
+ var importFromOpenApi = (parseResult, contractspecOptions, importOptions = {}) => {
1961
+ const { tags, exclude = [], include } = importOptions;
1962
+ const specs = [];
1963
+ const skipped = [];
1964
+ const errors = [];
1965
+ for (const operation2 of parseResult.operations) {
1966
+ if (tags && tags.length > 0) {
1967
+ const hasMatchingTag = operation2.tags.some((t) => tags.includes(t));
1968
+ if (!hasMatchingTag) {
1969
+ skipped.push({
1970
+ sourceId: operation2.operationId,
1971
+ reason: `No matching tags (has: ${operation2.tags.join(", ")})`
1972
+ });
1973
+ continue;
1974
+ }
1975
+ }
1976
+ if (include && include.length > 0) {
1977
+ if (!include.includes(operation2.operationId)) {
1978
+ skipped.push({
1979
+ sourceId: operation2.operationId,
1980
+ reason: "Not in include list"
1981
+ });
1982
+ continue;
1983
+ }
1984
+ } else if (exclude.includes(operation2.operationId)) {
1985
+ skipped.push({
1986
+ sourceId: operation2.operationId,
1987
+ reason: "In exclude list"
1988
+ });
1989
+ continue;
1990
+ }
1991
+ if (operation2.deprecated && importOptions.defaultStability !== "deprecated") {
1992
+ skipped.push({
1993
+ sourceId: operation2.operationId,
1994
+ reason: "Deprecated operation"
1995
+ });
1996
+ continue;
1997
+ }
1998
+ try {
1999
+ const inputSchemas = buildInputSchemas(operation2);
2000
+ const schemaFormat = importOptions.schemaFormat || contractspecOptions.schemaFormat || "contractspec";
2001
+ const inputModel = inputSchemas.body ? generateSchemaModelCode(inputSchemas.body, `${operation2.operationId}Input`, schemaFormat, contractspecOptions) : null;
2002
+ const queryModel = inputSchemas.query ? generateSchemaModelCode(inputSchemas.query, `${operation2.operationId}Query`, schemaFormat, contractspecOptions) : null;
2003
+ const paramsModel = inputSchemas.params ? generateSchemaModelCode(inputSchemas.params, `${operation2.operationId}Params`, schemaFormat, contractspecOptions) : null;
2004
+ const headersModel = inputSchemas.headers ? generateSchemaModelCode(inputSchemas.headers, `${operation2.operationId}Headers`, schemaFormat, contractspecOptions) : null;
2005
+ const outputSchema = getOutputSchema(operation2);
2006
+ let outputModel = outputSchema ? generateSchemaModelCode(outputSchema, `${operation2.operationId}Output`, schemaFormat, contractspecOptions) : null;
2007
+ if (outputModel && schemaFormat === "contractspec" && !outputModel.code.includes("defineSchemaModel")) {
2008
+ outputModel = null;
2009
+ }
2010
+ const code = generateSpecCode(operation2, contractspecOptions, importOptions, inputModel, outputModel, queryModel, paramsModel, headersModel);
2011
+ const specName = toSpecKey(operation2.operationId, importOptions.prefix);
2012
+ const fileName = toFileName(specName);
2013
+ const transportHints = {
2014
+ rest: {
2015
+ method: operation2.method.toUpperCase(),
2016
+ path: operation2.path,
2017
+ params: {
2018
+ path: operation2.pathParams.map((p) => p.name),
2019
+ query: operation2.queryParams.map((p) => p.name),
2020
+ header: operation2.headerParams.map((p) => p.name),
2021
+ cookie: operation2.cookieParams.map((p) => p.name)
2022
+ }
2023
+ }
2024
+ };
2025
+ const source = {
2026
+ type: "openapi",
2027
+ sourceId: operation2.operationId,
2028
+ operationId: operation2.operationId,
2029
+ openApiVersion: parseResult.version,
2030
+ importedAt: new Date
2031
+ };
2032
+ const groupFolder = resolveOperationGroupFolder(operation2, contractspecOptions.conventions);
2033
+ specs.push({
2034
+ code,
2035
+ fileName,
2036
+ groupFolder: groupFolder || undefined,
2037
+ source,
2038
+ transportHints
2039
+ });
2040
+ } catch (error) {
2041
+ errors.push({
2042
+ sourceId: operation2.operationId,
2043
+ error: error instanceof Error ? error.message : String(error)
2044
+ });
2045
+ }
2046
+ }
2047
+ for (const [name, schema] of Object.entries(parseResult.schemas)) {
2048
+ try {
2049
+ const code = generateModelCode(name, schema, {
2050
+ ...contractspecOptions,
2051
+ schemaFormat: importOptions.schemaFormat || contractspecOptions.schemaFormat
2052
+ });
2053
+ const fileName = toFileName(toSpecKey(name, importOptions.prefix));
2054
+ const groupFolder = resolveModelGroupFolder(name, contractspecOptions.conventions);
2055
+ specs.push({
2056
+ code,
2057
+ fileName,
2058
+ groupFolder: groupFolder || undefined,
2059
+ source: {
2060
+ type: "openapi",
2061
+ sourceId: name,
2062
+ operationId: name,
2063
+ openApiVersion: parseResult.version,
2064
+ importedAt: new Date
2065
+ },
2066
+ transportHints: {}
2067
+ });
2068
+ } catch (error) {
2069
+ errors.push({
2070
+ sourceId: name,
2071
+ error: error instanceof Error ? "Model conversion failed: " + error.message : String(error)
2072
+ });
2073
+ }
2074
+ }
2075
+ for (const event of parseResult.events) {
2076
+ try {
2077
+ const code = generateEventCode(event, {
2078
+ ...contractspecOptions,
2079
+ schemaFormat: importOptions.schemaFormat || contractspecOptions.schemaFormat
2080
+ });
2081
+ const fileName = toFileName(toSpecKey(event.name, importOptions.prefix));
2082
+ const groupFolder = resolveEventGroupFolder(event.name, contractspecOptions.conventions);
2083
+ specs.push({
2084
+ code,
2085
+ fileName,
2086
+ groupFolder: groupFolder || undefined,
2087
+ source: {
2088
+ type: "openapi",
2089
+ sourceId: event.name,
2090
+ operationId: event.name,
2091
+ openApiVersion: parseResult.version,
2092
+ importedAt: new Date
2093
+ },
2094
+ transportHints: {}
2095
+ });
2096
+ } catch (error) {
2097
+ errors.push({
2098
+ sourceId: event.name,
2099
+ error: error instanceof Error ? "Event conversion failed: " + error.message : String(error)
2100
+ });
2101
+ }
2102
+ }
2103
+ return {
2104
+ operationSpecs: specs,
2105
+ skipped,
2106
+ errors,
2107
+ summary: {
2108
+ total: parseResult.operations.length + Object.keys(parseResult.schemas).length + parseResult.events.length,
2109
+ imported: specs.length,
2110
+ skipped: skipped.length,
2111
+ errors: errors.length
2112
+ }
2113
+ };
2114
+ };
2115
+ function importOperation(operation2, options = {}, contractspecOptions) {
2116
+ const inputSchemas = buildInputSchemas(operation2);
2117
+ const schemaFormat = options.schemaFormat || contractspecOptions.schemaFormat || "contractspec";
2118
+ const inputModel = inputSchemas.body ? generateSchemaModelCode(inputSchemas.body, `${operation2.operationId}Input`, schemaFormat, contractspecOptions) : null;
2119
+ const queryModel = inputSchemas.query ? generateSchemaModelCode(inputSchemas.query, `${operation2.operationId}Query`, schemaFormat, contractspecOptions) : null;
2120
+ const paramsModel = inputSchemas.params ? generateSchemaModelCode(inputSchemas.params, `${operation2.operationId}Params`, schemaFormat, contractspecOptions) : null;
2121
+ const headersModel = inputSchemas.headers ? generateSchemaModelCode(inputSchemas.headers, `${operation2.operationId}Headers`, schemaFormat, contractspecOptions) : null;
2122
+ const outputSchema = getOutputSchema(operation2);
2123
+ const outputModel = outputSchema ? generateSchemaModelCode(outputSchema, `${operation2.operationId}Output`, schemaFormat, contractspecOptions) : null;
2124
+ return generateSpecCode(operation2, contractspecOptions, options, inputModel, outputModel, queryModel, paramsModel, headersModel);
2125
+ }
2126
+ // src/openapi/differ.ts
2127
+ function compareValues(path, oldValue, newValue, description) {
2128
+ if (deepEqual(oldValue, newValue)) {
2129
+ return null;
2130
+ }
2131
+ let changeType = "modified";
2132
+ if (oldValue === undefined || oldValue === null) {
2133
+ changeType = "added";
2134
+ } else if (newValue === undefined || newValue === null) {
2135
+ changeType = "removed";
2136
+ } else if (typeof oldValue !== typeof newValue) {
2137
+ changeType = "type_changed";
2138
+ }
2139
+ return {
2140
+ path,
2141
+ type: changeType,
2142
+ oldValue,
2143
+ newValue,
2144
+ description
2145
+ };
2146
+ }
2147
+ function diffObjects(path, oldObj, newObj, options) {
2148
+ const changes = [];
2149
+ if (!oldObj && !newObj)
2150
+ return changes;
2151
+ if (!oldObj) {
2152
+ changes.push({
2153
+ path,
2154
+ type: "added",
2155
+ newValue: newObj,
2156
+ description: `Added ${path}`
2157
+ });
2158
+ return changes;
2159
+ }
2160
+ if (!newObj) {
2161
+ changes.push({
2162
+ path,
2163
+ type: "removed",
2164
+ oldValue: oldObj,
2165
+ description: `Removed ${path}`
2166
+ });
2167
+ return changes;
2168
+ }
2169
+ const allKeys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);
2170
+ for (const key of allKeys) {
2171
+ const keyPath = path ? `${path}.${key}` : key;
2172
+ if (options.ignorePaths?.some((p) => keyPath.startsWith(p))) {
2173
+ continue;
2174
+ }
2175
+ const oldVal = oldObj[key];
2176
+ const newVal = newObj[key];
2177
+ if (typeof oldVal === "object" && typeof newVal === "object") {
2178
+ changes.push(...diffObjects(keyPath, oldVal, newVal, options));
2179
+ } else {
2180
+ const change = compareValues(keyPath, oldVal, newVal, `Changed ${keyPath}`);
2181
+ if (change) {
2182
+ changes.push(change);
2183
+ }
2184
+ }
2185
+ }
2186
+ return changes;
2187
+ }
2188
+ function diffSpecVsOperation(spec, operation2, options = {}) {
2189
+ const changes = [];
2190
+ if (!options.ignoreDescriptions) {
2191
+ const descChange = compareValues("meta.description", spec.meta.description, operation2.summary ?? operation2.description, "Description changed");
2192
+ if (descChange)
2193
+ changes.push(descChange);
2194
+ }
2195
+ if (!options.ignoreTags) {
2196
+ const oldTags = [...spec.meta.tags ?? []].sort();
2197
+ const newTags = [...operation2.tags].sort();
2198
+ if (!deepEqual(oldTags, newTags)) {
2199
+ changes.push({
2200
+ path: "meta.tags",
2201
+ type: "modified",
2202
+ oldValue: oldTags,
2203
+ newValue: newTags,
2204
+ description: "Tags changed"
2205
+ });
2206
+ }
2207
+ }
2208
+ if (!options.ignoreTransport) {
2209
+ const specMethod = spec.transport?.rest?.method ?? (spec.meta.kind === "query" ? "GET" : "POST");
2210
+ const opMethod = operation2.method.toUpperCase();
2211
+ if (specMethod !== opMethod) {
2212
+ changes.push({
2213
+ path: "transport.rest.method",
2214
+ type: "modified",
2215
+ oldValue: specMethod,
2216
+ newValue: opMethod,
2217
+ description: "HTTP method changed"
2218
+ });
2219
+ }
2220
+ const specPath = spec.transport?.rest?.path;
2221
+ if (specPath && specPath !== operation2.path) {
2222
+ changes.push({
2223
+ path: "transport.rest.path",
2224
+ type: "modified",
2225
+ oldValue: specPath,
2226
+ newValue: operation2.path,
2227
+ description: "Path changed"
2228
+ });
2229
+ }
2230
+ }
2231
+ const specDeprecated = spec.meta.stability === "deprecated";
2232
+ if (specDeprecated !== operation2.deprecated) {
2233
+ changes.push({
2234
+ path: "meta.stability",
2235
+ type: "modified",
2236
+ oldValue: spec.meta.stability,
2237
+ newValue: operation2.deprecated ? "deprecated" : "stable",
2238
+ description: "Deprecation status changed"
2239
+ });
2240
+ }
2241
+ return changes;
2242
+ }
2243
+ function diffSpecs(oldSpec, newSpec, options = {}) {
2244
+ const changes = [];
2245
+ const metaChanges = diffObjects("meta", oldSpec.meta, newSpec.meta, {
2246
+ ...options,
2247
+ ignorePaths: [
2248
+ ...options.ignorePaths ?? [],
2249
+ ...options.ignoreDescriptions ? ["meta.description", "meta.goal", "meta.context"] : [],
2250
+ ...options.ignoreTags ? ["meta.tags"] : []
2251
+ ]
2252
+ });
2253
+ changes.push(...metaChanges);
2254
+ if (!options.ignoreTransport) {
2255
+ const transportChanges = diffObjects("transport", oldSpec.transport, newSpec.transport, options);
2256
+ changes.push(...transportChanges);
2257
+ }
2258
+ const policyChanges = diffObjects("policy", oldSpec.policy, newSpec.policy, options);
2259
+ changes.push(...policyChanges);
2260
+ return changes;
2261
+ }
2262
+ function createSpecDiff(operationId, existing, incoming, options = {}) {
2263
+ let changes = [];
2264
+ let isEquivalent = false;
2265
+ if (existing && incoming.operationSpec) {
2266
+ changes = diffSpecs(existing, incoming.operationSpec, options);
2267
+ isEquivalent = changes.length === 0;
2268
+ } else if (existing && !incoming.operationSpec) {
2269
+ changes = [
2270
+ {
2271
+ path: "",
2272
+ type: "modified",
2273
+ oldValue: existing,
2274
+ newValue: incoming.code,
2275
+ description: "Spec code imported from OpenAPI (runtime comparison not available)"
2276
+ }
2277
+ ];
2278
+ } else {
2279
+ changes = [
2280
+ {
2281
+ path: "",
2282
+ type: "added",
2283
+ newValue: incoming.operationSpec ?? incoming.code,
2284
+ description: "New spec imported from OpenAPI"
2285
+ }
2286
+ ];
2287
+ }
2288
+ return {
2289
+ operationId,
2290
+ existing,
2291
+ incoming,
2292
+ changes,
2293
+ isEquivalent
2294
+ };
2295
+ }
2296
+ function diffAll(existingSpecs, importedSpecs, options = {}) {
2297
+ const diffs = [];
2298
+ const matchedExisting = new Set;
2299
+ for (const imported of importedSpecs) {
2300
+ const operationId = imported.source.sourceId;
2301
+ let existing;
2302
+ for (const [key, spec] of existingSpecs) {
2303
+ const specName = spec.meta.key;
2304
+ if (key === operationId || specName.includes(operationId)) {
2305
+ existing = spec;
2306
+ matchedExisting.add(key);
2307
+ break;
2308
+ }
2309
+ }
2310
+ diffs.push(createSpecDiff(operationId, existing, imported, options));
2311
+ }
2312
+ for (const [key, spec] of existingSpecs) {
2313
+ if (!matchedExisting.has(key)) {
2314
+ diffs.push({
2315
+ operationId: key,
2316
+ existing: spec,
2317
+ incoming: undefined,
2318
+ changes: [
2319
+ {
2320
+ path: "",
2321
+ type: "removed",
2322
+ oldValue: spec,
2323
+ description: "Spec no longer exists in OpenAPI source"
2324
+ }
2325
+ ],
2326
+ isEquivalent: false
2327
+ });
2328
+ }
2329
+ }
2330
+ return diffs;
2331
+ }
2332
+ function formatDiffChanges(changes) {
2333
+ if (changes.length === 0) {
2334
+ return "No changes detected";
2335
+ }
2336
+ const lines = [];
2337
+ for (const change of changes) {
2338
+ const prefix = {
2339
+ added: "+",
2340
+ removed: "-",
2341
+ modified: "~",
2342
+ type_changed: "!",
2343
+ required_changed: "?"
2344
+ }[change.type];
2345
+ lines.push(`${prefix} ${change.path}: ${change.description}`);
2346
+ if (change.type === "modified" || change.type === "type_changed") {
2347
+ lines.push(` old: ${JSON.stringify(change.oldValue)}`);
2348
+ lines.push(` new: ${JSON.stringify(change.newValue)}`);
2349
+ } else if (change.type === "added") {
2350
+ lines.push(` value: ${JSON.stringify(change.newValue)}`);
2351
+ } else if (change.type === "removed") {
2352
+ lines.push(` was: ${JSON.stringify(change.oldValue)}`);
2353
+ }
2354
+ }
2355
+ return lines.join(`
2356
+ `);
2357
+ }
2358
+ export {
2359
+ toValidIdentifier,
2360
+ toSpecKey,
2361
+ toSnakeCase,
2362
+ toSchemaName,
2363
+ toRestPath,
2364
+ toPascalCase,
2365
+ toOperationId,
2366
+ toKebabCase,
2367
+ toHttpMethod,
2368
+ toFileName,
2369
+ toCamelCase,
2370
+ schemaModelToJsonSchema,
2371
+ parseOpenApiString,
2372
+ parseOpenApiDocument,
2373
+ parseOpenApi,
2374
+ openApiToYaml,
2375
+ openApiToJson,
2376
+ openApiForRegistry,
2377
+ normalizePath,
2378
+ jsonSchemaToType,
2379
+ jsonSchemaForSpec,
2380
+ importOperation,
2381
+ importFromOpenApi,
2382
+ getScalarType,
2383
+ getByPath,
2384
+ generateWorkflowsRegistry,
2385
+ generateSchemaModelCode,
2386
+ generateRegistryIndex,
2387
+ generatePresentationsRegistry,
2388
+ generateOperationsRegistry,
2389
+ generateImports,
2390
+ generateFormsRegistry,
2391
+ generateFeaturesRegistry,
2392
+ generateEventsExports,
2393
+ generateDataViewsRegistry,
2394
+ formatDiffChanges,
2395
+ extractPathParams,
2396
+ exportWorkflows,
2397
+ exportPresentationsFromArray,
2398
+ exportPresentations,
2399
+ exportOperations,
2400
+ exportForms,
2401
+ exportFeatures,
2402
+ exportEvents,
2403
+ exportDataViews,
2404
+ exportContractSpec,
2405
+ diffSpecs,
2406
+ diffSpecVsOperation,
2407
+ diffAll,
2408
+ detectVersion,
2409
+ detectFormat,
2410
+ defaultRestPath,
2411
+ deepEqual,
2412
+ createSpecDiff,
2413
+ contractSpecToYaml,
2414
+ contractSpecToJson
2415
+ };