@cerios/openapi-to-zod 1.5.1 → 1.6.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/README.md +49 -0
- package/dist/cli.js +89 -26
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +93 -27
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +21 -4
- package/dist/index.d.ts +21 -4
- package/dist/index.js +88 -26
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +92 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -188,6 +188,7 @@ Examples:
|
|
|
188
188
|
| `name` | `string` | Optional identifier for logging |
|
|
189
189
|
| `input` | `string` | Input OpenAPI YAML file path (required) |
|
|
190
190
|
| `outputTypes` | `string` | Preferred output TypeScript file path (required unless deprecated `output` is set) |
|
|
191
|
+
| `outputZodSchemas` | `string` | Separate output path for Zod schemas (recommended for circular references, see below) |
|
|
191
192
|
| `output` | `string` | Deprecated alias for `outputTypes`; allowed for backward compatibility |
|
|
192
193
|
| `mode` | `"strict"` \| `"normal"` \| `"loose"` | Validation mode for top-level schemas (default: `"normal"`) |
|
|
193
194
|
| `emptyObjectBehavior` | `"strict"` \| `"loose"` \| `"record"` | How to handle empty objects (default: `"loose"`) |
|
|
@@ -1087,6 +1088,54 @@ export default defineConfig({
|
|
|
1087
1088
|
4. **Better Code Completion**: Easier to find schemas in IDE autocomplete
|
|
1088
1089
|
5. **Flexible Pattern Matching**: Use regex for dynamic prefixes
|
|
1089
1090
|
|
|
1091
|
+
## Circular References and `z.lazy()`
|
|
1092
|
+
|
|
1093
|
+
When your OpenAPI spec contains circular references (schemas that reference themselves or each other), Zod requires using `z.lazy()` for recursive types. However, this creates a TypeScript challenge:
|
|
1094
|
+
|
|
1095
|
+
```typescript
|
|
1096
|
+
// Combined mode - can cause TypeScript errors
|
|
1097
|
+
export const nodeSchema = z.object({
|
|
1098
|
+
id: z.string(),
|
|
1099
|
+
parent: z.lazy(() => nodeSchema).optional(), // ❌ Type errors with circular inference
|
|
1100
|
+
});
|
|
1101
|
+
export type Node = z.infer<typeof nodeSchema>; // Circular type reference
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
**Recommendation:** Use separate type and schema files (`outputZodSchemas`) for specs with circular references:
|
|
1105
|
+
|
|
1106
|
+
```typescript
|
|
1107
|
+
import { defineConfig } from "@cerios/openapi-to-zod";
|
|
1108
|
+
|
|
1109
|
+
export default defineConfig({
|
|
1110
|
+
specs: [
|
|
1111
|
+
{
|
|
1112
|
+
input: "openapi.yaml",
|
|
1113
|
+
outputTypes: "src/generated/types.ts", // TypeScript types
|
|
1114
|
+
outputZodSchemas: "src/generated/schemas.ts", // Zod schemas
|
|
1115
|
+
},
|
|
1116
|
+
],
|
|
1117
|
+
});
|
|
1118
|
+
```
|
|
1119
|
+
|
|
1120
|
+
This generates proper forward-declared types:
|
|
1121
|
+
|
|
1122
|
+
```typescript
|
|
1123
|
+
// types.ts
|
|
1124
|
+
export interface Node {
|
|
1125
|
+
id?: string;
|
|
1126
|
+
parent?: Node;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// schemas.ts
|
|
1130
|
+
import type { Node } from "./types";
|
|
1131
|
+
export const nodeSchema: z.ZodType<Node> = z.object({
|
|
1132
|
+
id: z.string().optional(),
|
|
1133
|
+
parent: z.lazy(() => nodeSchema).optional(),
|
|
1134
|
+
});
|
|
1135
|
+
```
|
|
1136
|
+
|
|
1137
|
+
This approach also helps avoid "Type instantiation is excessively deep" errors (TS2589) with large schemas.
|
|
1138
|
+
|
|
1090
1139
|
## Generation Statistics
|
|
1091
1140
|
|
|
1092
1141
|
Statistics are **included by default** in generated files. Use `showStats: false` to disable:
|
package/dist/cli.js
CHANGED
|
@@ -5132,9 +5132,11 @@ function isDiscriminatorRequired(schemas, discriminator, context) {
|
|
|
5132
5132
|
};
|
|
5133
5133
|
}
|
|
5134
5134
|
function generateUnion(schemas, discriminator, isNullable2, context, options, currentSchema) {
|
|
5135
|
+
var _a, _b;
|
|
5135
5136
|
if (schemas.length === 0) {
|
|
5136
|
-
|
|
5137
|
-
|
|
5137
|
+
(_a = context.warn) == null ? void 0 : _a.call(
|
|
5138
|
+
context,
|
|
5139
|
+
"Empty oneOf/anyOf array encountered. This is likely a malformed OpenAPI spec. Generating z.never() as fallback."
|
|
5138
5140
|
);
|
|
5139
5141
|
return wrapNullable(
|
|
5140
5142
|
'z.never().describe("Empty oneOf/anyOf in OpenAPI spec - no valid schema defined")',
|
|
@@ -5155,8 +5157,9 @@ function generateUnion(schemas, discriminator, isNullable2, context, options, cu
|
|
|
5155
5157
|
}
|
|
5156
5158
|
const discriminatorCheck = isDiscriminatorRequired(resolvedSchemas, discriminator, context);
|
|
5157
5159
|
if (!discriminatorCheck.valid) {
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
+
(_b = context.warn) == null ? void 0 : _b.call(
|
|
5161
|
+
context,
|
|
5162
|
+
`Discriminator "${discriminator}" is not required in schemas: ${discriminatorCheck.invalidSchemas.join(", ")}. Falling back to z.union() instead of z.discriminatedUnion().`
|
|
5160
5163
|
);
|
|
5161
5164
|
let schemaStrings3 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema, false, true));
|
|
5162
5165
|
if (options == null ? void 0 : options.passthrough) {
|
|
@@ -5234,6 +5237,7 @@ function detectConflictingProperties(schemas, context) {
|
|
|
5234
5237
|
return conflicts;
|
|
5235
5238
|
}
|
|
5236
5239
|
function generateAllOf(schemas, isNullable2, context, currentSchema) {
|
|
5240
|
+
var _a;
|
|
5237
5241
|
if (schemas.length === 1) {
|
|
5238
5242
|
const singleSchema = context.generatePropertySchema(schemas[0], currentSchema, false, true);
|
|
5239
5243
|
return { schema: wrapNullable(singleSchema, isNullable2), conflicts: [] };
|
|
@@ -5242,7 +5246,7 @@ function generateAllOf(schemas, isNullable2, context, currentSchema) {
|
|
|
5242
5246
|
const uniqueConflicts = [...new Set(conflicts)];
|
|
5243
5247
|
if (uniqueConflicts.length > 0) {
|
|
5244
5248
|
for (const conflict of uniqueConflicts) {
|
|
5245
|
-
|
|
5249
|
+
(_a = context.warn) == null ? void 0 : _a.call(context, `allOf composition conflict - ${conflict}`);
|
|
5246
5250
|
}
|
|
5247
5251
|
}
|
|
5248
5252
|
const allObjects = schemas.every((s) => s.type === "object" || s.properties || s.$ref || s.allOf);
|
|
@@ -6163,7 +6167,8 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
6163
6167
|
{
|
|
6164
6168
|
generatePropertySchema: this.generatePropertySchema.bind(this),
|
|
6165
6169
|
generateInlineObjectShape: this.generateInlineObjectShape.bind(this),
|
|
6166
|
-
resolveSchemaRef: this.resolveSchemaRef.bind(this)
|
|
6170
|
+
resolveSchemaRef: this.resolveSchemaRef.bind(this),
|
|
6171
|
+
warn: this.context.warn
|
|
6167
6172
|
},
|
|
6168
6173
|
currentSchema
|
|
6169
6174
|
);
|
|
@@ -6186,7 +6191,8 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
6186
6191
|
{
|
|
6187
6192
|
generatePropertySchema: this.generatePropertySchema.bind(this),
|
|
6188
6193
|
resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this),
|
|
6189
|
-
resolveSchemaRef: this.resolveSchemaRef.bind(this)
|
|
6194
|
+
resolveSchemaRef: this.resolveSchemaRef.bind(this),
|
|
6195
|
+
warn: this.context.warn
|
|
6190
6196
|
},
|
|
6191
6197
|
{
|
|
6192
6198
|
passthrough: needsPassthrough,
|
|
@@ -6209,7 +6215,8 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
6209
6215
|
{
|
|
6210
6216
|
generatePropertySchema: this.generatePropertySchema.bind(this),
|
|
6211
6217
|
resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this),
|
|
6212
|
-
resolveSchemaRef: this.resolveSchemaRef.bind(this)
|
|
6218
|
+
resolveSchemaRef: this.resolveSchemaRef.bind(this),
|
|
6219
|
+
warn: this.context.warn
|
|
6213
6220
|
},
|
|
6214
6221
|
{
|
|
6215
6222
|
passthrough: needsPassthrough,
|
|
@@ -6364,8 +6371,6 @@ var OpenApiGenerator = class {
|
|
|
6364
6371
|
this.schemaUsageMap = /* @__PURE__ */ new Map();
|
|
6365
6372
|
this.needsZodImport = true;
|
|
6366
6373
|
this.filterStats = (0, import_openapi_core6.createFilterStatistics)();
|
|
6367
|
-
/** Track total allOf conflicts detected across all schemas */
|
|
6368
|
-
this.allOfConflictCount = 0;
|
|
6369
6374
|
/** Track schemas involved in circular dependency chains */
|
|
6370
6375
|
this.circularDependencies = /* @__PURE__ */ new Set();
|
|
6371
6376
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
@@ -6373,6 +6378,11 @@ var OpenApiGenerator = class {
|
|
|
6373
6378
|
throw new import_openapi_core6.ConfigurationError("Input path is required", { providedOptions: options });
|
|
6374
6379
|
}
|
|
6375
6380
|
this.separateSchemasMode = Boolean(options.outputZodSchemas);
|
|
6381
|
+
const showWarnings = options.showWarnings !== false;
|
|
6382
|
+
this.warningCollector = new import_openapi_core6.WarningCollector({
|
|
6383
|
+
packageName: "@cerios/openapi-to-zod",
|
|
6384
|
+
enabled: showWarnings
|
|
6385
|
+
});
|
|
6376
6386
|
this.options = {
|
|
6377
6387
|
mode: options.mode || "normal",
|
|
6378
6388
|
input: options.input,
|
|
@@ -6391,13 +6401,16 @@ var OpenApiGenerator = class {
|
|
|
6391
6401
|
stripPathPrefix: options.stripPathPrefix,
|
|
6392
6402
|
useOperationId: (_f = options.useOperationId) != null ? _f : true,
|
|
6393
6403
|
showStats: (_g = options.showStats) != null ? _g : true,
|
|
6404
|
+
showWarnings,
|
|
6394
6405
|
request: options.request,
|
|
6395
6406
|
response: options.response,
|
|
6396
6407
|
operationFilters: options.operationFilters,
|
|
6397
6408
|
ignoreHeaders: options.ignoreHeaders,
|
|
6398
6409
|
cacheSize: (_h = options.cacheSize) != null ? _h : 1e3,
|
|
6399
6410
|
batchSize: (_i = options.batchSize) != null ? _i : 10,
|
|
6400
|
-
customDateTimeFormatRegex: options.customDateTimeFormatRegex
|
|
6411
|
+
customDateTimeFormatRegex: options.customDateTimeFormatRegex,
|
|
6412
|
+
includeHeader: options.includeHeader,
|
|
6413
|
+
fileHeader: options.fileHeader
|
|
6401
6414
|
};
|
|
6402
6415
|
this.patternCache = new import_openapi_core6.LRUCache((_j = this.options.cacheSize) != null ? _j : 1e3);
|
|
6403
6416
|
this.dateTimeValidation = buildDateTimeValidation(this.options.customDateTimeFormatRegex);
|
|
@@ -6422,7 +6435,10 @@ var OpenApiGenerator = class {
|
|
|
6422
6435
|
stripSchemaPrefix: this.options.stripSchemaPrefix,
|
|
6423
6436
|
dateTimeValidation: this.dateTimeValidation,
|
|
6424
6437
|
patternCache: this.patternCache,
|
|
6425
|
-
separateTypesFile: this.separateSchemasMode
|
|
6438
|
+
separateTypesFile: this.separateSchemasMode,
|
|
6439
|
+
warn: (msg) => {
|
|
6440
|
+
this.warningCollector.add(msg);
|
|
6441
|
+
}
|
|
6426
6442
|
});
|
|
6427
6443
|
}
|
|
6428
6444
|
/**
|
|
@@ -6431,7 +6447,7 @@ var OpenApiGenerator = class {
|
|
|
6431
6447
|
* @returns The generated TypeScript code as a string
|
|
6432
6448
|
*/
|
|
6433
6449
|
generateString() {
|
|
6434
|
-
var _a;
|
|
6450
|
+
var _a, _b, _c;
|
|
6435
6451
|
if (!((_a = this.spec.components) == null ? void 0 : _a.schemas)) {
|
|
6436
6452
|
throw new import_openapi_core6.SpecValidationError("No schemas found in OpenAPI spec", { filePath: this.options.input });
|
|
6437
6453
|
}
|
|
@@ -6448,9 +6464,26 @@ var OpenApiGenerator = class {
|
|
|
6448
6464
|
}
|
|
6449
6465
|
this.generateQueryParameterSchemas();
|
|
6450
6466
|
this.generateHeaderParameterSchemas();
|
|
6451
|
-
(0, import_openapi_core6.validateFilters)(this.filterStats, this.options.operationFilters)
|
|
6467
|
+
(0, import_openapi_core6.validateFilters)(this.filterStats, this.options.operationFilters, (msg) => {
|
|
6468
|
+
this.warningCollector.add(msg);
|
|
6469
|
+
});
|
|
6452
6470
|
const orderedSchemaNames = this.topologicalSort();
|
|
6453
|
-
const output = [
|
|
6471
|
+
const output = [];
|
|
6472
|
+
const customHeader = (0, import_openapi_core6.generateCustomFileHeader)(this.options.fileHeader);
|
|
6473
|
+
if (customHeader) {
|
|
6474
|
+
output.push(customHeader.trimEnd());
|
|
6475
|
+
output.push("");
|
|
6476
|
+
}
|
|
6477
|
+
if (this.options.includeHeader !== false) {
|
|
6478
|
+
output.push(
|
|
6479
|
+
(0, import_openapi_core6.generateFileHeader)({
|
|
6480
|
+
packageName: "@cerios/openapi-to-zod",
|
|
6481
|
+
apiTitle: (_b = this.spec.info) == null ? void 0 : _b.title,
|
|
6482
|
+
apiVersion: (_c = this.spec.info) == null ? void 0 : _c.version
|
|
6483
|
+
}).trimEnd()
|
|
6484
|
+
);
|
|
6485
|
+
output.push("");
|
|
6486
|
+
}
|
|
6454
6487
|
if (this.options.showStats === true) {
|
|
6455
6488
|
output.push(...this.generateStats());
|
|
6456
6489
|
output.push("");
|
|
@@ -6477,6 +6510,7 @@ var OpenApiGenerator = class {
|
|
|
6477
6510
|
output.push("");
|
|
6478
6511
|
}
|
|
6479
6512
|
}
|
|
6513
|
+
this.warningCollector.flush();
|
|
6480
6514
|
return output.join("\n");
|
|
6481
6515
|
}
|
|
6482
6516
|
/**
|
|
@@ -6516,6 +6550,7 @@ var OpenApiGenerator = class {
|
|
|
6516
6550
|
(0, import_node_fs.writeFileSync)(normalizedOutput, output);
|
|
6517
6551
|
console.log(` \u2713 Generated ${normalizedOutput}`);
|
|
6518
6552
|
}
|
|
6553
|
+
this.warningCollector.flush();
|
|
6519
6554
|
}
|
|
6520
6555
|
/**
|
|
6521
6556
|
* Generate Zod schemas with explicit type annotations (for outputZodSchemas mode)
|
|
@@ -6523,7 +6558,7 @@ var OpenApiGenerator = class {
|
|
|
6523
6558
|
* @returns The generated Zod schemas TypeScript code
|
|
6524
6559
|
*/
|
|
6525
6560
|
generateSeparateSchemasString() {
|
|
6526
|
-
var _a;
|
|
6561
|
+
var _a, _b, _c;
|
|
6527
6562
|
const schemas = (_a = this.spec.components) == null ? void 0 : _a.schemas;
|
|
6528
6563
|
if (!schemas) {
|
|
6529
6564
|
return "";
|
|
@@ -6542,9 +6577,24 @@ var OpenApiGenerator = class {
|
|
|
6542
6577
|
}
|
|
6543
6578
|
this.generateQueryParameterSchemas();
|
|
6544
6579
|
this.generateHeaderParameterSchemas();
|
|
6545
|
-
(0, import_openapi_core6.validateFilters)(this.filterStats, this.options.operationFilters)
|
|
6580
|
+
(0, import_openapi_core6.validateFilters)(this.filterStats, this.options.operationFilters, (msg) => {
|
|
6581
|
+
this.warningCollector.add(msg);
|
|
6582
|
+
});
|
|
6546
6583
|
const orderedSchemaNames = this.topologicalSort();
|
|
6547
|
-
const output = [
|
|
6584
|
+
const output = [];
|
|
6585
|
+
const customHeader = (0, import_openapi_core6.generateCustomFileHeader)(this.options.fileHeader);
|
|
6586
|
+
if (customHeader) {
|
|
6587
|
+
output.push(customHeader.trimEnd());
|
|
6588
|
+
output.push("");
|
|
6589
|
+
}
|
|
6590
|
+
output.push(
|
|
6591
|
+
(0, import_openapi_core6.generateFileHeader)({
|
|
6592
|
+
packageName: "@cerios/openapi-to-zod",
|
|
6593
|
+
apiTitle: (_b = this.spec.info) == null ? void 0 : _b.title,
|
|
6594
|
+
apiVersion: (_c = this.spec.info) == null ? void 0 : _c.version
|
|
6595
|
+
}).trimEnd()
|
|
6596
|
+
);
|
|
6597
|
+
output.push("");
|
|
6548
6598
|
if (this.options.showStats === true) {
|
|
6549
6599
|
output.push(...this.generateStats());
|
|
6550
6600
|
output.push("");
|
|
@@ -6582,8 +6632,8 @@ var OpenApiGenerator = class {
|
|
|
6582
6632
|
* @returns The generated TypeScript types code
|
|
6583
6633
|
*/
|
|
6584
6634
|
generateTypesString() {
|
|
6585
|
-
var _a;
|
|
6586
|
-
const
|
|
6635
|
+
var _a, _b, _c;
|
|
6636
|
+
const internalOptions = {
|
|
6587
6637
|
input: this.options.input,
|
|
6588
6638
|
outputTypes: this.options.outputTypes,
|
|
6589
6639
|
includeDescriptions: this.options.includeDescriptions,
|
|
@@ -6594,9 +6644,20 @@ var OpenApiGenerator = class {
|
|
|
6594
6644
|
stripPathPrefix: this.options.stripPathPrefix,
|
|
6595
6645
|
operationFilters: this.options.operationFilters,
|
|
6596
6646
|
showStats: this.options.showStats,
|
|
6597
|
-
enumFormat: (_a = this.options.enumFormat) != null ? _a : "const-object"
|
|
6647
|
+
enumFormat: (_a = this.options.enumFormat) != null ? _a : "const-object",
|
|
6648
|
+
includeHeader: false,
|
|
6649
|
+
// We add our own header for consistent branding
|
|
6650
|
+
showWarnings: false
|
|
6651
|
+
// We handle warnings ourselves
|
|
6652
|
+
};
|
|
6653
|
+
const tsGenerator = new import_openapi_to_typescript.TypeScriptGenerator(internalOptions);
|
|
6654
|
+
const customHeader = (0, import_openapi_core6.generateCustomFileHeader)(this.options.fileHeader);
|
|
6655
|
+
const header = (0, import_openapi_core6.generateFileHeader)({
|
|
6656
|
+
packageName: "@cerios/openapi-to-zod",
|
|
6657
|
+
apiTitle: (_b = this.spec.info) == null ? void 0 : _b.title,
|
|
6658
|
+
apiVersion: (_c = this.spec.info) == null ? void 0 : _c.version
|
|
6598
6659
|
});
|
|
6599
|
-
return tsGenerator.generateString();
|
|
6660
|
+
return customHeader + header + tsGenerator.generateString();
|
|
6600
6661
|
}
|
|
6601
6662
|
/**
|
|
6602
6663
|
* Add explicit type annotation to a schema declaration
|
|
@@ -6915,14 +6976,16 @@ ${typeCode}`;
|
|
|
6915
6976
|
stripSchemaPrefix: this.options.stripSchemaPrefix,
|
|
6916
6977
|
dateTimeValidation: this.dateTimeValidation,
|
|
6917
6978
|
patternCache: this.patternCache,
|
|
6918
|
-
separateTypesFile: this.separateSchemasMode
|
|
6979
|
+
separateTypesFile: this.separateSchemasMode,
|
|
6980
|
+
warn: (msg) => {
|
|
6981
|
+
this.warningCollector.add(msg);
|
|
6982
|
+
}
|
|
6919
6983
|
});
|
|
6920
6984
|
this.propertyGenerator.setCircularDependencies(this.circularDependencies);
|
|
6921
6985
|
this.propertyGenerator.clearAllOfConflicts();
|
|
6922
6986
|
const zodSchema = this.propertyGenerator.generatePropertySchema(schema, name, true);
|
|
6923
6987
|
const allOfConflicts = this.propertyGenerator.getAllOfConflicts();
|
|
6924
6988
|
if (allOfConflicts.length > 0) {
|
|
6925
|
-
this.allOfConflictCount += allOfConflicts.length;
|
|
6926
6989
|
const conflictWarning = this.generateConflictJSDoc(allOfConflicts);
|
|
6927
6990
|
if (jsdoc) {
|
|
6928
6991
|
jsdoc = jsdoc.replace(/ \*\/\n$/, `
|
|
@@ -7286,8 +7349,7 @@ ${propsCode}
|
|
|
7286
7349
|
`// Total schemas: ${stats.totalSchemas}`,
|
|
7287
7350
|
`// Circular references: ${stats.withCircularRefs}`,
|
|
7288
7351
|
`// Discriminated unions: ${stats.withDiscriminators}`,
|
|
7289
|
-
`// With constraints: ${stats.withConstraints}
|
|
7290
|
-
`// AllOf conflicts: ${this.allOfConflictCount}`
|
|
7352
|
+
`// With constraints: ${stats.withConstraints}`
|
|
7291
7353
|
];
|
|
7292
7354
|
if (this.options.operationFilters && this.filterStats.totalOperations > 0) {
|
|
7293
7355
|
output.push("//");
|
|
@@ -7519,6 +7581,7 @@ function mergeConfigWithDefaults(config) {
|
|
|
7519
7581
|
showStats: defaults.showStats,
|
|
7520
7582
|
customDateTimeFormatRegex: defaults.customDateTimeFormatRegex,
|
|
7521
7583
|
enumFormat: defaults.enumFormat,
|
|
7584
|
+
fileHeader: defaults.fileHeader,
|
|
7522
7585
|
// Override with spec-specific values
|
|
7523
7586
|
...specWithoutDeprecatedOutput,
|
|
7524
7587
|
outputTypes: resolvedOutputTypes
|