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.
@@ -0,0 +1,217 @@
1
+ // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2
+ // Built with zod-codegen@1.0.1
3
+ // Latest edit: Thu, 13 Nov 2025 13:36:35 GMT
4
+ // Source file: ./samples/swagger-petstore.yaml
5
+ /* eslint-disable */
6
+ // @ts-nocheck
7
+
8
+ // Imports
9
+ import {z} from 'zod';
10
+
11
+ // Components schemas
12
+ export const Order = z.object({
13
+ id: z.number().int().optional(),
14
+ petId: z.number().int().optional(),
15
+ quantity: z.number().int().optional(),
16
+ shipDate: z.string().datetime().optional(),
17
+ status: z.enum(['placed', 'approved', 'delivered']).optional(),
18
+ complete: z.boolean().optional(),
19
+ });
20
+ export const Address = z.object({
21
+ street: z.string().optional(),
22
+ city: z.string().optional(),
23
+ state: z.string().optional(),
24
+ zip: z.string().optional(),
25
+ });
26
+ export const Customer = z.object({
27
+ id: z.number().int().optional(),
28
+ username: z.string().optional(),
29
+ address: z.array(Address).optional(),
30
+ });
31
+ export const Category = z.object({
32
+ id: z.number().int().optional(),
33
+ name: z.string().optional(),
34
+ });
35
+ export const User = z.object({
36
+ id: z.number().int().optional(),
37
+ username: z.string().optional(),
38
+ firstName: z.string().optional(),
39
+ lastName: z.string().optional(),
40
+ email: z.string().optional(),
41
+ password: z.string().optional(),
42
+ phone: z.string().optional(),
43
+ userStatus: z.number().int().optional(),
44
+ });
45
+ export const Tag = z.object({
46
+ id: z.number().int().optional(),
47
+ name: z.string().optional(),
48
+ });
49
+ export const Pet = z.object({
50
+ id: z.number().int().optional(),
51
+ name: z.string(),
52
+ category: Category.optional(),
53
+ photoUrls: z.array(z.string()),
54
+ tags: z.array(Tag).optional(),
55
+ status: z.enum(['available', 'pending', 'sold']).optional(),
56
+ });
57
+ export const ApiResponse = z.object({
58
+ code: z.number().int().optional(),
59
+ type: z.string().optional(),
60
+ message: z.string().optional(),
61
+ });
62
+ export const serverConfigurations = [
63
+ {
64
+ url: 'https://petstore3.swagger.io/api/v3',
65
+ },
66
+ ];
67
+ export const defaultBaseUrl = 'https://petstore3.swagger.io/api/v3';
68
+ export type ClientOptions = {
69
+ baseUrl?: string;
70
+ serverIndex?: number;
71
+ serverVariables?: Record<string, string>;
72
+ };
73
+ function resolveServerUrl(serverIndex?: number | undefined, serverVariables?: Record<string, string> = {}): string {
74
+ const configs = [
75
+ {
76
+ url: 'https://petstore3.swagger.io/api/v3',
77
+ },
78
+ ];
79
+ const idx = serverIndex ?? 0;
80
+ if (idx < configs.length) {
81
+ const config = configs[idx];
82
+ let url = config.url;
83
+ if (config.variables && serverVariables) {
84
+ for (const [key, value] of Object.entries(serverVariables)) {
85
+ url = url.replace(new RegExp('\\{' + key + '\\}', 'g'), value);
86
+ }
87
+ }
88
+ return url;
89
+ }
90
+ return 'https://petstore3.swagger.io/api/v3';
91
+ }
92
+
93
+ // Client class
94
+ export class SwaggerPetstoreOpenAPI30 {
95
+ readonly #baseUrl: string;
96
+ constructor(options: ClientOptions) {
97
+ const resolvedUrl =
98
+ options.baseUrl !== null ? options.baseUrl : resolveServerUrl(options.serverIndex, options.serverVariables);
99
+ this.#baseUrl = resolvedUrl;
100
+ }
101
+ protected getBaseRequestOptions(): Partial<Omit<RequestInit, 'method' | 'body'>> {
102
+ return {};
103
+ }
104
+ async #makeRequest<T>(
105
+ method: string,
106
+ path: string,
107
+ options: _params___Record_string__string___number___boolean___data___unknown__contentType___string__headers___Record_string__string__ = {},
108
+ ): Promise<T> {
109
+ const baseUrl = `${this.#baseUrl}${path}`;
110
+ const url =
111
+ options.params && Object.keys(options.params).length > 0
112
+ ? (() => {
113
+ Object.entries(options.params).forEach(([key, value]) => {
114
+ new URL(baseUrl).searchParams.set(key, String(value));
115
+ });
116
+ return new URL(baseUrl).toString();
117
+ })()
118
+ : new URL(baseUrl).toString();
119
+ const baseOptions = this.getBaseRequestOptions();
120
+ const contentType =
121
+ options.contentType === 'application/x-www-form-urlencoded'
122
+ ? 'application/x-www-form-urlencoded'
123
+ : 'application/json';
124
+ const baseHeaders = baseOptions.headers !== undefined ? baseOptions.headers : {};
125
+ const headers = Object.assign(
126
+ {},
127
+ baseHeaders,
128
+ {'Content-Type': contentType},
129
+ options.headers !== undefined ? options.headers : {},
130
+ );
131
+ const body =
132
+ options.data !== undefined
133
+ ? options.contentType === 'application/x-www-form-urlencoded'
134
+ ? (() => {
135
+ const params = new URLSearchParams();
136
+ Object.entries(options.data).forEach(([key, value]) => {
137
+ params.set(key, String(value));
138
+ });
139
+ return params.toString();
140
+ })()
141
+ : JSON.stringify(options.data)
142
+ : null;
143
+ const response = await fetch(url, Object.assign({}, baseOptions, {method, headers: headers, body: body}));
144
+ if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
145
+ return await response.json();
146
+ }
147
+ async addPet(body: Pet): Promise<Pet> {
148
+ return Pet.parse(
149
+ await this.#makeRequest('POST', '/pet', {data: body, contentType: 'application/x-www-form-urlencoded'}),
150
+ );
151
+ }
152
+ async updatePet(body: Pet): Promise<Pet> {
153
+ return Pet.parse(
154
+ await this.#makeRequest('PUT', '/pet', {data: body, contentType: 'application/x-www-form-urlencoded'}),
155
+ );
156
+ }
157
+ async findPetsByStatus(status?: string): Promise<Pet[]> {
158
+ return await this.#makeRequest('GET', '/pet/findByStatus', {params: {status: status}});
159
+ }
160
+ async findPetsByTags(tags?: string[]): Promise<Pet[]> {
161
+ return await this.#makeRequest('GET', '/pet/findByTags', {params: {tags: tags}});
162
+ }
163
+ async getPetById(petId: number): Promise<Pet> {
164
+ return Pet.parse(await this.#makeRequest('GET', `/pet/${petId}`, {}));
165
+ }
166
+ async updatePetWithForm(petId: number, name?: string, status?: string): Promise<void> {
167
+ return await this.#makeRequest('POST', `/pet/${petId}`, {params: {name: name, status: status}});
168
+ }
169
+ async deletePet(petId: number): Promise<void> {
170
+ return await this.#makeRequest('DELETE', `/pet/${petId}`, {});
171
+ }
172
+ async uploadFile(petId: number, additionalMetadata?: string): Promise<ApiResponse> {
173
+ return ApiResponse.parse(
174
+ await this.#makeRequest('POST', `/pet/${petId}/uploadImage`, {params: {additionalMetadata: additionalMetadata}}),
175
+ );
176
+ }
177
+ async getInventory(): Promise<Record<string, unknown>> {
178
+ return await this.#makeRequest('GET', '/store/inventory', {});
179
+ }
180
+ async placeOrder(body?: Order): Promise<Order> {
181
+ return Order.parse(
182
+ await this.#makeRequest('POST', '/store/order', {data: body, contentType: 'application/x-www-form-urlencoded'}),
183
+ );
184
+ }
185
+ async getOrderById(orderId: number): Promise<Order> {
186
+ return Order.parse(await this.#makeRequest('GET', `/store/order/${orderId}`, {}));
187
+ }
188
+ async deleteOrder(orderId: number): Promise<void> {
189
+ return await this.#makeRequest('DELETE', `/store/order/${orderId}`, {});
190
+ }
191
+ async createUser(body?: User): Promise<User> {
192
+ return User.parse(
193
+ await this.#makeRequest('POST', '/user', {data: body, contentType: 'application/x-www-form-urlencoded'}),
194
+ );
195
+ }
196
+ async createUsersWithListInput(body?: User[]): Promise<User> {
197
+ return User.parse(await this.#makeRequest('POST', '/user/createWithList', {data: body}));
198
+ }
199
+ async loginUser(username?: string, password?: string): Promise<string> {
200
+ return await this.#makeRequest('GET', '/user/login', {params: {username: username, password: password}});
201
+ }
202
+ async logoutUser(): Promise<void> {
203
+ return await this.#makeRequest('GET', '/user/logout', {});
204
+ }
205
+ async getUserByName(username: string): Promise<User> {
206
+ return User.parse(await this.#makeRequest('GET', `/user/${username}`, {}));
207
+ }
208
+ async updateUser(username: string, body?: User): Promise<void> {
209
+ return await this.#makeRequest('PUT', `/user/${username}`, {
210
+ data: body,
211
+ contentType: 'application/x-www-form-urlencoded',
212
+ });
213
+ }
214
+ async deleteUser(username: string): Promise<void> {
215
+ return await this.#makeRequest('DELETE', `/user/${username}`, {});
216
+ }
217
+ }
@@ -0,0 +1,105 @@
1
+ # PokéAPI Example
2
+
3
+ This example demonstrates how to use `zod-codegen` with the PokéAPI OpenAPI specification.
4
+
5
+ ## Setup
6
+
7
+ First, generate the client from the PokéAPI OpenAPI spec:
8
+
9
+ ```bash
10
+ zod-codegen --input ./samples/pokeapi-openapi.json --output ./examples/pokeapi
11
+ ```
12
+
13
+ **Note**: PokéAPI doesn't provide an official OpenAPI specification, so we've created a simplified OpenAPI spec based on their API structure. For production use, you may want to create a more complete specification based on the [PokéAPI documentation](https://pokeapi.co/docs/v2).
14
+
15
+ ## Basic Usage
16
+
17
+ ```typescript
18
+ import {PokAPI, defaultBaseUrl} from './type.js';
19
+
20
+ // Create a client instance
21
+ const client = new PokAPI(defaultBaseUrl);
22
+
23
+ // Use the generated methods
24
+ const pokemon = await client.getPokemonById('pikachu');
25
+ console.log('Pokemon:', pokemon);
26
+ ```
27
+
28
+ ## Example: Fetching Pokémon Data
29
+
30
+ See [basic-usage.ts](./basic-usage.ts) for a complete example:
31
+
32
+ ```typescript
33
+ import {PokAPI, defaultBaseUrl} from './type.js';
34
+
35
+ async function getPokemonInfo() {
36
+ const client = new PokAPI(defaultBaseUrl);
37
+
38
+ try {
39
+ // Get a specific Pokémon by ID or name
40
+ const pikachu = await client.getPokemonById('pikachu');
41
+ console.log(`Name: ${pikachu.name}`);
42
+ console.log(`Height: ${pikachu.height} dm`);
43
+ console.log(`Weight: ${pikachu.weight} hg`);
44
+
45
+ if (pikachu.types && pikachu.types.length > 0) {
46
+ const types = pikachu.types
47
+ .map((t) => t.type?.name)
48
+ .filter(Boolean)
49
+ .join(', ');
50
+ console.log(`Types: ${types}`);
51
+ }
52
+
53
+ // Get Pokémon list with pagination
54
+ const pokemonList = await client.getPokemonList(20, 0);
55
+ console.log(`\nTotal Pokémon: ${pokemonList.count}`);
56
+ console.log(`Showing: ${pokemonList.results.length} results`);
57
+ } catch (error) {
58
+ console.error('Error fetching Pokémon:', error);
59
+ }
60
+ }
61
+
62
+ getPokemonInfo();
63
+ ```
64
+
65
+ **Run the example:**
66
+
67
+ ```bash
68
+ npx ts-node examples/pokeapi/basic-usage.ts
69
+ ```
70
+
71
+ ## Example: Extending for Custom Headers
72
+
73
+ See [custom-client.ts](./custom-client.ts) for a complete example:
74
+
75
+ ```typescript
76
+ import {PokAPI, defaultBaseUrl} from './type.js';
77
+
78
+ class CustomPokeAPI extends PokAPI {
79
+ protected getBaseRequestOptions(): Partial<Omit<RequestInit, 'method' | 'body'>> {
80
+ const options = super.getBaseRequestOptions();
81
+ return {
82
+ ...options,
83
+ headers: {
84
+ ...((options.headers as Record<string, string>) || {}),
85
+ 'User-Agent': 'MyPokemonApp/1.0.0',
86
+ Accept: 'application/json',
87
+ },
88
+ mode: 'cors',
89
+ cache: 'default',
90
+ };
91
+ }
92
+ }
93
+
94
+ const client = new CustomPokeAPI(defaultBaseUrl);
95
+ ```
96
+
97
+ **Run the example:**
98
+
99
+ ```bash
100
+ npx ts-node examples/pokeapi/custom-client.ts
101
+ ```
102
+
103
+ ## Note
104
+
105
+ PokéAPI is a public API that doesn't require authentication, making it perfect for testing and learning. The generated client will work out of the box without any additional configuration.
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Basic usage example for the generated PokéAPI client
3
+ *
4
+ * Run with: npx ts-node examples/pokeapi/basic-usage.ts
5
+ */
6
+
7
+ import {PokAPI, defaultBaseUrl} from './type.js';
8
+
9
+ async function main() {
10
+ const client = new PokAPI(defaultBaseUrl);
11
+
12
+ try {
13
+ console.log('🔍 Fetching Pokémon data...\n');
14
+
15
+ // Get a specific Pokémon by ID or name
16
+ console.log('📖 Getting Pikachu...');
17
+ const pikachu = await client.getPokemonById('pikachu');
18
+ console.log(`✅ Found: ${pikachu.name}`);
19
+ console.log(` ID: ${pikachu.id}`);
20
+ console.log(` Height: ${pikachu.height} dm`);
21
+ console.log(` Weight: ${pikachu.weight} hg`);
22
+
23
+ if (pikachu.types && pikachu.types.length > 0) {
24
+ const types = pikachu.types
25
+ .map((t) => t.type?.name)
26
+ .filter(Boolean)
27
+ .join(', ');
28
+ console.log(` Types: ${types}`);
29
+ }
30
+
31
+ if (pikachu.sprites?.front_default) {
32
+ console.log(` Sprite: ${pikachu.sprites.front_default}`);
33
+ }
34
+
35
+ // Get Pokémon list
36
+ console.log('\n📋 Getting Pokémon list...');
37
+ const pokemonList = await client.getPokemonList(10, 0);
38
+ console.log(`✅ Found ${pokemonList.count} total Pokémon`);
39
+ console.log(` Showing first ${pokemonList.results.length} results:`);
40
+
41
+ pokemonList.results.slice(0, 5).forEach((pokemon, index) => {
42
+ console.log(` ${index + 1}. ${pokemon.name}`);
43
+ });
44
+ } catch (error) {
45
+ if (error instanceof Error) {
46
+ console.error('❌ Error:', error.message);
47
+ if (error.message.includes('HTTP')) {
48
+ console.error(' This might be a network issue or the API endpoint may have changed.');
49
+ }
50
+ } else {
51
+ console.error('❌ Unknown error:', error);
52
+ }
53
+ process.exit(1);
54
+ }
55
+ }
56
+
57
+ void main();
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Example showing how to extend the PokéAPI client with custom configuration
3
+ *
4
+ * Run with: npx ts-node examples/pokeapi/custom-client.ts
5
+ */
6
+
7
+ import {PokAPI, defaultBaseUrl} from './type.js';
8
+
9
+ class CustomPokeAPIClient extends PokAPI {
10
+ protected getBaseRequestOptions(): Partial<Omit<RequestInit, 'method' | 'body'>> {
11
+ const options = super.getBaseRequestOptions();
12
+ return {
13
+ ...options,
14
+ headers: {
15
+ ...((options.headers as Record<string, string>) || {}),
16
+ 'User-Agent': 'MyPokemonApp/1.0.0 (https://myapp.com)',
17
+ Accept: 'application/json',
18
+ },
19
+ mode: 'cors',
20
+ cache: 'default',
21
+ };
22
+ }
23
+ }
24
+
25
+ async function main() {
26
+ const client = new CustomPokeAPIClient(defaultBaseUrl);
27
+
28
+ try {
29
+ console.log('🔍 Fetching Pokémon with custom client...\n');
30
+
31
+ // Get Charizard
32
+ const charizard = await client.getPokemonById('charizard');
33
+ console.log(`✅ ${charizard.name.toUpperCase()}`);
34
+ console.log(` ID: ${charizard.id}`);
35
+ console.log(` Height: ${charizard.height} dm`);
36
+ console.log(` Weight: ${charizard.weight} hg`);
37
+
38
+ if (charizard.abilities && charizard.abilities.length > 0) {
39
+ console.log('\n Abilities:');
40
+ charizard.abilities.forEach((ability, index) => {
41
+ const abilityName = ability.ability?.name || 'Unknown';
42
+ const hidden = ability.is_hidden ? ' (hidden)' : '';
43
+ console.log(` ${index + 1}. ${abilityName}${hidden}`);
44
+ });
45
+ }
46
+ } catch (error) {
47
+ if (error instanceof Error) {
48
+ console.error('❌ Error:', error.message);
49
+ } else {
50
+ console.error('❌ Unknown error:', error);
51
+ }
52
+ process.exit(1);
53
+ }
54
+ }
55
+
56
+ void main();
@@ -0,0 +1,109 @@
1
+ // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2
+ // Built with zod-codegen@1.0.1
3
+ // Latest edit: Thu, 13 Nov 2025 13:32:35 GMT
4
+ // Source file: ./samples/pokeapi-openapi.json
5
+ /* eslint-disable */
6
+ // @ts-nocheck
7
+
8
+ // Imports
9
+ import {z} from 'zod';
10
+
11
+ // Components schemas
12
+ export const NamedAPIResource = z.object({
13
+ name: z.string(),
14
+ url: z.string().url(),
15
+ });
16
+ export const PokemonType = z.object({
17
+ slot: z.number().int().optional(),
18
+ type: NamedAPIResource.optional(),
19
+ });
20
+ export const PokemonAbility = z.object({
21
+ ability: NamedAPIResource.optional(),
22
+ is_hidden: z.boolean().optional(),
23
+ slot: z.number().int().optional(),
24
+ });
25
+ export const PokemonSprites = z.object({
26
+ front_default: z.string().url().optional(),
27
+ front_shiny: z.string().url().optional(),
28
+ back_default: z.string().url().optional(),
29
+ back_shiny: z.string().url().optional(),
30
+ });
31
+ export const Pokemon = z.object({
32
+ id: z.number().int(),
33
+ name: z.string(),
34
+ height: z.number().int().optional(),
35
+ weight: z.number().int().optional(),
36
+ base_experience: z.number().int().optional(),
37
+ types: z.array(PokemonType).optional(),
38
+ abilities: z.array(PokemonAbility).optional(),
39
+ sprites: PokemonSprites.optional(),
40
+ });
41
+ export const PokemonListResponse = z.object({
42
+ count: z.number().int(),
43
+ next: z.string().url().optional(),
44
+ previous: z.string().url().optional(),
45
+ results: z.array(NamedAPIResource),
46
+ });
47
+ export const defaultBaseUrl = 'https://pokeapi.co/api/v2';
48
+
49
+ // Client class
50
+ export class PokAPI {
51
+ readonly #baseUrl: string;
52
+ constructor(baseUrl: string = defaultBaseUrl, _?: unknown) {
53
+ this.#baseUrl = baseUrl;
54
+ }
55
+ protected getBaseRequestOptions(): Partial<Omit<RequestInit, 'method' | 'body'>> {
56
+ return {};
57
+ }
58
+ async #makeRequest<T>(
59
+ method: string,
60
+ path: string,
61
+ options: _params___Record_string__string___number___boolean___data___unknown__contentType___string__headers___Record_string__string__ = {},
62
+ ): Promise<T> {
63
+ const baseUrl = `${this.#baseUrl}${path}`;
64
+ const url =
65
+ options.params && Object.keys(options.params).length > 0
66
+ ? (() => {
67
+ Object.entries(options.params).forEach(([key, value]) => {
68
+ new URL(baseUrl).searchParams.set(key, String(value));
69
+ });
70
+ return new URL(baseUrl).toString();
71
+ })()
72
+ : new URL(baseUrl).toString();
73
+ const baseOptions = this.getBaseRequestOptions();
74
+ const contentType =
75
+ options.contentType === 'application/x-www-form-urlencoded'
76
+ ? 'application/x-www-form-urlencoded'
77
+ : 'application/json';
78
+ const baseHeaders = baseOptions.headers !== undefined ? baseOptions.headers : {};
79
+ const headers = Object.assign(
80
+ {},
81
+ baseHeaders,
82
+ {'Content-Type': contentType},
83
+ options.headers !== undefined ? options.headers : {},
84
+ );
85
+ const body =
86
+ options.data !== undefined
87
+ ? options.contentType === 'application/x-www-form-urlencoded'
88
+ ? (() => {
89
+ const params = new URLSearchParams();
90
+ Object.entries(options.data).forEach(([key, value]) => {
91
+ params.set(key, String(value));
92
+ });
93
+ return params.toString();
94
+ })()
95
+ : JSON.stringify(options.data)
96
+ : null;
97
+ const response = await fetch(url, Object.assign({}, baseOptions, {method, headers: headers, body: body}));
98
+ if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
99
+ return await response.json();
100
+ }
101
+ async getPokemonById(id: string): Promise<Pokemon> {
102
+ return Pokemon.parse(await this.#makeRequest('GET', `/pokemon/${id}`, {}));
103
+ }
104
+ async getPokemonList(limit?: number, offset?: number): Promise<PokemonListResponse> {
105
+ return PokemonListResponse.parse(
106
+ await this.#makeRequest('GET', '/pokemon', {params: {limit: limit, offset: offset}}),
107
+ );
108
+ }
109
+ }
package/package.json CHANGED
@@ -79,8 +79,9 @@
79
79
  },
80
80
  "scripts": {
81
81
  "build": "rm -rf dist && tsc --project tsconfig.json && cp -r src/assets dist/src/ && chmod +x ./dist/src/cli.js",
82
+ "build:native": "rm -rf dist && npx tsgo --project tsconfig.json && cp -r src/assets dist/src/ && chmod +x ./dist/src/cli.js",
82
83
  "build:watch": "tsc --project tsconfig.json --watch",
83
- "dev": "npm run build && node ./dist/src/cli.js --input ./samples/saris-openapi.json && npm run format && npm run lint",
84
+ "dev": "npm run build && node ./dist/src/cli.js --input ./samples/swagger-petstore.yaml --output ./examples/petstore && npm run format && npm run lint",
84
85
  "lint": "eslint src --fix",
85
86
  "lint:check": "eslint src",
86
87
  "format": "prettier src --write --log-level error",
@@ -95,8 +96,9 @@
95
96
  "prepublishOnly": "npm run build && npm run test && npm run lint:check && npm run type-check",
96
97
  "clean": "rm -rf dist coverage node_modules/.cache",
97
98
  "validate": "npm run type-check && npm run lint:check && npm run format:check && npm run test",
99
+ "validate:examples": "tsc -p ./tsconfig.examples.json --noEmit",
98
100
  "release": "semantic-release",
99
101
  "release:dry": "semantic-release --dry-run"
100
102
  },
101
- "version": "1.0.2"
103
+ "version": "1.1.0"
102
104
  }