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