zod-codegen 1.0.2 → 1.1.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/PERFORMANCE.md ADDED
@@ -0,0 +1,59 @@
1
+ # Build Performance
2
+
3
+ ## Current Build Time
4
+
5
+ - **Standard TypeScript (`tsc`)**: ~1.2 seconds
6
+ - **Project size**: 19 TypeScript files, ~184KB
7
+
8
+ ## TypeScript Native Preview (TSGO)
9
+
10
+ For even faster builds, you can optionally use the TypeScript Native Preview (TSGO), a Go-based compiler that can be up to 10x faster.
11
+
12
+ ### Installation
13
+
14
+ ```bash
15
+ npm install -D @typescript/native-preview
16
+ ```
17
+
18
+ ### Usage
19
+
20
+ ```bash
21
+ # Use native compiler for builds
22
+ npm run build:native
23
+
24
+ # Or use directly
25
+ npx tsgo --project tsconfig.json
26
+ ```
27
+
28
+ ### Notes
29
+
30
+ - **Status**: Still in preview/experimental phase
31
+ - **Compatibility**: May not support all TypeScript features yet
32
+ - **Best for**: Large codebases where build time is a bottleneck
33
+ - **Current project**: Build is already fast (~1.2s), so the benefit is minimal
34
+
35
+ ### When to Use TSGO
36
+
37
+ ✅ **Use TSGO if:**
38
+
39
+ - You have a large codebase (>100 files)
40
+ - Build time is >5 seconds
41
+ - You're willing to test experimental features
42
+
43
+ ❌ **Stick with `tsc` if:**
44
+
45
+ - Your build is already fast (<2 seconds)
46
+ - You need 100% feature compatibility
47
+ - You're in production
48
+
49
+ ### Benchmarking
50
+
51
+ To compare performance:
52
+
53
+ ```bash
54
+ # Standard TypeScript
55
+ time npm run build
56
+
57
+ # Native TypeScript
58
+ time npm run build:native
59
+ ```
package/README.md CHANGED
@@ -10,13 +10,18 @@ A powerful TypeScript code generator that creates **Zod schemas** and **type-saf
10
10
 
11
11
  ## 🚀 Features
12
12
 
13
- - **🔥 Zod Schema Generation**: Automatically generate Zod validation schemas from OpenAPI specs
14
- - **🎯 Type-Safe**: Full TypeScript support with generated types
15
- - **📡 Multiple Formats**: Support for OpenAPI 3.x, Swagger 2.0, JSON, and YAML
16
- - **🌐 Remote Files**: Fetch OpenAPI specs from URLs
13
+ - **🔥 Zod Schema Generation**: Automatically generate Zod validation schemas from OpenAPI component schemas
14
+ - **🎯 Type-Safe Client**: Generate a fully type-safe API client class with methods for each endpoint
15
+ - **📡 Multiple Formats**: Support for OpenAPI 3.x specifications in JSON and YAML formats
16
+ - **🌐 Remote Files**: Fetch OpenAPI specs from URLs using native fetch API
17
17
  - **⚡ Fast**: Optimized for performance with minimal dependencies
18
- - **🔧 Configurable**: Flexible output options and customization
19
- - **📦 CLI & Programmatic**: Use as a CLI tool or integrate into your build process
18
+ - **🔧 Advanced Schema Support**: Handles logical operators (anyOf, oneOf, allOf, not), enums, discriminators, and complex nested schemas
19
+ - **📦 Single File Output**: Generates all schemas and client in one convenient TypeScript file
20
+ - **🛡️ Runtime Validation**: Built-in Zod validation for request/response data
21
+ - **🌍 Form Support**: Supports both JSON and form-urlencoded request bodies
22
+ - **🔐 Extensible**: Override `getBaseRequestOptions()` to add authentication, custom headers, CORS, and other fetch options
23
+ - **🌐 Server Configuration**: Full support for OpenAPI server variables and templating (e.g., `{environment}.example.com`)
24
+ - **⚙️ Flexible Client Options**: Options-based constructor supporting server selection, variable overrides, and custom base URLs
20
25
 
21
26
  ## 📦 Installation
22
27
 
@@ -61,35 +66,85 @@ zod-codegen -i ./swagger.yaml -o ./src/generated
61
66
  ```typescript
62
67
  import {Generator} from 'zod-codegen';
63
68
 
64
- const generator = new Generator();
65
-
66
- // Generate from local file
67
- await generator.generate({
68
- input: './openapi.json',
69
- output: './generated',
70
- });
71
-
72
- // Generate from URL
73
- await generator.generate({
74
- input: 'https://api.example.com/openapi.json',
75
- output: './api',
76
- });
69
+ // Create a simple reporter object
70
+ const reporter = {
71
+ log: (...args: unknown[]) => console.log(...args),
72
+ error: (...args: unknown[]) => console.error(...args),
73
+ };
74
+
75
+ // Create generator instance
76
+ const generator = new Generator(
77
+ 'my-app',
78
+ '1.0.0',
79
+ reporter,
80
+ './openapi.json', // Input path or URL
81
+ './generated', // Output directory
82
+ );
83
+
84
+ // Run the generator
85
+ const exitCode = await generator.run();
77
86
  ```
78
87
 
79
88
  ## 📁 Generated Output
80
89
 
81
- The generator creates the following structure:
90
+ The generator creates a single TypeScript file (`type.ts`) containing:
91
+
92
+ - **Zod Schemas**: Exported Zod validation schemas for all component schemas defined in your OpenAPI spec
93
+ - **API Client Class**: A type-safe client class with methods for each endpoint operation
94
+ - **Server Configuration**: `serverConfigurations` array and `defaultBaseUrl` constant extracted from OpenAPI servers
95
+ - **Client Options Type**: `ClientOptions` type for flexible server selection and variable overrides
96
+ - **Protected Extension Point**: A `getBaseRequestOptions()` method that can be overridden for customization
97
+
98
+ ### Generated Client Structure
99
+
100
+ The generated client class includes:
101
+
102
+ ```typescript
103
+ export class YourAPI {
104
+ readonly #baseUrl: string;
105
+
106
+ // Options-based constructor (if servers are defined in OpenAPI spec)
107
+ constructor(options: ClientOptions);
108
+
109
+ // Or simple baseUrl constructor (if no servers defined)
110
+ constructor(baseUrl: string = '/', _?: unknown);
111
+
112
+ // Protected method - override to customize request options
113
+ protected getBaseRequestOptions(): Partial<Omit<RequestInit, 'method' | 'body'>>;
114
+
115
+ // Private method - handles all HTTP requests
116
+ async #makeRequest<T>(method: string, path: string, options: {...}): Promise<T>;
117
+
118
+ // Generated endpoint methods (one per operationId)
119
+ async yourEndpointMethod(...): Promise<ResponseType>;
120
+ }
121
+
122
+ // ClientOptions type (when servers are defined)
123
+ export type ClientOptions = {
124
+ baseUrl?: string; // Override base URL directly
125
+ serverIndex?: number; // Select server by index (0-based)
126
+ serverVariables?: Record<string, string>; // Override server template variables
127
+ };
128
+
129
+ // Server configuration (when servers are defined)
130
+ export const serverConfigurations: Array<{
131
+ url: string;
132
+ description?: string;
133
+ variables?: Record<string, {
134
+ default: string;
135
+ enum?: string[];
136
+ description?: string;
137
+ }>;
138
+ }>;
139
+
140
+ export const defaultBaseUrl: string; // First server with default variables
141
+ ```
142
+
143
+ ### File Structure
82
144
 
83
145
  ```
84
146
  generated/
85
- ├── schemas/ # Zod validation schemas
86
- │ ├── user.schema.ts
87
- │ └── product.schema.ts
88
- ├── types/ # TypeScript type definitions
89
- │ ├── user.types.ts
90
- │ └── product.types.ts
91
- └── client/ # Type-safe API client
92
- └── api.client.ts
147
+ └── type.ts # All schemas and client in one file
93
148
  ```
94
149
 
95
150
  ## 🎯 Example
@@ -101,69 +156,226 @@ openapi: 3.0.0
101
156
  info:
102
157
  title: User API
103
158
  version: 1.0.0
159
+ servers:
160
+ - url: https://api.example.com
104
161
  paths:
105
- /users:
162
+ /users/{id}:
106
163
  get:
164
+ operationId: getUserById
165
+ parameters:
166
+ - name: id
167
+ in: path
168
+ required: true
169
+ schema:
170
+ type: integer
107
171
  responses:
108
172
  '200':
109
173
  content:
110
174
  application/json:
111
175
  schema:
112
- type: object
113
- properties:
114
- id:
115
- type: integer
116
- name:
117
- type: string
118
- email:
119
- type: string
120
- format: email
121
- required: [id, name, email]
176
+ $ref: '#/components/schemas/User'
177
+ components:
178
+ schemas:
179
+ User:
180
+ type: object
181
+ properties:
182
+ id:
183
+ type: integer
184
+ name:
185
+ type: string
186
+ email:
187
+ type: string
188
+ format: email
189
+ required: [id, name, email]
122
190
  ```
123
191
 
124
- **Generated Zod Schema** (`schemas/user.schema.ts`):
192
+ **Generated Output** (`generated/type.ts`):
125
193
 
126
194
  ```typescript
127
195
  import {z} from 'zod';
128
196
 
129
- export const UserSchema = z.object({
197
+ // Components schemas
198
+ export const User = z.object({
130
199
  id: z.number().int(),
131
200
  name: z.string(),
132
201
  email: z.string().email(),
133
202
  });
134
203
 
135
- export type User = z.infer<typeof UserSchema>;
204
+ // Server configuration (when servers are defined in OpenAPI spec)
205
+ export const serverConfigurations = [
206
+ {
207
+ url: 'https://api.example.com',
208
+ },
209
+ ];
210
+ export const defaultBaseUrl = 'https://api.example.com';
211
+ export type ClientOptions = {
212
+ baseUrl?: string;
213
+ serverIndex?: number;
214
+ serverVariables?: Record<string, string>;
215
+ };
216
+
217
+ // Client class
218
+ export class UserAPI {
219
+ readonly #baseUrl: string;
220
+
221
+ constructor(options: ClientOptions = {}) {
222
+ this.#baseUrl = options.baseUrl || defaultBaseUrl;
223
+ }
224
+
225
+ protected getBaseRequestOptions(): Partial<Omit<RequestInit, 'method' | 'body'>> {
226
+ return {};
227
+ }
228
+
229
+ async getUserById(id: number): Promise<z.infer<typeof User>> {
230
+ return User.parse(await this.#makeRequest<z.infer<typeof User>>('GET', `/users/${id}`, {}));
231
+ }
232
+
233
+ // ... private #makeRequest method
234
+ }
136
235
  ```
137
236
 
138
- **Generated Types** (`types/user.types.ts`):
237
+ **Usage:**
139
238
 
140
239
  ```typescript
141
- export interface User {
142
- id: number;
143
- name: string;
144
- email: string;
240
+ import {UserAPI, User} from './generated/type.js';
241
+
242
+ // Use default server from OpenAPI spec
243
+ const client = new UserAPI({});
244
+ const user = await client.getUserById(123);
245
+ // user is fully typed and validated!
246
+ ```
247
+
248
+ ### Extending the Client
249
+
250
+ The generated client includes a protected `getBaseRequestOptions()` method that you can override to customize request options. This method returns `Partial<Omit<RequestInit, 'method' | 'body'>>`, allowing you to configure:
251
+
252
+ - **Headers**: Authentication tokens, User-Agent, custom headers
253
+ - **CORS**: `mode`, `credentials` for cross-origin requests
254
+ - **Request Options**: `signal` (AbortController), `cache`, `redirect`, `referrer`, etc.
255
+
256
+ #### Basic Authentication Example
257
+
258
+ ```typescript
259
+ import {UserAPI, ClientOptions} from './generated/type.js';
260
+
261
+ class AuthenticatedUserAPI extends UserAPI {
262
+ private accessToken: string | null = null;
263
+
264
+ constructor(options: ClientOptions = {}) {
265
+ super(options);
266
+ }
267
+
268
+ protected getBaseRequestOptions(): Partial<Omit<RequestInit, 'method' | 'body'>> {
269
+ const options = super.getBaseRequestOptions();
270
+ return {
271
+ ...options,
272
+ headers: {
273
+ ...((options.headers as Record<string, string>) || {}),
274
+ ...(this.accessToken ? {Authorization: `Bearer ${this.accessToken}`} : {}),
275
+ },
276
+ };
277
+ }
278
+
279
+ setAccessToken(token: string): void {
280
+ this.accessToken = token;
281
+ }
145
282
  }
283
+
284
+ // Usage
285
+ const client = new AuthenticatedUserAPI({});
286
+ client.setAccessToken('your-token-here');
287
+ const user = await client.getUserById(123); // Includes Authorization header
146
288
  ```
147
289
 
148
- **Generated Client** (`client/api.client.ts`):
290
+ #### Complete Configuration Example
149
291
 
150
292
  ```typescript
151
- import {UserSchema, type User} from '../schemas/user.schema.js';
293
+ import {UserAPI, ClientOptions} from './generated/type.js';
294
+
295
+ class FullyConfiguredAPI extends UserAPI {
296
+ private accessToken: string | null = null;
152
297
 
153
- export class ApiClient {
154
- async getUsers(): Promise<User[]> {
155
- const response = await fetch('/api/users');
156
- const data = await response.json();
157
- return UserSchema.array().parse(data);
298
+ constructor(options: ClientOptions = {}) {
299
+ super(options);
300
+ }
301
+
302
+ protected getBaseRequestOptions(): Partial<Omit<RequestInit, 'method' | 'body'>> {
303
+ const options = super.getBaseRequestOptions();
304
+ return {
305
+ ...options,
306
+ headers: {
307
+ ...((options.headers as Record<string, string>) || {}),
308
+ 'User-Agent': 'MyApp/1.0.0',
309
+ ...(this.accessToken ? {Authorization: `Bearer ${this.accessToken}`} : {}),
310
+ },
311
+ mode: 'cors',
312
+ credentials: 'include',
313
+ cache: 'no-cache',
314
+ };
315
+ }
316
+
317
+ setAccessToken(token: string): void {
318
+ this.accessToken = token;
158
319
  }
159
320
  }
321
+
322
+ // Usage
323
+ const client = new FullyConfiguredAPI({});
324
+ client.setAccessToken('your-token');
160
325
  ```
161
326
 
327
+ #### Available Request Options
328
+
329
+ You can set any `RequestInit` option except `method` and `body` (which are controlled by the generated code):
330
+
331
+ | Option | Type | Description |
332
+ | ---------------- | -------------------- | ------------------------------------------------------------------- |
333
+ | `headers` | `HeadersInit` | Request headers (authentication, User-Agent, etc.) |
334
+ | `signal` | `AbortSignal` | AbortController signal for request cancellation |
335
+ | `credentials` | `RequestCredentials` | CORS credentials mode (`'include'`, `'same-origin'`, `'omit'`) |
336
+ | `mode` | `RequestMode` | Request mode (`'cors'`, `'no-cors'`, `'same-origin'`, `'navigate'`) |
337
+ | `cache` | `RequestCache` | Cache mode (`'default'`, `'no-cache'`, `'reload'`, etc.) |
338
+ | `redirect` | `RequestRedirect` | Redirect handling (`'follow'`, `'error'`, `'manual'`) |
339
+ | `referrer` | `string` | Referrer URL |
340
+ | `referrerPolicy` | `ReferrerPolicy` | Referrer policy |
341
+ | `integrity` | `string` | Subresource integrity hash |
342
+ | `keepalive` | `boolean` | Keep connection alive |
343
+
344
+ See [EXAMPLES.md](EXAMPLES.md) for comprehensive examples including:
345
+
346
+ - Bearer token authentication
347
+ - Session management with token refresh
348
+ - Custom User-Agent headers
349
+ - CORS configuration
350
+ - Request cancellation with AbortController
351
+ - Environment-specific configurations
352
+
353
+ ## 📖 Examples
354
+
355
+ Check out the [examples directory](./examples/) for complete, runnable examples:
356
+
357
+ - **[Petstore API](./examples/petstore/)** - Complete example with the Swagger Petstore API
358
+ - **[PokéAPI](./examples/pokeapi/)** - Example with a public REST API
359
+
360
+ Each example includes:
361
+
362
+ - Generated client code
363
+ - Basic usage examples
364
+ - Authentication examples
365
+ - README with instructions
366
+
367
+ ## 📚 Documentation
368
+
369
+ - **[README.md](README.md)** - Project overview and quick start guide
370
+ - **[EXAMPLES.md](EXAMPLES.md)** - Comprehensive examples and patterns for extending the client
371
+ - **[CONTRIBUTING.md](CONTRIBUTING.md)** - Guidelines for contributing to the project
372
+ - **[CHANGELOG.md](CHANGELOG.md)** - Version history and changes
373
+
162
374
  ## 🛠️ Development
163
375
 
164
376
  ### Prerequisites
165
377
 
166
- - Node.js ≥ 18.0.0
378
+ - Node.js ≥ 24.11.1
167
379
  - npm or yarn
168
380
 
169
381
  ### Setup
@@ -223,9 +435,9 @@ npm run test:ui
223
435
 
224
436
  ## 📋 Requirements
225
437
 
226
- - **Node.js**: ≥ 18.0.0
227
- - **TypeScript**: ≥ 5.0.0
228
- - **Zod**: ≥ 3.20.0
438
+ - **Node.js**: ≥ 24.11.1
439
+ - **TypeScript**: ≥ 5.9.3
440
+ - **Zod**: ≥ 4.1.12
229
441
 
230
442
  ## 🤝 Contributing
231
443
 
package/dist/src/cli.js CHANGED
@@ -9,14 +9,34 @@ import loudRejection from 'loud-rejection';
9
9
  import { handleErrors } from './utils/error-handler.js';
10
10
  import { handleSignals } from './utils/signal-handler.js';
11
11
  import debug from 'debug';
12
- import { isManifest } from './utils/manifest.js';
13
12
  import { Reporter } from './utils/reporter.js';
14
13
  const __dirname = dirname(fileURLToPath(import.meta.url));
15
- const manifestData = JSON.parse(readFileSync(join(__dirname, 'assets', 'manifest.json'), 'utf-8'));
16
- if (!isManifest(manifestData)) {
17
- process.exit(1);
14
+ // Read package.json from the project root
15
+ // Handle multiple scenarios:
16
+ // 1. Built locally: dist/src/cli.js -> go up 2 levels
17
+ // 2. Source: src/cli.ts -> go up 1 level
18
+ // 3. Installed via npm: node_modules/zod-codegen/dist/src/cli.js -> go up 2 levels
19
+ // Try multiple paths to ensure we find package.json
20
+ const possiblePaths = [
21
+ join(__dirname, '..', '..', 'package.json'), // dist/src/cli.js or node_modules/pkg/dist/src/cli.js
22
+ join(__dirname, '..', 'package.json'), // src/cli.ts
23
+ ];
24
+ let packageJsonPath;
25
+ for (const path of possiblePaths) {
26
+ try {
27
+ readFileSync(path, 'utf-8');
28
+ packageJsonPath = path;
29
+ break;
30
+ }
31
+ catch {
32
+ // Try next path
33
+ }
34
+ }
35
+ if (!packageJsonPath) {
36
+ throw new Error('Could not find package.json. Please ensure the package is properly installed.');
18
37
  }
19
- const { name, description, version } = manifestData;
38
+ const packageData = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
39
+ const { name, description, version } = packageData;
20
40
  const reporter = new Reporter(process.stdout);
21
41
  const startTime = process.hrtime.bigint();
22
42
  debug(`${name}:${String(process.pid)}`);