@contractspec/lib.contracts-transformers 3.7.6 → 3.7.7

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.
@@ -71,306 +71,395 @@ function normalizePath(path) {
71
71
  normalized = normalized.replace(/\/+/g, "/");
72
72
  return "/" + normalized;
73
73
  }
74
- // src/openapi/parser/utils.ts
75
- import { parse as parseYaml } from "yaml";
76
- var HTTP_METHODS = [
77
- "get",
78
- "post",
79
- "put",
80
- "delete",
81
- "patch",
82
- "head",
83
- "options",
84
- "trace"
85
- ];
86
- function parseOpenApiString(content, format = "json") {
87
- if (format === "yaml") {
88
- return parseYaml(content);
89
- }
90
- return JSON.parse(content);
91
- }
92
- function detectFormat(content) {
93
- const trimmed = content.trim();
94
- if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
95
- return "json";
74
+ // src/openapi/differ.ts
75
+ function compareValues(path, oldValue, newValue, description) {
76
+ if (deepEqual(oldValue, newValue)) {
77
+ return null;
96
78
  }
97
- return "yaml";
98
- }
99
- function detectVersion(doc) {
100
- const version = doc.openapi;
101
- if (version.startsWith("3.1")) {
102
- return "3.1";
79
+ let changeType = "modified";
80
+ if (oldValue === undefined || oldValue === null) {
81
+ changeType = "added";
82
+ } else if (newValue === undefined || newValue === null) {
83
+ changeType = "removed";
84
+ } else if (typeof oldValue !== typeof newValue) {
85
+ changeType = "type_changed";
103
86
  }
104
- return "3.0";
105
- }
106
- function generateOperationId(method, path) {
107
- const pathParts = path.split("/").filter(Boolean).map((part) => {
108
- if (part.startsWith("{") && part.endsWith("}")) {
109
- return "By" + part.slice(1, -1).charAt(0).toUpperCase() + part.slice(2, -1);
110
- }
111
- return part.charAt(0).toUpperCase() + part.slice(1);
112
- });
113
- return method + pathParts.join("");
114
- }
115
- // src/openapi/parser/resolvers.ts
116
- function isReference(obj) {
117
- return typeof obj === "object" && obj !== null && "$ref" in obj;
87
+ return {
88
+ path,
89
+ type: changeType,
90
+ oldValue,
91
+ newValue,
92
+ description
93
+ };
118
94
  }
119
- function resolveRef(doc, ref) {
120
- if (!ref.startsWith("#/")) {
121
- return;
95
+ function diffObjects(path, oldObj, newObj, options) {
96
+ const changes = [];
97
+ if (!oldObj && !newObj)
98
+ return changes;
99
+ if (!oldObj) {
100
+ changes.push({
101
+ path,
102
+ type: "added",
103
+ newValue: newObj,
104
+ description: `Added ${path}`
105
+ });
106
+ return changes;
122
107
  }
123
- const path = ref.slice(2).split("/");
124
- let current = doc;
125
- for (const part of path) {
126
- if (current === null || current === undefined)
127
- return;
128
- if (typeof current !== "object")
129
- return;
130
- current = current[part];
108
+ if (!newObj) {
109
+ changes.push({
110
+ path,
111
+ type: "removed",
112
+ oldValue: oldObj,
113
+ description: `Removed ${path}`
114
+ });
115
+ return changes;
131
116
  }
132
- return current;
133
- }
134
- function dereferenceSchema(doc, schema, seen = new Set) {
135
- if (!schema)
136
- return;
137
- if (isReference(schema)) {
138
- if (seen.has(schema.$ref)) {
139
- return schema;
117
+ const allKeys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);
118
+ for (const key of allKeys) {
119
+ const keyPath = path ? `${path}.${key}` : key;
120
+ if (options.ignorePaths?.some((p) => keyPath.startsWith(p))) {
121
+ continue;
140
122
  }
141
- const newSeen = new Set(seen);
142
- newSeen.add(schema.$ref);
143
- const resolved = resolveRef(doc, schema.$ref);
144
- if (!resolved)
145
- return schema;
146
- const dereferenced = dereferenceSchema(doc, resolved, newSeen);
147
- if (!dereferenced)
148
- return schema;
149
- const refParts = schema.$ref.split("/");
150
- const typeName = refParts[refParts.length - 1];
151
- return {
152
- ...dereferenced,
153
- _originalRef: schema.$ref,
154
- _originalTypeName: typeName
155
- };
156
- }
157
- const schemaObj = { ...schema };
158
- if (schemaObj.properties) {
159
- const props = schemaObj.properties;
160
- const newProps = {};
161
- for (const [key, prop] of Object.entries(props)) {
162
- newProps[key] = dereferenceSchema(doc, prop, seen) ?? prop;
123
+ const oldVal = oldObj[key];
124
+ const newVal = newObj[key];
125
+ if (typeof oldVal === "object" && typeof newVal === "object") {
126
+ changes.push(...diffObjects(keyPath, oldVal, newVal, options));
127
+ } else {
128
+ const change = compareValues(keyPath, oldVal, newVal, `Changed ${keyPath}`);
129
+ if (change) {
130
+ changes.push(change);
131
+ }
163
132
  }
164
- schemaObj.properties = newProps;
165
133
  }
166
- if (schemaObj.items) {
167
- schemaObj.items = dereferenceSchema(doc, schemaObj.items, seen);
134
+ return changes;
135
+ }
136
+ function diffSpecVsOperation(spec, operation, options = {}) {
137
+ const changes = [];
138
+ if (!options.ignoreDescriptions) {
139
+ const descChange = compareValues("meta.description", spec.meta.description, operation.summary ?? operation.description, "Description changed");
140
+ if (descChange)
141
+ changes.push(descChange);
168
142
  }
169
- const combinators = ["allOf", "anyOf", "oneOf"];
170
- for (const comb of combinators) {
171
- if (Array.isArray(schemaObj[comb])) {
172
- schemaObj[comb] = schemaObj[comb].map((s) => dereferenceSchema(doc, s, seen) ?? s);
143
+ if (!options.ignoreTags) {
144
+ const oldTags = [...spec.meta.tags ?? []].sort();
145
+ const newTags = [...operation.tags].sort();
146
+ if (!deepEqual(oldTags, newTags)) {
147
+ changes.push({
148
+ path: "meta.tags",
149
+ type: "modified",
150
+ oldValue: oldTags,
151
+ newValue: newTags,
152
+ description: "Tags changed"
153
+ });
173
154
  }
174
155
  }
175
- return schemaObj;
176
- }
177
- // src/openapi/parser/parameters.ts
178
- function parseParameters(doc, params) {
179
- const result = {
180
- path: [],
181
- query: [],
182
- header: [],
183
- cookie: []
184
- };
185
- if (!params)
186
- return result;
187
- for (const param of params) {
188
- let resolved;
189
- if (isReference(param)) {
190
- const ref = resolveRef(doc, param.$ref);
191
- if (!ref)
192
- continue;
193
- resolved = ref;
194
- } else {
195
- resolved = param;
156
+ if (!options.ignoreTransport) {
157
+ const specMethod = spec.transport?.rest?.method ?? (spec.meta.kind === "query" ? "GET" : "POST");
158
+ const opMethod = operation.method.toUpperCase();
159
+ if (specMethod !== opMethod) {
160
+ changes.push({
161
+ path: "transport.rest.method",
162
+ type: "modified",
163
+ oldValue: specMethod,
164
+ newValue: opMethod,
165
+ description: "HTTP method changed"
166
+ });
196
167
  }
197
- const parsed = {
198
- name: resolved.name,
199
- in: resolved.in,
200
- required: resolved.required ?? resolved.in === "path",
201
- description: resolved.description,
202
- schema: dereferenceSchema(doc, resolved.schema),
203
- deprecated: resolved.deprecated ?? false
204
- };
205
- result[resolved.in]?.push(parsed);
206
- }
207
- return result;
208
- }
209
- // src/openapi/parser/operation.ts
210
- function parseOperation(doc, method, path, operation, pathParams) {
211
- const allParams = [...pathParams ?? [], ...operation.parameters ?? []];
212
- const params = parseParameters(doc, allParams);
213
- let requestBody;
214
- if (operation.requestBody) {
215
- const body = isReference(operation.requestBody) ? resolveRef(doc, operation.requestBody.$ref) : operation.requestBody;
216
- if (body) {
217
- const contentType = Object.keys(body.content ?? {})[0] ?? "application/json";
218
- const content = body.content?.[contentType];
219
- if (content?.schema) {
220
- requestBody = {
221
- required: body.required ?? false,
222
- schema: dereferenceSchema(doc, content.schema) ?? {},
223
- contentType
224
- };
225
- }
168
+ const specPath = spec.transport?.rest?.path;
169
+ if (specPath && specPath !== operation.path) {
170
+ changes.push({
171
+ path: "transport.rest.path",
172
+ type: "modified",
173
+ oldValue: specPath,
174
+ newValue: operation.path,
175
+ description: "Path changed"
176
+ });
226
177
  }
227
178
  }
228
- const responses = {};
229
- for (const [status, response] of Object.entries(operation.responses ?? {})) {
230
- const resolved = isReference(response) ? resolveRef(doc, response.$ref) : response;
231
- if (resolved) {
232
- const contentType = Object.keys(resolved.content ?? {})[0];
233
- const content = contentType ? resolved.content?.[contentType] : undefined;
234
- responses[status] = {
235
- description: resolved.description,
236
- schema: content?.schema ? dereferenceSchema(doc, content.schema) : undefined,
237
- contentType
238
- };
239
- }
179
+ const specDeprecated = spec.meta.stability === "deprecated";
180
+ if (specDeprecated !== operation.deprecated) {
181
+ changes.push({
182
+ path: "meta.stability",
183
+ type: "modified",
184
+ oldValue: spec.meta.stability,
185
+ newValue: operation.deprecated ? "deprecated" : "stable",
186
+ description: "Deprecation status changed"
187
+ });
240
188
  }
241
- const contractSpecMeta = operation?.["x-contractspec"];
242
- return {
243
- operationId: operation.operationId ?? generateOperationId(method, path),
244
- method,
245
- path,
246
- summary: operation.summary,
247
- description: operation.description,
248
- tags: operation.tags ?? [],
249
- pathParams: params.path,
250
- queryParams: params.query,
251
- headerParams: params.header,
252
- cookieParams: params.cookie,
253
- requestBody,
254
- responses,
255
- deprecated: operation.deprecated ?? false,
256
- security: operation.security,
257
- contractSpecMeta
189
+ return changes;
190
+ }
191
+ function diffSpecs(oldSpec, newSpec, options = {}) {
192
+ const changes = [];
193
+ const metaChanges = diffObjects("meta", oldSpec.meta, newSpec.meta, {
194
+ ...options,
195
+ ignorePaths: [
196
+ ...options.ignorePaths ?? [],
197
+ ...options.ignoreDescriptions ? ["meta.description", "meta.goal", "meta.context"] : [],
198
+ ...options.ignoreTags ? ["meta.tags"] : []
199
+ ]
200
+ });
201
+ changes.push(...metaChanges);
202
+ if (!options.ignoreTransport) {
203
+ const transportChanges = diffObjects("transport", oldSpec.transport, newSpec.transport, options);
204
+ changes.push(...transportChanges);
205
+ }
206
+ const policyChanges = diffObjects("policy", oldSpec.policy, newSpec.policy, options);
207
+ changes.push(...policyChanges);
208
+ return changes;
209
+ }
210
+ function createSpecDiff(operationId, existing, incoming, options = {}) {
211
+ let changes = [];
212
+ let isEquivalent = false;
213
+ if (existing && incoming.operationSpec) {
214
+ changes = diffSpecs(existing, incoming.operationSpec, options);
215
+ isEquivalent = changes.length === 0;
216
+ } else if (existing && !incoming.operationSpec) {
217
+ changes = [
218
+ {
219
+ path: "",
220
+ type: "modified",
221
+ oldValue: existing,
222
+ newValue: incoming.code,
223
+ description: "Spec code imported from OpenAPI (runtime comparison not available)"
224
+ }
225
+ ];
226
+ } else {
227
+ changes = [
228
+ {
229
+ path: "",
230
+ type: "added",
231
+ newValue: incoming.operationSpec ?? incoming.code,
232
+ description: "New spec imported from OpenAPI"
233
+ }
234
+ ];
235
+ }
236
+ return {
237
+ operationId,
238
+ existing,
239
+ incoming,
240
+ changes,
241
+ isEquivalent
258
242
  };
259
243
  }
260
- // src/openapi/parser/document.ts
261
- function parseOpenApiDocument(doc, _options = {}) {
262
- const version = detectVersion(doc);
263
- const warnings = [];
264
- const operations = [];
265
- for (const [path, pathItem] of Object.entries(doc.paths ?? {})) {
266
- if (!pathItem)
267
- continue;
268
- const pathParams = pathItem.parameters;
269
- for (const method of HTTP_METHODS) {
270
- const operation = pathItem[method];
271
- if (operation) {
272
- try {
273
- operations.push(parseOperation(doc, method, path, operation, pathParams));
274
- } catch (error) {
275
- warnings.push(`Failed to parse ${method.toUpperCase()} ${path}: ${error}`);
276
- }
244
+ function diffAll(existingSpecs, importedSpecs, options = {}) {
245
+ const diffs = [];
246
+ const matchedExisting = new Set;
247
+ for (const imported of importedSpecs) {
248
+ const operationId = imported.source.sourceId;
249
+ let existing;
250
+ for (const [key, spec] of existingSpecs) {
251
+ const specName = spec.meta.key;
252
+ if (key === operationId || specName.includes(operationId)) {
253
+ existing = spec;
254
+ matchedExisting.add(key);
255
+ break;
277
256
  }
278
257
  }
258
+ diffs.push(createSpecDiff(operationId, existing, imported, options));
279
259
  }
280
- const schemas = {};
281
- const components = doc.components;
282
- if (components?.schemas) {
283
- for (const [name, schema] of Object.entries(components.schemas)) {
284
- schemas[name] = schema;
260
+ for (const [key, spec] of existingSpecs) {
261
+ if (!matchedExisting.has(key)) {
262
+ diffs.push({
263
+ operationId: key,
264
+ existing: spec,
265
+ incoming: undefined,
266
+ changes: [
267
+ {
268
+ path: "",
269
+ type: "removed",
270
+ oldValue: spec,
271
+ description: "Spec no longer exists in OpenAPI source"
272
+ }
273
+ ],
274
+ isEquivalent: false
275
+ });
285
276
  }
286
277
  }
287
- const servers = (doc.servers ?? []).map((s) => ({
288
- url: s.url,
289
- description: s.description,
290
- variables: s.variables
291
- }));
292
- const events = [];
293
- if ("webhooks" in doc && doc.webhooks) {
294
- for (const [name, pathItem] of Object.entries(doc.webhooks)) {
295
- if (typeof pathItem !== "object" || !pathItem)
296
- continue;
297
- const operation = pathItem["post"];
298
- if (operation && operation.requestBody) {
299
- if ("$ref" in operation.requestBody) {
300
- throw new Error(`'$ref' isn't supported`);
301
- }
302
- const content = operation.requestBody.content?.["application/json"];
303
- if (content?.schema) {
304
- events.push({
305
- name,
306
- description: operation.summary || operation.description,
307
- payload: content.schema
308
- });
309
- }
310
- }
278
+ return diffs;
279
+ }
280
+ function formatDiffChanges(changes) {
281
+ if (changes.length === 0) {
282
+ return "No changes detected";
283
+ }
284
+ const lines = [];
285
+ for (const change of changes) {
286
+ const prefix = {
287
+ added: "+",
288
+ removed: "-",
289
+ modified: "~",
290
+ type_changed: "!",
291
+ required_changed: "?"
292
+ }[change.type];
293
+ lines.push(`${prefix} ${change.path}: ${change.description}`);
294
+ if (change.type === "modified" || change.type === "type_changed") {
295
+ lines.push(` old: ${JSON.stringify(change.oldValue)}`);
296
+ lines.push(` new: ${JSON.stringify(change.newValue)}`);
297
+ } else if (change.type === "added") {
298
+ lines.push(` value: ${JSON.stringify(change.newValue)}`);
299
+ } else if (change.type === "removed") {
300
+ lines.push(` was: ${JSON.stringify(change.oldValue)}`);
311
301
  }
312
302
  }
303
+ return lines.join(`
304
+ `);
305
+ }
306
+ // src/openapi/exporter/data-views.ts
307
+ function exportDataViews(registry) {
308
+ return registry.list().map((dv) => ({
309
+ name: dv.meta.key,
310
+ version: dv.meta.version,
311
+ description: dv.meta.description,
312
+ stability: dv.meta.stability,
313
+ entity: dv.meta.entity,
314
+ kind: dv.view.kind,
315
+ source: dv.source,
316
+ fields: dv.view.fields
317
+ }));
318
+ }
319
+ function generateDataViewsRegistry(registry) {
320
+ const dataViews = registry.list();
321
+ const imports = new Set;
322
+ const registrations = [];
323
+ for (const dv of dataViews) {
324
+ const dvVarName = dv.meta.key.replace(/\./g, "_") + `_v${dv.meta.version}`;
325
+ imports.add(`import { ${dvVarName} } from './${dv.meta.key.split(".")[0]}';`);
326
+ registrations.push(` .register(${dvVarName})`);
327
+ }
328
+ const code = `/**
329
+ * Auto-generated data views registry.
330
+ * DO NOT EDIT - This file is generated by ContractSpec exporter.
331
+ */
332
+ import { DataViewRegistry } from '@contractspec/lib.contracts-spec/data-views';
333
+
334
+ ${Array.from(imports).join(`
335
+ `)}
336
+
337
+ export const dataViewsRegistry = new DataViewRegistry()
338
+ ${registrations.join(`
339
+ `)};
340
+ `;
313
341
  return {
314
- document: doc,
315
- version,
316
- info: {
317
- title: doc.info.title,
318
- version: doc.info.version,
319
- description: doc.info.description
320
- },
321
- operations,
322
- schemas,
323
- servers,
324
- warnings,
325
- events
342
+ code,
343
+ fileName: "dataviews-registry.ts"
326
344
  };
327
345
  }
328
- async function parseOpenApi(source, options = {}) {
329
- const {
330
- fetch: fetchFn = globalThis.fetch,
331
- readFile,
332
- timeout = 30000
333
- } = options;
334
- let content;
335
- let format;
336
- if (source.startsWith("http://") || source.startsWith("https://")) {
337
- const controller = new AbortController;
338
- const timeoutId = setTimeout(() => controller.abort(), timeout);
339
- try {
340
- const response = await fetchFn(source, { signal: controller.signal });
341
- if (!response.ok) {
342
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
343
- }
344
- content = await response.text();
345
- } finally {
346
- clearTimeout(timeoutId);
347
- }
348
- if (source.endsWith(".yaml") || source.endsWith(".yml")) {
349
- format = "yaml";
350
- } else if (source.endsWith(".json")) {
351
- format = "json";
352
- } else {
353
- format = detectFormat(content);
354
- }
355
- } else {
356
- if (!readFile) {
357
- throw new Error("readFile adapter required for file paths");
358
- }
359
- content = await readFile(source);
360
- if (source.endsWith(".yaml") || source.endsWith(".yml")) {
361
- format = "yaml";
362
- } else if (source.endsWith(".json")) {
363
- format = "json";
364
- } else {
365
- format = detectFormat(content);
366
- }
346
+
347
+ // src/openapi/exporter/events.ts
348
+ import { z } from "zod";
349
+ function exportEvents(events) {
350
+ return events.map((event) => ({
351
+ name: event.meta.key,
352
+ version: event.meta.version,
353
+ description: event.meta.description,
354
+ payload: event.payload ? z.toJSONSchema(event.payload.getZod()) : null,
355
+ pii: event.pii
356
+ }));
357
+ }
358
+ function generateEventsExports(events) {
359
+ const eventExports = [];
360
+ for (const event of events) {
361
+ const eventVarName = event.meta.key.replace(/\./g, "_") + `_v${event.meta.version}`;
362
+ eventExports.push(`export { ${eventVarName} } from './${event.meta.key.split(".")[0]}';`);
363
+ }
364
+ const code = `/**
365
+ * Auto-generated events exports.
366
+ * DO NOT EDIT - This file is generated by ContractSpec exporter.
367
+ */
368
+
369
+ ${eventExports.join(`
370
+ `)}
371
+ `;
372
+ return {
373
+ code,
374
+ fileName: "events-exports.ts"
375
+ };
376
+ }
377
+
378
+ // src/openapi/exporter/features.ts
379
+ function exportFeatures(registry) {
380
+ return registry.list().map((feature) => ({
381
+ key: feature.meta.key,
382
+ description: feature.meta.description,
383
+ owners: feature.meta.owners,
384
+ stability: feature.meta.stability,
385
+ operations: feature.operations,
386
+ events: feature.events,
387
+ presentations: feature.presentations
388
+ }));
389
+ }
390
+ function generateFeaturesRegistry(registry) {
391
+ const features = registry.list();
392
+ const imports = new Set;
393
+ const registrations = [];
394
+ for (const feature of features) {
395
+ const featureVarName = feature.meta.key.replace(/-/g, "_");
396
+ imports.add(`import { ${featureVarName} } from './${feature.meta.key}';`);
397
+ registrations.push(` .register(${featureVarName})`);
398
+ }
399
+ const code = `/**
400
+ * Auto-generated features registry.
401
+ * DO NOT EDIT - This file is generated by ContractSpec exporter.
402
+ */
403
+ import { FeatureRegistry } from '@contractspec/lib.contracts-spec';
404
+
405
+ ${Array.from(imports).join(`
406
+ `)}
407
+
408
+ export const featuresRegistry = new FeatureRegistry()
409
+ ${registrations.join(`
410
+ `)};
411
+ `;
412
+ return {
413
+ code,
414
+ fileName: "features-registry.ts"
415
+ };
416
+ }
417
+
418
+ // src/openapi/exporter/forms.ts
419
+ import { z as z2 } from "zod";
420
+ function exportForms(registry) {
421
+ return registry.list().map((form) => ({
422
+ key: form.meta.key,
423
+ version: form.meta.version,
424
+ description: form.meta.description,
425
+ stability: form.meta.stability,
426
+ owners: form.meta.owners,
427
+ fields: form.fields,
428
+ model: form.model ? z2.toJSONSchema(form.model.getZod()) : null,
429
+ actions: form.actions
430
+ }));
431
+ }
432
+ function generateFormsRegistry(registry) {
433
+ const forms = registry.list();
434
+ const imports = new Set;
435
+ const registrations = [];
436
+ for (const form of forms) {
437
+ const formVarName = form.meta.key.replace(/-/g, "_") + `_v${form.meta.version}`;
438
+ imports.add(`import { ${formVarName} } from './${form.meta.key}';`);
439
+ registrations.push(` .register(${formVarName})`);
367
440
  }
368
- const doc = parseOpenApiString(content, format);
369
- return parseOpenApiDocument(doc, options);
441
+ const code = `/**
442
+ * Auto-generated forms registry.
443
+ * DO NOT EDIT - This file is generated by ContractSpec exporter.
444
+ */
445
+ import { FormRegistry } from '@contractspec/lib.contracts-spec';
446
+
447
+ ${Array.from(imports).join(`
448
+ `)}
449
+
450
+ export const formsRegistry = new FormRegistry()
451
+ ${registrations.join(`
452
+ `)};
453
+ `;
454
+ return {
455
+ code,
456
+ fileName: "forms-registry.ts"
457
+ };
370
458
  }
459
+
371
460
  // src/openapi/exporter/operations.ts
372
- import { z } from "zod";
373
461
  import { compareVersions } from "compare-versions";
462
+ import { z as z3 } from "zod";
374
463
  function toOperationId(name, version) {
375
464
  return `${name.replace(/\./g, "_")}_v${version.replace(/\./g, "_")}`;
376
465
  }
@@ -391,7 +480,7 @@ function toRestPath(spec) {
391
480
  function schemaModelToJsonSchema(schema) {
392
481
  if (!schema)
393
482
  return null;
394
- return z.toJSONSchema(schema.getZod());
483
+ return z3.toJSONSchema(schema.getZod());
395
484
  }
396
485
  function jsonSchemaForSpec(spec) {
397
486
  return {
@@ -492,77 +581,6 @@ ${registrations.join(`
492
581
  };
493
582
  }
494
583
 
495
- // src/openapi/exporter/events.ts
496
- import { z as z2 } from "zod";
497
- function exportEvents(events) {
498
- return events.map((event) => ({
499
- name: event.meta.key,
500
- version: event.meta.version,
501
- description: event.meta.description,
502
- payload: event.payload ? z2.toJSONSchema(event.payload.getZod()) : null,
503
- pii: event.pii
504
- }));
505
- }
506
- function generateEventsExports(events) {
507
- const eventExports = [];
508
- for (const event of events) {
509
- const eventVarName = event.meta.key.replace(/\./g, "_") + `_v${event.meta.version}`;
510
- eventExports.push(`export { ${eventVarName} } from './${event.meta.key.split(".")[0]}';`);
511
- }
512
- const code = `/**
513
- * Auto-generated events exports.
514
- * DO NOT EDIT - This file is generated by ContractSpec exporter.
515
- */
516
-
517
- ${eventExports.join(`
518
- `)}
519
- `;
520
- return {
521
- code,
522
- fileName: "events-exports.ts"
523
- };
524
- }
525
-
526
- // src/openapi/exporter/features.ts
527
- function exportFeatures(registry) {
528
- return registry.list().map((feature) => ({
529
- key: feature.meta.key,
530
- description: feature.meta.description,
531
- owners: feature.meta.owners,
532
- stability: feature.meta.stability,
533
- operations: feature.operations,
534
- events: feature.events,
535
- presentations: feature.presentations
536
- }));
537
- }
538
- function generateFeaturesRegistry(registry) {
539
- const features = registry.list();
540
- const imports = new Set;
541
- const registrations = [];
542
- for (const feature of features) {
543
- const featureVarName = feature.meta.key.replace(/-/g, "_");
544
- imports.add(`import { ${featureVarName} } from './${feature.meta.key}';`);
545
- registrations.push(` .register(${featureVarName})`);
546
- }
547
- const code = `/**
548
- * Auto-generated features registry.
549
- * DO NOT EDIT - This file is generated by ContractSpec exporter.
550
- */
551
- import { FeatureRegistry } from '@contractspec/lib.contracts-spec';
552
-
553
- ${Array.from(imports).join(`
554
- `)}
555
-
556
- export const featuresRegistry = new FeatureRegistry()
557
- ${registrations.join(`
558
- `)};
559
- `;
560
- return {
561
- code,
562
- fileName: "features-registry.ts"
563
- };
564
- }
565
-
566
584
  // src/openapi/exporter/presentations.ts
567
585
  function exportPresentations(registry) {
568
586
  return registry.list().map((pres) => ({
@@ -614,137 +632,6 @@ ${registrations.join(`
614
632
  };
615
633
  }
616
634
 
617
- // src/openapi/exporter/forms.ts
618
- import { z as z3 } from "zod";
619
- function exportForms(registry) {
620
- return registry.list().map((form) => ({
621
- key: form.meta.key,
622
- version: form.meta.version,
623
- description: form.meta.description,
624
- stability: form.meta.stability,
625
- owners: form.meta.owners,
626
- fields: form.fields,
627
- model: form.model ? z3.toJSONSchema(form.model.getZod()) : null,
628
- actions: form.actions
629
- }));
630
- }
631
- function generateFormsRegistry(registry) {
632
- const forms = registry.list();
633
- const imports = new Set;
634
- const registrations = [];
635
- for (const form of forms) {
636
- const formVarName = form.meta.key.replace(/-/g, "_") + `_v${form.meta.version}`;
637
- imports.add(`import { ${formVarName} } from './${form.meta.key}';`);
638
- registrations.push(` .register(${formVarName})`);
639
- }
640
- const code = `/**
641
- * Auto-generated forms registry.
642
- * DO NOT EDIT - This file is generated by ContractSpec exporter.
643
- */
644
- import { FormRegistry } from '@contractspec/lib.contracts-spec';
645
-
646
- ${Array.from(imports).join(`
647
- `)}
648
-
649
- export const formsRegistry = new FormRegistry()
650
- ${registrations.join(`
651
- `)};
652
- `;
653
- return {
654
- code,
655
- fileName: "forms-registry.ts"
656
- };
657
- }
658
-
659
- // src/openapi/exporter/data-views.ts
660
- function exportDataViews(registry) {
661
- return registry.list().map((dv) => ({
662
- name: dv.meta.key,
663
- version: dv.meta.version,
664
- description: dv.meta.description,
665
- stability: dv.meta.stability,
666
- entity: dv.meta.entity,
667
- kind: dv.view.kind,
668
- source: dv.source,
669
- fields: dv.view.fields
670
- }));
671
- }
672
- function generateDataViewsRegistry(registry) {
673
- const dataViews = registry.list();
674
- const imports = new Set;
675
- const registrations = [];
676
- for (const dv of dataViews) {
677
- const dvVarName = dv.meta.key.replace(/\./g, "_") + `_v${dv.meta.version}`;
678
- imports.add(`import { ${dvVarName} } from './${dv.meta.key.split(".")[0]}';`);
679
- registrations.push(` .register(${dvVarName})`);
680
- }
681
- const code = `/**
682
- * Auto-generated data views registry.
683
- * DO NOT EDIT - This file is generated by ContractSpec exporter.
684
- */
685
- import { DataViewRegistry } from '@contractspec/lib.contracts-spec/data-views';
686
-
687
- ${Array.from(imports).join(`
688
- `)}
689
-
690
- export const dataViewsRegistry = new DataViewRegistry()
691
- ${registrations.join(`
692
- `)};
693
- `;
694
- return {
695
- code,
696
- fileName: "dataviews-registry.ts"
697
- };
698
- }
699
-
700
- // src/openapi/exporter/workflows.ts
701
- function exportWorkflows(registry) {
702
- return registry.list().map((wf) => ({
703
- name: wf.meta.key,
704
- version: wf.meta.version,
705
- description: wf.meta.description,
706
- stability: wf.meta.stability,
707
- owners: wf.meta.owners,
708
- steps: wf.definition.steps.map((s) => ({
709
- id: s.id,
710
- type: s.type,
711
- label: s.label
712
- })),
713
- transitions: wf.definition.transitions.map((t) => ({
714
- from: t.from,
715
- to: t.to,
716
- label: t.label
717
- }))
718
- }));
719
- }
720
- function generateWorkflowsRegistry(registry) {
721
- const workflows = registry.list();
722
- const imports = new Set;
723
- const registrations = [];
724
- for (const wf of workflows) {
725
- const wfVarName = wf.meta.key.replace(/\./g, "_") + `_v${wf.meta.version}`;
726
- imports.add(`import { ${wfVarName} } from './${wf.meta.key.split(".")[0]}';`);
727
- registrations.push(` .register(${wfVarName})`);
728
- }
729
- const code = `/**
730
- * Auto-generated workflows registry.
731
- * DO NOT EDIT - This file is generated by ContractSpec exporter.
732
- */
733
- import { WorkflowRegistry } from '@contractspec/lib.contracts-spec/workflow';
734
-
735
- ${Array.from(imports).join(`
736
- `)}
737
-
738
- export const workflowsRegistry = new WorkflowRegistry()
739
- ${registrations.join(`
740
- `)};
741
- `;
742
- return {
743
- code,
744
- fileName: "workflows-registry.ts"
745
- };
746
- }
747
-
748
635
  // src/openapi/exporter/registries.ts
749
636
  function generateRegistryIndex(options = {}) {
750
637
  const {
@@ -779,16 +666,64 @@ function generateRegistryIndex(options = {}) {
779
666
  exports.push("export * from './workflows-registry';");
780
667
  }
781
668
  const code = `/**
782
- * Auto-generated registry index.
669
+ * Auto-generated registry index.
670
+ * DO NOT EDIT - This file is generated by ContractSpec exporter.
671
+ */
672
+
673
+ ${exports.join(`
674
+ `)}
675
+ `;
676
+ return {
677
+ code,
678
+ fileName: "index.ts"
679
+ };
680
+ }
681
+
682
+ // src/openapi/exporter/workflows.ts
683
+ function exportWorkflows(registry) {
684
+ return registry.list().map((wf) => ({
685
+ name: wf.meta.key,
686
+ version: wf.meta.version,
687
+ description: wf.meta.description,
688
+ stability: wf.meta.stability,
689
+ owners: wf.meta.owners,
690
+ steps: wf.definition.steps.map((s) => ({
691
+ id: s.id,
692
+ type: s.type,
693
+ label: s.label
694
+ })),
695
+ transitions: wf.definition.transitions.map((t) => ({
696
+ from: t.from,
697
+ to: t.to,
698
+ label: t.label
699
+ }))
700
+ }));
701
+ }
702
+ function generateWorkflowsRegistry(registry) {
703
+ const workflows = registry.list();
704
+ const imports = new Set;
705
+ const registrations = [];
706
+ for (const wf of workflows) {
707
+ const wfVarName = wf.meta.key.replace(/\./g, "_") + `_v${wf.meta.version}`;
708
+ imports.add(`import { ${wfVarName} } from './${wf.meta.key.split(".")[0]}';`);
709
+ registrations.push(` .register(${wfVarName})`);
710
+ }
711
+ const code = `/**
712
+ * Auto-generated workflows registry.
783
713
  * DO NOT EDIT - This file is generated by ContractSpec exporter.
784
714
  */
715
+ import { WorkflowRegistry } from '@contractspec/lib.contracts-spec/workflow';
785
716
 
786
- ${exports.join(`
717
+ ${Array.from(imports).join(`
787
718
  `)}
719
+
720
+ export const workflowsRegistry = new WorkflowRegistry()
721
+ ${registrations.join(`
722
+ `)};
788
723
  `;
789
724
  return {
790
725
  code,
791
- fileName: "index.ts"
726
+ fileName: "workflows-registry.ts"
792
727
  };
793
728
  }
794
729
 
@@ -949,7 +884,7 @@ var JSON_SCHEMA_TO_SCALAR = {
949
884
  "string:uri": "ScalarTypeEnum.URL",
950
885
  "string:uuid": "ScalarTypeEnum.ID"
951
886
  };
952
- function isReference2(schema) {
887
+ function isReference(schema) {
953
888
  return typeof schema === "object" && schema !== null && "$ref" in schema;
954
889
  }
955
890
  function typeNameFromRef(ref) {
@@ -1004,7 +939,7 @@ class ContractSpecSchemaGenerator {
1004
939
  generateContractSpecSchema(schema, modelName, indent = 0) {
1005
940
  const spaces = " ".repeat(indent);
1006
941
  const fields = [];
1007
- if (isReference2(schema)) {
942
+ if (isReference(schema)) {
1008
943
  return {
1009
944
  name: toPascalCase(typeNameFromRef(schema.$ref)),
1010
945
  fields: [],
@@ -1140,7 +1075,7 @@ class ContractSpecSchemaGenerator {
1140
1075
  const scalarType = getScalarType(schema);
1141
1076
  let enumValues;
1142
1077
  let nestedModel;
1143
- if (!isReference2(schema)) {
1078
+ if (!isReference(schema)) {
1144
1079
  const schemaObj = schema;
1145
1080
  const enumArr = schemaObj["enum"];
1146
1081
  if (enumArr) {
@@ -1158,7 +1093,7 @@ class ContractSpecSchemaGenerator {
1158
1093
  type: {
1159
1094
  ...type,
1160
1095
  optional: !required || type.optional,
1161
- description: !isReference2(schema) ? schema["description"] : undefined
1096
+ description: !isReference(schema) ? schema["description"] : undefined
1162
1097
  },
1163
1098
  scalarType,
1164
1099
  enumValues,
@@ -1501,7 +1436,7 @@ var JSON_SCHEMA_TO_SCALAR2 = {
1501
1436
  "string:uri": "ScalarTypeEnum.URL",
1502
1437
  "string:uuid": "ScalarTypeEnum.ID"
1503
1438
  };
1504
- function isReference3(schema) {
1439
+ function isReference2(schema) {
1505
1440
  return typeof schema === "object" && schema !== null && "$ref" in schema;
1506
1441
  }
1507
1442
  function typeNameFromRef2(ref) {
@@ -1509,7 +1444,7 @@ function typeNameFromRef2(ref) {
1509
1444
  return parts[parts.length - 1] ?? "Unknown";
1510
1445
  }
1511
1446
  function jsonSchemaToType(schema, name) {
1512
- if (isReference3(schema)) {
1447
+ if (isReference2(schema)) {
1513
1448
  return {
1514
1449
  type: toPascalCase(typeNameFromRef2(schema.$ref)),
1515
1450
  optional: false,
@@ -1602,7 +1537,7 @@ function jsonSchemaToType(schema, name) {
1602
1537
  };
1603
1538
  }
1604
1539
  function getScalarType(schema) {
1605
- if (isReference3(schema)) {
1540
+ if (isReference2(schema)) {
1606
1541
  return;
1607
1542
  }
1608
1543
  const schemaObj = schema;
@@ -1648,65 +1583,42 @@ function generateImports(fields, options, sameDirectory = true) {
1648
1583
  `);
1649
1584
  }
1650
1585
 
1651
- // src/openapi/importer/schemas.ts
1652
- function buildInputSchemas(operation2) {
1653
- const result = {};
1654
- if (operation2.pathParams.length > 0) {
1655
- result.params = {
1656
- type: "object",
1657
- properties: operation2.pathParams.reduce((acc, p) => {
1658
- acc[p.name] = p.schema;
1659
- return acc;
1660
- }, {}),
1661
- required: operation2.pathParams.map((p) => p.name)
1662
- };
1663
- }
1664
- if (operation2.queryParams.length > 0) {
1665
- result.query = {
1666
- type: "object",
1667
- properties: operation2.queryParams.reduce((acc, p) => {
1668
- acc[p.name] = p.schema;
1669
- return acc;
1670
- }, {}),
1671
- required: operation2.queryParams.filter((p) => p.required).map((p) => p.name)
1672
- };
1673
- }
1674
- const excludedHeaders = [
1675
- "authorization",
1676
- "content-type",
1677
- "accept",
1678
- "user-agent"
1679
- ];
1680
- const actualHeaders = operation2.headerParams.filter((p) => !excludedHeaders.includes(p.name.toLowerCase()));
1681
- if (actualHeaders.length > 0) {
1682
- result.headers = {
1683
- type: "object",
1684
- properties: actualHeaders.reduce((acc, p) => {
1685
- acc[p.name] = p.schema;
1686
- return acc;
1687
- }, {}),
1688
- required: actualHeaders.filter((p) => p.required).map((p) => p.name)
1689
- };
1690
- }
1691
- if (operation2.requestBody?.schema) {
1692
- result.body = operation2.requestBody.schema;
1693
- }
1694
- return result;
1695
- }
1696
- function getOutputSchema(operation2) {
1697
- const successCodes = ["200", "201", "202", "204"];
1698
- for (const code of successCodes) {
1699
- const response = operation2.responses[code];
1700
- if (response?.schema) {
1701
- return response.schema;
1702
- }
1586
+ // src/openapi/importer/events.ts
1587
+ function generateEventCode(event, options) {
1588
+ const eventName = toValidIdentifier(event.name);
1589
+ const modelName = toPascalCase(eventName) + "Payload";
1590
+ const schemaFormat = options.schemaFormat || "contractspec";
1591
+ const payloadModel = generateSchemaModelCode(event.payload, modelName, schemaFormat, options);
1592
+ const imports = new Set;
1593
+ imports.add("import { defineEvent, type EventSpec } from '@contractspec/lib.contracts-spec';");
1594
+ if (payloadModel.imports && payloadModel.imports.length > 0) {
1595
+ payloadModel.imports.forEach((i) => imports.add(i));
1596
+ } else if (payloadModel.fields && payloadModel.fields.length > 0) {
1597
+ const modelImports = generateImports(payloadModel.fields, options);
1598
+ modelImports.split(`
1599
+ `).filter(Boolean).forEach((i) => imports.add(i));
1703
1600
  }
1704
- for (const [code, response] of Object.entries(operation2.responses)) {
1705
- if (code.startsWith("2") && response.schema) {
1706
- return response.schema;
1707
- }
1601
+ if (payloadModel.name !== modelName) {
1602
+ const modelsDir = `../${options.conventions.models}`;
1603
+ const kebabName = payloadModel.name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
1604
+ imports.add(`import { ${payloadModel.name} } from '${modelsDir}/${kebabName}';`);
1708
1605
  }
1709
- return null;
1606
+ const allImports = Array.from(imports).join(`
1607
+ `);
1608
+ return `
1609
+ ${allImports}
1610
+
1611
+ ${payloadModel.code}
1612
+
1613
+ export const ${eventName} = defineEvent({
1614
+ meta: {
1615
+ key: '${event.name}',
1616
+ version: '1.0.0',
1617
+ description: ${JSON.stringify(event.description ?? "")},
1618
+ },
1619
+ payload: ${payloadModel.name},
1620
+ });
1621
+ `.trim();
1710
1622
  }
1711
1623
 
1712
1624
  // src/openapi/importer/analyzer.ts
@@ -1714,11 +1626,11 @@ var COMMAND_METHODS = ["post", "put", "delete", "patch"];
1714
1626
  function inferOpKind(method) {
1715
1627
  return COMMAND_METHODS.includes(method.toLowerCase()) ? "command" : "query";
1716
1628
  }
1717
- function inferAuthLevel(operation2, defaultAuth) {
1718
- if (!operation2.security || operation2.security.length === 0) {
1629
+ function inferAuthLevel(operation, defaultAuth) {
1630
+ if (!operation.security || operation.security.length === 0) {
1719
1631
  return defaultAuth;
1720
1632
  }
1721
- for (const sec of operation2.security) {
1633
+ for (const sec of operation.security) {
1722
1634
  if (Object.keys(sec).length === 0) {
1723
1635
  return "anonymous";
1724
1636
  }
@@ -1727,10 +1639,10 @@ function inferAuthLevel(operation2, defaultAuth) {
1727
1639
  }
1728
1640
 
1729
1641
  // src/openapi/importer/generator.ts
1730
- function generateSpecCode(operation2, contractspecConfig, options = {}, inputModel, outputModel, queryModel = null, paramsModel = null, headersModel = null) {
1731
- const specKey = toSpecKey(operation2.operationId, options.prefix);
1732
- const kind = inferOpKind(operation2.method);
1733
- const auth = inferAuthLevel(operation2, options.defaultAuth ?? "user");
1642
+ function generateSpecCode(operation, contractspecConfig, options = {}, inputModel, outputModel, queryModel = null, paramsModel = null, headersModel = null) {
1643
+ const specKey = toSpecKey(operation.operationId, options.prefix);
1644
+ const kind = inferOpKind(operation.method);
1645
+ const auth = inferAuthLevel(operation, options.defaultAuth ?? "user");
1734
1646
  const lines = [];
1735
1647
  lines.push("import { defineCommand, defineQuery } from '@contractspec/lib.contracts-spec';");
1736
1648
  if (inputModel || outputModel || queryModel || paramsModel || headersModel) {
@@ -1775,15 +1687,15 @@ function generateSpecCode(operation2, contractspecConfig, options = {}, inputMod
1775
1687
  }
1776
1688
  }
1777
1689
  const defineFunc = kind === "command" ? "defineCommand" : "defineQuery";
1778
- const safeName = toValidIdentifier(toPascalCase(operation2.operationId));
1690
+ const safeName = toValidIdentifier(toPascalCase(operation.operationId));
1779
1691
  lines.push(`/**`);
1780
- lines.push(` * ${operation2.summary ?? operation2.operationId}`);
1781
- if (operation2.description) {
1692
+ lines.push(` * ${operation.summary ?? operation.operationId}`);
1693
+ if (operation.description) {
1782
1694
  lines.push(` *`);
1783
- lines.push(` * ${operation2.description}`);
1695
+ lines.push(` * ${operation.description}`);
1784
1696
  }
1785
1697
  lines.push(` *`);
1786
- lines.push(` * @source OpenAPI: ${operation2.method.toUpperCase()} ${operation2.path}`);
1698
+ lines.push(` * @source OpenAPI: ${operation.method.toUpperCase()} ${operation.path}`);
1787
1699
  lines.push(` */`);
1788
1700
  lines.push(`export const ${safeName}Spec = ${defineFunc}({`);
1789
1701
  lines.push(" meta: {");
@@ -1791,10 +1703,10 @@ function generateSpecCode(operation2, contractspecConfig, options = {}, inputMod
1791
1703
  lines.push(" version: '1.0.0',");
1792
1704
  lines.push(` stability: '${options.defaultStability ?? "stable"}',`);
1793
1705
  lines.push(` owners: [${(options.defaultOwners ?? []).map((o) => `'${o}'`).join(", ")}],`);
1794
- lines.push(` tags: [${operation2.tags.map((t) => `'${t}'`).join(", ")}],`);
1795
- lines.push(` description: ${JSON.stringify(operation2.summary ?? operation2.operationId)},`);
1796
- lines.push(` goal: ${JSON.stringify(operation2.description ?? "Imported from OpenAPI")},`);
1797
- lines.push(` context: 'Imported from OpenAPI: ${operation2.method.toUpperCase()} ${operation2.path}',`);
1706
+ lines.push(` tags: [${operation.tags.map((t) => `'${t}'`).join(", ")}],`);
1707
+ lines.push(` description: ${JSON.stringify(operation.summary ?? operation.operationId)},`);
1708
+ lines.push(` goal: ${JSON.stringify(operation.description ?? "Imported from OpenAPI")},`);
1709
+ lines.push(` context: 'Imported from OpenAPI: ${operation.method.toUpperCase()} ${operation.path}',`);
1798
1710
  lines.push(" },");
1799
1711
  lines.push(" io: {");
1800
1712
  lines.push(` input: ${inputModel?.name ?? "null"},`);
@@ -1813,12 +1725,12 @@ function generateSpecCode(operation2, contractspecConfig, options = {}, inputMod
1813
1725
  lines.push(" policy: {");
1814
1726
  lines.push(` auth: '${auth}',`);
1815
1727
  lines.push(" },");
1816
- const httpMethod = operation2.method.toUpperCase();
1728
+ const httpMethod = operation.method.toUpperCase();
1817
1729
  const restMethod = httpMethod === "GET" ? "GET" : "POST";
1818
1730
  lines.push(" transport: {");
1819
1731
  lines.push(" rest: {");
1820
1732
  lines.push(` method: '${restMethod}',`);
1821
- lines.push(` path: '${operation2.path}',`);
1733
+ lines.push(` path: '${operation.path}',`);
1822
1734
  lines.push(" },");
1823
1735
  lines.push(" },");
1824
1736
  lines.push("});");
@@ -1826,73 +1738,16 @@ function generateSpecCode(operation2, contractspecConfig, options = {}, inputMod
1826
1738
  `);
1827
1739
  }
1828
1740
 
1829
- // src/openapi/importer/models.ts
1830
- function generateModelCode(name, schema, options) {
1831
- const modelName = toPascalCase(toValidIdentifier(name));
1832
- const schemaFormat = options.schemaFormat || "contractspec";
1833
- const model = generateSchemaModelCode(schema, modelName, schemaFormat, options);
1834
- let imports = "";
1835
- if (model.imports && model.imports.length > 0) {
1836
- imports = model.imports.join(`
1837
- `);
1838
- } else if (model.fields.length > 0) {
1839
- imports = generateImports(model.fields, options);
1840
- }
1841
- return `
1842
- ${imports}
1843
-
1844
- ${model.code}
1845
- `.trim();
1846
- }
1847
-
1848
- // src/openapi/importer/events.ts
1849
- function generateEventCode(event, options) {
1850
- const eventName = toValidIdentifier(event.name);
1851
- const modelName = toPascalCase(eventName) + "Payload";
1852
- const schemaFormat = options.schemaFormat || "contractspec";
1853
- const payloadModel = generateSchemaModelCode(event.payload, modelName, schemaFormat, options);
1854
- const imports = new Set;
1855
- imports.add("import { defineEvent, type EventSpec } from '@contractspec/lib.contracts-spec';");
1856
- if (payloadModel.imports && payloadModel.imports.length > 0) {
1857
- payloadModel.imports.forEach((i) => imports.add(i));
1858
- } else if (payloadModel.fields && payloadModel.fields.length > 0) {
1859
- const modelImports = generateImports(payloadModel.fields, options);
1860
- modelImports.split(`
1861
- `).filter(Boolean).forEach((i) => imports.add(i));
1862
- }
1863
- if (payloadModel.name !== modelName) {
1864
- const modelsDir = `../${options.conventions.models}`;
1865
- const kebabName = payloadModel.name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
1866
- imports.add(`import { ${payloadModel.name} } from '${modelsDir}/${kebabName}';`);
1867
- }
1868
- const allImports = Array.from(imports).join(`
1869
- `);
1870
- return `
1871
- ${allImports}
1872
-
1873
- ${payloadModel.code}
1874
-
1875
- export const ${eventName} = defineEvent({
1876
- meta: {
1877
- key: '${event.name}',
1878
- version: '1.0.0',
1879
- description: ${JSON.stringify(event.description ?? "")},
1880
- },
1881
- payload: ${payloadModel.name},
1882
- });
1883
- `.trim();
1884
- }
1885
-
1886
1741
  // src/openapi/importer/grouping.ts
1887
- function resolveOperationGroupFolder(operation2, conventions) {
1742
+ function resolveOperationGroupFolder(operation, conventions) {
1888
1743
  const groupingRule = conventions.operationsGrouping;
1889
1744
  if (!groupingRule || groupingRule.strategy === "none") {
1890
1745
  return "";
1891
1746
  }
1892
1747
  return applyGroupingStrategy(groupingRule, {
1893
- name: operation2.operationId,
1894
- tags: operation2.tags,
1895
- path: operation2.path
1748
+ name: operation.operationId,
1749
+ tags: operation.tags,
1750
+ path: operation.path
1896
1751
  });
1897
1752
  }
1898
1753
  function resolveModelGroupFolder(modelName, conventions, relatedPath, relatedTags) {
@@ -1911,48 +1766,128 @@ function resolveEventGroupFolder(eventName, conventions, relatedTags) {
1911
1766
  if (!groupingRule || groupingRule.strategy === "none") {
1912
1767
  return "";
1913
1768
  }
1914
- return applyGroupingStrategy(groupingRule, {
1915
- name: eventName,
1916
- tags: relatedTags ?? []
1917
- });
1918
- }
1919
- function applyGroupingStrategy(rule, context) {
1920
- switch (rule.strategy) {
1921
- case "by-tag":
1922
- return context.tags?.[0] ?? "untagged";
1923
- case "by-owner":
1924
- return context.owners?.[0] ?? "unowned";
1925
- case "by-domain":
1926
- return extractDomain(context.name);
1927
- case "by-url-path-single":
1928
- return extractUrlPathLevel(context.path, 1);
1929
- case "by-url-path-multi":
1930
- return extractUrlPathLevel(context.path, rule.urlPathLevel ?? 2);
1931
- case "by-feature":
1932
- return extractDomain(context.name);
1933
- case "none":
1934
- default:
1935
- return "";
1769
+ return applyGroupingStrategy(groupingRule, {
1770
+ name: eventName,
1771
+ tags: relatedTags ?? []
1772
+ });
1773
+ }
1774
+ function applyGroupingStrategy(rule, context) {
1775
+ switch (rule.strategy) {
1776
+ case "by-tag":
1777
+ return context.tags?.[0] ?? "untagged";
1778
+ case "by-owner":
1779
+ return context.owners?.[0] ?? "unowned";
1780
+ case "by-domain":
1781
+ return extractDomain(context.name);
1782
+ case "by-url-path-single":
1783
+ return extractUrlPathLevel(context.path, 1);
1784
+ case "by-url-path-multi":
1785
+ return extractUrlPathLevel(context.path, rule.urlPathLevel ?? 2);
1786
+ case "by-feature":
1787
+ return extractDomain(context.name);
1788
+ case "none":
1789
+ default:
1790
+ return "";
1791
+ }
1792
+ }
1793
+ function extractDomain(name) {
1794
+ if (name.includes(".")) {
1795
+ return name.split(".")[0] ?? "default";
1796
+ }
1797
+ if (name.includes("_")) {
1798
+ return name.split("_")[0] ?? "default";
1799
+ }
1800
+ const match = name.match(/^([a-z]+)/i);
1801
+ return match?.[1]?.toLowerCase() ?? "default";
1802
+ }
1803
+ function extractUrlPathLevel(path, level) {
1804
+ if (!path)
1805
+ return "root";
1806
+ const segments = path.split("/").filter(Boolean);
1807
+ const nonParamSegments = segments.filter((s) => !s.startsWith("{"));
1808
+ if (nonParamSegments.length === 0)
1809
+ return "root";
1810
+ return nonParamSegments.slice(0, level).join("/");
1811
+ }
1812
+
1813
+ // src/openapi/importer/models.ts
1814
+ function generateModelCode(name, schema, options) {
1815
+ const modelName = toPascalCase(toValidIdentifier(name));
1816
+ const schemaFormat = options.schemaFormat || "contractspec";
1817
+ const model = generateSchemaModelCode(schema, modelName, schemaFormat, options);
1818
+ let imports = "";
1819
+ if (model.imports && model.imports.length > 0) {
1820
+ imports = model.imports.join(`
1821
+ `);
1822
+ } else if (model.fields.length > 0) {
1823
+ imports = generateImports(model.fields, options);
1824
+ }
1825
+ return `
1826
+ ${imports}
1827
+
1828
+ ${model.code}
1829
+ `.trim();
1830
+ }
1831
+
1832
+ // src/openapi/importer/schemas.ts
1833
+ function buildInputSchemas(operation) {
1834
+ const result = {};
1835
+ if (operation.pathParams.length > 0) {
1836
+ result.params = {
1837
+ type: "object",
1838
+ properties: operation.pathParams.reduce((acc, p) => {
1839
+ acc[p.name] = p.schema;
1840
+ return acc;
1841
+ }, {}),
1842
+ required: operation.pathParams.map((p) => p.name)
1843
+ };
1844
+ }
1845
+ if (operation.queryParams.length > 0) {
1846
+ result.query = {
1847
+ type: "object",
1848
+ properties: operation.queryParams.reduce((acc, p) => {
1849
+ acc[p.name] = p.schema;
1850
+ return acc;
1851
+ }, {}),
1852
+ required: operation.queryParams.filter((p) => p.required).map((p) => p.name)
1853
+ };
1854
+ }
1855
+ const excludedHeaders = [
1856
+ "authorization",
1857
+ "content-type",
1858
+ "accept",
1859
+ "user-agent"
1860
+ ];
1861
+ const actualHeaders = operation.headerParams.filter((p) => !excludedHeaders.includes(p.name.toLowerCase()));
1862
+ if (actualHeaders.length > 0) {
1863
+ result.headers = {
1864
+ type: "object",
1865
+ properties: actualHeaders.reduce((acc, p) => {
1866
+ acc[p.name] = p.schema;
1867
+ return acc;
1868
+ }, {}),
1869
+ required: actualHeaders.filter((p) => p.required).map((p) => p.name)
1870
+ };
1871
+ }
1872
+ if (operation.requestBody?.schema) {
1873
+ result.body = operation.requestBody.schema;
1936
1874
  }
1875
+ return result;
1937
1876
  }
1938
- function extractDomain(name) {
1939
- if (name.includes(".")) {
1940
- return name.split(".")[0] ?? "default";
1877
+ function getOutputSchema(operation) {
1878
+ const successCodes = ["200", "201", "202", "204"];
1879
+ for (const code of successCodes) {
1880
+ const response = operation.responses[code];
1881
+ if (response?.schema) {
1882
+ return response.schema;
1883
+ }
1941
1884
  }
1942
- if (name.includes("_")) {
1943
- return name.split("_")[0] ?? "default";
1885
+ for (const [code, response] of Object.entries(operation.responses)) {
1886
+ if (code.startsWith("2") && response.schema) {
1887
+ return response.schema;
1888
+ }
1944
1889
  }
1945
- const match = name.match(/^([a-z]+)/i);
1946
- return match?.[1]?.toLowerCase() ?? "default";
1947
- }
1948
- function extractUrlPathLevel(path, level) {
1949
- if (!path)
1950
- return "root";
1951
- const segments = path.split("/").filter(Boolean);
1952
- const nonParamSegments = segments.filter((s) => !s.startsWith("{"));
1953
- if (nonParamSegments.length === 0)
1954
- return "root";
1955
- return nonParamSegments.slice(0, level).join("/");
1890
+ return null;
1956
1891
  }
1957
1892
 
1958
1893
  // src/openapi/importer/index.ts
@@ -1961,74 +1896,74 @@ var importFromOpenApi = (parseResult, contractspecOptions, importOptions = {}) =
1961
1896
  const specs = [];
1962
1897
  const skipped = [];
1963
1898
  const errors = [];
1964
- for (const operation2 of parseResult.operations) {
1899
+ for (const operation of parseResult.operations) {
1965
1900
  if (tags && tags.length > 0) {
1966
- const hasMatchingTag = operation2.tags.some((t) => tags.includes(t));
1901
+ const hasMatchingTag = operation.tags.some((t) => tags.includes(t));
1967
1902
  if (!hasMatchingTag) {
1968
1903
  skipped.push({
1969
- sourceId: operation2.operationId,
1970
- reason: `No matching tags (has: ${operation2.tags.join(", ")})`
1904
+ sourceId: operation.operationId,
1905
+ reason: `No matching tags (has: ${operation.tags.join(", ")})`
1971
1906
  });
1972
1907
  continue;
1973
1908
  }
1974
1909
  }
1975
1910
  if (include && include.length > 0) {
1976
- if (!include.includes(operation2.operationId)) {
1911
+ if (!include.includes(operation.operationId)) {
1977
1912
  skipped.push({
1978
- sourceId: operation2.operationId,
1913
+ sourceId: operation.operationId,
1979
1914
  reason: "Not in include list"
1980
1915
  });
1981
1916
  continue;
1982
1917
  }
1983
- } else if (exclude.includes(operation2.operationId)) {
1918
+ } else if (exclude.includes(operation.operationId)) {
1984
1919
  skipped.push({
1985
- sourceId: operation2.operationId,
1920
+ sourceId: operation.operationId,
1986
1921
  reason: "In exclude list"
1987
1922
  });
1988
1923
  continue;
1989
1924
  }
1990
- if (operation2.deprecated && importOptions.defaultStability !== "deprecated") {
1925
+ if (operation.deprecated && importOptions.defaultStability !== "deprecated") {
1991
1926
  skipped.push({
1992
- sourceId: operation2.operationId,
1927
+ sourceId: operation.operationId,
1993
1928
  reason: "Deprecated operation"
1994
1929
  });
1995
1930
  continue;
1996
1931
  }
1997
1932
  try {
1998
- const inputSchemas = buildInputSchemas(operation2);
1933
+ const inputSchemas = buildInputSchemas(operation);
1999
1934
  const schemaFormat = importOptions.schemaFormat || contractspecOptions.schemaFormat || "contractspec";
2000
- const inputModel = inputSchemas.body ? generateSchemaModelCode(inputSchemas.body, `${operation2.operationId}Input`, schemaFormat, contractspecOptions) : null;
2001
- const queryModel = inputSchemas.query ? generateSchemaModelCode(inputSchemas.query, `${operation2.operationId}Query`, schemaFormat, contractspecOptions) : null;
2002
- const paramsModel = inputSchemas.params ? generateSchemaModelCode(inputSchemas.params, `${operation2.operationId}Params`, schemaFormat, contractspecOptions) : null;
2003
- const headersModel = inputSchemas.headers ? generateSchemaModelCode(inputSchemas.headers, `${operation2.operationId}Headers`, schemaFormat, contractspecOptions) : null;
2004
- const outputSchema = getOutputSchema(operation2);
2005
- let outputModel = outputSchema ? generateSchemaModelCode(outputSchema, `${operation2.operationId}Output`, schemaFormat, contractspecOptions) : null;
1935
+ const inputModel = inputSchemas.body ? generateSchemaModelCode(inputSchemas.body, `${operation.operationId}Input`, schemaFormat, contractspecOptions) : null;
1936
+ const queryModel = inputSchemas.query ? generateSchemaModelCode(inputSchemas.query, `${operation.operationId}Query`, schemaFormat, contractspecOptions) : null;
1937
+ const paramsModel = inputSchemas.params ? generateSchemaModelCode(inputSchemas.params, `${operation.operationId}Params`, schemaFormat, contractspecOptions) : null;
1938
+ const headersModel = inputSchemas.headers ? generateSchemaModelCode(inputSchemas.headers, `${operation.operationId}Headers`, schemaFormat, contractspecOptions) : null;
1939
+ const outputSchema = getOutputSchema(operation);
1940
+ let outputModel = outputSchema ? generateSchemaModelCode(outputSchema, `${operation.operationId}Output`, schemaFormat, contractspecOptions) : null;
2006
1941
  if (outputModel && schemaFormat === "contractspec" && !outputModel.code.includes("defineSchemaModel")) {
2007
1942
  outputModel = null;
2008
1943
  }
2009
- const code = generateSpecCode(operation2, contractspecOptions, importOptions, inputModel, outputModel, queryModel, paramsModel, headersModel);
2010
- const specName = toSpecKey(operation2.operationId, importOptions.prefix);
1944
+ const code = generateSpecCode(operation, contractspecOptions, importOptions, inputModel, outputModel, queryModel, paramsModel, headersModel);
1945
+ const specName = toSpecKey(operation.operationId, importOptions.prefix);
2011
1946
  const fileName = toFileName(specName);
2012
1947
  const transportHints = {
2013
1948
  rest: {
2014
- method: operation2.method.toUpperCase(),
2015
- path: operation2.path,
1949
+ method: operation.method.toUpperCase(),
1950
+ path: operation.path,
2016
1951
  params: {
2017
- path: operation2.pathParams.map((p) => p.name),
2018
- query: operation2.queryParams.map((p) => p.name),
2019
- header: operation2.headerParams.map((p) => p.name),
2020
- cookie: operation2.cookieParams.map((p) => p.name)
1952
+ path: operation.pathParams.map((p) => p.name),
1953
+ query: operation.queryParams.map((p) => p.name),
1954
+ header: operation.headerParams.map((p) => p.name),
1955
+ cookie: operation.cookieParams.map((p) => p.name)
2021
1956
  }
2022
1957
  }
2023
1958
  };
2024
1959
  const source = {
2025
1960
  type: "openapi",
2026
- sourceId: operation2.operationId,
2027
- operationId: operation2.operationId,
1961
+ sourceId: operation.operationId,
1962
+ operationId: operation.operationId,
2028
1963
  openApiVersion: parseResult.version,
2029
1964
  importedAt: new Date
2030
1965
  };
2031
- const groupFolder = resolveOperationGroupFolder(operation2, contractspecOptions.conventions);
1966
+ const groupFolder = resolveOperationGroupFolder(operation, contractspecOptions.conventions);
2032
1967
  specs.push({
2033
1968
  code,
2034
1969
  fileName,
@@ -2038,7 +1973,7 @@ var importFromOpenApi = (parseResult, contractspecOptions, importOptions = {}) =
2038
1973
  });
2039
1974
  } catch (error) {
2040
1975
  errors.push({
2041
- sourceId: operation2.operationId,
1976
+ sourceId: operation.operationId,
2042
1977
  error: error instanceof Error ? error.message : String(error)
2043
1978
  });
2044
1979
  }
@@ -2111,248 +2046,317 @@ var importFromOpenApi = (parseResult, contractspecOptions, importOptions = {}) =
2111
2046
  }
2112
2047
  };
2113
2048
  };
2114
- function importOperation(operation2, options = {}, contractspecOptions) {
2115
- const inputSchemas = buildInputSchemas(operation2);
2049
+ function importOperation(operation, options = {}, contractspecOptions) {
2050
+ const inputSchemas = buildInputSchemas(operation);
2116
2051
  const schemaFormat = options.schemaFormat || contractspecOptions.schemaFormat || "contractspec";
2117
- const inputModel = inputSchemas.body ? generateSchemaModelCode(inputSchemas.body, `${operation2.operationId}Input`, schemaFormat, contractspecOptions) : null;
2118
- const queryModel = inputSchemas.query ? generateSchemaModelCode(inputSchemas.query, `${operation2.operationId}Query`, schemaFormat, contractspecOptions) : null;
2119
- const paramsModel = inputSchemas.params ? generateSchemaModelCode(inputSchemas.params, `${operation2.operationId}Params`, schemaFormat, contractspecOptions) : null;
2120
- const headersModel = inputSchemas.headers ? generateSchemaModelCode(inputSchemas.headers, `${operation2.operationId}Headers`, schemaFormat, contractspecOptions) : null;
2121
- const outputSchema = getOutputSchema(operation2);
2122
- const outputModel = outputSchema ? generateSchemaModelCode(outputSchema, `${operation2.operationId}Output`, schemaFormat, contractspecOptions) : null;
2123
- return generateSpecCode(operation2, contractspecOptions, options, inputModel, outputModel, queryModel, paramsModel, headersModel);
2052
+ const inputModel = inputSchemas.body ? generateSchemaModelCode(inputSchemas.body, `${operation.operationId}Input`, schemaFormat, contractspecOptions) : null;
2053
+ const queryModel = inputSchemas.query ? generateSchemaModelCode(inputSchemas.query, `${operation.operationId}Query`, schemaFormat, contractspecOptions) : null;
2054
+ const paramsModel = inputSchemas.params ? generateSchemaModelCode(inputSchemas.params, `${operation.operationId}Params`, schemaFormat, contractspecOptions) : null;
2055
+ const headersModel = inputSchemas.headers ? generateSchemaModelCode(inputSchemas.headers, `${operation.operationId}Headers`, schemaFormat, contractspecOptions) : null;
2056
+ const outputSchema = getOutputSchema(operation);
2057
+ const outputModel = outputSchema ? generateSchemaModelCode(outputSchema, `${operation.operationId}Output`, schemaFormat, contractspecOptions) : null;
2058
+ return generateSpecCode(operation, contractspecOptions, options, inputModel, outputModel, queryModel, paramsModel, headersModel);
2124
2059
  }
2125
- // src/openapi/differ.ts
2126
- function compareValues(path, oldValue, newValue, description) {
2127
- if (deepEqual(oldValue, newValue)) {
2128
- return null;
2060
+ // src/openapi/parser/resolvers.ts
2061
+ function isReference3(obj) {
2062
+ return typeof obj === "object" && obj !== null && "$ref" in obj;
2063
+ }
2064
+ function resolveRef(doc, ref) {
2065
+ if (!ref.startsWith("#/")) {
2066
+ return;
2129
2067
  }
2130
- let changeType = "modified";
2131
- if (oldValue === undefined || oldValue === null) {
2132
- changeType = "added";
2133
- } else if (newValue === undefined || newValue === null) {
2134
- changeType = "removed";
2135
- } else if (typeof oldValue !== typeof newValue) {
2136
- changeType = "type_changed";
2068
+ const path = ref.slice(2).split("/");
2069
+ let current = doc;
2070
+ for (const part of path) {
2071
+ if (current === null || current === undefined)
2072
+ return;
2073
+ if (typeof current !== "object")
2074
+ return;
2075
+ current = current[part];
2137
2076
  }
2138
- return {
2139
- path,
2140
- type: changeType,
2141
- oldValue,
2142
- newValue,
2143
- description
2144
- };
2077
+ return current;
2145
2078
  }
2146
- function diffObjects(path, oldObj, newObj, options) {
2147
- const changes = [];
2148
- if (!oldObj && !newObj)
2149
- return changes;
2150
- if (!oldObj) {
2151
- changes.push({
2152
- path,
2153
- type: "added",
2154
- newValue: newObj,
2155
- description: `Added ${path}`
2156
- });
2157
- return changes;
2079
+ function dereferenceSchema(doc, schema, seen = new Set) {
2080
+ if (!schema)
2081
+ return;
2082
+ if (isReference3(schema)) {
2083
+ if (seen.has(schema.$ref)) {
2084
+ return schema;
2085
+ }
2086
+ const newSeen = new Set(seen);
2087
+ newSeen.add(schema.$ref);
2088
+ const resolved = resolveRef(doc, schema.$ref);
2089
+ if (!resolved)
2090
+ return schema;
2091
+ const dereferenced = dereferenceSchema(doc, resolved, newSeen);
2092
+ if (!dereferenced)
2093
+ return schema;
2094
+ const refParts = schema.$ref.split("/");
2095
+ const typeName = refParts[refParts.length - 1];
2096
+ return {
2097
+ ...dereferenced,
2098
+ _originalRef: schema.$ref,
2099
+ _originalTypeName: typeName
2100
+ };
2158
2101
  }
2159
- if (!newObj) {
2160
- changes.push({
2161
- path,
2162
- type: "removed",
2163
- oldValue: oldObj,
2164
- description: `Removed ${path}`
2165
- });
2166
- return changes;
2102
+ const schemaObj = { ...schema };
2103
+ if (schemaObj.properties) {
2104
+ const props = schemaObj.properties;
2105
+ const newProps = {};
2106
+ for (const [key, prop] of Object.entries(props)) {
2107
+ newProps[key] = dereferenceSchema(doc, prop, seen) ?? prop;
2108
+ }
2109
+ schemaObj.properties = newProps;
2167
2110
  }
2168
- const allKeys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);
2169
- for (const key of allKeys) {
2170
- const keyPath = path ? `${path}.${key}` : key;
2171
- if (options.ignorePaths?.some((p) => keyPath.startsWith(p))) {
2172
- continue;
2111
+ if (schemaObj.items) {
2112
+ schemaObj.items = dereferenceSchema(doc, schemaObj.items, seen);
2113
+ }
2114
+ const combinators = ["allOf", "anyOf", "oneOf"];
2115
+ for (const comb of combinators) {
2116
+ if (Array.isArray(schemaObj[comb])) {
2117
+ schemaObj[comb] = schemaObj[comb].map((s) => dereferenceSchema(doc, s, seen) ?? s);
2173
2118
  }
2174
- const oldVal = oldObj[key];
2175
- const newVal = newObj[key];
2176
- if (typeof oldVal === "object" && typeof newVal === "object") {
2177
- changes.push(...diffObjects(keyPath, oldVal, newVal, options));
2119
+ }
2120
+ return schemaObj;
2121
+ }
2122
+
2123
+ // src/openapi/parser/parameters.ts
2124
+ function parseParameters(doc, params) {
2125
+ const result = {
2126
+ path: [],
2127
+ query: [],
2128
+ header: [],
2129
+ cookie: []
2130
+ };
2131
+ if (!params)
2132
+ return result;
2133
+ for (const param of params) {
2134
+ let resolved;
2135
+ if (isReference3(param)) {
2136
+ const ref = resolveRef(doc, param.$ref);
2137
+ if (!ref)
2138
+ continue;
2139
+ resolved = ref;
2178
2140
  } else {
2179
- const change = compareValues(keyPath, oldVal, newVal, `Changed ${keyPath}`);
2180
- if (change) {
2181
- changes.push(change);
2182
- }
2141
+ resolved = param;
2183
2142
  }
2143
+ const parsed = {
2144
+ name: resolved.name,
2145
+ in: resolved.in,
2146
+ required: resolved.required ?? resolved.in === "path",
2147
+ description: resolved.description,
2148
+ schema: dereferenceSchema(doc, resolved.schema),
2149
+ deprecated: resolved.deprecated ?? false
2150
+ };
2151
+ result[resolved.in]?.push(parsed);
2184
2152
  }
2185
- return changes;
2153
+ return result;
2186
2154
  }
2187
- function diffSpecVsOperation(spec, operation2, options = {}) {
2188
- const changes = [];
2189
- if (!options.ignoreDescriptions) {
2190
- const descChange = compareValues("meta.description", spec.meta.description, operation2.summary ?? operation2.description, "Description changed");
2191
- if (descChange)
2192
- changes.push(descChange);
2193
- }
2194
- if (!options.ignoreTags) {
2195
- const oldTags = [...spec.meta.tags ?? []].sort();
2196
- const newTags = [...operation2.tags].sort();
2197
- if (!deepEqual(oldTags, newTags)) {
2198
- changes.push({
2199
- path: "meta.tags",
2200
- type: "modified",
2201
- oldValue: oldTags,
2202
- newValue: newTags,
2203
- description: "Tags changed"
2204
- });
2205
- }
2155
+
2156
+ // src/openapi/parser/utils.ts
2157
+ import { parse as parseYaml } from "yaml";
2158
+ var HTTP_METHODS = [
2159
+ "get",
2160
+ "post",
2161
+ "put",
2162
+ "delete",
2163
+ "patch",
2164
+ "head",
2165
+ "options",
2166
+ "trace"
2167
+ ];
2168
+ function parseOpenApiString(content, format = "json") {
2169
+ if (format === "yaml") {
2170
+ return parseYaml(content);
2206
2171
  }
2207
- if (!options.ignoreTransport) {
2208
- const specMethod = spec.transport?.rest?.method ?? (spec.meta.kind === "query" ? "GET" : "POST");
2209
- const opMethod = operation2.method.toUpperCase();
2210
- if (specMethod !== opMethod) {
2211
- changes.push({
2212
- path: "transport.rest.method",
2213
- type: "modified",
2214
- oldValue: specMethod,
2215
- newValue: opMethod,
2216
- description: "HTTP method changed"
2217
- });
2218
- }
2219
- const specPath = spec.transport?.rest?.path;
2220
- if (specPath && specPath !== operation2.path) {
2221
- changes.push({
2222
- path: "transport.rest.path",
2223
- type: "modified",
2224
- oldValue: specPath,
2225
- newValue: operation2.path,
2226
- description: "Path changed"
2227
- });
2228
- }
2172
+ return JSON.parse(content);
2173
+ }
2174
+ function detectFormat(content) {
2175
+ const trimmed = content.trim();
2176
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
2177
+ return "json";
2229
2178
  }
2230
- const specDeprecated = spec.meta.stability === "deprecated";
2231
- if (specDeprecated !== operation2.deprecated) {
2232
- changes.push({
2233
- path: "meta.stability",
2234
- type: "modified",
2235
- oldValue: spec.meta.stability,
2236
- newValue: operation2.deprecated ? "deprecated" : "stable",
2237
- description: "Deprecation status changed"
2238
- });
2179
+ return "yaml";
2180
+ }
2181
+ function detectVersion(doc) {
2182
+ const version = doc.openapi;
2183
+ if (version.startsWith("3.1")) {
2184
+ return "3.1";
2239
2185
  }
2240
- return changes;
2186
+ return "3.0";
2241
2187
  }
2242
- function diffSpecs(oldSpec, newSpec, options = {}) {
2243
- const changes = [];
2244
- const metaChanges = diffObjects("meta", oldSpec.meta, newSpec.meta, {
2245
- ...options,
2246
- ignorePaths: [
2247
- ...options.ignorePaths ?? [],
2248
- ...options.ignoreDescriptions ? ["meta.description", "meta.goal", "meta.context"] : [],
2249
- ...options.ignoreTags ? ["meta.tags"] : []
2250
- ]
2188
+ function generateOperationId(method, path) {
2189
+ const pathParts = path.split("/").filter(Boolean).map((part) => {
2190
+ if (part.startsWith("{") && part.endsWith("}")) {
2191
+ return "By" + part.slice(1, -1).charAt(0).toUpperCase() + part.slice(2, -1);
2192
+ }
2193
+ return part.charAt(0).toUpperCase() + part.slice(1);
2251
2194
  });
2252
- changes.push(...metaChanges);
2253
- if (!options.ignoreTransport) {
2254
- const transportChanges = diffObjects("transport", oldSpec.transport, newSpec.transport, options);
2255
- changes.push(...transportChanges);
2256
- }
2257
- const policyChanges = diffObjects("policy", oldSpec.policy, newSpec.policy, options);
2258
- changes.push(...policyChanges);
2259
- return changes;
2195
+ return method + pathParts.join("");
2260
2196
  }
2261
- function createSpecDiff(operationId, existing, incoming, options = {}) {
2262
- let changes = [];
2263
- let isEquivalent = false;
2264
- if (existing && incoming.operationSpec) {
2265
- changes = diffSpecs(existing, incoming.operationSpec, options);
2266
- isEquivalent = changes.length === 0;
2267
- } else if (existing && !incoming.operationSpec) {
2268
- changes = [
2269
- {
2270
- path: "",
2271
- type: "modified",
2272
- oldValue: existing,
2273
- newValue: incoming.code,
2274
- description: "Spec code imported from OpenAPI (runtime comparison not available)"
2275
- }
2276
- ];
2277
- } else {
2278
- changes = [
2279
- {
2280
- path: "",
2281
- type: "added",
2282
- newValue: incoming.operationSpec ?? incoming.code,
2283
- description: "New spec imported from OpenAPI"
2197
+
2198
+ // src/openapi/parser/operation.ts
2199
+ function parseOperation(doc, method, path, operation, pathParams) {
2200
+ const allParams = [...pathParams ?? [], ...operation.parameters ?? []];
2201
+ const params = parseParameters(doc, allParams);
2202
+ let requestBody;
2203
+ if (operation.requestBody) {
2204
+ const body = isReference3(operation.requestBody) ? resolveRef(doc, operation.requestBody.$ref) : operation.requestBody;
2205
+ if (body) {
2206
+ const contentType = Object.keys(body.content ?? {})[0] ?? "application/json";
2207
+ const content = body.content?.[contentType];
2208
+ if (content?.schema) {
2209
+ requestBody = {
2210
+ required: body.required ?? false,
2211
+ schema: dereferenceSchema(doc, content.schema) ?? {},
2212
+ contentType
2213
+ };
2284
2214
  }
2285
- ];
2215
+ }
2216
+ }
2217
+ const responses = {};
2218
+ for (const [status, response] of Object.entries(operation.responses ?? {})) {
2219
+ const resolved = isReference3(response) ? resolveRef(doc, response.$ref) : response;
2220
+ if (resolved) {
2221
+ const contentType = Object.keys(resolved.content ?? {})[0];
2222
+ const content = contentType ? resolved.content?.[contentType] : undefined;
2223
+ responses[status] = {
2224
+ description: resolved.description,
2225
+ schema: content?.schema ? dereferenceSchema(doc, content.schema) : undefined,
2226
+ contentType
2227
+ };
2228
+ }
2286
2229
  }
2230
+ const contractSpecMeta = operation?.["x-contractspec"];
2287
2231
  return {
2288
- operationId,
2289
- existing,
2290
- incoming,
2291
- changes,
2292
- isEquivalent
2232
+ operationId: operation.operationId ?? generateOperationId(method, path),
2233
+ method,
2234
+ path,
2235
+ summary: operation.summary,
2236
+ description: operation.description,
2237
+ tags: operation.tags ?? [],
2238
+ pathParams: params.path,
2239
+ queryParams: params.query,
2240
+ headerParams: params.header,
2241
+ cookieParams: params.cookie,
2242
+ requestBody,
2243
+ responses,
2244
+ deprecated: operation.deprecated ?? false,
2245
+ security: operation.security,
2246
+ contractSpecMeta
2293
2247
  };
2294
2248
  }
2295
- function diffAll(existingSpecs, importedSpecs, options = {}) {
2296
- const diffs = [];
2297
- const matchedExisting = new Set;
2298
- for (const imported of importedSpecs) {
2299
- const operationId = imported.source.sourceId;
2300
- let existing;
2301
- for (const [key, spec] of existingSpecs) {
2302
- const specName = spec.meta.key;
2303
- if (key === operationId || specName.includes(operationId)) {
2304
- existing = spec;
2305
- matchedExisting.add(key);
2306
- break;
2249
+
2250
+ // src/openapi/parser/document.ts
2251
+ function parseOpenApiDocument(doc, _options = {}) {
2252
+ const version = detectVersion(doc);
2253
+ const warnings = [];
2254
+ const operations = [];
2255
+ for (const [path, pathItem] of Object.entries(doc.paths ?? {})) {
2256
+ if (!pathItem)
2257
+ continue;
2258
+ const pathParams = pathItem.parameters;
2259
+ for (const method of HTTP_METHODS) {
2260
+ const operation = pathItem[method];
2261
+ if (operation) {
2262
+ try {
2263
+ operations.push(parseOperation(doc, method, path, operation, pathParams));
2264
+ } catch (error) {
2265
+ warnings.push(`Failed to parse ${method.toUpperCase()} ${path}: ${error}`);
2266
+ }
2307
2267
  }
2308
2268
  }
2309
- diffs.push(createSpecDiff(operationId, existing, imported, options));
2310
2269
  }
2311
- for (const [key, spec] of existingSpecs) {
2312
- if (!matchedExisting.has(key)) {
2313
- diffs.push({
2314
- operationId: key,
2315
- existing: spec,
2316
- incoming: undefined,
2317
- changes: [
2318
- {
2319
- path: "",
2320
- type: "removed",
2321
- oldValue: spec,
2322
- description: "Spec no longer exists in OpenAPI source"
2323
- }
2324
- ],
2325
- isEquivalent: false
2326
- });
2270
+ const schemas2 = {};
2271
+ const components = doc.components;
2272
+ if (components?.schemas) {
2273
+ for (const [name, schema] of Object.entries(components.schemas)) {
2274
+ schemas2[name] = schema;
2327
2275
  }
2328
2276
  }
2329
- return diffs;
2330
- }
2331
- function formatDiffChanges(changes) {
2332
- if (changes.length === 0) {
2333
- return "No changes detected";
2277
+ const servers = (doc.servers ?? []).map((s) => ({
2278
+ url: s.url,
2279
+ description: s.description,
2280
+ variables: s.variables
2281
+ }));
2282
+ const events2 = [];
2283
+ if ("webhooks" in doc && doc.webhooks) {
2284
+ for (const [name, pathItem] of Object.entries(doc.webhooks)) {
2285
+ if (typeof pathItem !== "object" || !pathItem)
2286
+ continue;
2287
+ const operation = pathItem["post"];
2288
+ if (operation && operation.requestBody) {
2289
+ if ("$ref" in operation.requestBody) {
2290
+ throw new Error(`'$ref' isn't supported`);
2291
+ }
2292
+ const content = operation.requestBody.content?.["application/json"];
2293
+ if (content?.schema) {
2294
+ events2.push({
2295
+ name,
2296
+ description: operation.summary || operation.description,
2297
+ payload: content.schema
2298
+ });
2299
+ }
2300
+ }
2301
+ }
2334
2302
  }
2335
- const lines = [];
2336
- for (const change of changes) {
2337
- const prefix = {
2338
- added: "+",
2339
- removed: "-",
2340
- modified: "~",
2341
- type_changed: "!",
2342
- required_changed: "?"
2343
- }[change.type];
2344
- lines.push(`${prefix} ${change.path}: ${change.description}`);
2345
- if (change.type === "modified" || change.type === "type_changed") {
2346
- lines.push(` old: ${JSON.stringify(change.oldValue)}`);
2347
- lines.push(` new: ${JSON.stringify(change.newValue)}`);
2348
- } else if (change.type === "added") {
2349
- lines.push(` value: ${JSON.stringify(change.newValue)}`);
2350
- } else if (change.type === "removed") {
2351
- lines.push(` was: ${JSON.stringify(change.oldValue)}`);
2303
+ return {
2304
+ document: doc,
2305
+ version,
2306
+ info: {
2307
+ title: doc.info.title,
2308
+ version: doc.info.version,
2309
+ description: doc.info.description
2310
+ },
2311
+ operations,
2312
+ schemas: schemas2,
2313
+ servers,
2314
+ warnings,
2315
+ events: events2
2316
+ };
2317
+ }
2318
+ async function parseOpenApi(source, options = {}) {
2319
+ const {
2320
+ fetch: fetchFn = globalThis.fetch,
2321
+ readFile,
2322
+ timeout = 30000
2323
+ } = options;
2324
+ let content;
2325
+ let format;
2326
+ if (source.startsWith("http://") || source.startsWith("https://")) {
2327
+ const controller = new AbortController;
2328
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
2329
+ try {
2330
+ const response = await fetchFn(source, { signal: controller.signal });
2331
+ if (!response.ok) {
2332
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
2333
+ }
2334
+ content = await response.text();
2335
+ } finally {
2336
+ clearTimeout(timeoutId);
2337
+ }
2338
+ if (source.endsWith(".yaml") || source.endsWith(".yml")) {
2339
+ format = "yaml";
2340
+ } else if (source.endsWith(".json")) {
2341
+ format = "json";
2342
+ } else {
2343
+ format = detectFormat(content);
2344
+ }
2345
+ } else {
2346
+ if (!readFile) {
2347
+ throw new Error("readFile adapter required for file paths");
2348
+ }
2349
+ content = await readFile(source);
2350
+ if (source.endsWith(".yaml") || source.endsWith(".yml")) {
2351
+ format = "yaml";
2352
+ } else if (source.endsWith(".json")) {
2353
+ format = "json";
2354
+ } else {
2355
+ format = detectFormat(content);
2352
2356
  }
2353
2357
  }
2354
- return lines.join(`
2355
- `);
2358
+ const doc = parseOpenApiString(content, format);
2359
+ return parseOpenApiDocument(doc, options);
2356
2360
  }
2357
2361
  export {
2358
2362
  toValidIdentifier,