@cerios/openapi-to-zod 1.1.0 → 1.2.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 +209 -28
- package/dist/cli.js +245 -80
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +246 -81
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +13 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.js +209 -77
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +209 -77
- package/dist/index.mjs.map +1 -1
- package/dist/internal.d.mts +34 -19
- package/dist/internal.d.ts +34 -19
- package/dist/internal.js +106 -50
- package/dist/internal.js.map +1 -1
- package/dist/internal.mjs +104 -50
- package/dist/internal.mjs.map +1 -1
- package/dist/{types-B7ePTDjr.d.mts → types--r0d47sd.d.mts} +86 -8
- package/dist/{types-B7ePTDjr.d.ts → types--r0d47sd.d.ts} +86 -8
- package/package.json +105 -102
package/README.md
CHANGED
|
@@ -184,10 +184,11 @@ Examples:
|
|
|
184
184
|
| `mode` | `"strict"` \| `"normal"` \| `"loose"` | Validation mode |
|
|
185
185
|
| `includeDescriptions` | `boolean` | Include JSDoc comments |
|
|
186
186
|
| `useDescribe` | `boolean` | Add `.describe()` calls |
|
|
187
|
+
| `defaultNullable` | `boolean` | Treat properties as nullable by default when not explicitly specified (default: `false`) |
|
|
187
188
|
| `schemaType` | `"all"` \| `"request"` \| `"response"` | Schema filtering |
|
|
188
189
|
| `prefix` | `string` | Prefix for schema names |
|
|
189
190
|
| `suffix` | `string` | Suffix for schema names |
|
|
190
|
-
| `stripSchemaPrefix` | `string
|
|
191
|
+
| `stripSchemaPrefix` | `string` | Strip prefix from schema names before generating using glob patterns (e.g., `"Company.Models."` or `"*.Models."`) |
|
|
191
192
|
| `showStats` | `boolean` | Include generation statistics |
|
|
192
193
|
| `request` | `object` | Request-specific options (mode, includeDescriptions, useDescribe) |
|
|
193
194
|
| `response` | `object` | Response-specific options (mode, includeDescriptions, useDescribe) |
|
|
@@ -410,6 +411,68 @@ The generator supports all OpenAPI string formats with Zod v4:
|
|
|
410
411
|
| `cidrv4` | `z.cidrv4()` |
|
|
411
412
|
| `cidrv6` | `z.cidrv6()` |
|
|
412
413
|
|
|
414
|
+
### Custom Date-Time Format
|
|
415
|
+
|
|
416
|
+
By default, the generator uses `z.iso.datetime()` for `date-time` format fields, which requires an ISO 8601 datetime string with a timezone suffix (e.g., `2026-01-07T14:30:00Z`).
|
|
417
|
+
|
|
418
|
+
If your API returns date-times **without the `Z` suffix** (e.g., `2026-01-07T14:30:00`), you can override this with a custom regex pattern:
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
import { defineConfig } from '@cerios/openapi-to-zod';
|
|
422
|
+
|
|
423
|
+
export default defineConfig({
|
|
424
|
+
defaults: {
|
|
425
|
+
// For date-times without Z suffix
|
|
426
|
+
customDateTimeFormatRegex: '^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$',
|
|
427
|
+
},
|
|
428
|
+
specs: [
|
|
429
|
+
{
|
|
430
|
+
input: 'openapi.yaml',
|
|
431
|
+
output: 'src/schemas.ts',
|
|
432
|
+
},
|
|
433
|
+
],
|
|
434
|
+
});
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
**TypeScript Config - RegExp Literals:**
|
|
438
|
+
|
|
439
|
+
In TypeScript config files, you can also use RegExp literals (which don't require double-escaping):
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
export default defineConfig({
|
|
443
|
+
defaults: {
|
|
444
|
+
// Use RegExp literal (single escaping)
|
|
445
|
+
customDateTimeFormatRegex: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/,
|
|
446
|
+
},
|
|
447
|
+
specs: [
|
|
448
|
+
{
|
|
449
|
+
input: 'openapi.yaml',
|
|
450
|
+
output: 'src/schemas.ts',
|
|
451
|
+
},
|
|
452
|
+
],
|
|
453
|
+
});
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
**Common Custom Formats:**
|
|
457
|
+
|
|
458
|
+
| Use Case | String Pattern (JSON/YAML) | RegExp Literal (TypeScript) |
|
|
459
|
+
|----------|----------------------------|----------------------------|
|
|
460
|
+
| No timezone suffix<br>`2026-01-07T14:30:00` | `'^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$'` | `/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/` |
|
|
461
|
+
| With milliseconds, no Z<br>`2026-01-07T14:30:00.123` | `'^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}$'` | `/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}$/` |
|
|
462
|
+
| Optional Z suffix<br>`2026-01-07T14:30:00` or<br>`2026-01-07T14:30:00Z` | `'^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z?$'` | `/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?$/` |
|
|
463
|
+
| With milliseconds + optional Z<br>`2026-01-07T14:30:00.123Z` | `'^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z?$'` | `/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z?$/` |
|
|
464
|
+
|
|
465
|
+
**Generated Output:**
|
|
466
|
+
|
|
467
|
+
When using a custom regex, the generator will produce:
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
// Instead of: z.iso.datetime()
|
|
471
|
+
// You get: z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/)
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
**Note:** This option only affects `date-time` format fields. Other formats (like `date`, `email`, `uuid`) remain unchanged.
|
|
475
|
+
|
|
413
476
|
## Advanced Features
|
|
414
477
|
|
|
415
478
|
### Operation Filtering
|
|
@@ -563,6 +626,68 @@ export const userSchema = z.object({
|
|
|
563
626
|
|
|
564
627
|
OpenAPI's `nullable: true` is converted to `.nullable()`
|
|
565
628
|
|
|
629
|
+
#### Default Nullable Behavior
|
|
630
|
+
|
|
631
|
+
By default, properties are only nullable when explicitly marked with `nullable: true` (OpenAPI 3.0) or `type: ["string", "null"]` (OpenAPI 3.1).
|
|
632
|
+
|
|
633
|
+
However, many teams follow the industry de facto standard for OpenAPI 3.0.x where properties are assumed nullable unless explicitly constrained. You can enable this behavior with the `defaultNullable` option:
|
|
634
|
+
|
|
635
|
+
```typescript
|
|
636
|
+
export default defineConfig({
|
|
637
|
+
specs: [{
|
|
638
|
+
input: 'openapi.yaml',
|
|
639
|
+
output: 'schemas.ts',
|
|
640
|
+
defaultNullable: true, // Treat unspecified properties as nullable
|
|
641
|
+
}]
|
|
642
|
+
});
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
**Behavior comparison:**
|
|
646
|
+
|
|
647
|
+
| Schema Property | `defaultNullable: false` (default) | `defaultNullable: true` |
|
|
648
|
+
|-----------------|-------------------------------------|-------------------------|
|
|
649
|
+
| `nullable: true` | `.nullable()` | `.nullable()` |
|
|
650
|
+
| `nullable: false` | No `.nullable()` | No `.nullable()` |
|
|
651
|
+
| No `nullable` specified | No `.nullable()` | `.nullable()` |
|
|
652
|
+
|
|
653
|
+
**Example:**
|
|
654
|
+
|
|
655
|
+
```yaml
|
|
656
|
+
User:
|
|
657
|
+
type: object
|
|
658
|
+
properties:
|
|
659
|
+
id:
|
|
660
|
+
type: integer
|
|
661
|
+
name:
|
|
662
|
+
type: string
|
|
663
|
+
email:
|
|
664
|
+
type: string
|
|
665
|
+
nullable: true
|
|
666
|
+
phone:
|
|
667
|
+
type: string
|
|
668
|
+
nullable: false
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
**With `defaultNullable: false` (default):**
|
|
672
|
+
```typescript
|
|
673
|
+
export const userSchema = z.object({
|
|
674
|
+
id: z.number().int(),
|
|
675
|
+
name: z.string(), // Not nullable (no annotation)
|
|
676
|
+
email: z.string().nullable(), // Explicitly nullable
|
|
677
|
+
phone: z.string(), // Explicitly not nullable
|
|
678
|
+
});
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
**With `defaultNullable: true`:**
|
|
682
|
+
```typescript
|
|
683
|
+
export const userSchema = z.object({
|
|
684
|
+
id: z.number().int().nullable(), // Nullable by default
|
|
685
|
+
name: z.string().nullable(), // Nullable by default
|
|
686
|
+
email: z.string().nullable(), // Explicitly nullable
|
|
687
|
+
phone: z.string(), // Explicitly NOT nullable (respected)
|
|
688
|
+
});
|
|
689
|
+
```
|
|
690
|
+
|
|
566
691
|
### Schema Composition
|
|
567
692
|
|
|
568
693
|
- `allOf` → `.merge()` for objects, `.and()` for primitives
|
|
@@ -571,10 +696,46 @@ OpenAPI's `nullable: true` is converted to `.nullable()`
|
|
|
571
696
|
|
|
572
697
|
### Enums
|
|
573
698
|
|
|
574
|
-
Enums are generated
|
|
575
|
-
|
|
576
|
-
-
|
|
577
|
-
-
|
|
699
|
+
Enums are generated based on their value types:
|
|
700
|
+
|
|
701
|
+
- **String enums**: `z.enum()` for type-safe string unions
|
|
702
|
+
- **Numeric enums**: `z.union([z.literal(n), ...])` for proper number types
|
|
703
|
+
- **Boolean enums**: `z.boolean()` for true/false values
|
|
704
|
+
- **Mixed enums**: `z.union([z.literal(...), ...])` for heterogeneous values
|
|
705
|
+
|
|
706
|
+
**Examples:**
|
|
707
|
+
|
|
708
|
+
```yaml
|
|
709
|
+
# String enum
|
|
710
|
+
Status:
|
|
711
|
+
type: string
|
|
712
|
+
enum: [active, inactive, pending]
|
|
713
|
+
|
|
714
|
+
# Integer enum
|
|
715
|
+
Priority:
|
|
716
|
+
type: integer
|
|
717
|
+
enum: [0, 1, 2, 3]
|
|
718
|
+
|
|
719
|
+
# Mixed enum
|
|
720
|
+
Value:
|
|
721
|
+
enum: [0, "none", 1, "some"]
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
**Generated schemas:**
|
|
725
|
+
|
|
726
|
+
```typescript
|
|
727
|
+
// String enum → z.enum()
|
|
728
|
+
export const statusSchema = z.enum(["active", "inactive", "pending"]);
|
|
729
|
+
export type Status = z.infer<typeof statusSchema>; // "active" | "inactive" | "pending"
|
|
730
|
+
|
|
731
|
+
// Integer enum → z.union with z.literal
|
|
732
|
+
export const prioritySchema = z.union([z.literal(0), z.literal(1), z.literal(2), z.literal(3)]);
|
|
733
|
+
export type Priority = z.infer<typeof prioritySchema>; // 0 | 1 | 2 | 3
|
|
734
|
+
|
|
735
|
+
// Mixed enum → z.union with z.literal
|
|
736
|
+
export const valueSchema = z.union([z.literal(0), z.literal("none"), z.literal(1), z.literal("some")]);
|
|
737
|
+
export type Value = z.infer<typeof valueSchema>; // 0 | "none" | 1 | "some"
|
|
738
|
+
```
|
|
578
739
|
|
|
579
740
|
## Schema Naming
|
|
580
741
|
|
|
@@ -684,39 +845,39 @@ export default defineConfig({
|
|
|
684
845
|
});
|
|
685
846
|
```
|
|
686
847
|
|
|
687
|
-
####
|
|
848
|
+
#### Glob Patterns
|
|
688
849
|
|
|
689
|
-
Use
|
|
850
|
+
Use glob patterns to strip dynamic prefixes:
|
|
690
851
|
|
|
691
852
|
```typescript
|
|
692
853
|
export default defineConfig({
|
|
693
854
|
specs: [{
|
|
694
855
|
input: 'openapi.yaml',
|
|
695
856
|
output: 'schemas.ts',
|
|
696
|
-
// Strip any namespace prefix
|
|
697
|
-
stripSchemaPrefix: '
|
|
857
|
+
// Strip any namespace prefix with wildcard
|
|
858
|
+
stripSchemaPrefix: '*.Models.'
|
|
698
859
|
}]
|
|
699
860
|
});
|
|
700
861
|
```
|
|
701
862
|
|
|
702
|
-
**
|
|
863
|
+
**Glob Pattern Syntax:**
|
|
703
864
|
|
|
704
|
-
|
|
865
|
+
Glob patterns support powerful matching using [minimatch](https://github.com/isaacs/minimatch):
|
|
866
|
+
- `*` matches any characters within a single segment (stops at `.`)
|
|
867
|
+
- `**` matches any characters across multiple segments (crosses `.` boundaries)
|
|
868
|
+
- `?` matches a single character
|
|
869
|
+
- `[abc]` matches any character in the set
|
|
870
|
+
- `{a,b}` matches any of the alternatives
|
|
871
|
+
- `!(pattern)` matches anything except the pattern
|
|
705
872
|
|
|
706
873
|
```typescript
|
|
707
|
-
//
|
|
708
|
-
stripSchemaPrefix: '
|
|
709
|
-
stripSchemaPrefix: '
|
|
710
|
-
stripSchemaPrefix: '
|
|
711
|
-
|
|
712
|
-
//
|
|
713
|
-
stripSchemaPrefix: '
|
|
714
|
-
```
|
|
715
|
-
|
|
716
|
-
For TypeScript configs, you can also use `RegExp` objects:
|
|
717
|
-
|
|
718
|
-
```typescript
|
|
719
|
-
stripSchemaPrefix: /^[A-Z][a-z]+\./
|
|
874
|
+
// Examples of glob patterns:
|
|
875
|
+
stripSchemaPrefix: '*.Models.' // Matches Company.Models., App.Models.
|
|
876
|
+
stripSchemaPrefix: '**.Models.' // Matches any depth: Company.Api.Models., App.V2.Models.
|
|
877
|
+
stripSchemaPrefix: 'Company.{Models,Services}.' // Matches Company.Models. or Company.Services.
|
|
878
|
+
stripSchemaPrefix: 'api_v[0-9]_' // Matches api_v1_, api_v2_, etc.
|
|
879
|
+
stripSchemaPrefix: 'v*.*.' // Matches v1.0., v2.1., etc.
|
|
880
|
+
stripSchemaPrefix: '!(Internal)*.' // Matches any prefix except those starting with Internal
|
|
720
881
|
```
|
|
721
882
|
|
|
722
883
|
#### Common Patterns
|
|
@@ -730,24 +891,44 @@ stripSchemaPrefix: /^[A-Z][a-z]+\./
|
|
|
730
891
|
// Company.Models.Post → Post
|
|
731
892
|
```
|
|
732
893
|
|
|
733
|
-
**Pattern 2: Multiple Namespaces**
|
|
894
|
+
**Pattern 2: Multiple Namespaces with Wildcard**
|
|
734
895
|
```typescript
|
|
735
896
|
{
|
|
736
|
-
stripSchemaPrefix: '
|
|
897
|
+
stripSchemaPrefix: '*.Models.'
|
|
737
898
|
}
|
|
738
899
|
// MyApp.Models.User → User
|
|
739
900
|
// OtherApp.Models.User → User
|
|
901
|
+
// Company.Models.Post → Post
|
|
740
902
|
```
|
|
741
903
|
|
|
742
|
-
**Pattern 3:
|
|
904
|
+
**Pattern 3: Multiple Namespace Types**
|
|
743
905
|
```typescript
|
|
744
906
|
{
|
|
745
|
-
stripSchemaPrefix: '
|
|
907
|
+
stripSchemaPrefix: '*.{Models,Services}.'
|
|
908
|
+
}
|
|
909
|
+
// App.Models.User → User
|
|
910
|
+
// App.Services.UserService → UserService
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
**Pattern 4: Version Prefixes with Character Class**
|
|
914
|
+
```typescript
|
|
915
|
+
{
|
|
916
|
+
stripSchemaPrefix: 'v[0-9].'
|
|
746
917
|
}
|
|
747
918
|
// v1.User → User
|
|
748
919
|
// v2.Product → Product
|
|
749
920
|
```
|
|
750
921
|
|
|
922
|
+
**Pattern 5: Versioned Prefixes with Wildcards**
|
|
923
|
+
```typescript
|
|
924
|
+
{
|
|
925
|
+
stripSchemaPrefix: 'api_v*_'
|
|
926
|
+
}
|
|
927
|
+
// api_v1_User → User
|
|
928
|
+
// api_v2_Product → Product
|
|
929
|
+
// api_v10_Comment → Comment
|
|
930
|
+
```
|
|
931
|
+
|
|
751
932
|
#### Interaction with prefix/suffix Options
|
|
752
933
|
|
|
753
934
|
`stripSchemaPrefix` is applied **before** `prefix` and `suffix` options:
|