zod-codegen 1.0.3 → 1.1.1
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/CHANGELOG.md +13 -0
- package/CONTRIBUTING.md +1 -1
- package/EXAMPLES.md +730 -0
- package/PERFORMANCE.md +59 -0
- package/README.md +272 -58
- package/dist/src/services/code-generator.service.js +249 -45
- package/dist/src/types/openapi.js +1 -1
- package/dist/tests/unit/code-generator.test.js +290 -0
- package/dist/tests/unit/file-reader.test.js +110 -0
- package/dist/tests/unit/generator.test.js +77 -7
- package/eslint.config.mjs +1 -0
- package/examples/.gitkeep +3 -0
- package/examples/README.md +74 -0
- package/examples/petstore/README.md +121 -0
- package/examples/petstore/authenticated-usage.ts +60 -0
- package/examples/petstore/basic-usage.ts +51 -0
- package/examples/petstore/server-variables-usage.ts +63 -0
- package/examples/petstore/type.ts +217 -0
- package/examples/pokeapi/README.md +105 -0
- package/examples/pokeapi/basic-usage.ts +57 -0
- package/examples/pokeapi/custom-client.ts +56 -0
- package/examples/pokeapi/type.ts +109 -0
- package/package.json +4 -2
- package/samples/pokeapi-openapi.json +212 -0
- package/samples/server-variables-example.yaml +49 -0
- package/src/services/code-generator.service.ts +707 -88
- package/src/types/openapi.ts +1 -1
- package/tests/unit/code-generator.test.ts +321 -0
- package/tests/unit/file-reader.test.ts +134 -0
- package/tests/unit/generator.test.ts +99 -7
- package/tsconfig.examples.json +17 -0
- package/tsconfig.json +1 -1
|
@@ -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/
|
|
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.
|
|
103
|
+
"version": "1.1.1"
|
|
102
104
|
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapi": "3.0.0",
|
|
3
|
+
"info": {
|
|
4
|
+
"title": "PokéAPI",
|
|
5
|
+
"version": "2.0.0",
|
|
6
|
+
"description": "PokéAPI is a RESTful API that provides data about Pokémon"
|
|
7
|
+
},
|
|
8
|
+
"servers": [
|
|
9
|
+
{
|
|
10
|
+
"url": "https://pokeapi.co/api/v2",
|
|
11
|
+
"description": "PokéAPI production server"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"paths": {
|
|
15
|
+
"/pokemon/{id}": {
|
|
16
|
+
"get": {
|
|
17
|
+
"operationId": "getPokemonById",
|
|
18
|
+
"summary": "Get Pokémon by ID or name",
|
|
19
|
+
"parameters": [
|
|
20
|
+
{
|
|
21
|
+
"name": "id",
|
|
22
|
+
"in": "path",
|
|
23
|
+
"required": true,
|
|
24
|
+
"description": "Pokémon ID or name",
|
|
25
|
+
"schema": {
|
|
26
|
+
"type": "string"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"responses": {
|
|
31
|
+
"200": {
|
|
32
|
+
"description": "Pokémon data",
|
|
33
|
+
"content": {
|
|
34
|
+
"application/json": {
|
|
35
|
+
"schema": {
|
|
36
|
+
"$ref": "#/components/schemas/Pokemon"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"/pokemon": {
|
|
45
|
+
"get": {
|
|
46
|
+
"operationId": "getPokemonList",
|
|
47
|
+
"summary": "Get list of Pokémon",
|
|
48
|
+
"parameters": [
|
|
49
|
+
{
|
|
50
|
+
"name": "limit",
|
|
51
|
+
"in": "query",
|
|
52
|
+
"required": false,
|
|
53
|
+
"schema": {
|
|
54
|
+
"type": "integer",
|
|
55
|
+
"default": 20
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "offset",
|
|
60
|
+
"in": "query",
|
|
61
|
+
"required": false,
|
|
62
|
+
"schema": {
|
|
63
|
+
"type": "integer",
|
|
64
|
+
"default": 0
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"responses": {
|
|
69
|
+
"200": {
|
|
70
|
+
"description": "List of Pokémon",
|
|
71
|
+
"content": {
|
|
72
|
+
"application/json": {
|
|
73
|
+
"schema": {
|
|
74
|
+
"$ref": "#/components/schemas/PokemonListResponse"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"components": {
|
|
84
|
+
"schemas": {
|
|
85
|
+
"Pokemon": {
|
|
86
|
+
"type": "object",
|
|
87
|
+
"properties": {
|
|
88
|
+
"id": {
|
|
89
|
+
"type": "integer",
|
|
90
|
+
"description": "Pokémon ID"
|
|
91
|
+
},
|
|
92
|
+
"name": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"description": "Pokémon name"
|
|
95
|
+
},
|
|
96
|
+
"height": {
|
|
97
|
+
"type": "integer",
|
|
98
|
+
"description": "Height in decimeters"
|
|
99
|
+
},
|
|
100
|
+
"weight": {
|
|
101
|
+
"type": "integer",
|
|
102
|
+
"description": "Weight in hectograms"
|
|
103
|
+
},
|
|
104
|
+
"base_experience": {
|
|
105
|
+
"type": "integer",
|
|
106
|
+
"description": "Base experience"
|
|
107
|
+
},
|
|
108
|
+
"types": {
|
|
109
|
+
"type": "array",
|
|
110
|
+
"items": {
|
|
111
|
+
"$ref": "#/components/schemas/PokemonType"
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"abilities": {
|
|
115
|
+
"type": "array",
|
|
116
|
+
"items": {
|
|
117
|
+
"$ref": "#/components/schemas/PokemonAbility"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"sprites": {
|
|
121
|
+
"$ref": "#/components/schemas/PokemonSprites"
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
"required": ["id", "name"]
|
|
125
|
+
},
|
|
126
|
+
"PokemonType": {
|
|
127
|
+
"type": "object",
|
|
128
|
+
"properties": {
|
|
129
|
+
"slot": {
|
|
130
|
+
"type": "integer"
|
|
131
|
+
},
|
|
132
|
+
"type": {
|
|
133
|
+
"$ref": "#/components/schemas/NamedAPIResource"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
"PokemonAbility": {
|
|
138
|
+
"type": "object",
|
|
139
|
+
"properties": {
|
|
140
|
+
"ability": {
|
|
141
|
+
"$ref": "#/components/schemas/NamedAPIResource"
|
|
142
|
+
},
|
|
143
|
+
"is_hidden": {
|
|
144
|
+
"type": "boolean"
|
|
145
|
+
},
|
|
146
|
+
"slot": {
|
|
147
|
+
"type": "integer"
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
"PokemonSprites": {
|
|
152
|
+
"type": "object",
|
|
153
|
+
"properties": {
|
|
154
|
+
"front_default": {
|
|
155
|
+
"type": "string",
|
|
156
|
+
"format": "uri"
|
|
157
|
+
},
|
|
158
|
+
"front_shiny": {
|
|
159
|
+
"type": "string",
|
|
160
|
+
"format": "uri"
|
|
161
|
+
},
|
|
162
|
+
"back_default": {
|
|
163
|
+
"type": "string",
|
|
164
|
+
"format": "uri"
|
|
165
|
+
},
|
|
166
|
+
"back_shiny": {
|
|
167
|
+
"type": "string",
|
|
168
|
+
"format": "uri"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"NamedAPIResource": {
|
|
173
|
+
"type": "object",
|
|
174
|
+
"properties": {
|
|
175
|
+
"name": {
|
|
176
|
+
"type": "string"
|
|
177
|
+
},
|
|
178
|
+
"url": {
|
|
179
|
+
"type": "string",
|
|
180
|
+
"format": "uri"
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
"required": ["name", "url"]
|
|
184
|
+
},
|
|
185
|
+
"PokemonListResponse": {
|
|
186
|
+
"type": "object",
|
|
187
|
+
"properties": {
|
|
188
|
+
"count": {
|
|
189
|
+
"type": "integer"
|
|
190
|
+
},
|
|
191
|
+
"next": {
|
|
192
|
+
"type": "string",
|
|
193
|
+
"format": "uri",
|
|
194
|
+
"nullable": true
|
|
195
|
+
},
|
|
196
|
+
"previous": {
|
|
197
|
+
"type": "string",
|
|
198
|
+
"format": "uri",
|
|
199
|
+
"nullable": true
|
|
200
|
+
},
|
|
201
|
+
"results": {
|
|
202
|
+
"type": "array",
|
|
203
|
+
"items": {
|
|
204
|
+
"$ref": "#/components/schemas/NamedAPIResource"
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
"required": ["count", "results"]
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
openapi: 3.0.0
|
|
2
|
+
info:
|
|
3
|
+
title: Server Variables Example API
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
description: Example API demonstrating server variables for different environments
|
|
6
|
+
servers:
|
|
7
|
+
- url: https://{environment}.example.com:{port}/v{version}
|
|
8
|
+
description: Main API server with templated variables
|
|
9
|
+
variables:
|
|
10
|
+
environment:
|
|
11
|
+
default: api
|
|
12
|
+
enum:
|
|
13
|
+
- api
|
|
14
|
+
- api.dev
|
|
15
|
+
- api.staging
|
|
16
|
+
description: The environment to use (api, api.dev, api.staging)
|
|
17
|
+
port:
|
|
18
|
+
default: '443'
|
|
19
|
+
enum:
|
|
20
|
+
- '443'
|
|
21
|
+
- '8443'
|
|
22
|
+
description: The port number
|
|
23
|
+
version:
|
|
24
|
+
default: '2'
|
|
25
|
+
description: API version
|
|
26
|
+
- url: https://api.production.example.com/v2
|
|
27
|
+
description: Production server (no variables)
|
|
28
|
+
paths:
|
|
29
|
+
/users:
|
|
30
|
+
get:
|
|
31
|
+
operationId: getUsers
|
|
32
|
+
summary: Get list of users
|
|
33
|
+
responses:
|
|
34
|
+
'200':
|
|
35
|
+
description: Success
|
|
36
|
+
content:
|
|
37
|
+
'application/json':
|
|
38
|
+
schema:
|
|
39
|
+
type: array
|
|
40
|
+
items:
|
|
41
|
+
type: object
|
|
42
|
+
properties:
|
|
43
|
+
id:
|
|
44
|
+
type: integer
|
|
45
|
+
name:
|
|
46
|
+
type: string
|
|
47
|
+
required:
|
|
48
|
+
- id
|
|
49
|
+
- name
|