@cerios/openapi-to-zod 1.3.2 → 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/README.md CHANGED
@@ -44,6 +44,7 @@ npx @cerios/openapi-to-zod init
44
44
  ```
45
45
 
46
46
  This interactive command will:
47
+
47
48
  - Prompt for your OpenAPI spec path
48
49
  - Prompt for output file path
49
50
  - Ask if you want to include commonly-used defaults
@@ -59,66 +60,73 @@ The tool will auto-discover your config file and generate schemas.
59
60
 
60
61
  ### Configuration
61
62
 
63
+ > `outputTypes` is the preferred config key.
64
+ > Deprecated alias: `output` is still supported for backward compatibility.
65
+ > You must provide one of `outputTypes` or `output` per spec.
66
+ > If both are provided, they must have the same value.
67
+
62
68
  #### TypeScript Config (Recommended)
63
69
 
64
70
  **Minimal:**
71
+
65
72
  ```typescript
66
- import { defineConfig } from '@cerios/openapi-to-zod';
73
+ import { defineConfig } from "@cerios/openapi-to-zod";
67
74
 
68
75
  export default defineConfig({
69
- specs: [
70
- {
71
- input: 'openapi.yaml',
72
- output: 'src/schemas.ts',
73
- },
74
- ],
76
+ specs: [
77
+ {
78
+ input: "openapi.yaml",
79
+ outputTypes: "src/schemas.ts",
80
+ },
81
+ ],
75
82
  });
76
83
  ```
77
84
 
78
85
  **With Commonly-Used Defaults:**
86
+
79
87
  ```typescript
80
- import { defineConfig } from '@cerios/openapi-to-zod';
88
+ import { defineConfig } from "@cerios/openapi-to-zod";
81
89
 
82
90
  export default defineConfig({
83
- defaults: {
84
- mode: 'strict', // Strictest validation
85
- includeDescriptions: true, // Useful JSDoc comments
86
- showStats: false, // Cleaner output
87
- },
88
- specs: [
89
- {
90
- input: 'openapi.yaml',
91
- output: 'src/schemas.ts',
92
- },
93
- ],
91
+ defaults: {
92
+ mode: "strict", // Strictest validation
93
+ includeDescriptions: true, // Useful JSDoc comments
94
+ showStats: false, // Cleaner output
95
+ },
96
+ specs: [
97
+ {
98
+ input: "openapi.yaml",
99
+ outputTypes: "src/schemas.ts",
100
+ },
101
+ ],
94
102
  });
95
103
  ```
96
104
 
97
105
  **Multi-Spec with Custom Options:**
98
106
 
99
107
  ```typescript
100
- import { defineConfig } from '@cerios/openapi-to-zod';
108
+ import { defineConfig } from "@cerios/openapi-to-zod";
101
109
 
102
110
  export default defineConfig({
103
- defaults: {
104
- mode: 'strict',
105
- includeDescriptions: true,
106
- },
107
- specs: [
108
- {
109
- name: 'api-v1',
110
- input: 'specs/api-v1.yaml',
111
- output: 'src/schemas/v1.ts',
112
- },
113
- {
114
- name: 'api-v2',
115
- input: 'specs/api-v2.yaml',
116
- output: 'src/schemas/v2.ts',
117
- mode: 'normal', // Override default
118
- prefix: 'v2',
119
- },
120
- ],
121
- executionMode: 'parallel', // Process specs in parallel (default)
111
+ defaults: {
112
+ mode: "strict",
113
+ includeDescriptions: true,
114
+ },
115
+ specs: [
116
+ {
117
+ name: "api-v1",
118
+ input: "specs/api-v1.yaml",
119
+ outputTypes: "src/schemas/v1.ts",
120
+ },
121
+ {
122
+ name: "api-v2",
123
+ input: "specs/api-v2.yaml",
124
+ outputTypes: "src/schemas/v2.ts",
125
+ mode: "normal", // Override default
126
+ prefix: "v2",
127
+ },
128
+ ],
129
+ executionMode: "parallel", // Process specs in parallel (default)
122
130
  });
123
131
  ```
124
132
 
@@ -128,16 +136,16 @@ export default defineConfig({
128
136
 
129
137
  ```json
130
138
  {
131
- "defaults": {
132
- "mode": "strict",
133
- "includeDescriptions": true
134
- },
135
- "specs": [
136
- {
137
- "input": "openapi.yaml",
138
- "output": "src/schemas.ts"
139
- }
140
- ]
139
+ "defaults": {
140
+ "mode": "strict",
141
+ "includeDescriptions": true
142
+ },
143
+ "specs": [
144
+ {
145
+ "input": "openapi.yaml",
146
+ "outputTypes": "src/schemas.ts"
147
+ }
148
+ ]
141
149
  }
142
150
  ```
143
151
 
@@ -167,86 +175,96 @@ Examples:
167
175
 
168
176
  ### Configuration Options
169
177
 
170
-
171
- | Option | Type | Description |
172
- |--------|------|-------------|
173
- | `defaults` | `object` | Global options applied to all specs (can be overridden per-spec) |
174
- | `specs` | `array` | Array of spec configurations (required, minimum 1) |
175
- | `executionMode` | `"parallel"` \| `"sequential"` | How to process specs (default: `"parallel"`) |
178
+ | Option | Type | Description |
179
+ | --------------- | ------------------------------ | ---------------------------------------------------------------- |
180
+ | `defaults` | `object` | Global options applied to all specs (can be overridden per-spec) |
181
+ | `specs` | `array` | Array of spec configurations (required, minimum 1) |
182
+ | `executionMode` | `"parallel"` \| `"sequential"` | How to process specs (default: `"parallel"`) |
176
183
 
177
184
  **Per-Spec Options:**
178
185
 
179
- | Spec Option | Type | Description |
180
- |-------------|------|-------------|
181
- | `name` | `string` | Optional identifier for logging |
182
- | `input` | `string` | Input OpenAPI YAML file path (required) |
183
- | `output` | `string` | Output TypeScript file path (required) |
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"`) |
186
- | `includeDescriptions` | `boolean` | Include JSDoc comments |
187
- | `useDescribe` | `boolean` | Add `.describe()` calls |
188
- | `defaultNullable` | `boolean` | Treat properties as nullable by default when not explicitly specified (default: `false`) |
189
- | `schemaType` | `"all"` \| `"request"` \| `"response"` | Schema filtering |
190
- | `prefix` | `string` | Prefix for schema names |
191
- | `suffix` | `string` | Suffix for schema names |
192
- | `stripSchemaPrefix` | `string` | Strip prefix from schema names before generating using glob patterns (e.g., `"Company.Models."` or `"*.Models."`) |
193
- | `showStats` | `boolean` | Include generation statistics |
194
- | `request` | `object` | Request-specific options (mode, includeDescriptions, useDescribe) |
195
- | `response` | `object` | Response-specific options (mode, includeDescriptions, useDescribe) |
196
- | `operationFilters` | `object` | Filter operations by tags, paths, methods, etc. (see below) |
186
+ | Spec Option | Type | Description |
187
+ | --------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
188
+ | `name` | `string` | Optional identifier for logging |
189
+ | `input` | `string` | Input OpenAPI YAML file path (required) |
190
+ | `outputTypes` | `string` | Preferred output TypeScript file path (required unless deprecated `output` is set) |
191
+ | `output` | `string` | Deprecated alias for `outputTypes`; allowed for backward compatibility |
192
+ | `mode` | `"strict"` \| `"normal"` \| `"loose"` | Validation mode for top-level schemas (default: `"normal"`) |
193
+ | `emptyObjectBehavior` | `"strict"` \| `"loose"` \| `"record"` | How to handle empty objects (default: `"loose"`) |
194
+ | `includeDescriptions` | `boolean` | Include JSDoc comments |
195
+ | `useDescribe` | `boolean` | Add `.describe()` calls |
196
+ | `defaultNullable` | `boolean` | Treat properties as nullable by default when not explicitly specified (default: `false`) |
197
+ | `schemaType` | `"all"` \| `"request"` \| `"response"` | Schema filtering |
198
+ | `prefix` | `string` | Prefix for schema names |
199
+ | `suffix` | `string` | Suffix for schema names |
200
+ | `stripSchemaPrefix` | `string` | Strip prefix from schema names before generating using glob patterns (e.g., `"Company.Models."` or `"*.Models."`) |
201
+ | `useOperationId` | `boolean` | Use operationId for operation-derived query/header schema names when available (default: `true`) |
202
+ | `showStats` | `boolean` | Include generation statistics |
203
+ | `request` | `object` | Request-specific options (mode, includeDescriptions, useDescribe) |
204
+ | `response` | `object` | Response-specific options (mode, includeDescriptions, useDescribe) |
205
+ | `operationFilters` | `object` | Filter operations by tags, paths, methods, etc. (see below) |
206
+
207
+ If `outputTypes` and `output` are both set with different values, configuration validation fails.
197
208
 
198
209
  #### Operation Filters
199
210
 
200
211
  Filter which operations to include/exclude during schema generation. Useful for generating separate schemas for different API subsets.
201
212
 
202
- | Filter | Type | Description |
203
- |--------|------|-------------|
204
- | `includeTags` | `string[]` | Include only operations with these tags |
205
- | `excludeTags` | `string[]` | Exclude operations with these tags |
206
- | `includePaths` | `string[]` | Include only these paths (supports glob patterns like `/users/**`) |
207
- | `excludePaths` | `string[]` | Exclude these paths (supports glob patterns) |
208
- | `includeMethods` | `string[]` | Include only these HTTP methods (`get`, `post`, etc.) |
209
- | `excludeMethods` | `string[]` | Exclude these HTTP methods |
210
- | `includeOperationIds` | `string[]` | Include only these operationIds (supports glob patterns) |
211
- | `excludeOperationIds` | `string[]` | Exclude these operationIds (supports glob patterns) |
212
- | `excludeDeprecated` | `boolean` | Exclude deprecated operations |
213
+ | Filter | Type | Description |
214
+ | --------------------- | ---------- | ------------------------------------------------------------------ |
215
+ | `includeTags` | `string[]` | Include only operations with these tags |
216
+ | `excludeTags` | `string[]` | Exclude operations with these tags |
217
+ | `includePaths` | `string[]` | Include only these paths (supports glob patterns like `/users/**`) |
218
+ | `excludePaths` | `string[]` | Exclude these paths (supports glob patterns) |
219
+ | `includeMethods` | `string[]` | Include only these HTTP methods (`get`, `post`, etc.) |
220
+ | `excludeMethods` | `string[]` | Exclude these HTTP methods |
221
+ | `includeOperationIds` | `string[]` | Include only these operationIds (supports glob patterns) |
222
+ | `excludeOperationIds` | `string[]` | Exclude these operationIds (supports glob patterns) |
223
+ | `excludeDeprecated` | `boolean` | Exclude deprecated operations |
213
224
 
214
225
  **Example:**
226
+
215
227
  ```typescript
216
228
  export default defineConfig({
217
- specs: [{
218
- input: 'openapi.yaml',
219
- output: 'schemas.ts',
220
- operationFilters: {
221
- includeTags: ['public'], // Only public endpoints
222
- excludeDeprecated: true, // Skip deprecated operations
223
- excludePaths: ['/internal/**'] // Exclude internal paths
224
- }
225
- }]
229
+ specs: [
230
+ {
231
+ input: "openapi.yaml",
232
+ outputTypes: "schemas.ts",
233
+ operationFilters: {
234
+ includeTags: ["public"], // Only public endpoints
235
+ excludeDeprecated: true, // Skip deprecated operations
236
+ excludePaths: ["/internal/**"], // Exclude internal paths
237
+ },
238
+ },
239
+ ],
226
240
  });
227
241
  ```
228
242
 
229
243
  ### Batch Execution
230
244
 
231
245
  **Parallel Mode** (default):
246
+
232
247
  - Processes all specs concurrently
233
248
  - Faster for multiple specs
234
249
  - Recommended for most use cases
235
250
  - Live progress shows all specs processing simultaneously
236
251
 
237
252
  **Sequential Mode**:
253
+
238
254
  - Processes specs one at a time
239
255
  - Useful for resource-constrained environments
240
256
  - Easier to debug issues
241
257
  - Live progress shows specs processing in order
242
258
 
243
259
  Both modes:
260
+
244
261
  - Continue processing even if some specs fail
245
262
  - Collect all errors and report at the end
246
263
  - Exit with code 1 if any spec fails
247
264
  - Show live progress updates to stderr
248
265
 
249
266
  Example output:
267
+
250
268
  ```
251
269
  Executing 3 spec(s) in parallel...
252
270
 
@@ -273,45 +291,54 @@ Failed specs:
273
291
  ## Programmatic Usage
274
292
 
275
293
  ```typescript
276
- import { generateZodSchemas } from '@cerios/openapi-to-zod';
294
+ import { OpenApiGenerator } from "@cerios/openapi-to-zod";
277
295
 
278
- generateZodSchemas({
279
- input: 'path/to/openapi.yaml',
280
- output: 'path/to/schemas.ts',
281
- mode: 'normal', // 'strict' | 'normal' | 'loose'
282
- includeDescriptions: true,
296
+ const generator = new OpenApiGenerator({
297
+ input: "path/to/openapi.yaml",
298
+ outputTypes: "path/to/schemas.ts",
299
+ mode: "normal", // 'strict' | 'normal' | 'loose'
300
+ includeDescriptions: true,
283
301
  });
302
+
303
+ // Generate and write to file
304
+ generator.generate();
305
+
306
+ // Or generate as string
307
+ const code = generator.generateString();
284
308
  ```
285
309
 
286
310
  ## Validation Modes
287
311
 
288
312
  ### Normal Mode (default)
313
+
289
314
  Uses `z.object()` which allows additional properties:
290
315
 
291
316
  ```typescript
292
317
  const userSchema = z.object({
293
- id: z.uuid(),
294
- name: z.string(),
318
+ id: z.uuid(),
319
+ name: z.string(),
295
320
  });
296
321
  ```
297
322
 
298
323
  ### Strict Mode
324
+
299
325
  Uses `z.strictObject()` which rejects additional properties:
300
326
 
301
327
  ```typescript
302
328
  const userSchema = z.strictObject({
303
- id: z.uuid(),
304
- name: z.string(),
329
+ id: z.uuid(),
330
+ name: z.string(),
305
331
  });
306
332
  ```
307
333
 
308
334
  ### Loose Mode
335
+
309
336
  Uses `z.looseObject()` which explicitly allows additional properties:
310
337
 
311
338
  ```typescript
312
339
  const userSchema = z.looseObject({
313
- id: z.uuid(),
314
- name: z.string(),
340
+ id: z.uuid(),
341
+ name: z.string(),
315
342
  });
316
343
  ```
317
344
 
@@ -320,6 +347,7 @@ const userSchema = z.looseObject({
320
347
  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
348
 
322
349
  ### Loose (default)
350
+
323
351
  Uses `z.looseObject({})` which allows any additional properties:
324
352
 
325
353
  ```typescript
@@ -330,6 +358,7 @@ const metadataSchema = z.looseObject({});
330
358
  ```
331
359
 
332
360
  ### Strict
361
+
333
362
  Uses `z.strictObject({})` which rejects any properties:
334
363
 
335
364
  ```typescript
@@ -341,6 +370,7 @@ const emptySchema = z.strictObject({});
341
370
  ```
342
371
 
343
372
  ### Record
373
+
344
374
  Uses `z.record(z.string(), z.unknown())` which treats it as an arbitrary key-value map:
345
375
 
346
376
  ```typescript
@@ -390,7 +420,7 @@ components:
390
420
  minimum: 0
391
421
  maximum: 150
392
422
  status:
393
- $ref: '#/components/schemas/UserStatusEnumOptions'
423
+ $ref: "#/components/schemas/UserStatusEnumOptions"
394
424
  ```
395
425
 
396
426
  ### Generated Output
@@ -403,20 +433,20 @@ import { z } from "zod";
403
433
 
404
434
  // Enums
405
435
  export enum UserStatusEnum {
406
- Active = "active",
407
- Inactive = "inactive",
408
- Pending = "pending",
436
+ Active = "active",
437
+ Inactive = "inactive",
438
+ Pending = "pending",
409
439
  }
410
440
 
411
441
  // Schemas
412
442
  export const userStatusEnumOptionsSchema = z.enum(UserStatusEnum);
413
443
 
414
444
  export const userSchema = z.object({
415
- id: z.uuid().min(36).max(36),
416
- email: z.email().max(255),
417
- name: z.string().min(1).max(100).optional(),
418
- age: z.number().int().gte(0).lte(150).optional(),
419
- status: userStatusEnumOptionsSchema.optional(),
445
+ id: z.uuid().min(36).max(36),
446
+ email: z.email().max(255),
447
+ name: z.string().min(1).max(100).optional(),
448
+ age: z.number().int().gte(0).lte(150).optional(),
449
+ status: userStatusEnumOptionsSchema.optional(),
420
450
  });
421
451
 
422
452
  // Types
@@ -428,26 +458,26 @@ export type User = z.infer<typeof userSchema>;
428
458
 
429
459
  The generator supports all OpenAPI string formats with Zod v4:
430
460
 
431
- | OpenAPI Format | Zod v4 Function |
432
- |----------------|-----------------|
433
- | `uuid` | `z.uuid()` |
434
- | `email` | `z.email()` |
435
- | `url`, `uri` | `z.url()` |
436
- | `date` | `z.iso.date()` |
437
- | `date-time` | `z.iso.datetime()` |
438
- | `time` | `z.iso.time()` |
439
- | `duration` | `z.iso.duration()` |
440
- | `ipv4` | `z.ipv4()` |
441
- | `ipv6` | `z.ipv6()` |
442
- | `emoji` | `z.emoji()` |
443
- | `base64` | `z.base64()` |
444
- | `base64url` | `z.base64url()` |
445
- | `nanoid` | `z.nanoid()` |
446
- | `cuid` | `z.cuid()` |
447
- | `cuid2` | `z.cuid2()` |
448
- | `ulid` | `z.ulid()` |
449
- | `cidrv4` | `z.cidrv4()` |
450
- | `cidrv6` | `z.cidrv6()` |
461
+ | OpenAPI Format | Zod v4 Function |
462
+ | -------------- | ------------------ |
463
+ | `uuid` | `z.uuid()` |
464
+ | `email` | `z.email()` |
465
+ | `url`, `uri` | `z.url()` |
466
+ | `date` | `z.iso.date()` |
467
+ | `date-time` | `z.iso.datetime()` |
468
+ | `time` | `z.iso.time()` |
469
+ | `duration` | `z.iso.duration()` |
470
+ | `ipv4` | `z.ipv4()` |
471
+ | `ipv6` | `z.ipv6()` |
472
+ | `emoji` | `z.emoji()` |
473
+ | `base64` | `z.base64()` |
474
+ | `base64url` | `z.base64url()` |
475
+ | `nanoid` | `z.nanoid()` |
476
+ | `cuid` | `z.cuid()` |
477
+ | `cuid2` | `z.cuid2()` |
478
+ | `ulid` | `z.ulid()` |
479
+ | `cidrv4` | `z.cidrv4()` |
480
+ | `cidrv6` | `z.cidrv6()` |
451
481
 
452
482
  ### Custom Date-Time Format
453
483
 
@@ -456,19 +486,19 @@ By default, the generator uses `z.iso.datetime()` for `date-time` format fields,
456
486
  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:
457
487
 
458
488
  ```typescript
459
- import { defineConfig } from '@cerios/openapi-to-zod';
489
+ import { defineConfig } from "@cerios/openapi-to-zod";
460
490
 
461
491
  export default defineConfig({
462
- defaults: {
463
- // For date-times without Z suffix
464
- customDateTimeFormatRegex: '^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$',
465
- },
466
- specs: [
467
- {
468
- input: 'openapi.yaml',
469
- output: 'src/schemas.ts',
470
- },
471
- ],
492
+ defaults: {
493
+ // For date-times without Z suffix
494
+ customDateTimeFormatRegex: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$",
495
+ },
496
+ specs: [
497
+ {
498
+ input: "openapi.yaml",
499
+ outputTypes: "src/schemas.ts",
500
+ },
501
+ ],
472
502
  });
473
503
  ```
474
504
 
@@ -478,27 +508,27 @@ In TypeScript config files, you can also use RegExp literals (which don't requir
478
508
 
479
509
  ```typescript
480
510
  export default defineConfig({
481
- defaults: {
482
- // Use RegExp literal (single escaping)
483
- customDateTimeFormatRegex: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/,
484
- },
485
- specs: [
486
- {
487
- input: 'openapi.yaml',
488
- output: 'src/schemas.ts',
489
- },
490
- ],
511
+ defaults: {
512
+ // Use RegExp literal (single escaping)
513
+ customDateTimeFormatRegex: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/,
514
+ },
515
+ specs: [
516
+ {
517
+ input: "openapi.yaml",
518
+ outputTypes: "src/schemas.ts",
519
+ },
520
+ ],
491
521
  });
492
522
  ```
493
523
 
494
524
  **Common Custom Formats:**
495
525
 
496
- | Use Case | String Pattern (JSON/YAML) | RegExp Literal (TypeScript) |
497
- |----------|----------------------------|----------------------------|
498
- | 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}$/` |
499
- | 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}$/` |
500
- | 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?$/` |
501
- | 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?$/` |
526
+ | Use Case | String Pattern (JSON/YAML) | RegExp Literal (TypeScript) |
527
+ | ----------------------------------------------------------------------- | ---------------------------------------------------------- | -------------------------------------------------- |
528
+ | 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}$/` |
529
+ | 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}$/` |
530
+ | 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?$/` |
531
+ | 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?$/` |
502
532
 
503
533
  **Generated Output:**
504
534
 
@@ -518,46 +548,56 @@ When using a custom regex, the generator will produce:
518
548
  Filter which operations are included in schema generation. This is useful when you want to generate schemas for only a subset of your API.
519
549
 
520
550
  **Example 1: Filter by tags**
551
+
521
552
  ```typescript
522
553
  export default defineConfig({
523
- specs: [{
524
- input: 'openapi.yaml',
525
- output: 'public-schemas.ts',
526
- operationFilters: {
527
- includeTags: ['public', 'users'] // Only include operations tagged with 'public' or 'users'
528
- }
529
- }]
554
+ specs: [
555
+ {
556
+ input: "openapi.yaml",
557
+ outputTypes: "public-schemas.ts",
558
+ operationFilters: {
559
+ includeTags: ["public", "users"], // Only include operations tagged with 'public' or 'users'
560
+ },
561
+ },
562
+ ],
530
563
  });
531
564
  ```
532
565
 
533
566
  **Example 2: Filter by paths**
567
+
534
568
  ```typescript
535
569
  export default defineConfig({
536
- specs: [{
537
- input: 'openapi.yaml',
538
- output: 'v1-schemas.ts',
539
- operationFilters: {
540
- includePaths: ['/api/v1/**'], // Only v1 endpoints
541
- excludePaths: ['/api/v1/admin/**'] // But exclude admin endpoints
542
- }
543
- }]
570
+ specs: [
571
+ {
572
+ input: "openapi.yaml",
573
+ outputTypes: "v1-schemas.ts",
574
+ operationFilters: {
575
+ includePaths: ["/api/v1/**"], // Only v1 endpoints
576
+ excludePaths: ["/api/v1/admin/**"], // But exclude admin endpoints
577
+ },
578
+ },
579
+ ],
544
580
  });
545
581
  ```
546
582
 
547
583
  **Example 3: Exclude deprecated operations**
584
+
548
585
  ```typescript
549
586
  export default defineConfig({
550
- specs: [{
551
- input: 'openapi.yaml',
552
- output: 'current-schemas.ts',
553
- operationFilters: {
554
- excludeDeprecated: true // Skip all deprecated operations
555
- }
556
- }]
587
+ specs: [
588
+ {
589
+ input: "openapi.yaml",
590
+ outputTypes: "current-schemas.ts",
591
+ operationFilters: {
592
+ excludeDeprecated: true, // Skip all deprecated operations
593
+ },
594
+ },
595
+ ],
557
596
  });
558
597
  ```
559
598
 
560
599
  **Filtering Logic:**
600
+
561
601
  1. If no filters specified, all operations are included
562
602
  2. Empty arrays are treated as "no constraint"
563
603
  3. Include filters are applied first (allowlist)
@@ -571,80 +611,92 @@ export default defineConfig({
571
611
  Generate separate schemas for requests and responses by filtering `readOnly` and `writeOnly` properties.
572
612
 
573
613
  **Example: Request schemas (exclude readOnly)**
614
+
574
615
  ```typescript
575
616
  export default defineConfig({
576
- specs: [{
577
- input: 'openapi.yaml',
578
- output: 'request-schemas.ts',
579
- schemaType: 'request' // Excludes readOnly properties like 'id', 'createdAt'
580
- }]
617
+ specs: [
618
+ {
619
+ input: "openapi.yaml",
620
+ outputTypes: "request-schemas.ts",
621
+ schemaType: "request", // Excludes readOnly properties like 'id', 'createdAt'
622
+ },
623
+ ],
581
624
  });
582
625
  ```
583
626
 
584
627
  **Example: Response schemas (exclude writeOnly)**
628
+
585
629
  ```typescript
586
630
  export default defineConfig({
587
- specs: [{
588
- input: 'openapi.yaml',
589
- output: 'response-schemas.ts',
590
- schemaType: 'response' // Excludes writeOnly properties like 'password'
591
- }]
631
+ specs: [
632
+ {
633
+ input: "openapi.yaml",
634
+ outputTypes: "response-schemas.ts",
635
+ schemaType: "response", // Excludes writeOnly properties like 'password'
636
+ },
637
+ ],
592
638
  });
593
639
  ```
594
640
 
595
641
  **Example: Context-specific validation**
642
+
596
643
  ```typescript
597
644
  export default defineConfig({
598
- specs: [{
599
- input: 'openapi.yaml',
600
- output: 'schemas.ts',
601
- request: {
602
- mode: 'strict', // Strict validation for incoming data
603
- includeDescriptions: false
604
- },
605
- response: {
606
- mode: 'loose', // Flexible validation for API responses
607
- includeDescriptions: true
608
- }
609
- }]
645
+ specs: [
646
+ {
647
+ input: "openapi.yaml",
648
+ outputTypes: "schemas.ts",
649
+ request: {
650
+ mode: "strict", // Strict validation for incoming data
651
+ includeDescriptions: false,
652
+ },
653
+ response: {
654
+ mode: "loose", // Flexible validation for API responses
655
+ includeDescriptions: true,
656
+ },
657
+ },
658
+ ],
610
659
  });
611
660
  ```
612
661
 
613
662
  **OpenAPI Spec:**
663
+
614
664
  ```yaml
615
665
  User:
616
666
  type: object
617
667
  properties:
618
668
  id:
619
669
  type: string
620
- readOnly: true # Excluded in 'request' mode
670
+ readOnly: true # Excluded in 'request' mode
621
671
  email:
622
672
  type: string
623
673
  password:
624
674
  type: string
625
- writeOnly: true # Excluded in 'response' mode
675
+ writeOnly: true # Excluded in 'response' mode
626
676
  createdAt:
627
677
  type: string
628
678
  format: date-time
629
- readOnly: true # Excluded in 'request' mode
679
+ readOnly: true # Excluded in 'request' mode
630
680
  ```
631
681
 
632
682
  **Generated Request Schema** (`schemaType: 'request'`):
683
+
633
684
  ```typescript
634
685
  export const userSchema = z.object({
635
- email: z.string(),
636
- password: z.string(), // writeOnly included
637
- // id and createdAt excluded (readOnly)
686
+ email: z.string(),
687
+ password: z.string(), // writeOnly included
688
+ // id and createdAt excluded (readOnly)
638
689
  });
639
690
  ```
640
691
 
641
692
  **Generated Response Schema** (`schemaType: 'response'`):
693
+
642
694
  ```typescript
643
695
  export const userSchema = z.object({
644
- id: z.string(), // readOnly included
645
- email: z.string(),
646
- createdAt: z.string().datetime(), // readOnly included
647
- // password excluded (writeOnly)
696
+ id: z.string(), // readOnly included
697
+ email: z.string(),
698
+ createdAt: z.string().datetime(), // readOnly included
699
+ // password excluded (writeOnly)
648
700
  });
649
701
  ```
650
702
 
@@ -672,11 +724,13 @@ However, many teams follow the industry de facto standard for OpenAPI 3.0.x wher
672
724
 
673
725
  ```typescript
674
726
  export default defineConfig({
675
- specs: [{
676
- input: 'openapi.yaml',
677
- output: 'schemas.ts',
678
- defaultNullable: true, // Treat unspecified properties as nullable
679
- }]
727
+ specs: [
728
+ {
729
+ input: "openapi.yaml",
730
+ outputTypes: "schemas.ts",
731
+ defaultNullable: true, // Treat unspecified properties as nullable
732
+ },
733
+ ],
680
734
  });
681
735
  ```
682
736
 
@@ -689,14 +743,14 @@ export default defineConfig({
689
743
 
690
744
  **Behavior comparison:**
691
745
 
692
- | Schema Property | `defaultNullable: false` (default) | `defaultNullable: true` |
693
- |-----------------|-------------------------------------|-------------------------|
694
- | `nullable: true` | `.nullable()` | `.nullable()` |
695
- | `nullable: false` | No `.nullable()` | No `.nullable()` |
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()` |
746
+ | Schema Property | `defaultNullable: false` (default) | `defaultNullable: true` |
747
+ | ------------------------- | ---------------------------------- | ----------------------- |
748
+ | `nullable: true` | `.nullable()` | `.nullable()` |
749
+ | `nullable: false` | No `.nullable()` | No `.nullable()` |
750
+ | No annotation (primitive) | No `.nullable()` | `.nullable()` |
751
+ | No annotation (`$ref`) | No `.nullable()` | No `.nullable()` |
752
+ | No annotation (enum) | No `.nullable()` | No `.nullable()` |
753
+ | No annotation (const) | No `.nullable()` | No `.nullable()` |
700
754
 
701
755
  **Example:**
702
756
 
@@ -714,34 +768,36 @@ components:
714
768
  name:
715
769
  type: string
716
770
  status:
717
- $ref: '#/components/schemas/Status'
771
+ $ref: "#/components/schemas/Status"
718
772
  nullableStatus:
719
773
  allOf:
720
- - $ref: '#/components/schemas/Status'
774
+ - $ref: "#/components/schemas/Status"
721
775
  nullable: true
722
776
  ```
723
777
 
724
778
  **With `defaultNullable: false` (default):**
779
+
725
780
  ```typescript
726
781
  export const statusSchema = z.enum(["active", "inactive"]);
727
782
 
728
783
  export const userSchema = z.object({
729
- id: z.number().int(),
730
- name: z.string(), // Not nullable (no annotation)
731
- status: statusSchema, // Not nullable ($ref)
732
- nullableStatus: statusSchema.nullable(), // Explicitly nullable
784
+ id: z.number().int(),
785
+ name: z.string(), // Not nullable (no annotation)
786
+ status: statusSchema, // Not nullable ($ref)
787
+ nullableStatus: statusSchema.nullable(), // Explicitly nullable
733
788
  });
734
789
  ```
735
790
 
736
791
  **With `defaultNullable: true`:**
792
+
737
793
  ```typescript
738
794
  export const statusSchema = z.enum(["active", "inactive"]);
739
795
 
740
796
  export const userSchema = z.object({
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
797
+ id: z.number().int().nullable(), // Nullable (primitive)
798
+ name: z.string().nullable(), // Nullable (primitive)
799
+ status: statusSchema, // NOT nullable ($ref - must be explicit)
800
+ nullableStatus: statusSchema.nullable(), // Explicitly nullable
745
801
  });
746
802
  ```
747
803
 
@@ -801,18 +857,19 @@ Customize schema names with prefixes and suffixes:
801
857
  ```typescript
802
858
  // In your config file
803
859
  export default defineConfig({
804
- specs: [
805
- {
806
- input: 'openapi.yaml',
807
- output: 'schemas.ts',
808
- prefix: 'api', // Output: apiUserSchema, apiProductSchema
809
- suffix: 'dto', // Output: userDtoSchema, productDtoSchema
810
- },
811
- ],
860
+ specs: [
861
+ {
862
+ input: "openapi.yaml",
863
+ outputTypes: "schemas.ts",
864
+ prefix: "api", // Output: apiUserSchema, apiProductSchema
865
+ suffix: "dto", // Output: userDtoSchema, productDtoSchema
866
+ },
867
+ ],
812
868
  });
813
869
  ```
814
870
 
815
871
  This is useful when:
872
+
816
873
  - Working with multiple API specs in the same project
817
874
  - Following specific naming conventions (DTO, Model, Entity)
818
875
  - Avoiding naming conflicts with existing code
@@ -822,6 +879,7 @@ This is useful when:
822
879
  The `stripSchemaPrefix` option removes common prefixes from schema names in your OpenAPI spec before generating Zod schemas. This is particularly useful when your OpenAPI spec uses namespaced schema names (like .NET-generated specs with "Company.Models.User").
823
880
 
824
881
  **OpenAPI Spec with Namespaced Schemas:**
882
+
825
883
  ```yaml
826
884
  components:
827
885
  schemas:
@@ -833,7 +891,7 @@ components:
833
891
  name:
834
892
  type: string
835
893
  role:
836
- $ref: '#/components/schemas/Company.Models.UserRole'
894
+ $ref: "#/components/schemas/Company.Models.UserRole"
837
895
  Company.Models.UserRole:
838
896
  type: string
839
897
  enum: [admin, user, guest]
@@ -845,23 +903,24 @@ components:
845
903
  title:
846
904
  type: string
847
905
  author:
848
- $ref: '#/components/schemas/Company.Models.User'
906
+ $ref: "#/components/schemas/Company.Models.User"
849
907
  ```
850
908
 
851
909
  **Without `stripSchemaPrefix`:**
910
+
852
911
  ```typescript
853
912
  export const companyModelsUserRoleSchema = z.enum(["admin", "user", "guest"]);
854
913
 
855
914
  export const companyModelsUserSchema = z.object({
856
- id: z.string(),
857
- name: z.string(),
858
- role: companyModelsUserRoleSchema // Long reference name
915
+ id: z.string(),
916
+ name: z.string(),
917
+ role: companyModelsUserRoleSchema, // Long reference name
859
918
  });
860
919
 
861
920
  export const companyModelsPostSchema = z.object({
862
- id: z.string(),
863
- title: z.string(),
864
- author: companyModelsUserSchema // Long reference name
921
+ id: z.string(),
922
+ title: z.string(),
923
+ author: companyModelsUserSchema, // Long reference name
865
924
  });
866
925
 
867
926
  export type CompanyModelsUserRole = z.infer<typeof companyModelsUserRoleSchema>;
@@ -870,19 +929,20 @@ export type CompanyModelsPost = z.infer<typeof companyModelsPostSchema>;
870
929
  ```
871
930
 
872
931
  **With `stripSchemaPrefix: "Company.Models."`:**
932
+
873
933
  ```typescript
874
934
  export const userRoleSchema = z.enum(["admin", "user", "guest"]);
875
935
 
876
936
  export const userSchema = z.object({
877
- id: z.string(),
878
- name: z.string(),
879
- role: userRoleSchema // Clean reference
937
+ id: z.string(),
938
+ name: z.string(),
939
+ role: userRoleSchema, // Clean reference
880
940
  });
881
941
 
882
942
  export const postSchema = z.object({
883
- id: z.string(),
884
- title: z.string(),
885
- author: userSchema // Clean reference
943
+ id: z.string(),
944
+ title: z.string(),
945
+ author: userSchema, // Clean reference
886
946
  });
887
947
 
888
948
  export type UserRole = z.infer<typeof userRoleSchema>;
@@ -894,11 +954,13 @@ export type Post = z.infer<typeof postSchema>;
894
954
 
895
955
  ```typescript
896
956
  export default defineConfig({
897
- specs: [{
898
- input: 'openapi.yaml',
899
- output: 'schemas.ts',
900
- stripSchemaPrefix: 'Company.Models.' // Strip this exact prefix
901
- }]
957
+ specs: [
958
+ {
959
+ input: "openapi.yaml",
960
+ outputTypes: "schemas.ts",
961
+ stripSchemaPrefix: "Company.Models.", // Strip this exact prefix
962
+ },
963
+ ],
902
964
  });
903
965
  ```
904
966
 
@@ -908,18 +970,21 @@ Use glob patterns to strip dynamic prefixes:
908
970
 
909
971
  ```typescript
910
972
  export default defineConfig({
911
- specs: [{
912
- input: 'openapi.yaml',
913
- output: 'schemas.ts',
914
- // Strip any namespace prefix with wildcard
915
- stripSchemaPrefix: '*.Models.'
916
- }]
973
+ specs: [
974
+ {
975
+ input: "openapi.yaml",
976
+ outputTypes: "schemas.ts",
977
+ // Strip any namespace prefix with wildcard
978
+ stripSchemaPrefix: "*.Models.",
979
+ },
980
+ ],
917
981
  });
918
982
  ```
919
983
 
920
984
  **Glob Pattern Syntax:**
921
985
 
922
986
  Glob patterns support powerful matching using [minimatch](https://github.com/isaacs/minimatch):
987
+
923
988
  - `*` matches any characters within a single segment (stops at `.`)
924
989
  - `**` matches any characters across multiple segments (crosses `.` boundaries)
925
990
  - `?` matches a single character
@@ -929,29 +994,31 @@ Glob patterns support powerful matching using [minimatch](https://github.com/isa
929
994
 
930
995
  ```typescript
931
996
  // Examples of glob patterns:
932
- stripSchemaPrefix: '*.Models.' // Matches Company.Models., App.Models.
933
- stripSchemaPrefix: '**.Models.' // Matches any depth: Company.Api.Models., App.V2.Models.
934
- stripSchemaPrefix: 'Company.{Models,Services}.' // Matches Company.Models. or Company.Services.
935
- stripSchemaPrefix: 'api_v[0-9]_' // Matches api_v1_, api_v2_, etc.
936
- stripSchemaPrefix: 'v*.*.' // Matches v1.0., v2.1., etc.
937
- stripSchemaPrefix: '!(Internal)*.' // Matches any prefix except those starting with Internal
997
+ stripSchemaPrefix: "*.Models."; // Matches Company.Models., App.Models.
998
+ stripSchemaPrefix: "**.Models."; // Matches any depth: Company.Api.Models., App.V2.Models.
999
+ stripSchemaPrefix: "Company.{Models,Services}."; // Matches Company.Models. or Company.Services.
1000
+ stripSchemaPrefix: "api_v[0-9]_"; // Matches api_v1_, api_v2_, etc.
1001
+ stripSchemaPrefix: "v*.*."; // Matches v1.0., v2.1., etc.
1002
+ stripSchemaPrefix: "!(Internal)*."; // Matches any prefix except those starting with Internal
938
1003
  ```
939
1004
 
940
1005
  #### Common Patterns
941
1006
 
942
1007
  **Pattern 1: .NET Namespaces**
1008
+
943
1009
  ```typescript
944
1010
  {
945
- stripSchemaPrefix: 'Company.Models.'
1011
+ stripSchemaPrefix: "Company.Models.";
946
1012
  }
947
1013
  // Company.Models.User → User
948
1014
  // Company.Models.Post → Post
949
1015
  ```
950
1016
 
951
1017
  **Pattern 2: Multiple Namespaces with Wildcard**
1018
+
952
1019
  ```typescript
953
1020
  {
954
- stripSchemaPrefix: '*.Models.'
1021
+ stripSchemaPrefix: "*.Models.";
955
1022
  }
956
1023
  // MyApp.Models.User → User
957
1024
  // OtherApp.Models.User → User
@@ -959,27 +1026,30 @@ stripSchemaPrefix: '!(Internal)*.' // Matches any prefix except
959
1026
  ```
960
1027
 
961
1028
  **Pattern 3: Multiple Namespace Types**
1029
+
962
1030
  ```typescript
963
1031
  {
964
- stripSchemaPrefix: '*.{Models,Services}.'
1032
+ stripSchemaPrefix: "*.{Models,Services}.";
965
1033
  }
966
1034
  // App.Models.User → User
967
1035
  // App.Services.UserService → UserService
968
1036
  ```
969
1037
 
970
1038
  **Pattern 4: Version Prefixes with Character Class**
1039
+
971
1040
  ```typescript
972
1041
  {
973
- stripSchemaPrefix: 'v[0-9].'
1042
+ stripSchemaPrefix: "v[0-9].";
974
1043
  }
975
1044
  // v1.User → User
976
1045
  // v2.Product → Product
977
1046
  ```
978
1047
 
979
1048
  **Pattern 5: Versioned Prefixes with Wildcards**
1049
+
980
1050
  ```typescript
981
1051
  {
982
- stripSchemaPrefix: 'api_v*_'
1052
+ stripSchemaPrefix: "api_v*_";
983
1053
  }
984
1054
  // api_v1_User → User
985
1055
  // api_v2_Product → Product
@@ -992,17 +1062,20 @@ stripSchemaPrefix: '!(Internal)*.' // Matches any prefix except
992
1062
 
993
1063
  ```typescript
994
1064
  export default defineConfig({
995
- specs: [{
996
- input: 'openapi.yaml',
997
- output: 'schemas.ts',
998
- stripSchemaPrefix: 'Company.Models.', // Applied first
999
- prefix: 'api', // Applied second
1000
- suffix: 'dto' // Applied third
1001
- }]
1065
+ specs: [
1066
+ {
1067
+ input: "openapi.yaml",
1068
+ outputTypes: "schemas.ts",
1069
+ stripSchemaPrefix: "Company.Models.", // Applied first
1070
+ prefix: "api", // Applied second
1071
+ suffix: "dto", // Applied third
1072
+ },
1073
+ ],
1002
1074
  });
1003
1075
  ```
1004
1076
 
1005
1077
  **Result:**
1078
+
1006
1079
  - `Company.Models.User` → `User` → `apiUserDtoSchema`
1007
1080
  - `Company.Models.Post` → `Post` → `apiPostDtoSchema`
1008
1081
 
@@ -1028,6 +1101,7 @@ Statistics are **included by default** in generated files. Use `showStats: false
1028
1101
  ```
1029
1102
 
1030
1103
  Helpful for:
1104
+
1031
1105
  - Understanding your API complexity
1032
1106
  - Tracking changes over time
1033
1107
  - Debugging generation issues
@@ -1037,12 +1111,14 @@ Helpful for:
1037
1111
  ### Basic Types
1038
1112
 
1039
1113
  #### String Constraints
1114
+
1040
1115
  - `minLength` → `.min(n)`
1041
1116
  - `maxLength` → `.max(n)`
1042
1117
  - `pattern` → `.regex(/pattern/)`
1043
1118
  - `format` → Specific Zod validators (see Format Support section)
1044
1119
 
1045
1120
  #### Number Constraints
1121
+
1046
1122
  - `minimum` → `.gte(n)` (inclusive)
1047
1123
  - `maximum` → `.lte(n)` (inclusive)
1048
1124
  - `exclusiveMinimum` → `.gt(n)` (OpenAPI 3.0 boolean or 3.1 number)
@@ -1051,15 +1127,17 @@ Helpful for:
1051
1127
  - `integer` type → `.int()`
1052
1128
 
1053
1129
  **Example:**
1130
+
1054
1131
  ```yaml
1055
1132
  Price:
1056
1133
  type: number
1057
1134
  minimum: 0
1058
1135
  maximum: 10000
1059
- multipleOf: 0.01 # Enforces 2 decimal places
1136
+ multipleOf: 0.01 # Enforces 2 decimal places
1060
1137
  ```
1061
1138
 
1062
1139
  **Generated:**
1140
+
1063
1141
  ```typescript
1064
1142
  export const priceSchema = z.number().gte(0).lte(10000).multipleOf(0.01);
1065
1143
  ```
@@ -1067,6 +1145,7 @@ export const priceSchema = z.number().gte(0).lte(10000).multipleOf(0.01);
1067
1145
  #### Exclusive Bounds (OpenAPI 3.0 & 3.1)
1068
1146
 
1069
1147
  **OpenAPI 3.0 Style (boolean):**
1148
+
1070
1149
  ```yaml
1071
1150
  Percentage:
1072
1151
  type: number
@@ -1077,6 +1156,7 @@ Percentage:
1077
1156
  ```
1078
1157
 
1079
1158
  **OpenAPI 3.1 Style (number):**
1159
+
1080
1160
  ```yaml
1081
1161
  Score:
1082
1162
  type: number
@@ -1085,6 +1165,7 @@ Score:
1085
1165
  ```
1086
1166
 
1087
1167
  **Both generate:**
1168
+
1088
1169
  ```typescript
1089
1170
  export const percentageSchema = z.number().gt(0).lt(100);
1090
1171
  ```
@@ -1092,11 +1173,13 @@ export const percentageSchema = z.number().gt(0).lt(100);
1092
1173
  ### Array Features
1093
1174
 
1094
1175
  #### Basic Array Constraints
1176
+
1095
1177
  - `minItems` → `.min(n)`
1096
1178
  - `maxItems` → `.max(n)`
1097
1179
  - `uniqueItems: true` → `.refine()` with Set-based validation
1098
1180
 
1099
1181
  **Example:**
1182
+
1100
1183
  ```yaml
1101
1184
  UniqueTags:
1102
1185
  type: array
@@ -1108,14 +1191,15 @@ UniqueTags:
1108
1191
  ```
1109
1192
 
1110
1193
  **Generated:**
1194
+
1111
1195
  ```typescript
1112
1196
  export const uniqueTagsSchema = z
1113
- .array(z.string())
1114
- .min(1)
1115
- .max(10)
1116
- .refine((items) => new Set(items).size === items.length, {
1117
- message: "Array items must be unique"
1118
- });
1197
+ .array(z.string())
1198
+ .min(1)
1199
+ .max(10)
1200
+ .refine(items => new Set(items).size === items.length, {
1201
+ message: "Array items must be unique",
1202
+ });
1119
1203
  ```
1120
1204
 
1121
1205
  #### Tuple Validation (OpenAPI 3.1)
@@ -1140,34 +1224,33 @@ Coordinates:
1140
1224
  ```
1141
1225
 
1142
1226
  **Generated:**
1227
+
1143
1228
  ```typescript
1144
- export const coordinatesSchema = z.tuple([
1145
- z.number().gte(-90).lte(90),
1146
- z.number().gte(-180).lte(180)
1147
- ]);
1229
+ export const coordinatesSchema = z.tuple([z.number().gte(-90).lte(90), z.number().gte(-180).lte(180)]);
1148
1230
  ```
1149
1231
 
1150
1232
  **With Rest Items:**
1233
+
1151
1234
  ```yaml
1152
1235
  CommandArgs:
1153
1236
  type: array
1154
1237
  prefixItems:
1155
- - type: string # Command name
1156
- - type: string # Action
1238
+ - type: string # Command name
1239
+ - type: string # Action
1157
1240
  items:
1158
- type: string # Additional arguments
1241
+ type: string # Additional arguments
1159
1242
  ```
1160
1243
 
1161
1244
  **Generated:**
1245
+
1162
1246
  ```typescript
1163
- export const commandArgsSchema = z
1164
- .tuple([z.string(), z.string()])
1165
- .rest(z.string());
1247
+ export const commandArgsSchema = z.tuple([z.string(), z.string()]).rest(z.string());
1166
1248
  ```
1167
1249
 
1168
1250
  ### Object Features
1169
1251
 
1170
1252
  #### Property Constraints
1253
+
1171
1254
  - `required` array → Properties without `.optional()`
1172
1255
  - `additionalProperties: false` → `.strict()` (or implicit in strict mode)
1173
1256
  - `additionalProperties: true` → `.catchall(z.unknown())`
@@ -1176,6 +1259,7 @@ export const commandArgsSchema = z
1176
1259
  - `maxProperties` → `.refine()` with property count validation
1177
1260
 
1178
1261
  **Example:**
1262
+
1179
1263
  ```yaml
1180
1264
  FlexibleMetadata:
1181
1265
  type: object
@@ -1186,13 +1270,14 @@ FlexibleMetadata:
1186
1270
  ```
1187
1271
 
1188
1272
  **Generated:**
1273
+
1189
1274
  ```typescript
1190
1275
  export const flexibleMetadataSchema = z
1191
- .object({})
1192
- .catchall(z.string())
1193
- .refine((obj) => Object.keys(obj).length >= 1 && Object.keys(obj).length <= 10, {
1194
- message: "Object must have between 1 and 10 properties"
1195
- });
1276
+ .object({})
1277
+ .catchall(z.string())
1278
+ .refine(obj => Object.keys(obj).length >= 1 && Object.keys(obj).length <= 10, {
1279
+ message: "Object must have between 1 and 10 properties",
1280
+ });
1196
1281
  ```
1197
1282
 
1198
1283
  ### Schema Composition
@@ -1202,6 +1287,7 @@ export const flexibleMetadataSchema = z
1202
1287
  Uses `.extend()` for objects (Zod v4 compliant - `.merge()` is deprecated), `.and()` for primitives:
1203
1288
 
1204
1289
  **Object Extending:**
1290
+
1205
1291
  ```yaml
1206
1292
  User:
1207
1293
  allOf:
@@ -1216,12 +1302,13 @@ User:
1216
1302
  ```
1217
1303
 
1218
1304
  **Generated:**
1305
+
1219
1306
  ```typescript
1220
- export const userSchema = baseEntitySchema
1221
- .extend(timestampedSchema.shape)
1222
- .extend(z.object({
1223
- username: z.string()
1224
- }).shape);
1307
+ export const userSchema = baseEntitySchema.extend(timestampedSchema.shape).extend(
1308
+ z.object({
1309
+ username: z.string(),
1310
+ }).shape
1311
+ );
1225
1312
  ```
1226
1313
 
1227
1314
  #### OneOf / AnyOf
@@ -1232,6 +1319,7 @@ export const userSchema = baseEntitySchema
1232
1319
  ### Nullable Types
1233
1320
 
1234
1321
  **OpenAPI 3.0 Style:**
1322
+
1235
1323
  ```yaml
1236
1324
  NullableString:
1237
1325
  type: string
@@ -1239,12 +1327,14 @@ NullableString:
1239
1327
  ```
1240
1328
 
1241
1329
  **OpenAPI 3.1 Style:**
1330
+
1242
1331
  ```yaml
1243
1332
  NullableString:
1244
1333
  type: ["string", "null"]
1245
1334
  ```
1246
1335
 
1247
1336
  **Both generate:**
1337
+
1248
1338
  ```typescript
1249
1339
  export const nullableStringSchema = z.string().nullable();
1250
1340
  ```
@@ -1260,6 +1350,7 @@ Environment:
1260
1350
  ```
1261
1351
 
1262
1352
  **Generated:**
1353
+
1263
1354
  ```typescript
1264
1355
  export const environmentSchema = z.literal("production");
1265
1356
  ```
@@ -1281,11 +1372,12 @@ OldUser:
1281
1372
  ```
1282
1373
 
1283
1374
  **Generated:**
1375
+
1284
1376
  ```typescript
1285
1377
  /** Legacy user schema @deprecated */
1286
1378
  export const oldUserSchema = z.object({
1287
- /** Old ID format, use uuid instead @deprecated */
1288
- legacyId: z.number().int().optional()
1379
+ /** Old ID format, use uuid instead @deprecated */
1380
+ legacyId: z.number().int().optional(),
1289
1381
  });
1290
1382
  ```
1291
1383
 
@@ -1301,9 +1393,12 @@ UserAccount:
1301
1393
  ```
1302
1394
 
1303
1395
  **Generated:**
1396
+
1304
1397
  ```typescript
1305
1398
  /** User Account Represents a user account in the system */
1306
- export const userAccountSchema = z.object({ /* ... */ });
1399
+ export const userAccountSchema = z.object({
1400
+ /* ... */
1401
+ });
1307
1402
  ```
1308
1403
 
1309
1404
  #### Examples
@@ -1320,6 +1415,7 @@ StatusCode:
1320
1415
  ```
1321
1416
 
1322
1417
  **Generated:**
1418
+
1323
1419
  ```typescript
1324
1420
  /** HTTP Status Code @example "200", "404", "500" */
1325
1421
  export const statusCodeSchema = z.enum(["200", "201", "400", "404", "500"]);
@@ -1327,49 +1423,52 @@ export const statusCodeSchema = z.enum(["200", "201", "400", "404", "500"]);
1327
1423
 
1328
1424
  ### Feature Matrix
1329
1425
 
1330
- | Feature | OpenAPI 3.0 | OpenAPI 3.1 | Zod Method |
1331
- |---------|-------------|-------------|------------|
1332
- | Basic types | ✅ | ✅ | `z.string()`, `z.number()`, etc. |
1333
- | String constraints | ✅ | ✅ | `.min()`, `.max()`, `.regex()` |
1334
- | Number constraints | ✅ | ✅ | `.gte()`, `.lte()`, `.int()` |
1335
- | Exclusive bounds (boolean) | ✅ | ✅ | `.gt()`, `.lt()` |
1336
- | Exclusive bounds (number) | ❌ | ✅ | `.gt()`, `.lt()` |
1337
- | multipleOf | ✅ | ✅ | `.multipleOf()` |
1338
- | Array constraints | ✅ | ✅ | `.min()`, `.max()` |
1339
- | uniqueItems | ✅ | ✅ | `.refine()` with Set |
1340
- | prefixItems (tuples) | ❌ | ✅ | `z.tuple()` |
1341
- | additionalProperties | ✅ | ✅ | `.strict()`, `.catchall()` |
1342
- | minProperties/maxProperties | ✅ | ✅ | `.refine()` |
1343
- | const | ✅ | ✅ | `z.literal()` |
1344
- | nullable (property) | ✅ | ✅ | `.nullable()` |
1345
- | nullable (type array) | ❌ | ✅ | `.nullable()` |
1346
- | allOf (objects) | ✅ | ✅ | `.extend()` |
1347
- | allOf (primitives) | ✅ | ✅ | `.and()` |
1348
- | oneOf/anyOf | ✅ | ✅ | `z.union()` |
1349
- | discriminators | ✅ | ✅ | `z.discriminatedUnion()` |
1350
- | deprecated | ✅ | ✅ | JSDoc `@deprecated` |
1351
- | title | ✅ | ✅ | JSDoc comment |
1352
- | examples | ✅ | ✅ | JSDoc `@example` |
1353
- | format | ✅ | ✅ | Specific Zod validators |
1354
- | readOnly/writeOnly | ✅ | ✅ | Schema filtering |
1426
+ | Feature | OpenAPI 3.0 | OpenAPI 3.1 | Zod Method |
1427
+ | --------------------------- | ----------- | ----------- | -------------------------------- |
1428
+ | Basic types | ✅ | ✅ | `z.string()`, `z.number()`, etc. |
1429
+ | String constraints | ✅ | ✅ | `.min()`, `.max()`, `.regex()` |
1430
+ | Number constraints | ✅ | ✅ | `.gte()`, `.lte()`, `.int()` |
1431
+ | Exclusive bounds (boolean) | ✅ | ✅ | `.gt()`, `.lt()` |
1432
+ | Exclusive bounds (number) | ❌ | ✅ | `.gt()`, `.lt()` |
1433
+ | multipleOf | ✅ | ✅ | `.multipleOf()` |
1434
+ | Array constraints | ✅ | ✅ | `.min()`, `.max()` |
1435
+ | uniqueItems | ✅ | ✅ | `.refine()` with Set |
1436
+ | prefixItems (tuples) | ❌ | ✅ | `z.tuple()` |
1437
+ | additionalProperties | ✅ | ✅ | `.strict()`, `.catchall()` |
1438
+ | minProperties/maxProperties | ✅ | ✅ | `.refine()` |
1439
+ | const | ✅ | ✅ | `z.literal()` |
1440
+ | nullable (property) | ✅ | ✅ | `.nullable()` |
1441
+ | nullable (type array) | ❌ | ✅ | `.nullable()` |
1442
+ | allOf (objects) | ✅ | ✅ | `.extend()` |
1443
+ | allOf (primitives) | ✅ | ✅ | `.and()` |
1444
+ | oneOf/anyOf | ✅ | ✅ | `z.union()` |
1445
+ | discriminators | ✅ | ✅ | `z.discriminatedUnion()` |
1446
+ | deprecated | ✅ | ✅ | JSDoc `@deprecated` |
1447
+ | title | ✅ | ✅ | JSDoc comment |
1448
+ | examples | ✅ | ✅ | JSDoc `@example` |
1449
+ | format | ✅ | ✅ | Specific Zod validators |
1450
+ | readOnly/writeOnly | ✅ | ✅ | Schema filtering |
1355
1451
 
1356
1452
  ## Error Messages
1357
1453
 
1358
1454
  The generator provides clear, actionable error messages:
1359
1455
 
1360
1456
  ### Invalid References
1457
+
1361
1458
  ```
1362
1459
  Error: Invalid schema 'User': Invalid reference at 'profile':
1363
1460
  '#/components/schemas/NonExistentProfile' points to non-existent schema 'NonExistentProfile'
1364
1461
  ```
1365
1462
 
1366
1463
  ### YAML Syntax Errors
1464
+
1367
1465
  ```
1368
1466
  Error: Failed to parse OpenAPI YAML file at openapi.yaml:
1369
1467
  Implicit keys need to be on a single line at line 12, column 9
1370
1468
  ```
1371
1469
 
1372
1470
  All errors include:
1471
+
1373
1472
  - File path
1374
1473
  - Line and column numbers (when available)
1375
1474
  - Clear description of the problem
@@ -1384,11 +1483,11 @@ Starting from **v0.7.0**, this package exports several utilities that can be use
1384
1483
  A Least Recently Used (LRU) cache implementation for efficient caching.
1385
1484
 
1386
1485
  ```typescript
1387
- import { LRUCache } from '@cerios/openapi-to-zod';
1486
+ import { LRUCache } from "@cerios/openapi-to-zod";
1388
1487
 
1389
1488
  const cache = new LRUCache<string, ParsedSpec>(50);
1390
- cache.set('spec-key', parsedSpec);
1391
- const spec = cache.get('spec-key');
1489
+ cache.set("spec-key", parsedSpec);
1490
+ const spec = cache.get("spec-key");
1392
1491
  ```
1393
1492
 
1394
1493
  ### `toPascalCase(str: string | number): string`
@@ -1396,10 +1495,10 @@ const spec = cache.get('spec-key');
1396
1495
  Converts strings to PascalCase, handling kebab-case, snake_case, and special characters.
1397
1496
 
1398
1497
  ```typescript
1399
- import { toPascalCase } from '@cerios/openapi-to-zod';
1498
+ import { toPascalCase } from "@cerios/openapi-to-zod";
1400
1499
 
1401
- toPascalCase('my-api-client'); // => 'MyApiClient'
1402
- toPascalCase('user_name'); // => 'UserName'
1500
+ toPascalCase("my-api-client"); // => 'MyApiClient'
1501
+ toPascalCase("user_name"); // => 'UserName'
1403
1502
  ```
1404
1503
 
1405
1504
  ### `escapeJSDoc(str: string): string`
@@ -1407,9 +1506,9 @@ toPascalCase('user_name'); // => 'UserName'
1407
1506
  Escapes JSDoc comment terminators to prevent injection.
1408
1507
 
1409
1508
  ```typescript
1410
- import { escapeJSDoc } from '@cerios/openapi-to-zod';
1509
+ import { escapeJSDoc } from "@cerios/openapi-to-zod";
1411
1510
 
1412
- escapeJSDoc('Comment with */ terminator'); // => 'Comment with *\\/ terminator'
1511
+ escapeJSDoc("Comment with */ terminator"); // => 'Comment with *\\/ terminator'
1413
1512
  ```
1414
1513
 
1415
1514
  ### `executeBatch<T>()` and `Generator` Interface
@@ -1417,18 +1516,18 @@ escapeJSDoc('Comment with */ terminator'); // => 'Comment with *\\/ terminator'
1417
1516
  Execute batch processing with custom generators.
1418
1517
 
1419
1518
  ```typescript
1420
- import { executeBatch, type Generator } from '@cerios/openapi-to-zod';
1519
+ import { executeBatch, type Generator } from "@cerios/openapi-to-zod";
1421
1520
 
1422
1521
  class MyGenerator implements Generator {
1423
- generate(): void {
1424
- // Your generation logic
1425
- }
1522
+ generate(): void {
1523
+ // Your generation logic
1524
+ }
1426
1525
  }
1427
1526
 
1428
1527
  await executeBatch(
1429
- specs,
1430
- 'sequential', // or 'parallel'
1431
- spec => new MyGenerator(spec)
1528
+ specs,
1529
+ "sequential", // or 'parallel'
1530
+ spec => new MyGenerator(spec)
1432
1531
  );
1433
1532
  ```
1434
1533
 
@@ -1438,61 +1537,113 @@ Shared utilities for configuration file validation:
1438
1537
 
1439
1538
  ```typescript
1440
1539
  import {
1441
- createTypeScriptLoader,
1442
- formatConfigValidationError,
1443
- type RequestResponseOptions,
1444
- type BaseOperationFilters
1445
- } from '@cerios/openapi-to-zod';
1540
+ createTypeScriptLoader,
1541
+ formatConfigValidationError,
1542
+ type RequestResponseOptions,
1543
+ type BaseOperationFilters,
1544
+ } from "@cerios/openapi-to-zod";
1446
1545
 
1447
1546
  // Create TypeScript config loader for cosmiconfig
1448
1547
  const loader = createTypeScriptLoader();
1449
1548
 
1450
1549
  // Format Zod validation errors
1451
- const errorMessage = formatConfigValidationError(
1452
- zodError,
1453
- filePath,
1454
- configPath,
1455
- ['Additional note 1', 'Additional note 2']
1456
- );
1550
+ const errorMessage = formatConfigValidationError(zodError, filePath, configPath, [
1551
+ "Additional note 1",
1552
+ "Additional note 2",
1553
+ ]);
1457
1554
  ```
1458
1555
 
1459
1556
  These utilities are marked with `@shared` tags in the source code and are covered by comprehensive tests.
1460
1557
 
1461
1558
  ## API Reference
1462
1559
 
1463
- ### `generateZodSchemas(options: OpenApiGeneratorOptions): void`
1560
+ ### `OpenApiGenerator`
1561
+
1562
+ Main class for generating Zod schemas from OpenAPI specifications.
1563
+
1564
+ ```typescript
1565
+ import { OpenApiGenerator } from "@cerios/openapi-to-zod";
1566
+
1567
+ const generator = new OpenApiGenerator(options);
1568
+
1569
+ // Generate and write to file
1570
+ generator.generate();
1464
1571
 
1465
- Main function to generate schemas.
1572
+ // Or generate as string
1573
+ const code = generator.generateString();
1574
+ ```
1466
1575
 
1467
1576
  #### Options
1468
1577
 
1469
1578
  ```typescript
1470
1579
  interface OpenApiGeneratorOptions {
1471
- /**
1472
- * Object validation mode
1473
- * - 'strict': Uses z.strictObject() - no additional properties allowed
1474
- * - 'normal': Uses z.object() - additional properties allowed
1475
- * - 'loose': Uses z.looseObject() - explicitly allows additional properties
1476
- */
1477
- mode?: 'strict' | 'normal' | 'loose';
1478
-
1479
- /**
1480
- * Input OpenAPI YAML file path
1481
- */
1482
- input: string;
1483
-
1484
- /**
1485
- * Output TypeScript file path
1486
- */
1487
- output: string;
1488
-
1489
- /**
1490
- * Whether to include descriptions as JSDoc comments
1491
- */
1492
- includeDescriptions?: boolean;
1580
+ /**
1581
+ * Input OpenAPI YAML/JSON file path
1582
+ */
1583
+ input: string;
1584
+
1585
+ /**
1586
+ * Output TypeScript file path
1587
+ */
1588
+ outputTypes: string;
1589
+
1590
+ /**
1591
+ * Object validation mode
1592
+ * - 'strict': Uses z.strictObject() - no additional properties allowed
1593
+ * - 'normal': Uses z.object() - additional properties allowed
1594
+ * - 'loose': Uses z.looseObject() - explicitly allows additional properties
1595
+ */
1596
+ mode?: "strict" | "normal" | "loose";
1597
+
1598
+ /**
1599
+ * Whether to include descriptions as JSDoc comments
1600
+ */
1601
+ includeDescriptions?: boolean;
1602
+
1603
+ /**
1604
+ * Add custom prefix to schema names
1605
+ */
1606
+ prefix?: string;
1607
+
1608
+ /**
1609
+ * Add custom suffix to schema names
1610
+ */
1611
+ suffix?: string;
1612
+
1613
+ /**
1614
+ * Strip prefix from schema names using glob patterns
1615
+ */
1616
+ stripSchemaPrefix?: string | string[];
1617
+
1618
+ /**
1619
+ * Show generation statistics in output
1620
+ */
1621
+ showStats?: boolean;
1622
+
1623
+ /**
1624
+ * Schema filtering mode
1625
+ */
1626
+ schemaType?: "all" | "request" | "response";
1627
+
1628
+ /**
1629
+ * Operation filters for including/excluding operations
1630
+ */
1631
+ operationFilters?: OperationFilters;
1493
1632
  }
1494
1633
  ```
1495
1634
 
1635
+ ### `defineConfig`
1636
+
1637
+ Type-safe helper for creating configuration files.
1638
+
1639
+ ```typescript
1640
+ import { defineConfig } from "@cerios/openapi-to-zod";
1641
+
1642
+ export default defineConfig({
1643
+ specs: [{ input: "api.yaml", outputTypes: "schemas.ts" }],
1644
+ });
1645
+ ```
1646
+
1496
1647
  ## Requirements
1497
1648
 
1498
1649
  - Node.js >= 16
@@ -1527,4 +1678,4 @@ Contributions are welcome! Please feel free to submit a Pull Request.
1527
1678
 
1528
1679
  ## Support
1529
1680
 
1530
- For issues and questions, please use the [GitHub issues](https://github.com/CeriosTesting/openapi-to-zod/issues) page.
1681
+ For issues and questions, please use the [GitHub issues](https://github.com/CeriosTesting/openapi-codegen/issues) page.