@cerios/openapi-to-zod 1.2.0 → 1.3.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 +87 -30
- package/dist/cli.js +283 -37
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +289 -37
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +282 -37
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +282 -37
- package/dist/index.mjs.map +1 -1
- package/dist/internal.d.mts +51 -2
- package/dist/internal.d.ts +51 -2
- package/dist/internal.js +69 -0
- package/dist/internal.js.map +1 -1
- package/dist/internal.mjs +64 -0
- package/dist/internal.mjs.map +1 -1
- package/dist/{types--r0d47sd.d.mts → types-B3GgqGzM.d.mts} +72 -1
- package/dist/{types--r0d47sd.d.ts → types-B3GgqGzM.d.ts} +72 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ Transform OpenAPI YAML specifications into Zod v4 compliant schemas with full Ty
|
|
|
19
19
|
- 📊 **Statistics**: Optional generation statistics in output files
|
|
20
20
|
- ❗ **Better Errors**: Clear error messages with file paths and line numbers
|
|
21
21
|
- 🎭 **Tuple Validation**: OpenAPI 3.1 `prefixItems` support with `.tuple()` and `.rest()`
|
|
22
|
-
- 🔗 **Smart AllOf**: Uses `.
|
|
22
|
+
- 🔗 **Smart AllOf**: Uses `.extend()` for objects (Zod v4), `.and()` for primitives
|
|
23
23
|
- 🎯 **Literal Types**: `const` keyword support with `z.literal()`
|
|
24
24
|
- 🔢 **Exclusive Bounds**: `exclusiveMinimum`/`exclusiveMaximum` with `.gt()`/`.lt()`
|
|
25
25
|
- 🎨 **Unique Arrays**: `uniqueItems` validation with Set-based checking
|
|
@@ -181,7 +181,8 @@ Examples:
|
|
|
181
181
|
| `name` | `string` | Optional identifier for logging |
|
|
182
182
|
| `input` | `string` | Input OpenAPI YAML file path (required) |
|
|
183
183
|
| `output` | `string` | Output TypeScript file path (required) |
|
|
184
|
-
| `mode` | `"strict"` \| `"normal"` \| `"loose"` | Validation mode |
|
|
184
|
+
| `mode` | `"strict"` \| `"normal"` \| `"loose"` | Validation mode for top-level schemas (default: `"normal"`) |
|
|
185
|
+
| `emptyObjectBehavior` | `"strict"` \| `"loose"` \| `"record"` | How to handle empty objects (default: `"loose"`) |
|
|
185
186
|
| `includeDescriptions` | `boolean` | Include JSDoc comments |
|
|
186
187
|
| `useDescribe` | `boolean` | Add `.describe()` calls |
|
|
187
188
|
| `defaultNullable` | `boolean` | Treat properties as nullable by default when not explicitly specified (default: `false`) |
|
|
@@ -314,6 +315,43 @@ const userSchema = z.looseObject({
|
|
|
314
315
|
});
|
|
315
316
|
```
|
|
316
317
|
|
|
318
|
+
## Empty Object Behavior
|
|
319
|
+
|
|
320
|
+
When OpenAPI schemas define an object without any properties (e.g., `type: object` with no `properties`), the generator needs to decide how to represent it. The `emptyObjectBehavior` option controls this:
|
|
321
|
+
|
|
322
|
+
### Loose (default)
|
|
323
|
+
Uses `z.looseObject({})` which allows any additional properties:
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
// OpenAPI: { type: object }
|
|
327
|
+
const metadataSchema = z.looseObject({});
|
|
328
|
+
|
|
329
|
+
// Accepts: {}, { foo: "bar" }, { any: "properties" }
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Strict
|
|
333
|
+
Uses `z.strictObject({})` which rejects any properties:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
// OpenAPI: { type: object }
|
|
337
|
+
const emptySchema = z.strictObject({});
|
|
338
|
+
|
|
339
|
+
// Accepts: {}
|
|
340
|
+
// Rejects: { foo: "bar" }
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Record
|
|
344
|
+
Uses `z.record(z.string(), z.unknown())` which treats it as an arbitrary key-value map:
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
// OpenAPI: { type: object }
|
|
348
|
+
const mapSchema = z.record(z.string(), z.unknown());
|
|
349
|
+
|
|
350
|
+
// Accepts: {}, { foo: "bar" }, { any: "properties" }
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
> **Note:** The `mode` option controls how top-level schema definitions are wrapped, while `emptyObjectBehavior` controls how nested empty objects (properties without defined structure) are generated. These are independent settings.
|
|
354
|
+
|
|
317
355
|
## Examples
|
|
318
356
|
|
|
319
357
|
### Input OpenAPI YAML
|
|
@@ -642,55 +680,74 @@ export default defineConfig({
|
|
|
642
680
|
});
|
|
643
681
|
```
|
|
644
682
|
|
|
683
|
+
**Important:** `defaultNullable` only applies to **primitive property values** within objects. It does NOT apply to:
|
|
684
|
+
|
|
685
|
+
- **Top-level schema definitions** - Schemas are not made nullable at the definition level
|
|
686
|
+
- **Schema references (`$ref`)** - References preserve the nullability of the target schema; add explicit `nullable: true` if needed
|
|
687
|
+
- **Enum values** - Enums define discrete values and are not nullable by default
|
|
688
|
+
- **Const/literal values** - Literals are exact values and are not nullable by default
|
|
689
|
+
|
|
645
690
|
**Behavior comparison:**
|
|
646
691
|
|
|
647
692
|
| Schema Property | `defaultNullable: false` (default) | `defaultNullable: true` |
|
|
648
693
|
|-----------------|-------------------------------------|-------------------------|
|
|
649
694
|
| `nullable: true` | `.nullable()` | `.nullable()` |
|
|
650
695
|
| `nullable: false` | No `.nullable()` | No `.nullable()` |
|
|
651
|
-
| No
|
|
696
|
+
| No annotation (primitive) | No `.nullable()` | `.nullable()` |
|
|
697
|
+
| No annotation (`$ref`) | No `.nullable()` | No `.nullable()` |
|
|
698
|
+
| No annotation (enum) | No `.nullable()` | No `.nullable()` |
|
|
699
|
+
| No annotation (const) | No `.nullable()` | No `.nullable()` |
|
|
652
700
|
|
|
653
701
|
**Example:**
|
|
654
702
|
|
|
655
703
|
```yaml
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
id:
|
|
660
|
-
type: integer
|
|
661
|
-
name:
|
|
662
|
-
type: string
|
|
663
|
-
email:
|
|
664
|
-
type: string
|
|
665
|
-
nullable: true
|
|
666
|
-
phone:
|
|
704
|
+
components:
|
|
705
|
+
schemas:
|
|
706
|
+
Status:
|
|
667
707
|
type: string
|
|
668
|
-
|
|
708
|
+
enum: [active, inactive]
|
|
709
|
+
User:
|
|
710
|
+
type: object
|
|
711
|
+
properties:
|
|
712
|
+
id:
|
|
713
|
+
type: integer
|
|
714
|
+
name:
|
|
715
|
+
type: string
|
|
716
|
+
status:
|
|
717
|
+
$ref: '#/components/schemas/Status'
|
|
718
|
+
nullableStatus:
|
|
719
|
+
allOf:
|
|
720
|
+
- $ref: '#/components/schemas/Status'
|
|
721
|
+
nullable: true
|
|
669
722
|
```
|
|
670
723
|
|
|
671
724
|
**With `defaultNullable: false` (default):**
|
|
672
725
|
```typescript
|
|
726
|
+
export const statusSchema = z.enum(["active", "inactive"]);
|
|
727
|
+
|
|
673
728
|
export const userSchema = z.object({
|
|
674
729
|
id: z.number().int(),
|
|
675
|
-
name: z.string(),
|
|
676
|
-
|
|
677
|
-
|
|
730
|
+
name: z.string(), // Not nullable (no annotation)
|
|
731
|
+
status: statusSchema, // Not nullable ($ref)
|
|
732
|
+
nullableStatus: statusSchema.nullable(), // Explicitly nullable
|
|
678
733
|
});
|
|
679
734
|
```
|
|
680
735
|
|
|
681
736
|
**With `defaultNullable: true`:**
|
|
682
737
|
```typescript
|
|
738
|
+
export const statusSchema = z.enum(["active", "inactive"]);
|
|
739
|
+
|
|
683
740
|
export const userSchema = z.object({
|
|
684
|
-
id: z.number().int().nullable(), // Nullable
|
|
685
|
-
name: z.string().nullable(), // Nullable
|
|
686
|
-
|
|
687
|
-
|
|
741
|
+
id: z.number().int().nullable(), // Nullable (primitive)
|
|
742
|
+
name: z.string().nullable(), // Nullable (primitive)
|
|
743
|
+
status: statusSchema, // NOT nullable ($ref - must be explicit)
|
|
744
|
+
nullableStatus: statusSchema.nullable(), // Explicitly nullable
|
|
688
745
|
});
|
|
689
746
|
```
|
|
690
747
|
|
|
691
748
|
### Schema Composition
|
|
692
749
|
|
|
693
|
-
- `allOf` → `.
|
|
750
|
+
- `allOf` → `.extend()` for objects (Zod v4), `.and()` for primitives
|
|
694
751
|
- `oneOf`, `anyOf` → `z.union()` or `z.discriminatedUnion()`
|
|
695
752
|
- `$ref` → Proper schema references
|
|
696
753
|
|
|
@@ -1140,11 +1197,11 @@ export const flexibleMetadataSchema = z
|
|
|
1140
1197
|
|
|
1141
1198
|
### Schema Composition
|
|
1142
1199
|
|
|
1143
|
-
#### AllOf - Smart
|
|
1200
|
+
#### AllOf - Smart Extending
|
|
1144
1201
|
|
|
1145
|
-
Uses `.
|
|
1202
|
+
Uses `.extend()` for objects (Zod v4 compliant - `.merge()` is deprecated), `.and()` for primitives:
|
|
1146
1203
|
|
|
1147
|
-
**Object
|
|
1204
|
+
**Object Extending:**
|
|
1148
1205
|
```yaml
|
|
1149
1206
|
User:
|
|
1150
1207
|
allOf:
|
|
@@ -1161,10 +1218,10 @@ User:
|
|
|
1161
1218
|
**Generated:**
|
|
1162
1219
|
```typescript
|
|
1163
1220
|
export const userSchema = baseEntitySchema
|
|
1164
|
-
.
|
|
1165
|
-
.
|
|
1221
|
+
.extend(timestampedSchema.shape)
|
|
1222
|
+
.extend(z.object({
|
|
1166
1223
|
username: z.string()
|
|
1167
|
-
}));
|
|
1224
|
+
}).shape);
|
|
1168
1225
|
```
|
|
1169
1226
|
|
|
1170
1227
|
#### OneOf / AnyOf
|
|
@@ -1286,7 +1343,7 @@ export const statusCodeSchema = z.enum(["200", "201", "400", "404", "500"]);
|
|
|
1286
1343
|
| const | ✅ | ✅ | `z.literal()` |
|
|
1287
1344
|
| nullable (property) | ✅ | ✅ | `.nullable()` |
|
|
1288
1345
|
| nullable (type array) | ❌ | ✅ | `.nullable()` |
|
|
1289
|
-
| allOf (objects) | ✅ | ✅ | `.
|
|
1346
|
+
| allOf (objects) | ✅ | ✅ | `.extend()` |
|
|
1290
1347
|
| allOf (primitives) | ✅ | ✅ | `.and()` |
|
|
1291
1348
|
| oneOf/anyOf | ✅ | ✅ | `z.union()` |
|
|
1292
1349
|
| discriminators | ✅ | ✅ | `z.discriminatedUnion()` |
|
package/dist/cli.js
CHANGED
|
@@ -5480,12 +5480,56 @@ function generateArrayValidation(schema, context) {
|
|
|
5480
5480
|
|
|
5481
5481
|
// src/validators/composition-validator.ts
|
|
5482
5482
|
init_cjs_shims();
|
|
5483
|
+
function isDiscriminatorRequired(schemas, discriminator, context) {
|
|
5484
|
+
const invalidSchemas = [];
|
|
5485
|
+
for (const schema of schemas) {
|
|
5486
|
+
const resolved = resolveSchema(schema, context);
|
|
5487
|
+
const required = resolved.required || [];
|
|
5488
|
+
if (!required.includes(discriminator)) {
|
|
5489
|
+
const schemaName = schema.$ref ? schema.$ref.split("/").pop() || "inline" : "inline";
|
|
5490
|
+
invalidSchemas.push(schemaName);
|
|
5491
|
+
}
|
|
5492
|
+
}
|
|
5493
|
+
return {
|
|
5494
|
+
valid: invalidSchemas.length === 0,
|
|
5495
|
+
invalidSchemas
|
|
5496
|
+
};
|
|
5497
|
+
}
|
|
5483
5498
|
function generateUnion(schemas, discriminator, isNullable2, context, options, currentSchema) {
|
|
5499
|
+
if (schemas.length === 0) {
|
|
5500
|
+
console.warn(
|
|
5501
|
+
"[openapi-to-zod] Warning: Empty oneOf/anyOf array encountered. This is likely a malformed OpenAPI spec. Generating z.never() as fallback."
|
|
5502
|
+
);
|
|
5503
|
+
return wrapNullable(
|
|
5504
|
+
'z.never().describe("Empty oneOf/anyOf in OpenAPI spec - no valid schema defined")',
|
|
5505
|
+
isNullable2
|
|
5506
|
+
);
|
|
5507
|
+
}
|
|
5508
|
+
if (schemas.length === 1) {
|
|
5509
|
+
let singleSchema = context.generatePropertySchema(schemas[0], currentSchema);
|
|
5510
|
+
if ((options == null ? void 0 : options.passthrough) && !singleSchema.includes(".catchall(")) {
|
|
5511
|
+
singleSchema = `${singleSchema}.catchall(z.unknown())`;
|
|
5512
|
+
}
|
|
5513
|
+
return wrapNullable(singleSchema, isNullable2);
|
|
5514
|
+
}
|
|
5484
5515
|
if (discriminator) {
|
|
5485
5516
|
let resolvedSchemas = schemas;
|
|
5486
5517
|
if ((options == null ? void 0 : options.discriminatorMapping) && context.resolveDiscriminatorMapping) {
|
|
5487
5518
|
resolvedSchemas = context.resolveDiscriminatorMapping(options.discriminatorMapping, schemas);
|
|
5488
5519
|
}
|
|
5520
|
+
const discriminatorCheck = isDiscriminatorRequired(resolvedSchemas, discriminator, context);
|
|
5521
|
+
if (!discriminatorCheck.valid) {
|
|
5522
|
+
console.warn(
|
|
5523
|
+
`[openapi-to-zod] Warning: Discriminator "${discriminator}" is not required in schemas: ${discriminatorCheck.invalidSchemas.join(", ")}. Falling back to z.union() instead of z.discriminatedUnion().`
|
|
5524
|
+
);
|
|
5525
|
+
let schemaStrings3 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema));
|
|
5526
|
+
if (options == null ? void 0 : options.passthrough) {
|
|
5527
|
+
schemaStrings3 = schemaStrings3.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
|
|
5528
|
+
}
|
|
5529
|
+
const fallbackDescription = `Discriminator "${discriminator}" is optional in some schemas (${discriminatorCheck.invalidSchemas.join(", ")}), using z.union() instead of z.discriminatedUnion()`;
|
|
5530
|
+
const union3 = `z.union([${schemaStrings3.join(", ")}]).describe("${fallbackDescription}")`;
|
|
5531
|
+
return wrapNullable(union3, isNullable2);
|
|
5532
|
+
}
|
|
5489
5533
|
let schemaStrings2 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema));
|
|
5490
5534
|
if (options == null ? void 0 : options.passthrough) {
|
|
5491
5535
|
schemaStrings2 = schemaStrings2.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
|
|
@@ -5500,25 +5544,102 @@ function generateUnion(schemas, discriminator, isNullable2, context, options, cu
|
|
|
5500
5544
|
const union = `z.union([${schemaStrings.join(", ")}])`;
|
|
5501
5545
|
return wrapNullable(union, isNullable2);
|
|
5502
5546
|
}
|
|
5547
|
+
function resolveSchema(schema, context) {
|
|
5548
|
+
if (schema.$ref && context.resolveSchemaRef) {
|
|
5549
|
+
const resolved = context.resolveSchemaRef(schema.$ref);
|
|
5550
|
+
if (resolved) {
|
|
5551
|
+
return resolved;
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
return schema;
|
|
5555
|
+
}
|
|
5556
|
+
function collectProperties(schema, context) {
|
|
5557
|
+
const resolved = resolveSchema(schema, context);
|
|
5558
|
+
const props = /* @__PURE__ */ new Map();
|
|
5559
|
+
const sourceName = schema.$ref ? schema.$ref.split("/").pop() || "unknown" : "inline";
|
|
5560
|
+
if (resolved.properties) {
|
|
5561
|
+
for (const [key, value] of Object.entries(resolved.properties)) {
|
|
5562
|
+
props.set(key, { schema: value, source: sourceName });
|
|
5563
|
+
}
|
|
5564
|
+
}
|
|
5565
|
+
if (resolved.allOf) {
|
|
5566
|
+
for (const subSchema of resolved.allOf) {
|
|
5567
|
+
const subProps = collectProperties(subSchema, context);
|
|
5568
|
+
for (const [key, value] of subProps) {
|
|
5569
|
+
if (!props.has(key)) {
|
|
5570
|
+
props.set(key, value);
|
|
5571
|
+
}
|
|
5572
|
+
}
|
|
5573
|
+
}
|
|
5574
|
+
}
|
|
5575
|
+
return props;
|
|
5576
|
+
}
|
|
5577
|
+
function schemasMatch(a, b) {
|
|
5578
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
5579
|
+
}
|
|
5580
|
+
function detectConflictingProperties(schemas, context) {
|
|
5581
|
+
const conflicts = [];
|
|
5582
|
+
const propertyMap = /* @__PURE__ */ new Map();
|
|
5583
|
+
for (const schema of schemas) {
|
|
5584
|
+
const schemaProps = collectProperties(schema, context);
|
|
5585
|
+
for (const [propName, propInfo] of schemaProps) {
|
|
5586
|
+
const existing = propertyMap.get(propName);
|
|
5587
|
+
if (existing) {
|
|
5588
|
+
if (!schemasMatch(existing.schema, propInfo.schema)) {
|
|
5589
|
+
conflicts.push(
|
|
5590
|
+
`Property "${propName}" has conflicting definitions in ${existing.source} and ${propInfo.source}`
|
|
5591
|
+
);
|
|
5592
|
+
}
|
|
5593
|
+
} else {
|
|
5594
|
+
propertyMap.set(propName, propInfo);
|
|
5595
|
+
}
|
|
5596
|
+
}
|
|
5597
|
+
}
|
|
5598
|
+
return conflicts;
|
|
5599
|
+
}
|
|
5503
5600
|
function generateAllOf(schemas, isNullable2, context, currentSchema) {
|
|
5504
5601
|
if (schemas.length === 1) {
|
|
5505
5602
|
const singleSchema = context.generatePropertySchema(schemas[0], currentSchema, false);
|
|
5506
5603
|
return wrapNullable(singleSchema, isNullable2);
|
|
5507
5604
|
}
|
|
5605
|
+
const conflicts = detectConflictingProperties(schemas, context);
|
|
5606
|
+
let conflictDescription = "";
|
|
5607
|
+
if (conflicts.length > 0) {
|
|
5608
|
+
for (const conflict of conflicts) {
|
|
5609
|
+
console.warn(`[openapi-to-zod] Warning: allOf composition conflict - ${conflict}`);
|
|
5610
|
+
}
|
|
5611
|
+
conflictDescription = `allOf property conflicts detected: ${conflicts.join("; ")}`;
|
|
5612
|
+
}
|
|
5508
5613
|
const allObjects = schemas.every((s) => s.type === "object" || s.properties || s.$ref || s.allOf);
|
|
5509
|
-
|
|
5614
|
+
let result;
|
|
5510
5615
|
if (allObjects) {
|
|
5511
|
-
let
|
|
5616
|
+
let merged = context.generatePropertySchema(schemas[0], currentSchema, false);
|
|
5617
|
+
for (let i = 1; i < schemas.length; i++) {
|
|
5618
|
+
const schema = schemas[i];
|
|
5619
|
+
if (schema.$ref) {
|
|
5620
|
+
const refSchema = context.generatePropertySchema(schema, currentSchema, false);
|
|
5621
|
+
merged = `${merged}.extend(${refSchema}.shape)`;
|
|
5622
|
+
} else if (context.generateInlineObjectShape && (schema.properties || schema.type === "object")) {
|
|
5623
|
+
const inlineShape = context.generateInlineObjectShape(schema, currentSchema);
|
|
5624
|
+
merged = `${merged}.extend(${inlineShape})`;
|
|
5625
|
+
} else {
|
|
5626
|
+
const schemaString = context.generatePropertySchema(schema, currentSchema, false);
|
|
5627
|
+
merged = `${merged}.extend(${schemaString}.shape)`;
|
|
5628
|
+
}
|
|
5629
|
+
}
|
|
5630
|
+
result = merged;
|
|
5631
|
+
} else {
|
|
5632
|
+
const schemaStrings = schemas.map((s) => context.generatePropertySchema(s, currentSchema, false));
|
|
5633
|
+
let merged = schemaStrings[0];
|
|
5512
5634
|
for (let i = 1; i < schemaStrings.length; i++) {
|
|
5513
|
-
|
|
5635
|
+
merged = `${merged}.and(${schemaStrings[i]})`;
|
|
5514
5636
|
}
|
|
5515
|
-
|
|
5637
|
+
result = merged;
|
|
5516
5638
|
}
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
merged = `${merged}.and(${schemaStrings[i]})`;
|
|
5639
|
+
if (conflictDescription) {
|
|
5640
|
+
result = `${result}.describe("${conflictDescription}")`;
|
|
5520
5641
|
}
|
|
5521
|
-
return wrapNullable(
|
|
5642
|
+
return wrapNullable(result, isNullable2);
|
|
5522
5643
|
}
|
|
5523
5644
|
|
|
5524
5645
|
// src/validators/number-validator.ts
|
|
@@ -6200,6 +6321,15 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
6200
6321
|
}
|
|
6201
6322
|
return mappedSchemas;
|
|
6202
6323
|
}
|
|
6324
|
+
/**
|
|
6325
|
+
* Resolve a $ref string to the actual schema
|
|
6326
|
+
*/
|
|
6327
|
+
resolveSchemaRef(ref) {
|
|
6328
|
+
var _a, _b;
|
|
6329
|
+
const schemaName = ref.split("/").pop();
|
|
6330
|
+
if (!schemaName) return void 0;
|
|
6331
|
+
return (_b = (_a = this.context.spec.components) == null ? void 0 : _a.schemas) == null ? void 0 : _b[schemaName];
|
|
6332
|
+
}
|
|
6203
6333
|
/**
|
|
6204
6334
|
* Resolve a schema name through any aliases to get the actual schema name
|
|
6205
6335
|
* If the schema is an alias (allOf with single $ref), return the target name
|
|
@@ -6277,7 +6407,7 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
6277
6407
|
let schemaWithCatchall = baseSchema;
|
|
6278
6408
|
if (baseSchema.includes(".union([") || baseSchema.includes(".discriminatedUnion(")) {
|
|
6279
6409
|
schemaWithCatchall = baseSchema;
|
|
6280
|
-
} else if (baseSchema.includes(".
|
|
6410
|
+
} else if (baseSchema.includes(".extend(")) {
|
|
6281
6411
|
schemaWithCatchall = `${baseSchema}.catchall(z.unknown())`;
|
|
6282
6412
|
}
|
|
6283
6413
|
if (schema.unevaluatedProperties === false) {
|
|
@@ -6304,7 +6434,11 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
6304
6434
|
if ((this.context.schemaType === "request" || this.context.schemaType === "response") && schema.properties) {
|
|
6305
6435
|
schema = this.filterNestedProperties(schema);
|
|
6306
6436
|
}
|
|
6307
|
-
const
|
|
6437
|
+
const isSchemaRef = !!schema.$ref;
|
|
6438
|
+
const isEnum = !!schema.enum;
|
|
6439
|
+
const isConst = schema.const !== void 0;
|
|
6440
|
+
const shouldApplyDefaultNullable = !isTopLevel && !isSchemaRef && !isEnum && !isConst;
|
|
6441
|
+
const effectiveDefaultNullable = shouldApplyDefaultNullable ? this.context.defaultNullable : false;
|
|
6308
6442
|
const nullable = isNullable(schema, effectiveDefaultNullable);
|
|
6309
6443
|
if (hasMultipleTypes(schema)) {
|
|
6310
6444
|
const union = this.generateMultiTypeUnion(schema, currentSchema);
|
|
@@ -6357,7 +6491,11 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
6357
6491
|
let composition = generateAllOf(
|
|
6358
6492
|
schema.allOf,
|
|
6359
6493
|
nullable,
|
|
6360
|
-
{
|
|
6494
|
+
{
|
|
6495
|
+
generatePropertySchema: this.generatePropertySchema.bind(this),
|
|
6496
|
+
generateInlineObjectShape: this.generateInlineObjectShape.bind(this),
|
|
6497
|
+
resolveSchemaRef: this.resolveSchemaRef.bind(this)
|
|
6498
|
+
},
|
|
6361
6499
|
currentSchema
|
|
6362
6500
|
);
|
|
6363
6501
|
if (schema.unevaluatedProperties !== void 0) {
|
|
@@ -6373,7 +6511,8 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
6373
6511
|
nullable,
|
|
6374
6512
|
{
|
|
6375
6513
|
generatePropertySchema: this.generatePropertySchema.bind(this),
|
|
6376
|
-
resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this)
|
|
6514
|
+
resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this),
|
|
6515
|
+
resolveSchemaRef: this.resolveSchemaRef.bind(this)
|
|
6377
6516
|
},
|
|
6378
6517
|
{
|
|
6379
6518
|
passthrough: needsPassthrough,
|
|
@@ -6394,7 +6533,8 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
6394
6533
|
nullable,
|
|
6395
6534
|
{
|
|
6396
6535
|
generatePropertySchema: this.generatePropertySchema.bind(this),
|
|
6397
|
-
resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this)
|
|
6536
|
+
resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this),
|
|
6537
|
+
resolveSchemaRef: this.resolveSchemaRef.bind(this)
|
|
6398
6538
|
},
|
|
6399
6539
|
{
|
|
6400
6540
|
passthrough: needsPassthrough,
|
|
@@ -6457,7 +6597,17 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
6457
6597
|
);
|
|
6458
6598
|
validation = addDescription(validation, schema.description, this.context.useDescribe);
|
|
6459
6599
|
} else {
|
|
6460
|
-
|
|
6600
|
+
switch (this.context.emptyObjectBehavior) {
|
|
6601
|
+
case "strict":
|
|
6602
|
+
validation = "z.strictObject({})";
|
|
6603
|
+
break;
|
|
6604
|
+
case "loose":
|
|
6605
|
+
validation = "z.looseObject({})";
|
|
6606
|
+
break;
|
|
6607
|
+
default:
|
|
6608
|
+
validation = "z.record(z.string(), z.unknown())";
|
|
6609
|
+
break;
|
|
6610
|
+
}
|
|
6461
6611
|
validation = addDescription(validation, schema.description, this.context.useDescribe);
|
|
6462
6612
|
}
|
|
6463
6613
|
break;
|
|
@@ -6472,6 +6622,44 @@ var _PropertyGenerator = class _PropertyGenerator {
|
|
|
6472
6622
|
}
|
|
6473
6623
|
return result;
|
|
6474
6624
|
}
|
|
6625
|
+
/**
|
|
6626
|
+
* Generate inline object shape for use with .extend()
|
|
6627
|
+
* Returns just the shape object literal: { prop1: z.string(), prop2: z.number() }
|
|
6628
|
+
*
|
|
6629
|
+
* This method is specifically for allOf compositions where we need to pass
|
|
6630
|
+
* the shape directly to .extend() instead of using z.object({...}).shape.
|
|
6631
|
+
* This avoids the .nullable().shape bug when inline objects have nullable: true.
|
|
6632
|
+
*
|
|
6633
|
+
* According to Zod docs (https://zod.dev/api?id=extend):
|
|
6634
|
+
* - .extend() accepts an object of shape definitions
|
|
6635
|
+
* - e.g., baseSchema.extend({ prop: z.string() })
|
|
6636
|
+
*/
|
|
6637
|
+
generateInlineObjectShape(schema, currentSchema) {
|
|
6638
|
+
const required = new Set(schema.required || []);
|
|
6639
|
+
const properties = [];
|
|
6640
|
+
if (schema.properties) {
|
|
6641
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
6642
|
+
if (!this.shouldIncludeProperty(propSchema)) {
|
|
6643
|
+
continue;
|
|
6644
|
+
}
|
|
6645
|
+
const isRequired = required.has(propName);
|
|
6646
|
+
const zodSchema = this.generatePropertySchema(propSchema, currentSchema);
|
|
6647
|
+
const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
6648
|
+
const quotedPropName = validIdentifier.test(propName) ? propName : `"${propName}"`;
|
|
6649
|
+
let propertyDef = `${quotedPropName}: ${zodSchema}`;
|
|
6650
|
+
if (!isRequired) {
|
|
6651
|
+
propertyDef += ".optional()";
|
|
6652
|
+
}
|
|
6653
|
+
properties.push(propertyDef);
|
|
6654
|
+
}
|
|
6655
|
+
}
|
|
6656
|
+
if (properties.length === 0) {
|
|
6657
|
+
return "{}";
|
|
6658
|
+
}
|
|
6659
|
+
return `{
|
|
6660
|
+
${properties.map((p) => ` ${p}`).join(",\n")}
|
|
6661
|
+
}`;
|
|
6662
|
+
}
|
|
6475
6663
|
};
|
|
6476
6664
|
// Performance optimization: Lookup table for faster inclusion checks
|
|
6477
6665
|
_PropertyGenerator.INCLUSION_RULES = {
|
|
@@ -6616,6 +6804,60 @@ function formatFilterStatistics(stats) {
|
|
|
6616
6804
|
return lines.join("\n");
|
|
6617
6805
|
}
|
|
6618
6806
|
|
|
6807
|
+
// src/utils/ref-resolver.ts
|
|
6808
|
+
init_cjs_shims();
|
|
6809
|
+
function resolveRef2(obj, spec, maxDepth = 10) {
|
|
6810
|
+
var _a, _b, _c, _d;
|
|
6811
|
+
if (!obj || typeof obj !== "object" || maxDepth <= 0) return obj;
|
|
6812
|
+
if (!obj.$ref) return obj;
|
|
6813
|
+
const ref = obj.$ref;
|
|
6814
|
+
let resolved = null;
|
|
6815
|
+
const paramMatch = ref.match(/^#\/components\/parameters\/(.+)$/);
|
|
6816
|
+
const requestBodyMatch = ref.match(/^#\/components\/requestBodies\/(.+)$/);
|
|
6817
|
+
const responseMatch = ref.match(/^#\/components\/responses\/(.+)$/);
|
|
6818
|
+
const schemaMatch = ref.match(/^#\/components\/schemas\/(.+)$/);
|
|
6819
|
+
if (paramMatch && ((_a = spec.components) == null ? void 0 : _a.parameters)) {
|
|
6820
|
+
const name = paramMatch[1];
|
|
6821
|
+
resolved = spec.components.parameters[name];
|
|
6822
|
+
} else if (requestBodyMatch && ((_b = spec.components) == null ? void 0 : _b.requestBodies)) {
|
|
6823
|
+
const name = requestBodyMatch[1];
|
|
6824
|
+
resolved = spec.components.requestBodies[name];
|
|
6825
|
+
} else if (responseMatch && ((_c = spec.components) == null ? void 0 : _c.responses)) {
|
|
6826
|
+
const name = responseMatch[1];
|
|
6827
|
+
resolved = spec.components.responses[name];
|
|
6828
|
+
} else if (schemaMatch && ((_d = spec.components) == null ? void 0 : _d.schemas)) {
|
|
6829
|
+
const name = schemaMatch[1];
|
|
6830
|
+
resolved = spec.components.schemas[name];
|
|
6831
|
+
}
|
|
6832
|
+
if (resolved) {
|
|
6833
|
+
if (resolved.$ref) {
|
|
6834
|
+
return resolveRef2(resolved, spec, maxDepth - 1);
|
|
6835
|
+
}
|
|
6836
|
+
return resolved;
|
|
6837
|
+
}
|
|
6838
|
+
return obj;
|
|
6839
|
+
}
|
|
6840
|
+
function resolveParameterRef(param, spec) {
|
|
6841
|
+
return resolveRef2(param, spec);
|
|
6842
|
+
}
|
|
6843
|
+
function mergeParameters(pathParams, operationParams, spec) {
|
|
6844
|
+
const resolvedPathParams = (pathParams || []).map((p) => resolveParameterRef(p, spec));
|
|
6845
|
+
const resolvedOperationParams = (operationParams || []).map((p) => resolveParameterRef(p, spec));
|
|
6846
|
+
const merged = [...resolvedPathParams];
|
|
6847
|
+
for (const opParam of resolvedOperationParams) {
|
|
6848
|
+
if (!opParam || typeof opParam !== "object") continue;
|
|
6849
|
+
const existingIndex = merged.findIndex(
|
|
6850
|
+
(p) => p && typeof p === "object" && p.name === opParam.name && p.in === opParam.in
|
|
6851
|
+
);
|
|
6852
|
+
if (existingIndex >= 0) {
|
|
6853
|
+
merged[existingIndex] = opParam;
|
|
6854
|
+
} else {
|
|
6855
|
+
merged.push(opParam);
|
|
6856
|
+
}
|
|
6857
|
+
}
|
|
6858
|
+
return merged;
|
|
6859
|
+
}
|
|
6860
|
+
|
|
6619
6861
|
// src/openapi-generator.ts
|
|
6620
6862
|
var OpenApiGenerator = class {
|
|
6621
6863
|
constructor(options) {
|
|
@@ -6625,7 +6867,7 @@ var OpenApiGenerator = class {
|
|
|
6625
6867
|
this.schemaUsageMap = /* @__PURE__ */ new Map();
|
|
6626
6868
|
this.needsZodImport = true;
|
|
6627
6869
|
this.filterStats = createFilterStatistics();
|
|
6628
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
6870
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
6629
6871
|
if (!options.input) {
|
|
6630
6872
|
throw new ConfigurationError("Input path is required", { providedOptions: options });
|
|
6631
6873
|
}
|
|
@@ -6636,18 +6878,19 @@ var OpenApiGenerator = class {
|
|
|
6636
6878
|
includeDescriptions: (_a = options.includeDescriptions) != null ? _a : true,
|
|
6637
6879
|
useDescribe: (_b = options.useDescribe) != null ? _b : false,
|
|
6638
6880
|
defaultNullable: (_c = options.defaultNullable) != null ? _c : false,
|
|
6881
|
+
emptyObjectBehavior: (_d = options.emptyObjectBehavior) != null ? _d : "loose",
|
|
6639
6882
|
schemaType: options.schemaType || "all",
|
|
6640
6883
|
prefix: options.prefix,
|
|
6641
6884
|
suffix: options.suffix,
|
|
6642
6885
|
stripSchemaPrefix: options.stripSchemaPrefix,
|
|
6643
6886
|
stripPathPrefix: options.stripPathPrefix,
|
|
6644
|
-
showStats: (
|
|
6887
|
+
showStats: (_e = options.showStats) != null ? _e : true,
|
|
6645
6888
|
request: options.request,
|
|
6646
6889
|
response: options.response,
|
|
6647
6890
|
operationFilters: options.operationFilters,
|
|
6648
6891
|
ignoreHeaders: options.ignoreHeaders,
|
|
6649
|
-
cacheSize: (
|
|
6650
|
-
batchSize: (
|
|
6892
|
+
cacheSize: (_f = options.cacheSize) != null ? _f : 1e3,
|
|
6893
|
+
batchSize: (_g = options.batchSize) != null ? _g : 10,
|
|
6651
6894
|
customDateTimeFormatRegex: options.customDateTimeFormatRegex
|
|
6652
6895
|
};
|
|
6653
6896
|
if (this.options.cacheSize) {
|
|
@@ -6718,7 +6961,8 @@ var OpenApiGenerator = class {
|
|
|
6718
6961
|
mode: this.requestOptions.mode,
|
|
6719
6962
|
includeDescriptions: this.requestOptions.includeDescriptions,
|
|
6720
6963
|
useDescribe: this.requestOptions.useDescribe,
|
|
6721
|
-
defaultNullable: (
|
|
6964
|
+
defaultNullable: (_h = this.options.defaultNullable) != null ? _h : false,
|
|
6965
|
+
emptyObjectBehavior: (_i = this.options.emptyObjectBehavior) != null ? _i : "loose",
|
|
6722
6966
|
namingOptions: {
|
|
6723
6967
|
prefix: this.options.prefix,
|
|
6724
6968
|
suffix: this.options.suffix
|
|
@@ -7072,7 +7316,7 @@ var OpenApiGenerator = class {
|
|
|
7072
7316
|
* Generate schema for a component
|
|
7073
7317
|
*/
|
|
7074
7318
|
generateComponentSchema(name, schema) {
|
|
7075
|
-
var _a, _b, _c;
|
|
7319
|
+
var _a, _b, _c, _d;
|
|
7076
7320
|
if (!this.schemaDependencies.has(name)) {
|
|
7077
7321
|
this.schemaDependencies.set(name, /* @__PURE__ */ new Set());
|
|
7078
7322
|
}
|
|
@@ -7105,6 +7349,7 @@ ${typeCode}`;
|
|
|
7105
7349
|
includeDescriptions: resolvedOptions.includeDescriptions,
|
|
7106
7350
|
useDescribe: resolvedOptions.useDescribe,
|
|
7107
7351
|
defaultNullable: (_b = this.options.defaultNullable) != null ? _b : false,
|
|
7352
|
+
emptyObjectBehavior: (_c = this.options.emptyObjectBehavior) != null ? _c : "loose",
|
|
7108
7353
|
namingOptions: {
|
|
7109
7354
|
prefix: this.options.prefix,
|
|
7110
7355
|
suffix: this.options.suffix
|
|
@@ -7121,7 +7366,7 @@ ${typeCode}`;
|
|
|
7121
7366
|
const depMatch = ref.match(/^([a-z][a-zA-Z0-9]*?)Schema$/);
|
|
7122
7367
|
if (depMatch) {
|
|
7123
7368
|
const depName = depMatch[1].charAt(0).toUpperCase() + depMatch[1].slice(1);
|
|
7124
|
-
(
|
|
7369
|
+
(_d = this.schemaDependencies.get(name)) == null ? void 0 : _d.add(depName);
|
|
7125
7370
|
}
|
|
7126
7371
|
}
|
|
7127
7372
|
}
|
|
@@ -7145,10 +7390,8 @@ ${typeCode}`;
|
|
|
7145
7390
|
if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters)) {
|
|
7146
7391
|
continue;
|
|
7147
7392
|
}
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
}
|
|
7151
|
-
const queryParams = operation.parameters.filter(
|
|
7393
|
+
const allParams = mergeParameters(pathItem.parameters, operation.parameters, this.spec);
|
|
7394
|
+
const queryParams = allParams.filter(
|
|
7152
7395
|
(param) => param && typeof param === "object" && param.in === "query"
|
|
7153
7396
|
);
|
|
7154
7397
|
if (queryParams.length === 0) {
|
|
@@ -7284,10 +7527,8 @@ ${propsCode}
|
|
|
7284
7527
|
if (!shouldIncludeOperation(operation, path, method, this.options.operationFilters)) {
|
|
7285
7528
|
continue;
|
|
7286
7529
|
}
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
}
|
|
7290
|
-
const headerParams = operation.parameters.filter(
|
|
7530
|
+
const allParams = mergeParameters(pathItem.parameters, operation.parameters, this.spec);
|
|
7531
|
+
const headerParams = allParams.filter(
|
|
7291
7532
|
(param) => param && typeof param === "object" && param.in === "header" && !this.shouldIgnoreHeader(param.name)
|
|
7292
7533
|
);
|
|
7293
7534
|
if (headerParams.length === 0) {
|
|
@@ -7377,13 +7618,23 @@ ${propsCode}
|
|
|
7377
7618
|
}
|
|
7378
7619
|
const type = schema.type;
|
|
7379
7620
|
if (type === "string") {
|
|
7621
|
+
const formatMap = {
|
|
7622
|
+
email: "z.email()",
|
|
7623
|
+
uri: "z.url()",
|
|
7624
|
+
url: "z.url()",
|
|
7625
|
+
uuid: "z.uuid()"
|
|
7626
|
+
};
|
|
7627
|
+
if (schema.format && formatMap[schema.format]) {
|
|
7628
|
+
let zodType2 = formatMap[schema.format];
|
|
7629
|
+
if (schema.minLength !== void 0) zodType2 = `${zodType2}.min(${schema.minLength})`;
|
|
7630
|
+
if (schema.maxLength !== void 0) zodType2 = `${zodType2}.max(${schema.maxLength})`;
|
|
7631
|
+
if (schema.pattern) zodType2 = `${zodType2}.regex(/${schema.pattern}/)`;
|
|
7632
|
+
return zodType2;
|
|
7633
|
+
}
|
|
7380
7634
|
let zodType = "z.string()";
|
|
7381
7635
|
if (schema.minLength !== void 0) zodType = `${zodType}.min(${schema.minLength})`;
|
|
7382
7636
|
if (schema.maxLength !== void 0) zodType = `${zodType}.max(${schema.maxLength})`;
|
|
7383
7637
|
if (schema.pattern) zodType = `${zodType}.regex(/${schema.pattern}/)`;
|
|
7384
|
-
if (schema.format === "email") zodType = `${zodType}.email()`;
|
|
7385
|
-
if (schema.format === "uri" || schema.format === "url") zodType = `${zodType}.url()`;
|
|
7386
|
-
if (schema.format === "uuid") zodType = `${zodType}.uuid()`;
|
|
7387
7638
|
return zodType;
|
|
7388
7639
|
}
|
|
7389
7640
|
if (type === "number" || type === "integer") {
|
|
@@ -7408,11 +7659,6 @@ ${propsCode}
|
|
|
7408
7659
|
}
|
|
7409
7660
|
return "z.unknown()";
|
|
7410
7661
|
}
|
|
7411
|
-
// REMOVED: generateNativeEnum method - no longer needed as we only generate Zod schemas
|
|
7412
|
-
// REMOVED: toEnumKey method - was only used by generateNativeEnum
|
|
7413
|
-
// REMOVED: addConstraintsToJSDoc method - was only used for native TypeScript types
|
|
7414
|
-
// REMOVED: generateNativeTypeDefinition method - was only used for native TypeScript types
|
|
7415
|
-
// REMOVED: generateObjectType method - was only used for native TypeScript types
|
|
7416
7662
|
/**
|
|
7417
7663
|
* Topological sort for schema dependencies
|
|
7418
7664
|
* Returns schemas in the order they should be declared
|