zod-codegen 1.0.3 → 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/CHANGELOG.md +7 -0
- package/CONTRIBUTING.md +1 -1
- package/EXAMPLES.md +704 -0
- package/PERFORMANCE.md +59 -0
- package/README.md +270 -58
- package/dist/src/services/code-generator.service.js +211 -26
- package/dist/src/types/openapi.js +1 -1
- package/dist/tests/unit/code-generator.test.js +219 -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 +641 -57
- package/src/types/openapi.ts +1 -1
- package/tests/unit/code-generator.test.ts +243 -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
package/EXAMPLES.md
ADDED
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
# Usage Examples
|
|
2
|
+
|
|
3
|
+
## Extending the Generated Client for Authentication and Configuration
|
|
4
|
+
|
|
5
|
+
The generated client class includes a protected `getBaseRequestOptions()` method that you can override to customize request options. This method returns `Partial<Omit<RequestInit, 'method' | 'body'>>`, allowing you to set:
|
|
6
|
+
|
|
7
|
+
- **Headers**: Authentication tokens, User-Agent, custom headers
|
|
8
|
+
- **CORS**: `mode`, `credentials` for cross-origin requests
|
|
9
|
+
- **Request Options**: `signal` (AbortController), `cache`, `redirect`, `referrer`, etc.
|
|
10
|
+
|
|
11
|
+
All examples below demonstrate how to extend the generated client class to add these features.
|
|
12
|
+
|
|
13
|
+
### Example: Adding Bearer Token Authentication
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import {SwaggerPetstoreOpenAPI30, ClientOptions} from './generated/type.js';
|
|
17
|
+
|
|
18
|
+
class AuthenticatedPetstoreAPI extends SwaggerPetstoreOpenAPI30 {
|
|
19
|
+
private accessToken: string | null = null;
|
|
20
|
+
|
|
21
|
+
constructor(options: ClientOptions = {}) {
|
|
22
|
+
super(options);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Override getBaseRequestOptions to add Authorization header
|
|
26
|
+
protected getBaseRequestOptions(): Partial<Omit<RequestInit, 'method' | 'body'>> {
|
|
27
|
+
const options = super.getBaseRequestOptions();
|
|
28
|
+
return {
|
|
29
|
+
...options,
|
|
30
|
+
headers: {
|
|
31
|
+
...((options.headers as Record<string, string>) || {}),
|
|
32
|
+
...(this.accessToken ? {Authorization: `Bearer ${this.accessToken}`} : {}),
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Helper method to set the access token
|
|
38
|
+
setAccessToken(token: string): void {
|
|
39
|
+
this.accessToken = token;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Helper method to clear the token
|
|
43
|
+
clearAccessToken(): void {
|
|
44
|
+
this.accessToken = null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Usage
|
|
49
|
+
async function main() {
|
|
50
|
+
const client = new AuthenticatedPetstoreAPI({});
|
|
51
|
+
|
|
52
|
+
// Set authentication token
|
|
53
|
+
client.setAccessToken('your-token-here');
|
|
54
|
+
|
|
55
|
+
// All subsequent requests will include the Authorization header
|
|
56
|
+
const pets = await client.findPetsByStatus('available');
|
|
57
|
+
console.log(pets);
|
|
58
|
+
|
|
59
|
+
// You can also manually set/update the token
|
|
60
|
+
client.setAccessToken('new-token-here');
|
|
61
|
+
|
|
62
|
+
// Or clear it
|
|
63
|
+
client.clearAccessToken();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
void main();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Example: Session Management with Token Refresh
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import {SwaggerPetstoreOpenAPI30} from './generated/type.js';
|
|
73
|
+
|
|
74
|
+
class SessionManagedPetstoreAPI extends SwaggerPetstoreOpenAPI30 {
|
|
75
|
+
private accessToken: string | null = null;
|
|
76
|
+
private refreshToken: string | null = null;
|
|
77
|
+
private tokenExpiry: Date | null = null;
|
|
78
|
+
|
|
79
|
+
protected getBaseRequestOptions(): Partial<Omit<RequestInit, 'method' | 'body'>> {
|
|
80
|
+
const options = super.getBaseRequestOptions();
|
|
81
|
+
|
|
82
|
+
// Check if token is expired and refresh if needed
|
|
83
|
+
if (this.tokenExpiry && this.tokenExpiry <= new Date()) {
|
|
84
|
+
this.refreshAccessToken().catch(console.error);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
...options,
|
|
89
|
+
headers: {
|
|
90
|
+
...((options.headers as Record<string, string>) || {}),
|
|
91
|
+
...(this.accessToken ? {Authorization: `Bearer ${this.accessToken}`} : {}),
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async login(username: string, password: string): Promise<void> {
|
|
97
|
+
// Example: If your API has a login endpoint
|
|
98
|
+
// const response = await this.loginUser({ username, password });
|
|
99
|
+
// this.setTokens(response.access_token, response.refresh_token, response.expires_in);
|
|
100
|
+
|
|
101
|
+
// For demonstration, setting tokens directly
|
|
102
|
+
this.setTokens('access-token-here', 'refresh-token-here', 3600);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private async refreshAccessToken(): Promise<void> {
|
|
106
|
+
if (!this.refreshToken) {
|
|
107
|
+
throw new Error('No refresh token available');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Example: If your API has a refresh endpoint
|
|
111
|
+
// const response = await this.refreshToken({ refresh_token: this.refreshToken });
|
|
112
|
+
// this.setTokens(response.access_token, response.refresh_token, response.expires_in);
|
|
113
|
+
|
|
114
|
+
// For demonstration
|
|
115
|
+
this.setTokens('new-access-token', this.refreshToken, 3600);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private setTokens(accessToken: string, refreshToken?: string, expiresIn?: number): void {
|
|
119
|
+
this.accessToken = accessToken;
|
|
120
|
+
if (refreshToken) {
|
|
121
|
+
this.refreshToken = refreshToken;
|
|
122
|
+
}
|
|
123
|
+
if (expiresIn) {
|
|
124
|
+
this.tokenExpiry = new Date(Date.now() + expiresIn * 1000);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Example: Custom Headers Per Request
|
|
131
|
+
|
|
132
|
+
If you need to pass custom headers for specific requests, you can extend the client and add helper methods:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import {SwaggerPetstoreOpenAPI30, ClientOptions} from './generated/type.js';
|
|
136
|
+
|
|
137
|
+
class CustomHeadersPetstoreAPI extends SwaggerPetstoreOpenAPI30 {
|
|
138
|
+
constructor(options: ClientOptions = {}) {
|
|
139
|
+
super(options);
|
|
140
|
+
}
|
|
141
|
+
protected getBaseRequestOptions(): Partial<Omit<RequestInit, 'method' | 'body'>> {
|
|
142
|
+
const options = super.getBaseRequestOptions();
|
|
143
|
+
return {
|
|
144
|
+
...options,
|
|
145
|
+
headers: {
|
|
146
|
+
...((options.headers as Record<string, string>) || {}),
|
|
147
|
+
'X-Custom-Header': 'custom-value',
|
|
148
|
+
'X-Request-ID': this.generateRequestId(),
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private generateRequestId(): string {
|
|
154
|
+
return `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Example: API Key Authentication
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import {SwaggerPetstoreOpenAPI30, ClientOptions} from './generated/type.js';
|
|
163
|
+
|
|
164
|
+
class ApiKeyAuthenticatedPetstoreAPI extends SwaggerPetstoreOpenAPI30 {
|
|
165
|
+
constructor(options: ClientOptions & {apiKey: string}) {
|
|
166
|
+
super(options);
|
|
167
|
+
this.apiKey = options.apiKey;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private apiKey: string;
|
|
171
|
+
|
|
172
|
+
protected getBaseRequestOptions(): Partial<Omit<RequestInit, 'method' | 'body'>> {
|
|
173
|
+
const options = super.getBaseRequestOptions();
|
|
174
|
+
return {
|
|
175
|
+
...options,
|
|
176
|
+
headers: {
|
|
177
|
+
...((options.headers as Record<string, string>) || {}),
|
|
178
|
+
'X-API-Key': this.apiKey,
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Example: Using AbortController for Request Cancellation
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import {SwaggerPetstoreOpenAPI30, ClientOptions} from './generated/type.js';
|
|
189
|
+
|
|
190
|
+
class CancellablePetstoreAPI extends SwaggerPetstoreOpenAPI30 {
|
|
191
|
+
constructor(options: ClientOptions = {}) {
|
|
192
|
+
super(options);
|
|
193
|
+
}
|
|
194
|
+
private abortController: AbortController | null = null;
|
|
195
|
+
|
|
196
|
+
protected getBaseRequestOptions(): Partial<Omit<RequestInit, 'method' | 'body'>> {
|
|
197
|
+
const options = super.getBaseRequestOptions();
|
|
198
|
+
this.abortController = new AbortController();
|
|
199
|
+
return {
|
|
200
|
+
...options,
|
|
201
|
+
signal: this.abortController.signal,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
cancelRequests(): void {
|
|
206
|
+
if (this.abortController) {
|
|
207
|
+
this.abortController.abort();
|
|
208
|
+
this.abortController = null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Usage
|
|
214
|
+
const client = new CancellablePetstoreAPI();
|
|
215
|
+
const promise = client.findPetsByStatus('available');
|
|
216
|
+
// Later, cancel the request
|
|
217
|
+
client.cancelRequests();
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Example: Custom Credentials and CORS Mode
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
import {SwaggerPetstoreOpenAPI30, ClientOptions} from './generated/type.js';
|
|
224
|
+
|
|
225
|
+
class CustomCorsPetstoreAPI extends SwaggerPetstoreOpenAPI30 {
|
|
226
|
+
constructor(options: ClientOptions = {}) {
|
|
227
|
+
super(options);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
protected getBaseRequestOptions(): Partial<Omit<RequestInit, 'method' | 'body'>> {
|
|
231
|
+
const options = super.getBaseRequestOptions();
|
|
232
|
+
return {
|
|
233
|
+
...options,
|
|
234
|
+
credentials: 'include', // Include cookies in CORS requests
|
|
235
|
+
mode: 'cors', // Enable CORS
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Usage
|
|
241
|
+
const client = new CustomCorsPetstoreAPI({});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
<|tool▁call▁begin|>
|
|
245
|
+
grep
|
|
246
|
+
|
|
247
|
+
### Example: Complete Configuration (CORS, User-Agent, Authentication)
|
|
248
|
+
|
|
249
|
+
Here's a comprehensive example showing how to combine CORS settings, custom User-Agent, and authentication:
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
import {SwaggerPetstoreOpenAPI30, ClientOptions} from './generated/type.js';
|
|
253
|
+
|
|
254
|
+
class FullyConfiguredPetstoreAPI extends SwaggerPetstoreOpenAPI30 {
|
|
255
|
+
private accessToken: string | null = null;
|
|
256
|
+
private readonly userAgent: string;
|
|
257
|
+
|
|
258
|
+
constructor(options: ClientOptions & {userAgent?: string} = {}) {
|
|
259
|
+
super(options);
|
|
260
|
+
this.userAgent = options.userAgent || 'MyApp/1.0.0 (https://myapp.com)';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
protected getBaseRequestOptions(): Partial<Omit<RequestInit, 'method' | 'body'>> {
|
|
264
|
+
const options = super.getBaseRequestOptions();
|
|
265
|
+
|
|
266
|
+
// Build headers object
|
|
267
|
+
const headers: Record<string, string> = {
|
|
268
|
+
...((options.headers as Record<string, string>) || {}),
|
|
269
|
+
'User-Agent': this.userAgent,
|
|
270
|
+
...(this.accessToken ? {Authorization: `Bearer ${this.accessToken}`} : {}),
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
...options,
|
|
275
|
+
headers,
|
|
276
|
+
// CORS configuration
|
|
277
|
+
mode: 'cors', // Enable CORS
|
|
278
|
+
credentials: 'include', // Include cookies and credentials in CORS requests
|
|
279
|
+
// Cache control
|
|
280
|
+
cache: 'no-cache', // Don't cache requests
|
|
281
|
+
// Redirect handling
|
|
282
|
+
redirect: 'follow', // Follow redirects automatically
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
setAccessToken(token: string): void {
|
|
287
|
+
this.accessToken = token;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
clearAccessToken(): void {
|
|
291
|
+
this.accessToken = null;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Usage
|
|
296
|
+
const client = new FullyConfiguredPetstoreAPI({
|
|
297
|
+
userAgent: 'MyCustomApp/2.0.0 (Custom User Agent)',
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Set authentication token
|
|
301
|
+
client.setAccessToken('your-bearer-token-here');
|
|
302
|
+
|
|
303
|
+
// All requests will now include:
|
|
304
|
+
// - Custom User-Agent header
|
|
305
|
+
// - Authorization header (Bearer token)
|
|
306
|
+
// - CORS mode enabled
|
|
307
|
+
// - Credentials included
|
|
308
|
+
// - No caching
|
|
309
|
+
// - Automatic redirect following
|
|
310
|
+
const pets = await client.findPetsByStatus('available');
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Example: Environment-Specific Configuration
|
|
314
|
+
|
|
315
|
+
You can also create different configurations for different environments:
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import {SwaggerPetstoreOpenAPI30, ClientOptions} from './generated/type.js';
|
|
319
|
+
|
|
320
|
+
interface ClientConfig {
|
|
321
|
+
userAgent?: string;
|
|
322
|
+
enableCors?: boolean;
|
|
323
|
+
includeCredentials?: boolean;
|
|
324
|
+
cachePolicy?: RequestCache;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
class ConfigurablePetstoreAPI extends SwaggerPetstoreOpenAPI30 {
|
|
328
|
+
private accessToken: string | null = null;
|
|
329
|
+
private readonly config: Required<ClientConfig>;
|
|
330
|
+
|
|
331
|
+
constructor(options: ClientOptions & {config?: ClientConfig} = {}) {
|
|
332
|
+
super(options);
|
|
333
|
+
const config = options.config || {};
|
|
334
|
+
this.config = {
|
|
335
|
+
userAgent: config.userAgent || 'PetstoreAPIClient/1.0.0',
|
|
336
|
+
enableCors: config.enableCors ?? true,
|
|
337
|
+
includeCredentials: config.includeCredentials ?? true,
|
|
338
|
+
cachePolicy: config.cachePolicy || 'no-cache',
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
protected getBaseRequestOptions(): Partial<Omit<RequestInit, 'method' | 'body'>> {
|
|
343
|
+
const options = super.getBaseRequestOptions();
|
|
344
|
+
|
|
345
|
+
const headers: Record<string, string> = {
|
|
346
|
+
...((options.headers as Record<string, string>) || {}),
|
|
347
|
+
'User-Agent': this.config.userAgent,
|
|
348
|
+
...(this.accessToken ? {Authorization: `Bearer ${this.accessToken}`} : {}),
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const requestOptions: Partial<Omit<RequestInit, 'method' | 'body'>> = {
|
|
352
|
+
...options,
|
|
353
|
+
headers,
|
|
354
|
+
cache: this.config.cachePolicy,
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
// Conditionally add CORS options
|
|
358
|
+
if (this.config.enableCors) {
|
|
359
|
+
requestOptions.mode = 'cors';
|
|
360
|
+
if (this.config.includeCredentials) {
|
|
361
|
+
requestOptions.credentials = 'include';
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return requestOptions;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
setAccessToken(token: string): void {
|
|
369
|
+
this.accessToken = token;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Usage for production
|
|
374
|
+
const productionClient = new ConfigurablePetstoreAPI({
|
|
375
|
+
config: {
|
|
376
|
+
userAgent: 'MyApp/1.0.0 Production',
|
|
377
|
+
enableCors: true,
|
|
378
|
+
includeCredentials: true,
|
|
379
|
+
cachePolicy: 'default',
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// Usage for development
|
|
384
|
+
const devClient = new ConfigurablePetstoreAPI({
|
|
385
|
+
config: {
|
|
386
|
+
userAgent: 'MyApp/1.0.0 Development',
|
|
387
|
+
enableCors: true,
|
|
388
|
+
includeCredentials: false, // Don't send credentials in dev
|
|
389
|
+
cachePolicy: 'no-cache',
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
## How It Works
|
|
395
|
+
|
|
396
|
+
### Architecture Overview
|
|
397
|
+
|
|
398
|
+
The generated client uses a layered approach to request configuration:
|
|
399
|
+
|
|
400
|
+
1. **Base Options Layer**: `getBaseRequestOptions()` - Override this to set default options for all requests
|
|
401
|
+
2. **Request-Specific Layer**: Options passed to individual endpoint methods (via `options.headers`)
|
|
402
|
+
3. **Generated Layer**: Method, body, and Content-Type headers are set automatically
|
|
403
|
+
|
|
404
|
+
### Request Options Merging Order
|
|
405
|
+
|
|
406
|
+
When a request is made, options are merged in this order (later values override earlier ones):
|
|
407
|
+
|
|
408
|
+
1. **Base Options** from `getBaseRequestOptions()` (headers, signal, credentials, etc.)
|
|
409
|
+
2. **Content-Type Header** (automatically set based on request body: `application/json` or `application/x-www-form-urlencoded`)
|
|
410
|
+
3. **Request-Specific Headers** from `options.headers` parameter (if provided)
|
|
411
|
+
4. **Method and Body** (always set by generated code, cannot be overridden)
|
|
412
|
+
|
|
413
|
+
### Type Safety
|
|
414
|
+
|
|
415
|
+
The `getBaseRequestOptions()` method returns `Partial<Omit<RequestInit, 'method' | 'body'>>`, which means:
|
|
416
|
+
|
|
417
|
+
- ✅ **You CAN set**: `headers`, `signal`, `credentials`, `mode`, `cache`, `redirect`, `referrer`, `referrerPolicy`, `integrity`, `keepalive`
|
|
418
|
+
- ❌ **You CANNOT set**: `method` (controlled by endpoint), `body` (controlled by request data)
|
|
419
|
+
|
|
420
|
+
This ensures type safety while preventing accidental overrides of critical request properties.
|
|
421
|
+
|
|
422
|
+
### Header Merging Details
|
|
423
|
+
|
|
424
|
+
Headers are merged using `Object.assign()` with this priority:
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
const finalHeaders = Object.assign(
|
|
428
|
+
{}, // Start with empty object
|
|
429
|
+
baseOptions.headers || {}, // 1. Base headers from getBaseRequestOptions()
|
|
430
|
+
{'Content-Type': contentType}, // 2. Content-Type (may override base)
|
|
431
|
+
options.headers || {}, // 3. Request-specific headers (highest priority)
|
|
432
|
+
);
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**Important**: Always return `Record<string, string>` for headers in `getBaseRequestOptions()` for predictable merging behavior.
|
|
436
|
+
|
|
437
|
+
### Request Flow
|
|
438
|
+
|
|
439
|
+
```
|
|
440
|
+
User calls endpoint method
|
|
441
|
+
↓
|
|
442
|
+
getBaseRequestOptions() called → Returns base options
|
|
443
|
+
↓
|
|
444
|
+
#makeRequest() merges:
|
|
445
|
+
- Base options (headers, signal, credentials, etc.)
|
|
446
|
+
- Content-Type header
|
|
447
|
+
- Request-specific headers
|
|
448
|
+
- Method and body
|
|
449
|
+
↓
|
|
450
|
+
fetch() called with merged options
|
|
451
|
+
↓
|
|
452
|
+
Response returned and validated with Zod
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## Best Practices
|
|
456
|
+
|
|
457
|
+
### 1. Always Call Super Method
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
protected getBaseRequestOptions() {
|
|
461
|
+
const options = super.getBaseRequestOptions(); // ✅ Preserve base options
|
|
462
|
+
return { ...options, /* your additions */ };
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### 2. Proper Header Merging
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
protected getBaseRequestOptions() {
|
|
470
|
+
const options = super.getBaseRequestOptions();
|
|
471
|
+
return {
|
|
472
|
+
...options,
|
|
473
|
+
headers: {
|
|
474
|
+
...(options.headers as Record<string, string> || {}), // ✅ Handle undefined
|
|
475
|
+
'Authorization': `Bearer ${this.token}`,
|
|
476
|
+
},
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### 3. Store Sensitive Data Privately
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
class SecureAPI extends YourAPI {
|
|
485
|
+
private accessToken: string | null = null; // ✅ Private property
|
|
486
|
+
// Never expose tokens in public methods
|
|
487
|
+
}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### 4. Handle Token Expiration
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
protected getBaseRequestOptions() {
|
|
494
|
+
// ✅ Check expiration before using token
|
|
495
|
+
if (this.tokenExpiry && this.tokenExpiry <= new Date()) {
|
|
496
|
+
this.refreshToken();
|
|
497
|
+
}
|
|
498
|
+
// ... rest of implementation
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### 5. Provide Helper Methods
|
|
503
|
+
|
|
504
|
+
```typescript
|
|
505
|
+
class UserFriendlyAPI extends YourAPI {
|
|
506
|
+
// ✅ Provide convenient methods
|
|
507
|
+
async login(username: string, password: string) {
|
|
508
|
+
const response = await this.auth_login_post({username, password});
|
|
509
|
+
this.setAccessToken(response.token);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
logout() {
|
|
513
|
+
this.clearAccessToken();
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### 6. Use TypeScript Strictly
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
// ✅ Type your headers explicitly
|
|
522
|
+
const headers: Record<string, string> = {
|
|
523
|
+
'User-Agent': this.userAgent,
|
|
524
|
+
...(this.token ? {Authorization: `Bearer ${this.token}`} : {}),
|
|
525
|
+
};
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### 7. Environment-Specific Configuration
|
|
529
|
+
|
|
530
|
+
```typescript
|
|
531
|
+
// ✅ Different configs for different environments
|
|
532
|
+
const prodClient = new ConfigurableAPI({
|
|
533
|
+
config: {
|
|
534
|
+
userAgent: 'MyApp/1.0.0 Production',
|
|
535
|
+
enableCors: true,
|
|
536
|
+
},
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
const devClient = new ConfigurableAPI({
|
|
540
|
+
config: {
|
|
541
|
+
userAgent: 'MyApp/1.0.0 Development',
|
|
542
|
+
enableCors: false,
|
|
543
|
+
},
|
|
544
|
+
});
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### 8. Error Handling
|
|
548
|
+
|
|
549
|
+
```typescript
|
|
550
|
+
class RobustAPI extends YourAPI {
|
|
551
|
+
protected getBaseRequestOptions() {
|
|
552
|
+
const options = super.getBaseRequestOptions();
|
|
553
|
+
return {
|
|
554
|
+
...options,
|
|
555
|
+
signal: this.createAbortSignal(), // ✅ Handle cancellation
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
private createAbortSignal(): AbortSignal {
|
|
560
|
+
const controller = new AbortController();
|
|
561
|
+
// Set timeout, etc.
|
|
562
|
+
return controller.signal;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
## Common Patterns
|
|
568
|
+
|
|
569
|
+
### Pattern 1: Simple Authentication
|
|
570
|
+
|
|
571
|
+
```typescript
|
|
572
|
+
import {YourAPI, ClientOptions} from './generated/type.js';
|
|
573
|
+
|
|
574
|
+
class SimpleAuthAPI extends YourAPI {
|
|
575
|
+
private token: string | null = null;
|
|
576
|
+
|
|
577
|
+
constructor(options: ClientOptions = {}) {
|
|
578
|
+
super(options);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
protected getBaseRequestOptions() {
|
|
582
|
+
const options = super.getBaseRequestOptions();
|
|
583
|
+
return {
|
|
584
|
+
...options,
|
|
585
|
+
headers: {
|
|
586
|
+
...((options.headers as Record<string, string>) || {}),
|
|
587
|
+
...(this.token ? {Authorization: `Bearer ${this.token}`} : {}),
|
|
588
|
+
},
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Pattern 2: Multiple Headers
|
|
595
|
+
|
|
596
|
+
```typescript
|
|
597
|
+
import {YourAPI, ClientOptions} from './generated/type.js';
|
|
598
|
+
|
|
599
|
+
class MultiHeaderAPI extends YourAPI {
|
|
600
|
+
constructor(options: ClientOptions = {}) {
|
|
601
|
+
super(options);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
protected getBaseRequestOptions() {
|
|
605
|
+
const options = super.getBaseRequestOptions();
|
|
606
|
+
return {
|
|
607
|
+
...options,
|
|
608
|
+
headers: {
|
|
609
|
+
...((options.headers as Record<string, string>) || {}),
|
|
610
|
+
'User-Agent': 'MyApp/1.0.0',
|
|
611
|
+
'X-API-Version': 'v2',
|
|
612
|
+
'X-Request-ID': this.generateId(),
|
|
613
|
+
},
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
### Pattern 3: Conditional Options
|
|
620
|
+
|
|
621
|
+
```typescript
|
|
622
|
+
import {YourAPI, ClientOptions} from './generated/type.js';
|
|
623
|
+
|
|
624
|
+
class ConditionalAPI extends YourAPI {
|
|
625
|
+
constructor(options: ClientOptions = {}) {
|
|
626
|
+
super(options);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
protected getBaseRequestOptions() {
|
|
630
|
+
const options = super.getBaseRequestOptions();
|
|
631
|
+
const config: Partial<Omit<RequestInit, 'method' | 'body'>> = {...options};
|
|
632
|
+
|
|
633
|
+
if (this.needsCors) {
|
|
634
|
+
config.mode = 'cors';
|
|
635
|
+
config.credentials = 'include';
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return config;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
## Troubleshooting
|
|
644
|
+
|
|
645
|
+
### Headers Not Being Applied
|
|
646
|
+
|
|
647
|
+
**Problem**: Custom headers aren't appearing in requests.
|
|
648
|
+
|
|
649
|
+
**Solution**: Ensure you're spreading base options and handling undefined:
|
|
650
|
+
|
|
651
|
+
```typescript
|
|
652
|
+
headers: {
|
|
653
|
+
...(options.headers as Record<string, string> || {}), // ✅ Handle undefined
|
|
654
|
+
'Your-Header': 'value',
|
|
655
|
+
}
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
### CORS Errors
|
|
659
|
+
|
|
660
|
+
**Problem**: CORS errors when making requests.
|
|
661
|
+
|
|
662
|
+
**Solution**: Set CORS options in `getBaseRequestOptions()`:
|
|
663
|
+
|
|
664
|
+
```typescript
|
|
665
|
+
return {
|
|
666
|
+
...options,
|
|
667
|
+
mode: 'cors',
|
|
668
|
+
credentials: 'include', // If needed
|
|
669
|
+
};
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
### Token Not Persisting
|
|
673
|
+
|
|
674
|
+
**Problem**: Token is lost between requests.
|
|
675
|
+
|
|
676
|
+
**Solution**: Store token as instance property:
|
|
677
|
+
|
|
678
|
+
```typescript
|
|
679
|
+
import {YourAPI, ClientOptions} from './generated/type.js';
|
|
680
|
+
|
|
681
|
+
class MyAPI extends YourAPI {
|
|
682
|
+
private token: string | null = null; // ✅ Instance property
|
|
683
|
+
|
|
684
|
+
constructor(options: ClientOptions = {}) {
|
|
685
|
+
super(options);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
setToken(token: string) {
|
|
689
|
+
this.token = token; // ✅ Persists across requests
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
### Type Errors
|
|
695
|
+
|
|
696
|
+
**Problem**: TypeScript errors when overriding.
|
|
697
|
+
|
|
698
|
+
**Solution**: Ensure return type matches exactly:
|
|
699
|
+
|
|
700
|
+
```typescript
|
|
701
|
+
protected getBaseRequestOptions(): Partial<Omit<RequestInit, 'method' | 'body'>> {
|
|
702
|
+
// ✅ Correct return type
|
|
703
|
+
}
|
|
704
|
+
```
|