@cerios/openapi-to-zod 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +8 -0
- package/README.md +546 -395
- package/dist/cli.js +806 -1293
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +839 -1343
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +468 -59
- package/dist/index.d.ts +468 -59
- package/dist/index.js +644 -881
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +650 -856
- package/dist/index.mjs.map +1 -1
- package/package.json +89 -104
- package/dist/internal.d.mts +0 -470
- package/dist/internal.d.ts +0 -470
- package/dist/internal.js +0 -2025
- package/dist/internal.js.map +0 -1
- package/dist/internal.mjs +0 -1972
- package/dist/internal.mjs.map +0 -1
- package/dist/types-DZ4Bw-D5.d.mts +0 -505
- package/dist/types-DZ4Bw-D5.d.ts +0 -505
package/dist/index.mjs
CHANGED
|
@@ -1,193 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
var _a;
|
|
12
|
-
super(message);
|
|
13
|
-
this.code = code;
|
|
14
|
-
this.context = context;
|
|
15
|
-
this.name = "GeneratorError";
|
|
16
|
-
(_a = Error.captureStackTrace) == null ? void 0 : _a.call(Error, this, this.constructor);
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
var SpecValidationError = class extends GeneratorError {
|
|
20
|
-
constructor(message, context) {
|
|
21
|
-
super(message, "SPEC_VALIDATION_ERROR", context);
|
|
22
|
-
this.name = "SpecValidationError";
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
var FileOperationError = class extends GeneratorError {
|
|
26
|
-
constructor(message, filePath, context) {
|
|
27
|
-
super(message, "FILE_OPERATION_ERROR", { ...context, filePath });
|
|
28
|
-
this.filePath = filePath;
|
|
29
|
-
this.name = "FileOperationError";
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
var ConfigValidationError = class extends GeneratorError {
|
|
33
|
-
constructor(message, configPath, context) {
|
|
34
|
-
super(message, "CONFIG_VALIDATION_ERROR", { ...context, configPath });
|
|
35
|
-
this.configPath = configPath;
|
|
36
|
-
this.name = "ConfigValidationError";
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
var SchemaGenerationError = class extends GeneratorError {
|
|
40
|
-
constructor(message, schemaName, context) {
|
|
41
|
-
super(message, "SCHEMA_GENERATION_ERROR", { ...context, schemaName });
|
|
42
|
-
this.schemaName = schemaName;
|
|
43
|
-
this.name = "SchemaGenerationError";
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
var CircularReferenceError = class extends SchemaGenerationError {
|
|
47
|
-
constructor(schemaName, referencePath) {
|
|
48
|
-
const pathStr = referencePath.join(" -> ");
|
|
49
|
-
super(`Circular reference detected in schema: ${pathStr}`, schemaName, { referencePath, circularPath: pathStr });
|
|
50
|
-
this.referencePath = referencePath;
|
|
51
|
-
this.name = "CircularReferenceError";
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
var CliOptionsError = class extends GeneratorError {
|
|
55
|
-
constructor(message, context) {
|
|
56
|
-
super(message, "CLI_OPTIONS_ERROR", context);
|
|
57
|
-
this.name = "CliOptionsError";
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
var ConfigurationError = class extends GeneratorError {
|
|
61
|
-
constructor(message, context) {
|
|
62
|
-
super(message, "CONFIGURATION_ERROR", context);
|
|
63
|
-
this.name = "ConfigurationError";
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// src/openapi-generator.ts
|
|
68
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
69
|
-
import { dirname, normalize } from "path";
|
|
70
|
-
import { minimatch as minimatch3 } from "minimatch";
|
|
71
|
-
import { parse } from "yaml";
|
|
72
|
-
|
|
73
|
-
// src/utils/name-utils.ts
|
|
74
|
-
function sanitizeIdentifier(str) {
|
|
75
|
-
return str.replace(/[^a-zA-Z0-9._\-\s]+/g, "_");
|
|
76
|
-
}
|
|
77
|
-
function toCamelCase(str, options) {
|
|
78
|
-
const sanitized = sanitizeIdentifier(str);
|
|
79
|
-
const words = sanitized.split(/[.\-_\s]+/).filter((word) => word.length > 0);
|
|
80
|
-
let name;
|
|
81
|
-
if (words.length === 0) {
|
|
82
|
-
name = str.charAt(0).toLowerCase() + str.slice(1);
|
|
83
|
-
} else if (words.length === 1) {
|
|
84
|
-
name = words[0].charAt(0).toLowerCase() + words[0].slice(1);
|
|
85
|
-
} else {
|
|
86
|
-
name = words[0].charAt(0).toLowerCase() + words[0].slice(1) + words.slice(1).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
87
|
-
}
|
|
88
|
-
if (options == null ? void 0 : options.prefix) {
|
|
89
|
-
const prefix = options.prefix.charAt(0).toLowerCase() + options.prefix.slice(1);
|
|
90
|
-
name = prefix + name.charAt(0).toUpperCase() + name.slice(1);
|
|
91
|
-
}
|
|
92
|
-
if (options == null ? void 0 : options.suffix) {
|
|
93
|
-
const suffix = options.suffix.charAt(0).toUpperCase() + options.suffix.slice(1);
|
|
94
|
-
name = name + suffix;
|
|
95
|
-
}
|
|
96
|
-
return name;
|
|
97
|
-
}
|
|
98
|
-
function toPascalCase(str) {
|
|
99
|
-
const stringValue = String(str);
|
|
100
|
-
const isAlreadyValidCase = /^[a-zA-Z][a-zA-Z0-9]*$/.test(stringValue);
|
|
101
|
-
if (isAlreadyValidCase) {
|
|
102
|
-
return stringValue.charAt(0).toUpperCase() + stringValue.slice(1);
|
|
103
|
-
}
|
|
104
|
-
const sanitized = sanitizeIdentifier(stringValue);
|
|
105
|
-
const words = sanitized.split(/[.\-_\s]+/).filter((word) => word.length > 0);
|
|
106
|
-
let result;
|
|
107
|
-
if (words.length === 0) {
|
|
108
|
-
result = "Value";
|
|
109
|
-
} else {
|
|
110
|
-
result = words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
111
|
-
}
|
|
112
|
-
if (/^\d/.test(result)) {
|
|
113
|
-
result = `N${result}`;
|
|
114
|
-
}
|
|
115
|
-
if (!result || /^_+$/.test(result)) {
|
|
116
|
-
return "Value";
|
|
117
|
-
}
|
|
118
|
-
return result;
|
|
119
|
-
}
|
|
120
|
-
function resolveRef(ref) {
|
|
121
|
-
const parts = ref.split("/");
|
|
122
|
-
return parts[parts.length - 1];
|
|
123
|
-
}
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
CircularReferenceError,
|
|
4
|
+
CliOptionsError,
|
|
5
|
+
ConfigValidationError,
|
|
6
|
+
FileOperationError,
|
|
7
|
+
GeneratorError,
|
|
8
|
+
SchemaGenerationError as SchemaGenerationError2,
|
|
9
|
+
SpecValidationError as SpecValidationError2
|
|
10
|
+
} from "@cerios/openapi-core";
|
|
124
11
|
|
|
125
|
-
// src/generators/
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (allStrings) {
|
|
137
|
-
const enumValues = values.map((v) => `"${v}"`).join(", ");
|
|
138
|
-
const schemaCode2 = `export const ${schemaName} = z.enum([${enumValues}]);`;
|
|
139
|
-
const typeCode2 = `export type ${typeName} = z.infer<typeof ${schemaName}>;`;
|
|
140
|
-
return { schemaCode: schemaCode2, typeCode: typeCode2 };
|
|
141
|
-
}
|
|
142
|
-
const literalValues = values.map((v) => {
|
|
143
|
-
if (typeof v === "string") {
|
|
144
|
-
return `z.literal("${v}")`;
|
|
145
|
-
}
|
|
146
|
-
return `z.literal(${v})`;
|
|
147
|
-
}).join(", ");
|
|
148
|
-
const schemaCode = `export const ${schemaName} = z.union([${literalValues}]);`;
|
|
149
|
-
const typeCode = `export type ${typeName} = z.infer<typeof ${schemaName}>;`;
|
|
150
|
-
return { schemaCode, typeCode };
|
|
151
|
-
}
|
|
12
|
+
// src/generators/property-generator.ts
|
|
13
|
+
import {
|
|
14
|
+
getPrimaryType,
|
|
15
|
+
hasMultipleTypes,
|
|
16
|
+
isNullable,
|
|
17
|
+
LRUCache,
|
|
18
|
+
resolveRefName,
|
|
19
|
+
stripPrefix,
|
|
20
|
+
toCamelCase,
|
|
21
|
+
toPascalCase
|
|
22
|
+
} from "@cerios/openapi-core";
|
|
152
23
|
|
|
153
24
|
// src/utils/string-utils.ts
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
function escapePattern(str) {
|
|
158
|
-
return str.replace(/(?<!\\)\//g, "\\/");
|
|
159
|
-
}
|
|
160
|
-
function escapeJSDoc(str) {
|
|
161
|
-
return str.replace(/\*\//g, "*\\/");
|
|
162
|
-
}
|
|
163
|
-
function wrapNullable(validation, isNullable2) {
|
|
164
|
-
return isNullable2 ? `${validation}.nullable()` : validation;
|
|
165
|
-
}
|
|
166
|
-
function isNullable(schema, defaultNullable = false) {
|
|
167
|
-
if (schema.nullable === true) {
|
|
168
|
-
return true;
|
|
169
|
-
}
|
|
170
|
-
if (schema.nullable === false) {
|
|
171
|
-
return false;
|
|
172
|
-
}
|
|
173
|
-
if (Array.isArray(schema.type)) {
|
|
174
|
-
return schema.type.includes("null");
|
|
175
|
-
}
|
|
176
|
-
return defaultNullable;
|
|
177
|
-
}
|
|
178
|
-
function getPrimaryType(schema) {
|
|
179
|
-
if (Array.isArray(schema.type)) {
|
|
180
|
-
const nonNullType = schema.type.find((t) => t !== "null");
|
|
181
|
-
return nonNullType;
|
|
182
|
-
}
|
|
183
|
-
return schema.type;
|
|
184
|
-
}
|
|
185
|
-
function hasMultipleTypes(schema) {
|
|
186
|
-
if (Array.isArray(schema.type)) {
|
|
187
|
-
const nonNullTypes = schema.type.filter((t) => t !== "null");
|
|
188
|
-
return nonNullTypes.length > 1;
|
|
189
|
-
}
|
|
190
|
-
return false;
|
|
25
|
+
import { escapeDescription } from "@cerios/openapi-core";
|
|
26
|
+
function wrapNullable(validation, nullable) {
|
|
27
|
+
return nullable ? `${validation}.nullable()` : validation;
|
|
191
28
|
}
|
|
192
29
|
function addDescription(validation, description, useDescribe) {
|
|
193
30
|
if (!description || !useDescribe) return validation;
|
|
@@ -195,167 +32,6 @@ function addDescription(validation, description, useDescribe) {
|
|
|
195
32
|
return `${validation}.describe("${escapedDesc}")`;
|
|
196
33
|
}
|
|
197
34
|
|
|
198
|
-
// src/generators/jsdoc-generator.ts
|
|
199
|
-
function generateJSDoc(schema, name, options = { includeDescriptions: true }) {
|
|
200
|
-
if (!schema || typeof schema !== "object") {
|
|
201
|
-
return "";
|
|
202
|
-
}
|
|
203
|
-
if (!options.includeDescriptions) {
|
|
204
|
-
if (schema.deprecated) {
|
|
205
|
-
return "/** @deprecated */\n";
|
|
206
|
-
}
|
|
207
|
-
return "";
|
|
208
|
-
}
|
|
209
|
-
if (!schema.description && !schema.title && !schema.deprecated && !schema.examples && schema.example === void 0) {
|
|
210
|
-
return "";
|
|
211
|
-
}
|
|
212
|
-
const parts = [];
|
|
213
|
-
if (schema.title && typeof schema.title === "string" && (!name || schema.title !== name)) {
|
|
214
|
-
const sanitizedTitle = escapeJSDoc(schema.title).replace(/@/g, "\\@");
|
|
215
|
-
parts.push(sanitizedTitle);
|
|
216
|
-
}
|
|
217
|
-
if (schema.description && typeof schema.description === "string") {
|
|
218
|
-
const sanitizedDesc = escapeJSDoc(schema.description).replace(/@/g, "\\@").replace(/\*\//g, "*\\/");
|
|
219
|
-
parts.push(sanitizedDesc);
|
|
220
|
-
}
|
|
221
|
-
if (schema.examples && Array.isArray(schema.examples) && schema.examples.length > 0) {
|
|
222
|
-
try {
|
|
223
|
-
const examplesStr = schema.examples.map((ex) => JSON.stringify(ex)).join(", ");
|
|
224
|
-
parts.push(`@example ${examplesStr}`);
|
|
225
|
-
} catch (error) {
|
|
226
|
-
console.warn("Warning: Could not serialize schema examples", error);
|
|
227
|
-
}
|
|
228
|
-
} else if (schema.example !== void 0) {
|
|
229
|
-
try {
|
|
230
|
-
parts.push(`@example ${JSON.stringify(schema.example)}`);
|
|
231
|
-
} catch (error) {
|
|
232
|
-
console.warn("Warning: Could not serialize schema example", error);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
if (schema.deprecated) {
|
|
236
|
-
parts.push("@deprecated");
|
|
237
|
-
}
|
|
238
|
-
if (parts.length === 0) {
|
|
239
|
-
return "";
|
|
240
|
-
}
|
|
241
|
-
const fullComment = parts.join(" ");
|
|
242
|
-
return `/** ${fullComment} */
|
|
243
|
-
`;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// src/utils/lru-cache.ts
|
|
247
|
-
var LRUCache = class {
|
|
248
|
-
constructor(maxSize) {
|
|
249
|
-
this.cache = /* @__PURE__ */ new Map();
|
|
250
|
-
this.maxSize = maxSize;
|
|
251
|
-
}
|
|
252
|
-
get capacity() {
|
|
253
|
-
return this.maxSize;
|
|
254
|
-
}
|
|
255
|
-
get(key) {
|
|
256
|
-
if (!this.cache.has(key)) return void 0;
|
|
257
|
-
const value = this.cache.get(key);
|
|
258
|
-
if (value === void 0) return void 0;
|
|
259
|
-
this.cache.delete(key);
|
|
260
|
-
this.cache.set(key, value);
|
|
261
|
-
return value;
|
|
262
|
-
}
|
|
263
|
-
set(key, value) {
|
|
264
|
-
if (this.cache.has(key)) {
|
|
265
|
-
this.cache.delete(key);
|
|
266
|
-
} else if (this.cache.size >= this.maxSize) {
|
|
267
|
-
const firstKey = this.cache.keys().next().value;
|
|
268
|
-
if (firstKey !== void 0) {
|
|
269
|
-
this.cache.delete(firstKey);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
this.cache.set(key, value);
|
|
273
|
-
}
|
|
274
|
-
has(key) {
|
|
275
|
-
return this.cache.has(key);
|
|
276
|
-
}
|
|
277
|
-
clear() {
|
|
278
|
-
this.cache.clear();
|
|
279
|
-
}
|
|
280
|
-
size() {
|
|
281
|
-
return this.cache.size;
|
|
282
|
-
}
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
// src/utils/pattern-utils.ts
|
|
286
|
-
import { minimatch } from "minimatch";
|
|
287
|
-
function isValidGlobPattern(pattern) {
|
|
288
|
-
try {
|
|
289
|
-
new minimatch.Minimatch(pattern);
|
|
290
|
-
return true;
|
|
291
|
-
} catch {
|
|
292
|
-
return false;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
function isGlobPattern(pattern) {
|
|
296
|
-
return /[*?[\]{}!]/.test(pattern);
|
|
297
|
-
}
|
|
298
|
-
function stripPrefix(input, pattern, ensureLeadingChar) {
|
|
299
|
-
if (!pattern) {
|
|
300
|
-
return input;
|
|
301
|
-
}
|
|
302
|
-
if (isGlobPattern(pattern) && !isValidGlobPattern(pattern)) {
|
|
303
|
-
console.warn(`\u26A0\uFE0F Invalid glob pattern "${pattern}": Pattern is malformed`);
|
|
304
|
-
return input;
|
|
305
|
-
}
|
|
306
|
-
if (isGlobPattern(pattern)) {
|
|
307
|
-
let longestMatch = -1;
|
|
308
|
-
for (let i = 1; i <= input.length; i++) {
|
|
309
|
-
const testPrefix = input.substring(0, i);
|
|
310
|
-
if (minimatch(testPrefix, pattern)) {
|
|
311
|
-
longestMatch = i;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
if (longestMatch > 0) {
|
|
315
|
-
const stripped = input.substring(longestMatch);
|
|
316
|
-
if (ensureLeadingChar) {
|
|
317
|
-
if (stripped === "") {
|
|
318
|
-
return ensureLeadingChar;
|
|
319
|
-
}
|
|
320
|
-
if (!stripped.startsWith(ensureLeadingChar)) {
|
|
321
|
-
return `${ensureLeadingChar}${stripped}`;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
return stripped === "" && !ensureLeadingChar ? input : stripped;
|
|
325
|
-
}
|
|
326
|
-
return input;
|
|
327
|
-
}
|
|
328
|
-
if (input.startsWith(pattern)) {
|
|
329
|
-
const stripped = input.substring(pattern.length);
|
|
330
|
-
if (ensureLeadingChar) {
|
|
331
|
-
if (stripped === "") {
|
|
332
|
-
return ensureLeadingChar;
|
|
333
|
-
}
|
|
334
|
-
if (!stripped.startsWith(ensureLeadingChar)) {
|
|
335
|
-
return `${ensureLeadingChar}${stripped}`;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
return stripped;
|
|
339
|
-
}
|
|
340
|
-
return input;
|
|
341
|
-
}
|
|
342
|
-
function stripPathPrefix(path, pattern) {
|
|
343
|
-
if (!pattern) {
|
|
344
|
-
return path;
|
|
345
|
-
}
|
|
346
|
-
if (!isGlobPattern(pattern)) {
|
|
347
|
-
let normalizedPattern = pattern.trim();
|
|
348
|
-
if (!normalizedPattern.startsWith("/")) {
|
|
349
|
-
normalizedPattern = `/${normalizedPattern}`;
|
|
350
|
-
}
|
|
351
|
-
if (normalizedPattern.endsWith("/") && normalizedPattern !== "/") {
|
|
352
|
-
normalizedPattern = normalizedPattern.slice(0, -1);
|
|
353
|
-
}
|
|
354
|
-
return stripPrefix(path, normalizedPattern, "/");
|
|
355
|
-
}
|
|
356
|
-
return stripPrefix(path, pattern, "/");
|
|
357
|
-
}
|
|
358
|
-
|
|
359
35
|
// src/validators/array-validator.ts
|
|
360
36
|
function generateArrayValidation(schema, context) {
|
|
361
37
|
var _a;
|
|
@@ -523,15 +199,14 @@ function detectConflictingProperties(schemas, context) {
|
|
|
523
199
|
function generateAllOf(schemas, isNullable2, context, currentSchema) {
|
|
524
200
|
if (schemas.length === 1) {
|
|
525
201
|
const singleSchema = context.generatePropertySchema(schemas[0], currentSchema, false, true);
|
|
526
|
-
return wrapNullable(singleSchema, isNullable2);
|
|
202
|
+
return { schema: wrapNullable(singleSchema, isNullable2), conflicts: [] };
|
|
527
203
|
}
|
|
528
204
|
const conflicts = detectConflictingProperties(schemas, context);
|
|
529
|
-
|
|
530
|
-
if (
|
|
531
|
-
for (const conflict of
|
|
205
|
+
const uniqueConflicts = [...new Set(conflicts)];
|
|
206
|
+
if (uniqueConflicts.length > 0) {
|
|
207
|
+
for (const conflict of uniqueConflicts) {
|
|
532
208
|
console.warn(`[openapi-to-zod] Warning: allOf composition conflict - ${conflict}`);
|
|
533
209
|
}
|
|
534
|
-
conflictDescription = `allOf property conflicts detected: ${conflicts.join("; ")}`;
|
|
535
210
|
}
|
|
536
211
|
const allObjects = schemas.every((s) => s.type === "object" || s.properties || s.$ref || s.allOf);
|
|
537
212
|
let result;
|
|
@@ -559,10 +234,7 @@ function generateAllOf(schemas, isNullable2, context, currentSchema) {
|
|
|
559
234
|
}
|
|
560
235
|
result = merged;
|
|
561
236
|
}
|
|
562
|
-
|
|
563
|
-
result = `${result}.describe("${conflictDescription}")`;
|
|
564
|
-
}
|
|
565
|
-
return wrapNullable(result, isNullable2);
|
|
237
|
+
return { schema: wrapNullable(result, isNullable2), conflicts: uniqueConflicts };
|
|
566
238
|
}
|
|
567
239
|
|
|
568
240
|
// src/validators/number-validator.ts
|
|
@@ -586,6 +258,53 @@ function generateNumberValidation(schema, isInt, useDescribe) {
|
|
|
586
258
|
return addDescription(validation, schema.description, useDescribe);
|
|
587
259
|
}
|
|
588
260
|
|
|
261
|
+
// src/generators/jsdoc-generator.ts
|
|
262
|
+
import { escapeJSDoc } from "@cerios/openapi-core";
|
|
263
|
+
function generateJSDoc(schema, name, options = { includeDescriptions: true }) {
|
|
264
|
+
if (!schema || typeof schema !== "object") {
|
|
265
|
+
return "";
|
|
266
|
+
}
|
|
267
|
+
if (!options.includeDescriptions) {
|
|
268
|
+
if (schema.deprecated) {
|
|
269
|
+
return "/** @deprecated */\n";
|
|
270
|
+
}
|
|
271
|
+
return "";
|
|
272
|
+
}
|
|
273
|
+
if (!schema.description && !schema.title && !schema.deprecated && !schema.examples && schema.example === void 0) {
|
|
274
|
+
return "";
|
|
275
|
+
}
|
|
276
|
+
const parts = [];
|
|
277
|
+
if (schema.title && typeof schema.title === "string" && (!name || schema.title !== name)) {
|
|
278
|
+
parts.push(escapeJSDoc(schema.title));
|
|
279
|
+
}
|
|
280
|
+
if (schema.description && typeof schema.description === "string") {
|
|
281
|
+
parts.push(escapeJSDoc(schema.description));
|
|
282
|
+
}
|
|
283
|
+
if (schema.examples && Array.isArray(schema.examples) && schema.examples.length > 0) {
|
|
284
|
+
try {
|
|
285
|
+
const examplesStr = schema.examples.map((ex) => JSON.stringify(ex)).join(", ");
|
|
286
|
+
parts.push(`@example ${examplesStr}`);
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.warn("Warning: Could not serialize schema examples", error);
|
|
289
|
+
}
|
|
290
|
+
} else if (schema.example !== void 0) {
|
|
291
|
+
try {
|
|
292
|
+
parts.push(`@example ${JSON.stringify(schema.example)}`);
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.warn("Warning: Could not serialize schema example", error);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (schema.deprecated) {
|
|
298
|
+
parts.push("@deprecated");
|
|
299
|
+
}
|
|
300
|
+
if (parts.length === 0) {
|
|
301
|
+
return "";
|
|
302
|
+
}
|
|
303
|
+
const fullComment = parts.join(" ");
|
|
304
|
+
return `/** ${fullComment} */
|
|
305
|
+
`;
|
|
306
|
+
}
|
|
307
|
+
|
|
589
308
|
// src/validators/conditional-validator.ts
|
|
590
309
|
function generatePropertyAccess(propName) {
|
|
591
310
|
const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
@@ -644,7 +363,8 @@ function generateConditionalCheck(schema) {
|
|
|
644
363
|
for (const [prop, propSchema] of Object.entries(schema.properties)) {
|
|
645
364
|
const propAccess = generatePropertyAccess(prop);
|
|
646
365
|
if (propSchema.type) {
|
|
647
|
-
|
|
366
|
+
const schemaType = Array.isArray(propSchema.type) ? propSchema.type[0] : propSchema.type;
|
|
367
|
+
conditions.push(`typeof ${propAccess} === "${schemaType}"`);
|
|
648
368
|
}
|
|
649
369
|
if (propSchema.const !== void 0) {
|
|
650
370
|
const value = typeof propSchema.const === "string" ? `"${propSchema.const}"` : propSchema.const;
|
|
@@ -703,7 +423,7 @@ function generateIfThenElse(schema) {
|
|
|
703
423
|
if (!thenValid) {
|
|
704
424
|
${thenRequiredProps.length > 0 ? `
|
|
705
425
|
const missingThenProps = ${JSON.stringify(thenRequiredProps)}.filter(p => obj[p] === undefined);
|
|
706
|
-
const message = missingThenProps.length > 0
|
|
426
|
+
const message = missingThenProps.length > 0
|
|
707
427
|
? \`When condition is met, required properties are missing: \${missingThenProps.join(', ')}\`
|
|
708
428
|
: "When condition is met, validation constraints failed";
|
|
709
429
|
` : `
|
|
@@ -721,7 +441,7 @@ function generateIfThenElse(schema) {
|
|
|
721
441
|
if (!elseValid) {
|
|
722
442
|
${elseRequiredProps2.length > 0 ? `
|
|
723
443
|
const missingElseProps = ${JSON.stringify(elseRequiredProps2)}.filter(p => obj[p] === undefined);
|
|
724
|
-
const message = missingElseProps.length > 0
|
|
444
|
+
const message = missingElseProps.length > 0
|
|
725
445
|
? \`When condition is not met, required properties are missing: \${missingElseProps.join(', ')}\`
|
|
726
446
|
: "When condition is not met, validation constraints failed";
|
|
727
447
|
` : `
|
|
@@ -746,7 +466,7 @@ function generateIfThenElse(schema) {
|
|
|
746
466
|
if (!thenValid) {
|
|
747
467
|
${thenRequiredProps.length > 0 ? `
|
|
748
468
|
const missingProps = ${JSON.stringify(thenRequiredProps)}.filter(p => obj[p] === undefined);
|
|
749
|
-
const message = missingProps.length > 0
|
|
469
|
+
const message = missingProps.length > 0
|
|
750
470
|
? \`When condition is met, required properties are missing: \${missingProps.join(', ')}\`
|
|
751
471
|
: "When condition is met, validation constraints failed";
|
|
752
472
|
` : `
|
|
@@ -771,7 +491,7 @@ function generateIfThenElse(schema) {
|
|
|
771
491
|
if (!elseValid) {
|
|
772
492
|
${elseRequiredProps.length > 0 ? `
|
|
773
493
|
const missingProps = ${JSON.stringify(elseRequiredProps)}.filter(p => obj[p] === undefined);
|
|
774
|
-
const message = missingProps.length > 0
|
|
494
|
+
const message = missingProps.length > 0
|
|
775
495
|
? \`When condition is not met, required properties are missing: \${missingProps.join(', ')}\`
|
|
776
496
|
: "When condition is not met, validation constraints failed";
|
|
777
497
|
` : `
|
|
@@ -859,8 +579,9 @@ ${propertyDef}`);
|
|
|
859
579
|
case "loose":
|
|
860
580
|
objectMethod = "z.looseObject";
|
|
861
581
|
break;
|
|
862
|
-
|
|
582
|
+
case "normal":
|
|
863
583
|
objectMethod = "z.object";
|
|
584
|
+
break;
|
|
864
585
|
}
|
|
865
586
|
}
|
|
866
587
|
let objectDef = `${objectMethod}({
|
|
@@ -870,7 +591,7 @@ ${properties.join(",\n")}
|
|
|
870
591
|
if (typeof schema.additionalProperties === "object") {
|
|
871
592
|
const additionalSchema = context.generatePropertySchema(schema.additionalProperties, currentSchema);
|
|
872
593
|
objectDef += `.catchall(${additionalSchema})`;
|
|
873
|
-
} else if (schema.additionalProperties
|
|
594
|
+
} else if (schema.additionalProperties) {
|
|
874
595
|
objectDef += ".catchall(z.unknown())";
|
|
875
596
|
}
|
|
876
597
|
} else if (schema.patternProperties) {
|
|
@@ -995,6 +716,7 @@ ${properties.join(",\n")}
|
|
|
995
716
|
}
|
|
996
717
|
|
|
997
718
|
// src/validators/string-validator.ts
|
|
719
|
+
import { escapePattern } from "@cerios/openapi-core";
|
|
998
720
|
var DEFAULT_FORMAT_MAP = {
|
|
999
721
|
uuid: "z.uuid()",
|
|
1000
722
|
email: "z.email()",
|
|
@@ -1120,8 +842,32 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1120
842
|
this.filteredPropsCache = /* @__PURE__ */ new Map();
|
|
1121
843
|
// Performance optimization: LRU cache for generated schemas
|
|
1122
844
|
this.schemaCache = new LRUCache(500);
|
|
845
|
+
// Track allOf conflicts detected during schema generation
|
|
846
|
+
this.allOfConflicts = [];
|
|
847
|
+
// Schemas that are part of circular dependency chains (need z.lazy for forward refs)
|
|
848
|
+
this.circularDependencies = /* @__PURE__ */ new Set();
|
|
1123
849
|
this.context = context;
|
|
1124
850
|
}
|
|
851
|
+
/**
|
|
852
|
+
* Set the schemas that are involved in circular dependency chains.
|
|
853
|
+
* These schemas will use z.lazy() for forward references.
|
|
854
|
+
*/
|
|
855
|
+
setCircularDependencies(deps) {
|
|
856
|
+
this.circularDependencies = deps;
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Get allOf conflicts detected during the last schema generation
|
|
860
|
+
* @returns Array of conflict description strings
|
|
861
|
+
*/
|
|
862
|
+
getAllOfConflicts() {
|
|
863
|
+
return [...this.allOfConflicts];
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Clear tracked allOf conflicts (call before generating a new schema)
|
|
867
|
+
*/
|
|
868
|
+
clearAllOfConflicts() {
|
|
869
|
+
this.allOfConflicts = [];
|
|
870
|
+
}
|
|
1125
871
|
/**
|
|
1126
872
|
* Check if a property should be included based on schemaType and readOnly/writeOnly flags
|
|
1127
873
|
*/
|
|
@@ -1154,7 +900,9 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1154
900
|
filterNestedProperties(schema) {
|
|
1155
901
|
var _a, _b;
|
|
1156
902
|
const propKeys = schema.properties ? Object.keys(schema.properties).sort().join(",") : "";
|
|
1157
|
-
const
|
|
903
|
+
const requiredKeys = Array.isArray(schema.required) ? schema.required.join(",") : String((_a = schema.required) != null ? _a : "");
|
|
904
|
+
const schemaType = Array.isArray(schema.type) ? schema.type.join("|") : schema.type || "unknown";
|
|
905
|
+
const cacheKey = `${this.context.schemaType}:${schemaType}:${propKeys}:${requiredKeys}`;
|
|
1158
906
|
const cached = this.filteredPropsCache.get(cacheKey);
|
|
1159
907
|
if (cached) {
|
|
1160
908
|
return cached;
|
|
@@ -1250,7 +998,7 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1250
998
|
const schema = (_b = (_a = this.context.spec.components) == null ? void 0 : _a.schemas) == null ? void 0 : _b[schemaName];
|
|
1251
999
|
if (!schema) return schemaName;
|
|
1252
1000
|
if (schema.allOf && schema.allOf.length === 1 && schema.allOf[0].$ref && !schema.properties && !schema.oneOf && !schema.anyOf) {
|
|
1253
|
-
const targetName =
|
|
1001
|
+
const targetName = resolveRefName(schema.allOf[0].$ref);
|
|
1254
1002
|
return this.resolveSchemaAlias(targetName);
|
|
1255
1003
|
}
|
|
1256
1004
|
return schemaName;
|
|
@@ -1263,7 +1011,7 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1263
1011
|
const toSchemaSpec = (_b = (_a = this.context.spec.components) == null ? void 0 : _a.schemas) == null ? void 0 : _b[toSchema];
|
|
1264
1012
|
if (!toSchemaSpec) return false;
|
|
1265
1013
|
if (toSchemaSpec.allOf && toSchemaSpec.allOf.length === 1 && toSchemaSpec.allOf[0].$ref) {
|
|
1266
|
-
const aliasTarget =
|
|
1014
|
+
const aliasTarget = resolveRefName(toSchemaSpec.allOf[0].$ref);
|
|
1267
1015
|
return aliasTarget === fromSchema;
|
|
1268
1016
|
}
|
|
1269
1017
|
return false;
|
|
@@ -1364,7 +1112,7 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1364
1112
|
return wrapNullable(union, nullable);
|
|
1365
1113
|
}
|
|
1366
1114
|
if (schema.$ref) {
|
|
1367
|
-
const refName =
|
|
1115
|
+
const refName = resolveRefName(schema.$ref);
|
|
1368
1116
|
const resolvedRefName = this.resolveSchemaAlias(refName);
|
|
1369
1117
|
if (currentSchema && refName !== currentSchema && !isTopLevel) {
|
|
1370
1118
|
if (!this.context.schemaDependencies.has(currentSchema)) {
|
|
@@ -1374,8 +1122,13 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1374
1122
|
}
|
|
1375
1123
|
const strippedRefName = stripPrefix(resolvedRefName, this.context.stripSchemaPrefix);
|
|
1376
1124
|
const schemaName = `${toCamelCase(strippedRefName, this.context.namingOptions)}Schema`;
|
|
1377
|
-
|
|
1378
|
-
|
|
1125
|
+
const typeName = toPascalCase(strippedRefName);
|
|
1126
|
+
const isDirectSelfRef = currentSchema && refName === currentSchema;
|
|
1127
|
+
const isCircularAlias = currentSchema && this.isCircularThroughAlias(currentSchema, refName);
|
|
1128
|
+
const isMutuallyCircular = currentSchema && this.circularDependencies.has(currentSchema) && this.circularDependencies.has(refName);
|
|
1129
|
+
if (isDirectSelfRef || isCircularAlias || isMutuallyCircular) {
|
|
1130
|
+
const lazyTypeAnnotation = this.context.separateTypesFile ? `z.ZodType<${typeName}>` : "z.ZodTypeAny";
|
|
1131
|
+
const lazySchema = `z.lazy((): ${lazyTypeAnnotation} => ${schemaName})`;
|
|
1379
1132
|
return wrapNullable(lazySchema, nullable);
|
|
1380
1133
|
}
|
|
1381
1134
|
return wrapNullable(schemaName, nullable);
|
|
@@ -1408,7 +1161,7 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1408
1161
|
}
|
|
1409
1162
|
if (schema.allOf) {
|
|
1410
1163
|
const compositionNullable = isNullable(schema, false);
|
|
1411
|
-
|
|
1164
|
+
const allOfResult = generateAllOf(
|
|
1412
1165
|
schema.allOf,
|
|
1413
1166
|
compositionNullable,
|
|
1414
1167
|
{
|
|
@@ -1418,6 +1171,10 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1418
1171
|
},
|
|
1419
1172
|
currentSchema
|
|
1420
1173
|
);
|
|
1174
|
+
if (allOfResult.conflicts.length > 0) {
|
|
1175
|
+
this.allOfConflicts.push(...allOfResult.conflicts);
|
|
1176
|
+
}
|
|
1177
|
+
let composition = allOfResult.schema;
|
|
1421
1178
|
if (schema.unevaluatedProperties !== void 0) {
|
|
1422
1179
|
composition = this.applyUnevaluatedProperties(composition, schema);
|
|
1423
1180
|
}
|
|
@@ -1529,13 +1286,14 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
1529
1286
|
case "loose":
|
|
1530
1287
|
validation = "z.looseObject({})";
|
|
1531
1288
|
break;
|
|
1532
|
-
|
|
1289
|
+
case "record":
|
|
1533
1290
|
validation = "z.record(z.string(), z.unknown())";
|
|
1534
1291
|
break;
|
|
1535
1292
|
}
|
|
1536
1293
|
validation = addDescription(validation, schema.description, this.context.useDescribe);
|
|
1537
1294
|
}
|
|
1538
1295
|
break;
|
|
1296
|
+
case void 0:
|
|
1539
1297
|
default:
|
|
1540
1298
|
validation = "z.unknown()";
|
|
1541
1299
|
validation = addDescription(validation, schema.description, this.context.useDescribe);
|
|
@@ -1594,194 +1352,71 @@ _PropertyGenerator.INCLUSION_RULES = {
|
|
|
1594
1352
|
};
|
|
1595
1353
|
var PropertyGenerator = _PropertyGenerator;
|
|
1596
1354
|
|
|
1597
|
-
// src/
|
|
1598
|
-
import {
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1355
|
+
// src/openapi-generator.ts
|
|
1356
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
1357
|
+
import { dirname, normalize, relative } from "path";
|
|
1358
|
+
import {
|
|
1359
|
+
analyzeSchemaUsage,
|
|
1360
|
+
ConfigurationError,
|
|
1361
|
+
createFilterStatistics,
|
|
1362
|
+
detectCircularReferences,
|
|
1363
|
+
expandTransitiveReferences,
|
|
1364
|
+
extractSchemaRefs,
|
|
1365
|
+
formatFilterStatistics,
|
|
1366
|
+
getOperationName,
|
|
1367
|
+
LRUCache as LRUCache2,
|
|
1368
|
+
loadOpenAPISpec,
|
|
1369
|
+
mergeParameters,
|
|
1370
|
+
resolveRefName as resolveRefName2,
|
|
1371
|
+
SchemaGenerationError,
|
|
1372
|
+
SpecValidationError,
|
|
1373
|
+
shouldIncludeOperation,
|
|
1374
|
+
stripPathPrefix,
|
|
1375
|
+
stripPrefix as stripPrefix2,
|
|
1376
|
+
toCamelCase as toCamelCase3,
|
|
1377
|
+
toPascalCase as toPascalCase3,
|
|
1378
|
+
validateFilters
|
|
1379
|
+
} from "@cerios/openapi-core";
|
|
1380
|
+
import { TypeScriptGenerator } from "@cerios/openapi-to-typescript";
|
|
1381
|
+
import { minimatch } from "minimatch";
|
|
1382
|
+
|
|
1383
|
+
// src/generators/enum-generator.ts
|
|
1384
|
+
import { toCamelCase as toCamelCase2, toPascalCase as toPascalCase2 } from "@cerios/openapi-core";
|
|
1385
|
+
function generateEnum(name, values, options) {
|
|
1386
|
+
const schemaName = `${toCamelCase2(name, options)}Schema`;
|
|
1387
|
+
const typeName = toPascalCase2(name);
|
|
1388
|
+
const allBooleans = values.every((v) => typeof v === "boolean");
|
|
1389
|
+
if (allBooleans) {
|
|
1390
|
+
const schemaCode2 = `export const ${schemaName} = z.boolean();`;
|
|
1391
|
+
const typeCode2 = `export type ${typeName} = z.infer<typeof ${schemaName}>;`;
|
|
1392
|
+
return { schemaCode: schemaCode2, typeCode: typeCode2 };
|
|
1622
1393
|
}
|
|
1623
|
-
|
|
1624
|
-
|
|
1394
|
+
const allStrings = values.every((v) => typeof v === "string");
|
|
1395
|
+
if (allStrings) {
|
|
1396
|
+
const enumValues = values.map((v) => `"${v}"`).join(", ");
|
|
1397
|
+
const schemaCode2 = `export const ${schemaName} = z.enum([${enumValues}]);`;
|
|
1398
|
+
const typeCode2 = `export type ${typeName} = z.infer<typeof ${schemaName}>;`;
|
|
1399
|
+
return { schemaCode: schemaCode2, typeCode: typeCode2 };
|
|
1625
1400
|
}
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
if (!filters) {
|
|
1630
|
-
return true;
|
|
1631
|
-
}
|
|
1632
|
-
const methodLower = method.toLowerCase();
|
|
1633
|
-
const operationId = operation == null ? void 0 : operation.operationId;
|
|
1634
|
-
const tags = (operation == null ? void 0 : operation.tags) || [];
|
|
1635
|
-
const deprecated = (operation == null ? void 0 : operation.deprecated) === true;
|
|
1636
|
-
if (filters.includeTags && filters.includeTags.length > 0) {
|
|
1637
|
-
if (!containsAny(tags, filters.includeTags)) {
|
|
1638
|
-
if (stats) stats.filteredByTags++;
|
|
1639
|
-
return false;
|
|
1401
|
+
const literalValues = values.map((v) => {
|
|
1402
|
+
if (typeof v === "string") {
|
|
1403
|
+
return `z.literal("${v}")`;
|
|
1640
1404
|
}
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
}
|
|
1647
|
-
}
|
|
1648
|
-
if (filters.includeMethods && filters.includeMethods.length > 0) {
|
|
1649
|
-
const methodsLower = filters.includeMethods.map((m) => m.toLowerCase());
|
|
1650
|
-
if (!methodsLower.includes(methodLower)) {
|
|
1651
|
-
if (stats) stats.filteredByMethods++;
|
|
1652
|
-
return false;
|
|
1653
|
-
}
|
|
1654
|
-
}
|
|
1655
|
-
if (filters.includeOperationIds && filters.includeOperationIds.length > 0) {
|
|
1656
|
-
if (!matchesAnyPattern(operationId, filters.includeOperationIds)) {
|
|
1657
|
-
if (stats) stats.filteredByOperationIds++;
|
|
1658
|
-
return false;
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
if (filters.excludeDeprecated === true && deprecated) {
|
|
1662
|
-
if (stats) stats.filteredByDeprecated++;
|
|
1663
|
-
return false;
|
|
1664
|
-
}
|
|
1665
|
-
if (filters.excludeTags && filters.excludeTags.length > 0) {
|
|
1666
|
-
if (containsAny(tags, filters.excludeTags)) {
|
|
1667
|
-
if (stats) stats.filteredByTags++;
|
|
1668
|
-
return false;
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
if (filters.excludePaths && filters.excludePaths.length > 0) {
|
|
1672
|
-
if (matchesAnyPattern(path, filters.excludePaths)) {
|
|
1673
|
-
if (stats) stats.filteredByPaths++;
|
|
1674
|
-
return false;
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
if (filters.excludeMethods && filters.excludeMethods.length > 0) {
|
|
1678
|
-
const methodsLower = filters.excludeMethods.map((m) => m.toLowerCase());
|
|
1679
|
-
if (methodsLower.includes(methodLower)) {
|
|
1680
|
-
if (stats) stats.filteredByMethods++;
|
|
1681
|
-
return false;
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
if (filters.excludeOperationIds && filters.excludeOperationIds.length > 0) {
|
|
1685
|
-
if (matchesAnyPattern(operationId, filters.excludeOperationIds)) {
|
|
1686
|
-
if (stats) stats.filteredByOperationIds++;
|
|
1687
|
-
return false;
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
return true;
|
|
1691
|
-
}
|
|
1692
|
-
function validateFilters(stats, filters) {
|
|
1693
|
-
if (!filters || stats.totalOperations === 0) {
|
|
1694
|
-
return;
|
|
1695
|
-
}
|
|
1696
|
-
if (stats.includedOperations === 0) {
|
|
1697
|
-
console.warn(
|
|
1698
|
-
`\u26A0\uFE0F Warning: All ${stats.totalOperations} operations were filtered out. Check your operationFilters configuration.`
|
|
1699
|
-
);
|
|
1700
|
-
const filterBreakdown = [];
|
|
1701
|
-
if (stats.filteredByTags > 0) filterBreakdown.push(`${stats.filteredByTags} by tags`);
|
|
1702
|
-
if (stats.filteredByPaths > 0) filterBreakdown.push(`${stats.filteredByPaths} by paths`);
|
|
1703
|
-
if (stats.filteredByMethods > 0) filterBreakdown.push(`${stats.filteredByMethods} by methods`);
|
|
1704
|
-
if (stats.filteredByOperationIds > 0) filterBreakdown.push(`${stats.filteredByOperationIds} by operationIds`);
|
|
1705
|
-
if (stats.filteredByDeprecated > 0) filterBreakdown.push(`${stats.filteredByDeprecated} by deprecated flag`);
|
|
1706
|
-
if (filterBreakdown.length > 0) {
|
|
1707
|
-
console.warn(` Filtered: ${filterBreakdown.join(", ")}`);
|
|
1708
|
-
}
|
|
1709
|
-
}
|
|
1710
|
-
}
|
|
1711
|
-
function formatFilterStatistics(stats) {
|
|
1712
|
-
if (stats.totalOperations === 0) {
|
|
1713
|
-
return "";
|
|
1714
|
-
}
|
|
1715
|
-
const lines = [];
|
|
1716
|
-
lines.push("Operation Filtering:");
|
|
1717
|
-
lines.push(` Total operations: ${stats.totalOperations}`);
|
|
1718
|
-
lines.push(` Included operations: ${stats.includedOperations}`);
|
|
1719
|
-
const filteredCount = stats.filteredByTags + stats.filteredByPaths + stats.filteredByMethods + stats.filteredByOperationIds + stats.filteredByDeprecated;
|
|
1720
|
-
if (filteredCount > 0) {
|
|
1721
|
-
lines.push(` Filtered operations: ${filteredCount}`);
|
|
1722
|
-
if (stats.filteredByTags > 0) lines.push(` - By tags: ${stats.filteredByTags}`);
|
|
1723
|
-
if (stats.filteredByPaths > 0) lines.push(` - By paths: ${stats.filteredByPaths}`);
|
|
1724
|
-
if (stats.filteredByMethods > 0) lines.push(` - By methods: ${stats.filteredByMethods}`);
|
|
1725
|
-
if (stats.filteredByOperationIds > 0) lines.push(` - By operationIds: ${stats.filteredByOperationIds}`);
|
|
1726
|
-
if (stats.filteredByDeprecated > 0) lines.push(` - By deprecated: ${stats.filteredByDeprecated}`);
|
|
1727
|
-
}
|
|
1728
|
-
return lines.join("\n");
|
|
1405
|
+
return `z.literal(${v})`;
|
|
1406
|
+
}).join(", ");
|
|
1407
|
+
const schemaCode = `export const ${schemaName} = z.union([${literalValues}]);`;
|
|
1408
|
+
const typeCode = `export type ${typeName} = z.infer<typeof ${schemaName}>;`;
|
|
1409
|
+
return { schemaCode, typeCode };
|
|
1729
1410
|
}
|
|
1730
1411
|
|
|
1731
|
-
// src/
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
if (!obj.$ref) return obj;
|
|
1736
|
-
const ref = obj.$ref;
|
|
1737
|
-
let resolved = null;
|
|
1738
|
-
const paramMatch = ref.match(/^#\/components\/parameters\/(.+)$/);
|
|
1739
|
-
const requestBodyMatch = ref.match(/^#\/components\/requestBodies\/(.+)$/);
|
|
1740
|
-
const responseMatch = ref.match(/^#\/components\/responses\/(.+)$/);
|
|
1741
|
-
const schemaMatch = ref.match(/^#\/components\/schemas\/(.+)$/);
|
|
1742
|
-
if (paramMatch && ((_a = spec.components) == null ? void 0 : _a.parameters)) {
|
|
1743
|
-
const name = paramMatch[1];
|
|
1744
|
-
resolved = spec.components.parameters[name];
|
|
1745
|
-
} else if (requestBodyMatch && ((_b = spec.components) == null ? void 0 : _b.requestBodies)) {
|
|
1746
|
-
const name = requestBodyMatch[1];
|
|
1747
|
-
resolved = spec.components.requestBodies[name];
|
|
1748
|
-
} else if (responseMatch && ((_c = spec.components) == null ? void 0 : _c.responses)) {
|
|
1749
|
-
const name = responseMatch[1];
|
|
1750
|
-
resolved = spec.components.responses[name];
|
|
1751
|
-
} else if (schemaMatch && ((_d = spec.components) == null ? void 0 : _d.schemas)) {
|
|
1752
|
-
const name = schemaMatch[1];
|
|
1753
|
-
resolved = spec.components.schemas[name];
|
|
1754
|
-
}
|
|
1755
|
-
if (resolved) {
|
|
1756
|
-
if (resolved.$ref) {
|
|
1757
|
-
return resolveRef2(resolved, spec, maxDepth - 1);
|
|
1758
|
-
}
|
|
1759
|
-
return resolved;
|
|
1760
|
-
}
|
|
1761
|
-
return obj;
|
|
1762
|
-
}
|
|
1763
|
-
function resolveParameterRef(param, spec) {
|
|
1764
|
-
return resolveRef2(param, spec);
|
|
1412
|
+
// src/openapi-generator.ts
|
|
1413
|
+
var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"];
|
|
1414
|
+
function isResolvedParameter(param) {
|
|
1415
|
+
return typeof param === "object" && param !== null && "name" in param && typeof param.name === "string" && "in" in param;
|
|
1765
1416
|
}
|
|
1766
|
-
function
|
|
1767
|
-
|
|
1768
|
-
const resolvedOperationParams = (operationParams || []).map((p) => resolveParameterRef(p, spec));
|
|
1769
|
-
const merged = [...resolvedPathParams];
|
|
1770
|
-
for (const opParam of resolvedOperationParams) {
|
|
1771
|
-
if (!opParam || typeof opParam !== "object") continue;
|
|
1772
|
-
const existingIndex = merged.findIndex(
|
|
1773
|
-
(p) => p && typeof p === "object" && p.name === opParam.name && p.in === opParam.in
|
|
1774
|
-
);
|
|
1775
|
-
if (existingIndex >= 0) {
|
|
1776
|
-
merged[existingIndex] = opParam;
|
|
1777
|
-
} else {
|
|
1778
|
-
merged.push(opParam);
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
return merged;
|
|
1417
|
+
function isOpenAPIPathItem(value) {
|
|
1418
|
+
return typeof value === "object" && value !== null;
|
|
1782
1419
|
}
|
|
1783
|
-
|
|
1784
|
-
// src/openapi-generator.ts
|
|
1785
1420
|
var OpenApiGenerator = class {
|
|
1786
1421
|
constructor(options) {
|
|
1787
1422
|
this.schemas = /* @__PURE__ */ new Map();
|
|
@@ -1790,89 +1425,48 @@ var OpenApiGenerator = class {
|
|
|
1790
1425
|
this.schemaUsageMap = /* @__PURE__ */ new Map();
|
|
1791
1426
|
this.needsZodImport = true;
|
|
1792
1427
|
this.filterStats = createFilterStatistics();
|
|
1793
|
-
|
|
1428
|
+
/** Track total allOf conflicts detected across all schemas */
|
|
1429
|
+
this.allOfConflictCount = 0;
|
|
1430
|
+
/** Track schemas involved in circular dependency chains */
|
|
1431
|
+
this.circularDependencies = /* @__PURE__ */ new Set();
|
|
1432
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
1794
1433
|
if (!options.input) {
|
|
1795
1434
|
throw new ConfigurationError("Input path is required", { providedOptions: options });
|
|
1796
1435
|
}
|
|
1436
|
+
this.separateSchemasMode = Boolean(options.outputZodSchemas);
|
|
1797
1437
|
this.options = {
|
|
1798
1438
|
mode: options.mode || "normal",
|
|
1799
1439
|
input: options.input,
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1440
|
+
outputTypes: options.outputTypes,
|
|
1441
|
+
outputZodSchemas: options.outputZodSchemas,
|
|
1442
|
+
enumFormat: options.enumFormat,
|
|
1443
|
+
typeAssertionThreshold: (_a = options.typeAssertionThreshold) != null ? _a : 0,
|
|
1444
|
+
includeDescriptions: (_b = options.includeDescriptions) != null ? _b : true,
|
|
1445
|
+
useDescribe: (_c = options.useDescribe) != null ? _c : false,
|
|
1446
|
+
defaultNullable: (_d = options.defaultNullable) != null ? _d : false,
|
|
1447
|
+
emptyObjectBehavior: (_e = options.emptyObjectBehavior) != null ? _e : "loose",
|
|
1805
1448
|
schemaType: options.schemaType || "all",
|
|
1806
1449
|
prefix: options.prefix,
|
|
1807
1450
|
suffix: options.suffix,
|
|
1808
1451
|
stripSchemaPrefix: options.stripSchemaPrefix,
|
|
1809
1452
|
stripPathPrefix: options.stripPathPrefix,
|
|
1810
|
-
|
|
1453
|
+
useOperationId: (_f = options.useOperationId) != null ? _f : true,
|
|
1454
|
+
showStats: (_g = options.showStats) != null ? _g : true,
|
|
1811
1455
|
request: options.request,
|
|
1812
1456
|
response: options.response,
|
|
1813
1457
|
operationFilters: options.operationFilters,
|
|
1814
1458
|
ignoreHeaders: options.ignoreHeaders,
|
|
1815
|
-
cacheSize: (
|
|
1816
|
-
batchSize: (
|
|
1459
|
+
cacheSize: (_h = options.cacheSize) != null ? _h : 1e3,
|
|
1460
|
+
batchSize: (_i = options.batchSize) != null ? _i : 10,
|
|
1817
1461
|
customDateTimeFormatRegex: options.customDateTimeFormatRegex
|
|
1818
1462
|
};
|
|
1819
|
-
this.patternCache = new
|
|
1463
|
+
this.patternCache = new LRUCache2((_j = this.options.cacheSize) != null ? _j : 1e3);
|
|
1820
1464
|
this.dateTimeValidation = buildDateTimeValidation(this.options.customDateTimeFormatRegex);
|
|
1821
|
-
|
|
1822
|
-
const fs = __require("fs");
|
|
1823
|
-
if (!fs.existsSync(this.options.input)) {
|
|
1824
|
-
throw new FileOperationError(`Input file not found: ${this.options.input}`, this.options.input);
|
|
1825
|
-
}
|
|
1826
|
-
} catch (error) {
|
|
1827
|
-
if (error instanceof FileOperationError) {
|
|
1828
|
-
throw error;
|
|
1829
|
-
}
|
|
1830
|
-
}
|
|
1831
|
-
try {
|
|
1832
|
-
const content = readFileSync(this.options.input, "utf-8");
|
|
1833
|
-
try {
|
|
1834
|
-
this.spec = parse(content);
|
|
1835
|
-
} catch (yamlError) {
|
|
1836
|
-
try {
|
|
1837
|
-
this.spec = JSON.parse(content);
|
|
1838
|
-
} catch {
|
|
1839
|
-
if (yamlError instanceof Error) {
|
|
1840
|
-
const errorMessage = [
|
|
1841
|
-
`Failed to parse OpenAPI specification from: ${this.options.input}`,
|
|
1842
|
-
"",
|
|
1843
|
-
`Error: ${yamlError.message}`,
|
|
1844
|
-
"",
|
|
1845
|
-
"Please ensure:",
|
|
1846
|
-
" - The file exists and is readable",
|
|
1847
|
-
" - The file contains valid YAML or JSON syntax",
|
|
1848
|
-
" - The file is a valid OpenAPI 3.x specification"
|
|
1849
|
-
].join("\n");
|
|
1850
|
-
throw new SpecValidationError(errorMessage, {
|
|
1851
|
-
filePath: this.options.input,
|
|
1852
|
-
originalError: yamlError.message
|
|
1853
|
-
});
|
|
1854
|
-
}
|
|
1855
|
-
throw yamlError;
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
} catch (error) {
|
|
1859
|
-
if (error instanceof SpecValidationError) {
|
|
1860
|
-
throw error;
|
|
1861
|
-
}
|
|
1862
|
-
if (error instanceof Error) {
|
|
1863
|
-
const errorMessage = [
|
|
1864
|
-
`Failed to read OpenAPI specification from: ${this.options.input}`,
|
|
1865
|
-
"",
|
|
1866
|
-
`Error: ${error.message}`
|
|
1867
|
-
].join("\n");
|
|
1868
|
-
throw new SpecValidationError(errorMessage, { filePath: this.options.input, originalError: error.message });
|
|
1869
|
-
}
|
|
1870
|
-
throw error;
|
|
1871
|
-
}
|
|
1465
|
+
this.spec = loadOpenAPISpec(this.options.input);
|
|
1872
1466
|
this.validateSpec();
|
|
1873
1467
|
this.requestOptions = this.resolveOptionsForContext("request");
|
|
1874
1468
|
this.responseOptions = this.resolveOptionsForContext("response");
|
|
1875
|
-
this.
|
|
1469
|
+
this.initializeSchemaUsage();
|
|
1876
1470
|
this.propertyGenerator = new PropertyGenerator({
|
|
1877
1471
|
spec: this.spec,
|
|
1878
1472
|
schemaDependencies: this.schemaDependencies,
|
|
@@ -1880,19 +1474,21 @@ var OpenApiGenerator = class {
|
|
|
1880
1474
|
mode: this.requestOptions.mode,
|
|
1881
1475
|
includeDescriptions: this.requestOptions.includeDescriptions,
|
|
1882
1476
|
useDescribe: this.requestOptions.useDescribe,
|
|
1883
|
-
defaultNullable: (
|
|
1884
|
-
emptyObjectBehavior: (
|
|
1477
|
+
defaultNullable: (_k = this.options.defaultNullable) != null ? _k : false,
|
|
1478
|
+
emptyObjectBehavior: (_l = this.options.emptyObjectBehavior) != null ? _l : "loose",
|
|
1885
1479
|
namingOptions: {
|
|
1886
1480
|
prefix: this.options.prefix,
|
|
1887
1481
|
suffix: this.options.suffix
|
|
1888
1482
|
},
|
|
1889
1483
|
stripSchemaPrefix: this.options.stripSchemaPrefix,
|
|
1890
1484
|
dateTimeValidation: this.dateTimeValidation,
|
|
1891
|
-
patternCache: this.patternCache
|
|
1485
|
+
patternCache: this.patternCache,
|
|
1486
|
+
separateTypesFile: this.separateSchemasMode
|
|
1892
1487
|
});
|
|
1893
1488
|
}
|
|
1894
1489
|
/**
|
|
1895
1490
|
* Generate schemas as a string (without writing to file)
|
|
1491
|
+
* When separateSchemasMode is active, generates Zod schemas with explicit type annotations
|
|
1896
1492
|
* @returns The generated TypeScript code as a string
|
|
1897
1493
|
*/
|
|
1898
1494
|
generateString() {
|
|
@@ -1900,6 +1496,11 @@ var OpenApiGenerator = class {
|
|
|
1900
1496
|
if (!((_a = this.spec.components) == null ? void 0 : _a.schemas)) {
|
|
1901
1497
|
throw new SpecValidationError("No schemas found in OpenAPI spec", { filePath: this.options.input });
|
|
1902
1498
|
}
|
|
1499
|
+
if (this.separateSchemasMode) {
|
|
1500
|
+
return this.generateSeparateSchemasString();
|
|
1501
|
+
}
|
|
1502
|
+
this.analyzeCircularDependencies();
|
|
1503
|
+
this.propertyGenerator.setCircularDependencies(this.circularDependencies);
|
|
1903
1504
|
for (const [name, schema] of Object.entries(this.spec.components.schemas)) {
|
|
1904
1505
|
if (this.options.operationFilters && this.schemaUsageMap.size > 0 && !this.schemaUsageMap.has(name)) {
|
|
1905
1506
|
continue;
|
|
@@ -1925,10 +1526,10 @@ var OpenApiGenerator = class {
|
|
|
1925
1526
|
const typeCode = this.types.get(name);
|
|
1926
1527
|
if (schemaCode) {
|
|
1927
1528
|
output.push(schemaCode);
|
|
1928
|
-
const strippedName =
|
|
1929
|
-
const typeName =
|
|
1529
|
+
const strippedName = stripPrefix2(name, this.options.stripSchemaPrefix);
|
|
1530
|
+
const typeName = toPascalCase3(strippedName);
|
|
1930
1531
|
if (!schemaCode.includes(`export type ${typeName}`)) {
|
|
1931
|
-
const schemaName = `${
|
|
1532
|
+
const schemaName = `${toCamelCase3(strippedName, { prefix: this.options.prefix, suffix: this.options.suffix })}Schema`;
|
|
1932
1533
|
output.push(`export type ${typeName} = z.infer<typeof ${schemaName}>;`);
|
|
1933
1534
|
}
|
|
1934
1535
|
output.push("");
|
|
@@ -1950,14 +1551,224 @@ var OpenApiGenerator = class {
|
|
|
1950
1551
|
}
|
|
1951
1552
|
}
|
|
1952
1553
|
/**
|
|
1953
|
-
* Generate the complete output file
|
|
1554
|
+
* Generate the complete output file(s)
|
|
1555
|
+
* When separateSchemasMode is active, generates both types and schemas files
|
|
1954
1556
|
*/
|
|
1955
1557
|
generate() {
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1558
|
+
if (this.separateSchemasMode) {
|
|
1559
|
+
const typesContent = this.generateTypesString();
|
|
1560
|
+
const schemasContent = this.generateString();
|
|
1561
|
+
const normalizedTypes = normalize(this.options.outputTypes);
|
|
1562
|
+
this.ensureDirectoryExists(normalizedTypes);
|
|
1563
|
+
writeFileSync(normalizedTypes, typesContent, "utf-8");
|
|
1564
|
+
console.log(` \u2713 Generated ${normalizedTypes}`);
|
|
1565
|
+
if (!this.options.outputZodSchemas) {
|
|
1566
|
+
throw new Error("Internal error: outputZodSchemas should be defined in separateSchemasMode");
|
|
1567
|
+
}
|
|
1568
|
+
const outputZodSchemas = this.options.outputZodSchemas;
|
|
1569
|
+
const normalizedSchemas = normalize(outputZodSchemas);
|
|
1570
|
+
this.ensureDirectoryExists(normalizedSchemas);
|
|
1571
|
+
writeFileSync(normalizedSchemas, schemasContent, "utf-8");
|
|
1572
|
+
console.log(` \u2713 Generated ${normalizedSchemas}`);
|
|
1573
|
+
} else {
|
|
1574
|
+
const output = this.generateString();
|
|
1575
|
+
const normalizedOutput = normalize(this.options.outputTypes);
|
|
1576
|
+
this.ensureDirectoryExists(normalizedOutput);
|
|
1577
|
+
writeFileSync(normalizedOutput, output);
|
|
1578
|
+
console.log(` \u2713 Generated ${normalizedOutput}`);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
/**
|
|
1582
|
+
* Generate Zod schemas with explicit type annotations (for outputZodSchemas mode)
|
|
1583
|
+
* Generates schemas like: `export const userSchema: z.ZodType<User> = z.object({...})`
|
|
1584
|
+
* @returns The generated Zod schemas TypeScript code
|
|
1585
|
+
*/
|
|
1586
|
+
generateSeparateSchemasString() {
|
|
1587
|
+
var _a;
|
|
1588
|
+
const schemas = (_a = this.spec.components) == null ? void 0 : _a.schemas;
|
|
1589
|
+
if (!schemas) {
|
|
1590
|
+
return "";
|
|
1591
|
+
}
|
|
1592
|
+
if (!this.options.outputZodSchemas) {
|
|
1593
|
+
throw new Error("Internal error: outputZodSchemas should be defined in separateSchemasMode");
|
|
1594
|
+
}
|
|
1595
|
+
const outputZodSchemas = this.options.outputZodSchemas;
|
|
1596
|
+
this.analyzeCircularDependencies();
|
|
1597
|
+
this.propertyGenerator.setCircularDependencies(this.circularDependencies);
|
|
1598
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
1599
|
+
if (this.options.operationFilters && this.schemaUsageMap.size > 0 && !this.schemaUsageMap.has(name)) {
|
|
1600
|
+
continue;
|
|
1601
|
+
}
|
|
1602
|
+
this.generateComponentSchema(name, schema);
|
|
1603
|
+
}
|
|
1604
|
+
this.generateQueryParameterSchemas();
|
|
1605
|
+
this.generateHeaderParameterSchemas();
|
|
1606
|
+
validateFilters(this.filterStats, this.options.operationFilters);
|
|
1607
|
+
const orderedSchemaNames = this.topologicalSort();
|
|
1608
|
+
const output = ["// Auto-generated by @cerios/openapi-to-zod", "// Do not edit this file manually", ""];
|
|
1609
|
+
if (this.options.showStats === true) {
|
|
1610
|
+
output.push(...this.generateStats());
|
|
1611
|
+
output.push("");
|
|
1612
|
+
}
|
|
1613
|
+
output.push('import { z } from "zod";');
|
|
1614
|
+
const typesImportPath = this.calculateRelativeImportPath(outputZodSchemas, this.options.outputTypes);
|
|
1615
|
+
const typeNames = [];
|
|
1616
|
+
for (const name of orderedSchemaNames) {
|
|
1617
|
+
const strippedName = stripPrefix2(name, this.options.stripSchemaPrefix);
|
|
1618
|
+
const typeName = toPascalCase3(strippedName);
|
|
1619
|
+
typeNames.push(typeName);
|
|
1620
|
+
}
|
|
1621
|
+
if (typeNames.length > 0) {
|
|
1622
|
+
output.push(`import type { ${typeNames.join(", ")} } from "${typesImportPath}";`);
|
|
1623
|
+
}
|
|
1624
|
+
output.push("");
|
|
1625
|
+
output.push("// Schemas");
|
|
1626
|
+
for (const name of orderedSchemaNames) {
|
|
1627
|
+
const schemaCode = this.schemas.get(name);
|
|
1628
|
+
if (schemaCode) {
|
|
1629
|
+
const strippedName = stripPrefix2(name, this.options.stripSchemaPrefix);
|
|
1630
|
+
const typeName = toPascalCase3(strippedName);
|
|
1631
|
+
const schemaName = `${toCamelCase3(strippedName, { prefix: this.options.prefix, suffix: this.options.suffix })}Schema`;
|
|
1632
|
+
const schemaDefinition = this.isRecordObject(schemas[name]) ? schemas[name] : void 0;
|
|
1633
|
+
const transformedCode = this.addExplicitTypeAnnotation(schemaCode, schemaName, typeName, schemaDefinition);
|
|
1634
|
+
output.push(transformedCode);
|
|
1635
|
+
output.push("");
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
return output.join("\n");
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Generate TypeScript types as a string (for outputZodSchemas mode)
|
|
1642
|
+
* Uses @cerios/openapi-to-typescript internally
|
|
1643
|
+
* @returns The generated TypeScript types code
|
|
1644
|
+
*/
|
|
1645
|
+
generateTypesString() {
|
|
1646
|
+
var _a;
|
|
1647
|
+
const tsGenerator = new TypeScriptGenerator({
|
|
1648
|
+
input: this.options.input,
|
|
1649
|
+
outputTypes: this.options.outputTypes,
|
|
1650
|
+
includeDescriptions: this.options.includeDescriptions,
|
|
1651
|
+
defaultNullable: this.options.defaultNullable,
|
|
1652
|
+
prefix: this.options.prefix,
|
|
1653
|
+
suffix: this.options.suffix,
|
|
1654
|
+
stripSchemaPrefix: this.options.stripSchemaPrefix,
|
|
1655
|
+
stripPathPrefix: this.options.stripPathPrefix,
|
|
1656
|
+
operationFilters: this.options.operationFilters,
|
|
1657
|
+
showStats: this.options.showStats,
|
|
1658
|
+
enumFormat: (_a = this.options.enumFormat) != null ? _a : "const-object"
|
|
1659
|
+
});
|
|
1660
|
+
return tsGenerator.generateString();
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Add explicit type annotation to a schema declaration
|
|
1664
|
+
* Transforms: `export const userSchema = z.object({...})`
|
|
1665
|
+
* To: `export const userSchema: z.ZodType<User> = z.object({...})` (annotation)
|
|
1666
|
+
* Or: `export const userSchema = z.object({...}) as unknown as z.ZodType<User>` (double assertion)
|
|
1667
|
+
*
|
|
1668
|
+
* Uses double assertion via `unknown` when typeAssertionThreshold is set and schema complexity
|
|
1669
|
+
* meets or exceeds the threshold. This completely bypasses TypeScript's structural checking
|
|
1670
|
+
* to avoid "Type instantiation is excessively deep" errors on very large schemas.
|
|
1671
|
+
*
|
|
1672
|
+
* Also removes any `export type X = z.infer<...>` lines since types
|
|
1673
|
+
* are imported from the separate types file.
|
|
1674
|
+
*/
|
|
1675
|
+
addExplicitTypeAnnotation(schemaCode, schemaName, typeName, schemaDefinition) {
|
|
1676
|
+
var _a;
|
|
1677
|
+
const code = schemaCode.replace(/\nexport type \w+ = z\.infer<typeof \w+>;/g, "");
|
|
1678
|
+
const jsdocMatch = code.match(/^(\/\*\*[\s\S]*?\*\/\n)?/);
|
|
1679
|
+
const jsdoc = (jsdocMatch == null ? void 0 : jsdocMatch[1]) || "";
|
|
1680
|
+
const codeWithoutJsdoc = code.slice(jsdoc.length);
|
|
1681
|
+
const threshold = (_a = this.options.typeAssertionThreshold) != null ? _a : 0;
|
|
1682
|
+
const useAssertion = threshold > 0 && schemaDefinition && this.calculateSchemaComplexity(schemaDefinition) >= threshold;
|
|
1683
|
+
const pattern = new RegExp(`export const ${schemaName} = `);
|
|
1684
|
+
if (pattern.test(codeWithoutJsdoc)) {
|
|
1685
|
+
let schemaBody = codeWithoutJsdoc.replace(pattern, "");
|
|
1686
|
+
if (useAssertion) {
|
|
1687
|
+
schemaBody = schemaBody.replace(/;$/, "");
|
|
1688
|
+
return `${jsdoc}export const ${schemaName} = ${schemaBody} as unknown as z.ZodType<${typeName}>;`;
|
|
1689
|
+
}
|
|
1690
|
+
return `${jsdoc}export const ${schemaName}: z.ZodType<${typeName}> = ${schemaBody}`;
|
|
1691
|
+
}
|
|
1692
|
+
return code;
|
|
1693
|
+
}
|
|
1694
|
+
/**
|
|
1695
|
+
* Type guard to check if a value is a Record<string, unknown>
|
|
1696
|
+
*/
|
|
1697
|
+
isRecordObject(value) {
|
|
1698
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1699
|
+
}
|
|
1700
|
+
/**
|
|
1701
|
+
* Calculate the complexity of a schema for threshold comparison
|
|
1702
|
+
* Complexity formula: properties + (nested levels * 10) + (array/union members * 2)
|
|
1703
|
+
*/
|
|
1704
|
+
calculateSchemaComplexity(schema, depth = 0) {
|
|
1705
|
+
if (!schema || typeof schema !== "object") {
|
|
1706
|
+
return 0;
|
|
1707
|
+
}
|
|
1708
|
+
let complexity = depth * 10;
|
|
1709
|
+
if (schema.$ref) {
|
|
1710
|
+
return complexity + 5;
|
|
1711
|
+
}
|
|
1712
|
+
const properties = schema.properties;
|
|
1713
|
+
if (this.isRecordObject(properties)) {
|
|
1714
|
+
const propCount = Object.keys(properties).length;
|
|
1715
|
+
complexity += propCount;
|
|
1716
|
+
for (const prop of Object.values(properties)) {
|
|
1717
|
+
if (this.isRecordObject(prop)) {
|
|
1718
|
+
complexity += this.calculateSchemaComplexity(prop, depth + 1);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
const allOf = schema.allOf;
|
|
1723
|
+
if (Array.isArray(allOf)) {
|
|
1724
|
+
complexity += allOf.length * 2;
|
|
1725
|
+
for (const subSchema of allOf) {
|
|
1726
|
+
if (this.isRecordObject(subSchema)) {
|
|
1727
|
+
complexity += this.calculateSchemaComplexity(subSchema, depth + 1);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
const oneOf = schema.oneOf;
|
|
1732
|
+
if (Array.isArray(oneOf)) {
|
|
1733
|
+
complexity += oneOf.length * 2;
|
|
1734
|
+
for (const subSchema of oneOf) {
|
|
1735
|
+
if (this.isRecordObject(subSchema)) {
|
|
1736
|
+
complexity += this.calculateSchemaComplexity(subSchema, depth + 1);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
const anyOf = schema.anyOf;
|
|
1741
|
+
if (Array.isArray(anyOf)) {
|
|
1742
|
+
complexity += anyOf.length * 2;
|
|
1743
|
+
for (const subSchema of anyOf) {
|
|
1744
|
+
if (this.isRecordObject(subSchema)) {
|
|
1745
|
+
complexity += this.calculateSchemaComplexity(subSchema, depth + 1);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
const items = schema.items;
|
|
1750
|
+
if (this.isRecordObject(items)) {
|
|
1751
|
+
complexity += 2;
|
|
1752
|
+
complexity += this.calculateSchemaComplexity(items, depth + 1);
|
|
1753
|
+
}
|
|
1754
|
+
const additionalProps = schema.additionalProperties;
|
|
1755
|
+
if (this.isRecordObject(additionalProps)) {
|
|
1756
|
+
complexity += 2;
|
|
1757
|
+
complexity += this.calculateSchemaComplexity(additionalProps, depth + 1);
|
|
1758
|
+
}
|
|
1759
|
+
return complexity;
|
|
1760
|
+
}
|
|
1761
|
+
/**
|
|
1762
|
+
* Calculate relative import path from schema file to types file
|
|
1763
|
+
*/
|
|
1764
|
+
calculateRelativeImportPath(fromPath, toPath) {
|
|
1765
|
+
const fromDir = dirname(normalize(fromPath));
|
|
1766
|
+
const toFile = normalize(toPath).replace(/\.[tj]s$/, "");
|
|
1767
|
+
let relativePath = relative(fromDir, toFile);
|
|
1768
|
+
if (!relativePath.startsWith(".") && !relativePath.startsWith("..")) {
|
|
1769
|
+
relativePath = `./${relativePath}`;
|
|
1770
|
+
}
|
|
1771
|
+
return relativePath.replace(/\\/g, "/");
|
|
1961
1772
|
}
|
|
1962
1773
|
/**
|
|
1963
1774
|
* Resolve options for a specific context (request or response)
|
|
@@ -1973,191 +1784,84 @@ var OpenApiGenerator = class {
|
|
|
1973
1784
|
};
|
|
1974
1785
|
}
|
|
1975
1786
|
/**
|
|
1976
|
-
*
|
|
1977
|
-
*
|
|
1787
|
+
* Initialize schema usage map using core utilities with operation filtering
|
|
1788
|
+
* This is a wrapper around core's analyzeSchemaUsage that adds operation filtering
|
|
1978
1789
|
*/
|
|
1979
|
-
|
|
1980
|
-
var _a
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1790
|
+
initializeSchemaUsage() {
|
|
1791
|
+
var _a;
|
|
1792
|
+
if (this.options.operationFilters && this.spec.paths) {
|
|
1793
|
+
const requestSchemas = /* @__PURE__ */ new Set();
|
|
1794
|
+
const responseSchemas = /* @__PURE__ */ new Set();
|
|
1984
1795
|
for (const [path, pathItem] of Object.entries(this.spec.paths)) {
|
|
1985
|
-
|
|
1986
|
-
for (const method of
|
|
1796
|
+
if (!isOpenAPIPathItem(pathItem)) continue;
|
|
1797
|
+
for (const method of HTTP_METHODS) {
|
|
1987
1798
|
const operation = pathItem[method];
|
|
1988
|
-
if (
|
|
1799
|
+
if (!operation) continue;
|
|
1989
1800
|
this.filterStats.totalOperations++;
|
|
1990
1801
|
if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters, this.filterStats)) {
|
|
1991
1802
|
continue;
|
|
1992
1803
|
}
|
|
1993
1804
|
this.filterStats.includedOperations++;
|
|
1994
|
-
if (
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1805
|
+
if (operation.requestBody && typeof operation.requestBody === "object") {
|
|
1806
|
+
const reqBody = operation.requestBody;
|
|
1807
|
+
if (reqBody.content && typeof reqBody.content === "object") {
|
|
1808
|
+
for (const mediaType of Object.values(reqBody.content)) {
|
|
1809
|
+
if (mediaType && typeof mediaType === "object" && "schema" in mediaType && mediaType.schema) {
|
|
1810
|
+
extractSchemaRefs(mediaType.schema, requestSchemas);
|
|
1811
|
+
}
|
|
1998
1812
|
}
|
|
1999
1813
|
}
|
|
2000
1814
|
}
|
|
2001
|
-
if (
|
|
1815
|
+
if (operation.responses && typeof operation.responses === "object") {
|
|
2002
1816
|
for (const response of Object.values(operation.responses)) {
|
|
2003
1817
|
if (response && typeof response === "object" && "content" in response && response.content && typeof response.content === "object") {
|
|
2004
1818
|
for (const mediaType of Object.values(response.content)) {
|
|
2005
1819
|
if (mediaType && typeof mediaType === "object" && "schema" in mediaType && mediaType.schema) {
|
|
2006
|
-
|
|
1820
|
+
extractSchemaRefs(mediaType.schema, responseSchemas);
|
|
2007
1821
|
}
|
|
2008
1822
|
}
|
|
2009
1823
|
}
|
|
2010
1824
|
}
|
|
2011
1825
|
}
|
|
2012
|
-
if (
|
|
1826
|
+
if (operation.parameters && Array.isArray(operation.parameters)) {
|
|
2013
1827
|
for (const param of operation.parameters) {
|
|
2014
|
-
if (param &&
|
|
2015
|
-
|
|
1828
|
+
if (isResolvedParameter(param) && param.schema) {
|
|
1829
|
+
extractSchemaRefs(param.schema, requestSchemas);
|
|
2016
1830
|
}
|
|
2017
1831
|
}
|
|
2018
1832
|
}
|
|
2019
1833
|
}
|
|
2020
1834
|
}
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
if (
|
|
2029
|
-
|
|
2030
|
-
} else if (hasReadOnly && !hasWriteOnly) {
|
|
2031
|
-
responseSchemas.add(name);
|
|
1835
|
+
expandTransitiveReferences(requestSchemas, this.spec);
|
|
1836
|
+
expandTransitiveReferences(responseSchemas, this.spec);
|
|
1837
|
+
for (const [name] of Object.entries(((_a = this.spec.components) == null ? void 0 : _a.schemas) || {})) {
|
|
1838
|
+
if (requestSchemas.has(name) && responseSchemas.has(name)) {
|
|
1839
|
+
this.schemaUsageMap.set(name, "both");
|
|
1840
|
+
} else if (requestSchemas.has(name)) {
|
|
1841
|
+
this.schemaUsageMap.set(name, "request");
|
|
1842
|
+
} else if (responseSchemas.has(name)) {
|
|
1843
|
+
this.schemaUsageMap.set(name, "response");
|
|
2032
1844
|
}
|
|
2033
1845
|
}
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
if (requestSchemas.has(name) && responseSchemas.has(name)) {
|
|
1846
|
+
const circularSchemas = detectCircularReferences(this.spec);
|
|
1847
|
+
for (const name of circularSchemas) {
|
|
2037
1848
|
this.schemaUsageMap.set(name, "both");
|
|
2038
|
-
} else if (requestSchemas.has(name)) {
|
|
2039
|
-
this.schemaUsageMap.set(name, "request");
|
|
2040
|
-
} else if (responseSchemas.has(name)) {
|
|
2041
|
-
this.schemaUsageMap.set(name, "response");
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
this.detectCircularReferences();
|
|
2045
|
-
}
|
|
2046
|
-
/**
|
|
2047
|
-
* Expand a set of schemas to include all transitively referenced schemas
|
|
2048
|
-
*/
|
|
2049
|
-
expandTransitiveReferences(schemas) {
|
|
2050
|
-
var _a, _b;
|
|
2051
|
-
const toProcess = Array.from(schemas);
|
|
2052
|
-
const processed = /* @__PURE__ */ new Set();
|
|
2053
|
-
while (toProcess.length > 0) {
|
|
2054
|
-
const schemaName = toProcess.pop();
|
|
2055
|
-
if (!schemaName || processed.has(schemaName)) continue;
|
|
2056
|
-
processed.add(schemaName);
|
|
2057
|
-
const schema = (_b = (_a = this.spec.components) == null ? void 0 : _a.schemas) == null ? void 0 : _b[schemaName];
|
|
2058
|
-
if (schema) {
|
|
2059
|
-
const refs = /* @__PURE__ */ new Set();
|
|
2060
|
-
this.extractSchemaRefs(schema, refs);
|
|
2061
|
-
for (const ref of refs) {
|
|
2062
|
-
if (!schemas.has(ref)) {
|
|
2063
|
-
schemas.add(ref);
|
|
2064
|
-
toProcess.push(ref);
|
|
2065
|
-
}
|
|
2066
|
-
}
|
|
2067
|
-
}
|
|
2068
|
-
}
|
|
2069
|
-
}
|
|
2070
|
-
/**
|
|
2071
|
-
* Extract schema names from $ref and nested structures
|
|
2072
|
-
*/
|
|
2073
|
-
extractSchemaRefs(schema, refs) {
|
|
2074
|
-
if (!schema) return;
|
|
2075
|
-
if (schema.$ref) {
|
|
2076
|
-
const refName = resolveRef(schema.$ref);
|
|
2077
|
-
refs.add(refName);
|
|
2078
|
-
}
|
|
2079
|
-
if (schema.allOf) {
|
|
2080
|
-
for (const subSchema of schema.allOf) {
|
|
2081
|
-
this.extractSchemaRefs(subSchema, refs);
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
if (schema.oneOf) {
|
|
2085
|
-
for (const subSchema of schema.oneOf) {
|
|
2086
|
-
this.extractSchemaRefs(subSchema, refs);
|
|
2087
|
-
}
|
|
2088
|
-
}
|
|
2089
|
-
if (schema.anyOf) {
|
|
2090
|
-
for (const subSchema of schema.anyOf) {
|
|
2091
|
-
this.extractSchemaRefs(subSchema, refs);
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
2094
|
-
if (schema.items) {
|
|
2095
|
-
this.extractSchemaRefs(schema.items, refs);
|
|
2096
|
-
}
|
|
2097
|
-
if (schema.properties) {
|
|
2098
|
-
for (const prop of Object.values(schema.properties)) {
|
|
2099
|
-
this.extractSchemaRefs(prop, refs);
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2102
|
-
}
|
|
2103
|
-
/**
|
|
2104
|
-
* Check if schema has readOnly properties
|
|
2105
|
-
*/
|
|
2106
|
-
hasReadOnlyProperties(schema) {
|
|
2107
|
-
if (schema.readOnly) return true;
|
|
2108
|
-
if (schema.properties) {
|
|
2109
|
-
for (const prop of Object.values(schema.properties)) {
|
|
2110
|
-
if (this.hasReadOnlyProperties(prop)) return true;
|
|
2111
1849
|
}
|
|
2112
|
-
}
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
}
|
|
2125
|
-
return false;
|
|
2126
|
-
}
|
|
2127
|
-
/**
|
|
2128
|
-
* Detect circular references and mark them as "both" context for safety
|
|
2129
|
-
*/
|
|
2130
|
-
detectCircularReferences() {
|
|
2131
|
-
var _a;
|
|
2132
|
-
const visited = /* @__PURE__ */ new Set();
|
|
2133
|
-
const recursionStack = /* @__PURE__ */ new Set();
|
|
2134
|
-
const detectCycle = (name) => {
|
|
2135
|
-
var _a2, _b;
|
|
2136
|
-
if (recursionStack.has(name)) {
|
|
2137
|
-
return true;
|
|
2138
|
-
}
|
|
2139
|
-
if (visited.has(name)) {
|
|
2140
|
-
return false;
|
|
2141
|
-
}
|
|
2142
|
-
visited.add(name);
|
|
2143
|
-
recursionStack.add(name);
|
|
2144
|
-
const schema = (_b = (_a2 = this.spec.components) == null ? void 0 : _a2.schemas) == null ? void 0 : _b[name];
|
|
2145
|
-
if (schema) {
|
|
2146
|
-
const refs = /* @__PURE__ */ new Set();
|
|
2147
|
-
this.extractSchemaRefs(schema, refs);
|
|
2148
|
-
for (const ref of refs) {
|
|
2149
|
-
if (detectCycle(ref)) {
|
|
2150
|
-
this.schemaUsageMap.set(name, "both");
|
|
2151
|
-
recursionStack.delete(name);
|
|
2152
|
-
return true;
|
|
1850
|
+
} else {
|
|
1851
|
+
const analysis = analyzeSchemaUsage(this.spec);
|
|
1852
|
+
this.schemaUsageMap = analysis.usageMap;
|
|
1853
|
+
if (this.spec.paths) {
|
|
1854
|
+
for (const pathItem of Object.values(this.spec.paths)) {
|
|
1855
|
+
if (!isOpenAPIPathItem(pathItem)) continue;
|
|
1856
|
+
for (const method of HTTP_METHODS) {
|
|
1857
|
+
const operation = pathItem[method];
|
|
1858
|
+
if (typeof operation === "object" && operation) {
|
|
1859
|
+
this.filterStats.totalOperations++;
|
|
1860
|
+
this.filterStats.includedOperations++;
|
|
1861
|
+
}
|
|
2153
1862
|
}
|
|
2154
1863
|
}
|
|
2155
1864
|
}
|
|
2156
|
-
recursionStack.delete(name);
|
|
2157
|
-
return false;
|
|
2158
|
-
};
|
|
2159
|
-
for (const name of Object.keys(((_a = this.spec.components) == null ? void 0 : _a.schemas) || {})) {
|
|
2160
|
-
detectCycle(name);
|
|
2161
1865
|
}
|
|
2162
1866
|
}
|
|
2163
1867
|
/**
|
|
@@ -2190,7 +1894,7 @@ var OpenApiGenerator = class {
|
|
|
2190
1894
|
*/
|
|
2191
1895
|
validateSchemaRefs(schemaName, schema, allSchemas, path = "") {
|
|
2192
1896
|
if (schema.$ref) {
|
|
2193
|
-
const refName =
|
|
1897
|
+
const refName = resolveRefName2(schema.$ref);
|
|
2194
1898
|
if (!allSchemas.includes(refName)) {
|
|
2195
1899
|
throw new SpecValidationError(
|
|
2196
1900
|
`Invalid reference${path ? ` at '${path}'` : ""}: '${schema.$ref}' points to non-existent schema '${refName}'`,
|
|
@@ -2239,7 +1943,7 @@ var OpenApiGenerator = class {
|
|
|
2239
1943
|
const resolvedOptions = context === "response" ? this.responseOptions : this.requestOptions;
|
|
2240
1944
|
if (schema.enum) {
|
|
2241
1945
|
const jsdoc2 = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
|
|
2242
|
-
const strippedName2 =
|
|
1946
|
+
const strippedName2 = stripPrefix2(name, this.options.stripSchemaPrefix);
|
|
2243
1947
|
const { schemaCode, typeCode } = generateEnum(strippedName2, schema.enum, {
|
|
2244
1948
|
prefix: this.options.prefix,
|
|
2245
1949
|
suffix: this.options.suffix
|
|
@@ -2249,11 +1953,11 @@ ${typeCode}`;
|
|
|
2249
1953
|
this.schemas.set(name, enumSchemaCode);
|
|
2250
1954
|
return;
|
|
2251
1955
|
}
|
|
2252
|
-
const strippedName =
|
|
2253
|
-
const schemaName = `${
|
|
2254
|
-
|
|
1956
|
+
const strippedName = stripPrefix2(name, this.options.stripSchemaPrefix);
|
|
1957
|
+
const schemaName = `${toCamelCase3(strippedName, { prefix: this.options.prefix, suffix: this.options.suffix })}Schema`;
|
|
1958
|
+
let jsdoc = generateJSDoc(schema, name, { includeDescriptions: resolvedOptions.includeDescriptions });
|
|
2255
1959
|
if (schema.allOf && schema.allOf.length === 1 && schema.allOf[0].$ref) {
|
|
2256
|
-
const refName =
|
|
1960
|
+
const refName = resolveRefName2(schema.allOf[0].$ref);
|
|
2257
1961
|
(_a = this.schemaDependencies.get(name)) == null ? void 0 : _a.add(refName);
|
|
2258
1962
|
}
|
|
2259
1963
|
this.propertyGenerator = new PropertyGenerator({
|
|
@@ -2271,9 +1975,26 @@ ${typeCode}`;
|
|
|
2271
1975
|
},
|
|
2272
1976
|
stripSchemaPrefix: this.options.stripSchemaPrefix,
|
|
2273
1977
|
dateTimeValidation: this.dateTimeValidation,
|
|
2274
|
-
patternCache: this.patternCache
|
|
1978
|
+
patternCache: this.patternCache,
|
|
1979
|
+
separateTypesFile: this.separateSchemasMode
|
|
2275
1980
|
});
|
|
1981
|
+
this.propertyGenerator.setCircularDependencies(this.circularDependencies);
|
|
1982
|
+
this.propertyGenerator.clearAllOfConflicts();
|
|
2276
1983
|
const zodSchema = this.propertyGenerator.generatePropertySchema(schema, name, true);
|
|
1984
|
+
const allOfConflicts = this.propertyGenerator.getAllOfConflicts();
|
|
1985
|
+
if (allOfConflicts.length > 0) {
|
|
1986
|
+
this.allOfConflictCount += allOfConflicts.length;
|
|
1987
|
+
const conflictWarning = this.generateConflictJSDoc(allOfConflicts);
|
|
1988
|
+
if (jsdoc) {
|
|
1989
|
+
jsdoc = jsdoc.replace(/ \*\/\n$/, `
|
|
1990
|
+
${conflictWarning} */
|
|
1991
|
+
`);
|
|
1992
|
+
} else {
|
|
1993
|
+
jsdoc = `/**
|
|
1994
|
+
${conflictWarning} */
|
|
1995
|
+
`;
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
2277
1998
|
const zodSchemaCode = `${jsdoc}export const ${schemaName} = ${zodSchema};`;
|
|
2278
1999
|
if (zodSchema.includes("z.discriminatedUnion(")) {
|
|
2279
2000
|
const match = zodSchema.match(/z\.discriminatedUnion\([^,]+,\s*\[([^\]]+)\]/);
|
|
@@ -2299,9 +2020,8 @@ ${typeCode}`;
|
|
|
2299
2020
|
return;
|
|
2300
2021
|
}
|
|
2301
2022
|
for (const [path, pathItem] of Object.entries(this.spec.paths)) {
|
|
2302
|
-
if (!pathItem
|
|
2303
|
-
const
|
|
2304
|
-
for (const method of methods) {
|
|
2023
|
+
if (!isOpenAPIPathItem(pathItem)) continue;
|
|
2024
|
+
for (const method of HTTP_METHODS) {
|
|
2305
2025
|
const operation = pathItem[method];
|
|
2306
2026
|
if (!operation) continue;
|
|
2307
2027
|
if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters)) {
|
|
@@ -2309,18 +2029,18 @@ ${typeCode}`;
|
|
|
2309
2029
|
}
|
|
2310
2030
|
const allParams = mergeParameters(pathItem.parameters, operation.parameters, this.spec);
|
|
2311
2031
|
const queryParams = allParams.filter(
|
|
2312
|
-
(param) => param &&
|
|
2032
|
+
(param) => isResolvedParameter(param) && param.in === "query"
|
|
2313
2033
|
);
|
|
2314
2034
|
if (queryParams.length === 0) {
|
|
2315
2035
|
continue;
|
|
2316
2036
|
}
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2037
|
+
const strippedPath = stripPathPrefix(path, this.options.stripPathPrefix);
|
|
2038
|
+
const pascalOperationId = getOperationName(
|
|
2039
|
+
operation.operationId,
|
|
2040
|
+
method,
|
|
2041
|
+
strippedPath,
|
|
2042
|
+
this.options.useOperationId
|
|
2043
|
+
);
|
|
2324
2044
|
const schemaName = `${pascalOperationId}QueryParams`;
|
|
2325
2045
|
if (!this.schemaDependencies.has(schemaName)) {
|
|
2326
2046
|
this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
|
|
@@ -2350,7 +2070,7 @@ ${typeCode}`;
|
|
|
2350
2070
|
required.push(paramName);
|
|
2351
2071
|
}
|
|
2352
2072
|
if (paramSchema.$ref) {
|
|
2353
|
-
const refName =
|
|
2073
|
+
const refName = resolveRefName2(paramSchema.$ref);
|
|
2354
2074
|
(_a = this.schemaDependencies.get(schemaName)) == null ? void 0 : _a.add(refName);
|
|
2355
2075
|
}
|
|
2356
2076
|
}
|
|
@@ -2365,9 +2085,7 @@ ${typeCode}`;
|
|
|
2365
2085
|
${propsCode}
|
|
2366
2086
|
})`;
|
|
2367
2087
|
const operationName = pascalOperationId;
|
|
2368
|
-
const
|
|
2369
|
-
const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
|
|
2370
|
-
const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}QueryParamsSchema`;
|
|
2088
|
+
const camelCaseSchemaName = `${toCamelCase3(operationName, { prefix: this.options.prefix, suffix: this.options.suffix })}QueryParamsSchema`;
|
|
2371
2089
|
const jsdocOperationName = operation.operationId || `${method.toUpperCase()} ${path}`;
|
|
2372
2090
|
const jsdoc = `/**
|
|
2373
2091
|
* Query parameters for ${jsdocOperationName}
|
|
@@ -2379,35 +2097,6 @@ ${propsCode}
|
|
|
2379
2097
|
}
|
|
2380
2098
|
}
|
|
2381
2099
|
}
|
|
2382
|
-
/**
|
|
2383
|
-
* Generate a PascalCase method name from HTTP method and path
|
|
2384
|
-
* Used as fallback when operationId is not available
|
|
2385
|
-
* @internal
|
|
2386
|
-
*/
|
|
2387
|
-
generateMethodNameFromPath(method, path) {
|
|
2388
|
-
const segments = path.split("/").filter(Boolean).map((segment) => {
|
|
2389
|
-
if (segment.startsWith("{") && segment.endsWith("}")) {
|
|
2390
|
-
const paramName = segment.slice(1, -1);
|
|
2391
|
-
return `By${this.capitalizeSegment(paramName)}`;
|
|
2392
|
-
}
|
|
2393
|
-
return this.capitalizeSegment(segment);
|
|
2394
|
-
}).join("");
|
|
2395
|
-
const capitalizedMethod = method.charAt(0).toUpperCase() + method.slice(1).toLowerCase();
|
|
2396
|
-
return `${capitalizedMethod}${segments}`;
|
|
2397
|
-
}
|
|
2398
|
-
/**
|
|
2399
|
-
* Capitalizes a path segment, handling special characters like dashes, underscores, and dots
|
|
2400
|
-
* @internal
|
|
2401
|
-
*/
|
|
2402
|
-
capitalizeSegment(str) {
|
|
2403
|
-
if (str.includes("-") || str.includes("_") || str.includes(".")) {
|
|
2404
|
-
return str.split(/[-_.]/).map((part) => {
|
|
2405
|
-
if (!part) return "";
|
|
2406
|
-
return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
|
|
2407
|
-
}).join("");
|
|
2408
|
-
}
|
|
2409
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
2410
|
-
}
|
|
2411
2100
|
/**
|
|
2412
2101
|
* Check if a header should be ignored based on filter patterns
|
|
2413
2102
|
* @internal
|
|
@@ -2423,7 +2112,7 @@ ${propsCode}
|
|
|
2423
2112
|
const headerLower = headerName.toLowerCase();
|
|
2424
2113
|
return ignorePatterns.some((pattern) => {
|
|
2425
2114
|
const patternLower = pattern.toLowerCase();
|
|
2426
|
-
return
|
|
2115
|
+
return minimatch(headerLower, patternLower);
|
|
2427
2116
|
});
|
|
2428
2117
|
}
|
|
2429
2118
|
/**
|
|
@@ -2436,9 +2125,8 @@ ${propsCode}
|
|
|
2436
2125
|
return;
|
|
2437
2126
|
}
|
|
2438
2127
|
for (const [path, pathItem] of Object.entries(this.spec.paths)) {
|
|
2439
|
-
if (!pathItem
|
|
2440
|
-
const
|
|
2441
|
-
for (const method of methods) {
|
|
2128
|
+
if (!isOpenAPIPathItem(pathItem)) continue;
|
|
2129
|
+
for (const method of HTTP_METHODS) {
|
|
2442
2130
|
const operation = pathItem[method];
|
|
2443
2131
|
if (!operation) continue;
|
|
2444
2132
|
if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters)) {
|
|
@@ -2446,18 +2134,18 @@ ${propsCode}
|
|
|
2446
2134
|
}
|
|
2447
2135
|
const allParams = mergeParameters(pathItem.parameters, operation.parameters, this.spec);
|
|
2448
2136
|
const headerParams = allParams.filter(
|
|
2449
|
-
(param) => param &&
|
|
2137
|
+
(param) => isResolvedParameter(param) && param.in === "header" && !this.shouldIgnoreHeader(param.name)
|
|
2450
2138
|
);
|
|
2451
2139
|
if (headerParams.length === 0) {
|
|
2452
2140
|
continue;
|
|
2453
2141
|
}
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2142
|
+
const strippedPath = stripPathPrefix(path, this.options.stripPathPrefix);
|
|
2143
|
+
const pascalOperationId = getOperationName(
|
|
2144
|
+
operation.operationId,
|
|
2145
|
+
method,
|
|
2146
|
+
strippedPath,
|
|
2147
|
+
this.options.useOperationId
|
|
2148
|
+
);
|
|
2461
2149
|
const schemaName = `${pascalOperationId}HeaderParams`;
|
|
2462
2150
|
if (!this.schemaDependencies.has(schemaName)) {
|
|
2463
2151
|
this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
|
|
@@ -2476,7 +2164,7 @@ ${propsCode}
|
|
|
2476
2164
|
zodType = `${zodType}.optional()`;
|
|
2477
2165
|
properties[paramName] = zodType;
|
|
2478
2166
|
if (paramSchema.$ref) {
|
|
2479
|
-
const refName =
|
|
2167
|
+
const refName = resolveRefName2(paramSchema.$ref);
|
|
2480
2168
|
(_a = this.schemaDependencies.get(schemaName)) == null ? void 0 : _a.add(refName);
|
|
2481
2169
|
}
|
|
2482
2170
|
}
|
|
@@ -2491,9 +2179,7 @@ ${propsCode}
|
|
|
2491
2179
|
${propsCode}
|
|
2492
2180
|
})`;
|
|
2493
2181
|
const operationName = pascalOperationId;
|
|
2494
|
-
const
|
|
2495
|
-
const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
|
|
2496
|
-
const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}HeaderParamsSchema`;
|
|
2182
|
+
const camelCaseSchemaName = `${toCamelCase3(operationName, { prefix: this.options.prefix, suffix: this.options.suffix })}HeaderParamsSchema`;
|
|
2497
2183
|
const jsdocOperationName = operation.operationId || `${method.toUpperCase()} ${path}`;
|
|
2498
2184
|
const jsdoc = `/**
|
|
2499
2185
|
* Header parameters for ${jsdocOperationName}
|
|
@@ -2510,9 +2196,9 @@ ${propsCode}
|
|
|
2510
2196
|
*/
|
|
2511
2197
|
generateQueryParamType(schema, param) {
|
|
2512
2198
|
if (schema.$ref) {
|
|
2513
|
-
const refName =
|
|
2514
|
-
const strippedRefName =
|
|
2515
|
-
const schemaName =
|
|
2199
|
+
const refName = resolveRefName2(schema.$ref);
|
|
2200
|
+
const strippedRefName = stripPrefix2(refName, this.options.stripSchemaPrefix);
|
|
2201
|
+
const schemaName = toCamelCase3(strippedRefName, { prefix: this.options.prefix, suffix: this.options.suffix });
|
|
2516
2202
|
return `${schemaName}Schema`;
|
|
2517
2203
|
}
|
|
2518
2204
|
if (schema.enum) {
|
|
@@ -2529,7 +2215,7 @@ ${propsCode}
|
|
|
2529
2215
|
if (typeof v === "string") {
|
|
2530
2216
|
return `z.literal("${v}")`;
|
|
2531
2217
|
}
|
|
2532
|
-
return `z.literal(${v})`;
|
|
2218
|
+
return `z.literal(${String(v)})`;
|
|
2533
2219
|
}).join(", ");
|
|
2534
2220
|
return `z.union([${literalValues}])`;
|
|
2535
2221
|
}
|
|
@@ -2609,17 +2295,23 @@ ${propsCode}
|
|
|
2609
2295
|
return;
|
|
2610
2296
|
}
|
|
2611
2297
|
const deps = this.schemaDependencies.get(name);
|
|
2298
|
+
let dependsOnCircular = false;
|
|
2612
2299
|
if (deps && deps.size > 0) {
|
|
2613
2300
|
for (const dep of deps) {
|
|
2614
2301
|
if (this.schemas.has(dep) || this.types.has(dep)) {
|
|
2615
2302
|
visit(dep);
|
|
2303
|
+
if (circularDeps.has(dep)) {
|
|
2304
|
+
dependsOnCircular = true;
|
|
2305
|
+
}
|
|
2616
2306
|
}
|
|
2617
2307
|
}
|
|
2618
2308
|
}
|
|
2619
2309
|
visiting.delete(name);
|
|
2620
2310
|
visited.add(name);
|
|
2621
|
-
if (!circularDeps.has(name)) {
|
|
2311
|
+
if (!circularDeps.has(name) && !dependsOnCircular) {
|
|
2622
2312
|
sorted.push(name);
|
|
2313
|
+
} else if (dependsOnCircular && !circularDeps.has(name)) {
|
|
2314
|
+
circularDeps.add(name);
|
|
2623
2315
|
}
|
|
2624
2316
|
};
|
|
2625
2317
|
const allNames = /* @__PURE__ */ new Set([...this.schemas.keys(), ...this.types.keys()]);
|
|
@@ -2627,9 +2319,8 @@ ${propsCode}
|
|
|
2627
2319
|
visit(name);
|
|
2628
2320
|
}
|
|
2629
2321
|
for (const name of circularDeps) {
|
|
2630
|
-
if (!
|
|
2322
|
+
if (!sorted.includes(name)) {
|
|
2631
2323
|
sorted.push(name);
|
|
2632
|
-
visited.add(name);
|
|
2633
2324
|
}
|
|
2634
2325
|
}
|
|
2635
2326
|
return [...sorted, ...aliases];
|
|
@@ -2656,7 +2347,8 @@ ${propsCode}
|
|
|
2656
2347
|
`// Total schemas: ${stats.totalSchemas}`,
|
|
2657
2348
|
`// Circular references: ${stats.withCircularRefs}`,
|
|
2658
2349
|
`// Discriminated unions: ${stats.withDiscriminators}`,
|
|
2659
|
-
`// With constraints: ${stats.withConstraints}
|
|
2350
|
+
`// With constraints: ${stats.withConstraints}`,
|
|
2351
|
+
`// AllOf conflicts: ${this.allOfConflictCount}`
|
|
2660
2352
|
];
|
|
2661
2353
|
if (this.options.operationFilters && this.filterStats.totalOperations > 0) {
|
|
2662
2354
|
output.push("//");
|
|
@@ -2668,6 +2360,106 @@ ${propsCode}
|
|
|
2668
2360
|
output.push(`// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
2669
2361
|
return output;
|
|
2670
2362
|
}
|
|
2363
|
+
/**
|
|
2364
|
+
* Pre-analyze schemas to detect circular dependencies before code generation.
|
|
2365
|
+
* This allows the property generator to use z.lazy() for forward references.
|
|
2366
|
+
*/
|
|
2367
|
+
analyzeCircularDependencies() {
|
|
2368
|
+
var _a;
|
|
2369
|
+
if (!((_a = this.spec.components) == null ? void 0 : _a.schemas)) return;
|
|
2370
|
+
const dependencies = /* @__PURE__ */ new Map();
|
|
2371
|
+
const collectDependencies = (name, schema, visited2 = /* @__PURE__ */ new Set()) => {
|
|
2372
|
+
if (visited2.has(name)) return /* @__PURE__ */ new Set();
|
|
2373
|
+
visited2.add(name);
|
|
2374
|
+
const deps = /* @__PURE__ */ new Set();
|
|
2375
|
+
if (schema.$ref) {
|
|
2376
|
+
const refName = resolveRefName2(schema.$ref);
|
|
2377
|
+
deps.add(refName);
|
|
2378
|
+
}
|
|
2379
|
+
if (schema.allOf) {
|
|
2380
|
+
for (const item of schema.allOf) {
|
|
2381
|
+
const itemDeps = collectDependencies(`${name}_allOf`, item, new Set(visited2));
|
|
2382
|
+
for (const dep of itemDeps) deps.add(dep);
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
if (schema.oneOf) {
|
|
2386
|
+
for (const item of schema.oneOf) {
|
|
2387
|
+
const itemDeps = collectDependencies(`${name}_oneOf`, item, new Set(visited2));
|
|
2388
|
+
for (const dep of itemDeps) deps.add(dep);
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
if (schema.anyOf) {
|
|
2392
|
+
for (const item of schema.anyOf) {
|
|
2393
|
+
const itemDeps = collectDependencies(`${name}_anyOf`, item, new Set(visited2));
|
|
2394
|
+
for (const dep of itemDeps) deps.add(dep);
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
if (schema.properties) {
|
|
2398
|
+
for (const propSchema of Object.values(schema.properties)) {
|
|
2399
|
+
const propDeps = collectDependencies(`${name}_prop`, propSchema, new Set(visited2));
|
|
2400
|
+
for (const dep of propDeps) deps.add(dep);
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
if (schema.items) {
|
|
2404
|
+
const itemDeps = collectDependencies(`${name}_items`, schema.items, new Set(visited2));
|
|
2405
|
+
for (const dep of itemDeps) deps.add(dep);
|
|
2406
|
+
}
|
|
2407
|
+
if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
|
|
2408
|
+
const addDeps = collectDependencies(`${name}_additional`, schema.additionalProperties, new Set(visited2));
|
|
2409
|
+
for (const dep of addDeps) deps.add(dep);
|
|
2410
|
+
}
|
|
2411
|
+
return deps;
|
|
2412
|
+
};
|
|
2413
|
+
for (const [name, schema] of Object.entries(this.spec.components.schemas)) {
|
|
2414
|
+
if (this.options.operationFilters && this.schemaUsageMap.size > 0 && !this.schemaUsageMap.has(name)) {
|
|
2415
|
+
continue;
|
|
2416
|
+
}
|
|
2417
|
+
dependencies.set(name, collectDependencies(name, schema));
|
|
2418
|
+
}
|
|
2419
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2420
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
2421
|
+
const detectCircular = (name, path = []) => {
|
|
2422
|
+
if (visited.has(name)) return;
|
|
2423
|
+
if (visiting.has(name)) {
|
|
2424
|
+
const cycleStart = path.indexOf(name);
|
|
2425
|
+
if (cycleStart >= 0) {
|
|
2426
|
+
for (let i = cycleStart; i < path.length; i++) {
|
|
2427
|
+
this.circularDependencies.add(path[i]);
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
this.circularDependencies.add(name);
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2433
|
+
visiting.add(name);
|
|
2434
|
+
path.push(name);
|
|
2435
|
+
const deps = dependencies.get(name);
|
|
2436
|
+
if (deps) {
|
|
2437
|
+
for (const dep of deps) {
|
|
2438
|
+
if (dependencies.has(dep)) {
|
|
2439
|
+
detectCircular(dep, [...path]);
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
visiting.delete(name);
|
|
2444
|
+
visited.add(name);
|
|
2445
|
+
};
|
|
2446
|
+
for (const name of dependencies.keys()) {
|
|
2447
|
+
detectCircular(name, []);
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
/**
|
|
2451
|
+
* Generate JSDoc warning for allOf conflicts
|
|
2452
|
+
* @param conflicts Array of conflict description strings
|
|
2453
|
+
* @returns JSDoc formatted warning string
|
|
2454
|
+
*/
|
|
2455
|
+
generateConflictJSDoc(conflicts) {
|
|
2456
|
+
const lines = [" * @warning allOf property conflicts detected:"];
|
|
2457
|
+
for (const conflict of conflicts) {
|
|
2458
|
+
lines.push(` * - ${conflict}`);
|
|
2459
|
+
}
|
|
2460
|
+
return `${lines.join("\n")}
|
|
2461
|
+
`;
|
|
2462
|
+
}
|
|
2671
2463
|
};
|
|
2672
2464
|
|
|
2673
2465
|
// src/types.ts
|
|
@@ -2681,8 +2473,10 @@ export {
|
|
|
2681
2473
|
FileOperationError,
|
|
2682
2474
|
GeneratorError,
|
|
2683
2475
|
OpenApiGenerator,
|
|
2684
|
-
|
|
2685
|
-
|
|
2476
|
+
PropertyGenerator,
|
|
2477
|
+
SchemaGenerationError2 as SchemaGenerationError,
|
|
2478
|
+
SpecValidationError2 as SpecValidationError,
|
|
2479
|
+
buildDateTimeValidation,
|
|
2686
2480
|
defineConfig
|
|
2687
2481
|
};
|
|
2688
2482
|
//# sourceMappingURL=index.mjs.map
|