zod-codegen 1.5.1 → 1.6.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.
Files changed (100) hide show
  1. package/.github/workflows/ci.yml +6 -0
  2. package/.github/workflows/release.yml +3 -3
  3. package/CHANGELOG.md +29 -0
  4. package/CONTRIBUTING.md +1 -1
  5. package/EXAMPLES.md +91 -12
  6. package/README.md +11 -4
  7. package/dist/scripts/add-js-extensions.d.ts +2 -0
  8. package/dist/scripts/add-js-extensions.d.ts.map +1 -0
  9. package/dist/scripts/add-js-extensions.js +66 -0
  10. package/dist/scripts/update-manifest.d.ts +14 -0
  11. package/dist/scripts/update-manifest.d.ts.map +1 -0
  12. package/dist/scripts/update-manifest.js +33 -0
  13. package/dist/src/assets/manifest.json +1 -1
  14. package/dist/src/cli.js +3 -3
  15. package/dist/src/generator.d.ts +46 -4
  16. package/dist/src/generator.d.ts.map +1 -1
  17. package/dist/src/generator.js +43 -1
  18. package/dist/src/interfaces/code-generator.d.ts +1 -1
  19. package/dist/src/interfaces/code-generator.d.ts.map +1 -1
  20. package/dist/src/services/code-generator.service.d.ts +5 -3
  21. package/dist/src/services/code-generator.service.d.ts.map +1 -1
  22. package/dist/src/services/code-generator.service.js +69 -1
  23. package/dist/src/services/file-reader.service.d.ts +2 -2
  24. package/dist/src/services/file-reader.service.d.ts.map +1 -1
  25. package/dist/src/services/file-writer.service.d.ts +1 -1
  26. package/dist/src/services/file-writer.service.d.ts.map +1 -1
  27. package/dist/src/services/import-builder.service.d.ts +1 -1
  28. package/dist/src/services/import-builder.service.d.ts.map +1 -1
  29. package/dist/src/services/type-builder.service.d.ts +1 -1
  30. package/dist/src/services/type-builder.service.d.ts.map +1 -1
  31. package/dist/src/types/generator-options.d.ts +1 -1
  32. package/dist/src/types/generator-options.d.ts.map +1 -1
  33. package/dist/src/utils/error-handler.d.ts +3 -2
  34. package/dist/src/utils/error-handler.d.ts.map +1 -1
  35. package/dist/src/utils/error-handler.js +4 -4
  36. package/dist/src/utils/reporter.d.ts +3 -2
  37. package/dist/src/utils/reporter.d.ts.map +1 -1
  38. package/dist/src/utils/reporter.js +7 -5
  39. package/dist/src/utils/signal-handler.d.ts +3 -2
  40. package/dist/src/utils/signal-handler.d.ts.map +1 -1
  41. package/dist/src/utils/signal-handler.js +4 -4
  42. package/examples/README.md +10 -1
  43. package/examples/petstore/README.md +6 -6
  44. package/examples/petstore/authenticated-usage.ts +1 -1
  45. package/examples/petstore/basic-usage.ts +1 -1
  46. package/examples/petstore/retry-handler-usage.ts +173 -0
  47. package/examples/petstore/server-variables-usage.ts +1 -1
  48. package/examples/petstore/type.ts +68 -47
  49. package/examples/pokeapi/README.md +3 -3
  50. package/examples/pokeapi/basic-usage.ts +1 -1
  51. package/examples/pokeapi/custom-client.ts +1 -1
  52. package/generated/type.ts +323 -0
  53. package/package.json +4 -7
  54. package/scripts/add-js-extensions.ts +79 -0
  55. package/scripts/update-manifest.ts +4 -2
  56. package/src/assets/manifest.json +1 -1
  57. package/src/cli.ts +7 -7
  58. package/src/generator.ts +51 -9
  59. package/src/interfaces/code-generator.ts +1 -1
  60. package/src/services/code-generator.service.ts +114 -8
  61. package/src/services/file-reader.service.ts +3 -3
  62. package/src/services/file-writer.service.ts +1 -1
  63. package/src/services/import-builder.service.ts +1 -1
  64. package/src/services/type-builder.service.ts +1 -1
  65. package/src/types/generator-options.ts +1 -1
  66. package/src/utils/error-handler.ts +6 -5
  67. package/src/utils/reporter.ts +6 -3
  68. package/src/utils/signal-handler.ts +10 -8
  69. package/tests/integration/cli-comprehensive.test.ts +123 -0
  70. package/tests/integration/cli.test.ts +2 -2
  71. package/tests/integration/error-scenarios.test.ts +240 -0
  72. package/tests/integration/snapshots.test.ts +131 -0
  73. package/tests/unit/code-generator-edge-cases.test.ts +551 -0
  74. package/tests/unit/code-generator.test.ts +385 -2
  75. package/tests/unit/file-reader.test.ts +16 -1
  76. package/tests/unit/generator.test.ts +19 -2
  77. package/tests/unit/naming-convention.test.ts +30 -1
  78. package/tests/unit/reporter.test.ts +63 -0
  79. package/tests/unit/type-builder.test.ts +131 -0
  80. package/tsconfig.json +3 -3
  81. package/dist/src/http/fetch-client.d.ts +0 -15
  82. package/dist/src/http/fetch-client.d.ts.map +0 -1
  83. package/dist/src/http/fetch-client.js +0 -140
  84. package/dist/src/polyfills/fetch.d.ts +0 -5
  85. package/dist/src/polyfills/fetch.d.ts.map +0 -1
  86. package/dist/src/polyfills/fetch.js +0 -18
  87. package/dist/src/types/http.d.ts +0 -25
  88. package/dist/src/types/http.d.ts.map +0 -1
  89. package/dist/src/types/http.js +0 -10
  90. package/dist/src/utils/manifest.d.ts +0 -8
  91. package/dist/src/utils/manifest.d.ts.map +0 -1
  92. package/dist/src/utils/manifest.js +0 -9
  93. package/dist/src/utils/tty.d.ts +0 -2
  94. package/dist/src/utils/tty.d.ts.map +0 -1
  95. package/dist/src/utils/tty.js +0 -3
  96. package/src/http/fetch-client.ts +0 -181
  97. package/src/polyfills/fetch.ts +0 -26
  98. package/src/types/http.ts +0 -35
  99. package/src/utils/manifest.ts +0 -17
  100. package/src/utils/tty.ts +0 -3
@@ -1 +1 @@
1
- {"version":3,"file":"code-generator.service.d.ts","sourceRoot":"","sources":["../../../src/services/code-generator.service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,KAAK,EAAC,aAAa,EAAE,aAAa,EAAC,MAAM,iCAAiC,CAAC;AAClF,OAAO,KAAK,EAAmB,eAAe,EAAgB,MAAM,qBAAqB,CAAC;AAC1F,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,+BAA+B,CAAC;AAWpE,qBAAa,8BAA+B,YAAW,aAAa,EAAE,aAAa;IACjF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsC;IAClE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAwC;IACtE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwD;IAChF,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA+B;IAChE,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAuC;IAGhF,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,iBAAiB,CAAuB;gBAEpC,OAAO,GAAE,gBAAqB;IAK1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAGpB;IAEH,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM;IAMvC,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,UAAO,GAAG,EAAE,CAAC,cAAc,GAAG,EAAE,CAAC,UAAU;IA2BhF,OAAO,CAAC,QAAQ;IAmBhB,OAAO,CAAC,YAAY;IA2CpB,OAAO,CAAC,sBAAsB;IAe9B,OAAO,CAAC,gBAAgB;IAsBxB,OAAO,CAAC,gBAAgB;IAmGxB,OAAO,CAAC,gCAAgC;IAwBxC,OAAO,CAAC,sBAAsB;IAqmB9B,OAAO,CAAC,kBAAkB;IAsB1B;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IA0B9B,OAAO,CAAC,mBAAmB;IAgH3B,OAAO,CAAC,mBAAmB;IA6D3B,OAAO,CAAC,qBAAqB;IAwF7B,OAAO,CAAC,gBAAgB;IA0CxB,OAAO,CAAC,iBAAiB;IAmCzB,OAAO,CAAC,iBAAiB;IA8BzB,OAAO,CAAC,eAAe;IAuDvB,OAAO,CAAC,wBAAwB;IA4IhC,OAAO,CAAC,gBAAgB;IAqBxB,OAAO,CAAC,6BAA6B;IAkPrC,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,aAAa;IAMrB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAuFzB,OAAO,CAAC,WAAW;IA0CnB,OAAO,CAAC,aAAa;IAmjBrB,OAAO,CAAC,iBAAiB;IAuCzB,OAAO,CAAC,qBAAqB;IAe7B,OAAO,CAAC,oBAAoB;IAyE5B,OAAO,CAAC,8BAA8B;IActC,OAAO,CAAC,wBAAwB;IAyBhC,OAAO,CAAC,yBAAyB;IA8BjC,OAAO,CAAC,wBAAwB;IAYhC,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,kBAAkB;IA8B1B;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAmB3B;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;IAiFlC,OAAO,CAAC,eAAe;CAuCxB"}
1
+ {"version":3,"file":"code-generator.service.d.ts","sourceRoot":"","sources":["../../../src/services/code-generator.service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,KAAK,EAAC,aAAa,EAAE,aAAa,EAAC,MAAM,8BAA8B,CAAC;AAC/E,OAAO,KAAK,EAAmB,eAAe,EAAgB,MAAM,kBAAkB,CAAC;AACvF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,4BAA4B,CAAC;AAWjE,qBAAa,8BAA+B,YAAW,aAAa,EAAE,aAAa;IACjF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsC;IAClE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAwC;IACtE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwD;IAChF,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA+B;IAChE,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAuC;IAGhF,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,iBAAiB,CAAuB;gBAEpC,OAAO,GAAE,gBAAqB;IAK1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAGpB;IAEH,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM;IAMvC,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,UAAO,GAAG,EAAE,CAAC,cAAc,GAAG,EAAE,CAAC,UAAU;IA2BhF,OAAO,CAAC,QAAQ;IAmBhB,OAAO,CAAC,YAAY;IA2CpB,OAAO,CAAC,sBAAsB;IAe9B,OAAO,CAAC,gBAAgB;IAuBxB,OAAO,CAAC,gBAAgB;IAmGxB,OAAO,CAAC,gCAAgC;IAwBxC,OAAO,CAAC,yBAAyB;IAuBjC,OAAO,CAAC,sBAAsB;IAkoB9B,OAAO,CAAC,kBAAkB;IAgE1B;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;IAoC9B,OAAO,CAAC,mBAAmB;IAgH3B,OAAO,CAAC,mBAAmB;IA6D3B,OAAO,CAAC,qBAAqB;IAwF7B,OAAO,CAAC,gBAAgB;IA0CxB,OAAO,CAAC,iBAAiB;IAmCzB,OAAO,CAAC,iBAAiB;IA8BzB,OAAO,CAAC,eAAe;IAuDvB,OAAO,CAAC,wBAAwB;IA4IhC,OAAO,CAAC,gBAAgB;IAqBxB,OAAO,CAAC,6BAA6B;IAkPrC,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,aAAa;IAMrB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAuFzB,OAAO,CAAC,WAAW;IA0CnB,OAAO,CAAC,aAAa;IAmjBrB,OAAO,CAAC,iBAAiB;IAuCzB,OAAO,CAAC,qBAAqB;IAe7B,OAAO,CAAC,oBAAoB;IAyE5B,OAAO,CAAC,8BAA8B;IActC,OAAO,CAAC,wBAAwB;IAyBhC,OAAO,CAAC,yBAAyB;IA8BjC,OAAO,CAAC,wBAAwB;IAYhC,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,kBAAkB;IA8B1B;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAmB3B;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;IAiFlC,OAAO,CAAC,eAAe;CAuCxB"}
@@ -101,6 +101,7 @@ export class TypeScriptCodeGeneratorService {
101
101
  this.typeBuilder.createProperty('#baseUrl', 'string', true),
102
102
  this.buildConstructor(openapi),
103
103
  this.buildGetBaseRequestOptionsMethod(),
104
+ this.buildHandleResponseMethod(),
104
105
  this.buildHttpRequestMethod(),
105
106
  ...methods,
106
107
  ]);
@@ -142,6 +143,16 @@ export class TypeScriptCodeGeneratorService {
142
143
  ]),
143
144
  ]), ts.factory.createBlock([ts.factory.createReturnStatement(ts.factory.createObjectLiteralExpression([], false))], true));
144
145
  }
146
+ buildHandleResponseMethod() {
147
+ return ts.factory.createMethodDeclaration([ts.factory.createToken(ts.SyntaxKind.ProtectedKeyword), ts.factory.createToken(ts.SyntaxKind.AsyncKeyword)], undefined, ts.factory.createIdentifier('handleResponse'), undefined, [this.typeBuilder.createGenericType('T')], [
148
+ this.typeBuilder.createParameter('response', 'Response'),
149
+ this.typeBuilder.createParameter('method', 'string'),
150
+ this.typeBuilder.createParameter('path', 'string'),
151
+ this.typeBuilder.createParameter('options', '{params?: Record<string, string | number | boolean>; data?: unknown; contentType?: string; headers?: Record<string, string>}'),
152
+ ], ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Promise'), [
153
+ ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Response'), undefined),
154
+ ]), ts.factory.createBlock([ts.factory.createReturnStatement(ts.factory.createIdentifier('response'))], true));
155
+ }
145
156
  buildHttpRequestMethod() {
146
157
  return ts.factory.createMethodDeclaration([ts.factory.createToken(ts.SyntaxKind.ProtectedKeyword), ts.factory.createToken(ts.SyntaxKind.AsyncKeyword)], undefined, ts.factory.createIdentifier('makeRequest'), undefined, [this.typeBuilder.createGenericType('T')], [
147
158
  this.typeBuilder.createParameter('method', 'string'),
@@ -242,7 +253,7 @@ export class TypeScriptCodeGeneratorService {
242
253
  ], ts.NodeFlags.Const)),
243
254
  // Make fetch request: merge base options with method, headers, and body
244
255
  ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList([
245
- ts.factory.createVariableDeclaration(ts.factory.createIdentifier('response'), undefined, undefined, ts.factory.createAwaitExpression(ts.factory.createCallExpression(ts.factory.createIdentifier('fetch'), undefined, [
256
+ ts.factory.createVariableDeclaration(ts.factory.createIdentifier('rawResponse'), undefined, undefined, ts.factory.createAwaitExpression(ts.factory.createCallExpression(ts.factory.createIdentifier('fetch'), undefined, [
246
257
  ts.factory.createIdentifier('url'),
247
258
  ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('Object'), ts.factory.createIdentifier('assign')), undefined, [
248
259
  ts.factory.createObjectLiteralExpression([], false),
@@ -255,6 +266,15 @@ export class TypeScriptCodeGeneratorService {
255
266
  ]),
256
267
  ]))),
257
268
  ], ts.NodeFlags.Const)),
269
+ // Handle response through hook (allows subclasses to intercept and modify response)
270
+ ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList([
271
+ ts.factory.createVariableDeclaration(ts.factory.createIdentifier('response'), undefined, undefined, ts.factory.createAwaitExpression(ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createThis(), ts.factory.createIdentifier('handleResponse')), [ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('T'), undefined)], [
272
+ ts.factory.createIdentifier('rawResponse'),
273
+ ts.factory.createIdentifier('method'),
274
+ ts.factory.createIdentifier('path'),
275
+ ts.factory.createIdentifier('options'),
276
+ ]))),
277
+ ], ts.NodeFlags.Const)),
258
278
  // Check response status
259
279
  ts.factory.createIfStatement(ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.ExclamationToken, ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('response'), ts.factory.createIdentifier('ok'))), ts.factory.createThrowStatement(ts.factory.createNewExpression(ts.factory.createIdentifier('Error'), undefined, [
260
280
  ts.factory.createTemplateExpression(ts.factory.createTemplateHead('HTTP ', 'HTTP '), [
@@ -267,6 +287,27 @@ export class TypeScriptCodeGeneratorService {
267
287
  ], true));
268
288
  }
269
289
  buildClientMethods(openapi, schemas) {
290
+ // Track operation IDs to detect duplicates
291
+ const operationIdMap = new Map();
292
+ // First pass: collect all operation IDs and their methods/paths
293
+ Object.entries(openapi.paths).forEach(([path, pathItem]) => {
294
+ Object.entries(pathItem)
295
+ .filter(([method]) => ['get', 'post', 'put', 'patch', 'delete', 'head', 'options'].includes(method))
296
+ .forEach(([method, methodSchema]) => {
297
+ const safeMethodSchema = MethodSchema.parse(methodSchema);
298
+ if (safeMethodSchema.operationId) {
299
+ const operationId = safeMethodSchema.operationId;
300
+ const existing = operationIdMap.get(operationId);
301
+ if (existing) {
302
+ existing.push({ method, path });
303
+ }
304
+ else {
305
+ operationIdMap.set(operationId, [{ method, path }]);
306
+ }
307
+ }
308
+ });
309
+ });
310
+ // Second pass: build methods, appending method name for HEAD/OPTIONS or when duplicates exist
270
311
  return Object.entries(openapi.paths).reduce((endpoints, [path, pathItem]) => {
271
312
  const methods = Object.entries(pathItem)
272
313
  .filter(([method]) => ['get', 'post', 'put', 'patch', 'delete', 'head', 'options'].includes(method))
@@ -275,6 +316,23 @@ export class TypeScriptCodeGeneratorService {
275
316
  if (!safeMethodSchema.operationId) {
276
317
  return null;
277
318
  }
319
+ const operationId = safeMethodSchema.operationId;
320
+ const methodLower = method.toLowerCase();
321
+ // Check if this operationId is used by multiple methods
322
+ const operations = operationIdMap.get(operationId);
323
+ const hasDuplicates = operations !== undefined && operations.length > 1;
324
+ // For HEAD/OPTIONS or when duplicates exist, we need to ensure uniqueness
325
+ // We'll handle this in transformOperationName by appending the method
326
+ // But we need to mark it here so transformOperationName knows to append
327
+ if (hasDuplicates || methodLower === 'head' || methodLower === 'options') {
328
+ // Temporarily modify the operationId to include method for uniqueness
329
+ // This will be handled in transformOperationName
330
+ const modifiedSchema = {
331
+ ...safeMethodSchema,
332
+ operationId: `${operationId}_${methodLower}`,
333
+ };
334
+ return this.buildEndpointMethod(method, path, modifiedSchema, schemas);
335
+ }
278
336
  return this.buildEndpointMethod(method, path, safeMethodSchema, schemas);
279
337
  })
280
338
  .filter((method) => method !== null);
@@ -284,6 +342,7 @@ export class TypeScriptCodeGeneratorService {
284
342
  /**
285
343
  * Transforms operation ID according to the configured naming convention or transformer
286
344
  * Ensures the result is a valid TypeScript identifier
345
+ * For HEAD and OPTIONS methods, appends the method name to ensure uniqueness when same operationId is used
287
346
  */
288
347
  transformOperationName(operationId, method, path, schema) {
289
348
  let transformed;
@@ -307,6 +366,15 @@ export class TypeScriptCodeGeneratorService {
307
366
  // Return original operationId if no transformation is configured
308
367
  transformed = operationId;
309
368
  }
369
+ // For HEAD and OPTIONS methods, append method name to ensure uniqueness
370
+ // This prevents duplicate method names when GET and HEAD share the same operationId
371
+ const methodLower = method.toLowerCase();
372
+ if (methodLower === 'head' || methodLower === 'options') {
373
+ // Only append if not already present to avoid double-appending
374
+ if (!transformed.toLowerCase().endsWith(`_${methodLower}`)) {
375
+ transformed = `${transformed}_${methodLower}`;
376
+ }
377
+ }
310
378
  // Sanitize to ensure valid TypeScript identifier (handles edge cases from custom transformers)
311
379
  return this.typeBuilder.sanitizeIdentifier(transformed);
312
380
  }
@@ -1,5 +1,5 @@
1
- import type { OpenApiFileParser, OpenApiFileReader } from '../interfaces/file-reader.js';
2
- import type { OpenApiSpecType } from '../types/openapi.js';
1
+ import type { OpenApiFileParser, OpenApiFileReader } from '../interfaces/file-reader';
2
+ import type { OpenApiSpecType } from '../types/openapi';
3
3
  export declare class SyncFileReaderService implements OpenApiFileReader {
4
4
  readFile(path: string): Promise<string>;
5
5
  }
@@ -1 +1 @@
1
- {"version":3,"file":"file-reader.service.d.ts","sourceRoot":"","sources":["../../../src/services/file-reader.service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,iBAAiB,EAAE,iBAAiB,EAAC,MAAM,8BAA8B,CAAC;AACvF,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,qBAAqB,CAAC;AAGzD,qBAAa,qBAAsB,YAAW,iBAAiB;IACvD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAe9C;AAED,qBAAa,wBAAyB,YAAW,iBAAiB,CAAC,eAAe,CAAC;IACjF,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe;CAgBvC"}
1
+ {"version":3,"file":"file-reader.service.d.ts","sourceRoot":"","sources":["../../../src/services/file-reader.service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,iBAAiB,EAAE,iBAAiB,EAAC,MAAM,2BAA2B,CAAC;AACpF,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,kBAAkB,CAAC;AAGtD,qBAAa,qBAAsB,YAAW,iBAAiB;IACvD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAe9C;AAED,qBAAa,wBAAyB,YAAW,iBAAiB,CAAC,eAAe,CAAC;IACjF,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe;CAgBvC"}
@@ -1,4 +1,4 @@
1
- import type { FileWriter } from '../interfaces/code-generator.js';
1
+ import type { FileWriter } from '../interfaces/code-generator';
2
2
  export declare class SyncFileWriterService implements FileWriter {
3
3
  private readonly name;
4
4
  private readonly version;
@@ -1 +1 @@
1
- {"version":3,"file":"file-writer.service.d.ts","sourceRoot":"","sources":["../../../src/services/file-writer.service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,iCAAiC,CAAC;AAEhE,qBAAa,qBAAsB,YAAW,UAAU;IAEpD,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAFT,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM;IAGpC,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAqBlD,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,SAAY,GAAG,MAAM;CAGnE"}
1
+ {"version":3,"file":"file-writer.service.d.ts","sourceRoot":"","sources":["../../../src/services/file-writer.service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,8BAA8B,CAAC;AAE7D,qBAAa,qBAAsB,YAAW,UAAU;IAEpD,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAFT,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM;IAGpC,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAqBlD,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,SAAY,GAAG,MAAM;CAGnE"}
@@ -1,6 +1,6 @@
1
1
  import * as ts from 'typescript';
2
2
  import { z } from 'zod';
3
- import type { ImportBuilder } from '../interfaces/code-generator.js';
3
+ import type { ImportBuilder } from '../interfaces/code-generator';
4
4
  declare const ImportOptions: z.ZodObject<{
5
5
  defaultImport: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
6
6
  namedImports: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
@@ -1 +1 @@
1
- {"version":3,"file":"import-builder.service.d.ts","sourceRoot":"","sources":["../../../src/services/import-builder.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,iCAAiC,CAAC;AAKnE,QAAA,MAAM,aAAa;;;iBAGjB,CAAC;AAEH,KAAK,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEvD,qBAAa,8BAA+B,YAAW,aAAa;IAClE,YAAY,IAAI,EAAE,CAAC,iBAAiB,EAAE;IAQtC,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,EAAE,CAAC,iBAAiB;CAqC/E"}
1
+ {"version":3,"file":"import-builder.service.d.ts","sourceRoot":"","sources":["../../../src/services/import-builder.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,8BAA8B,CAAC;AAKhE,QAAA,MAAM,aAAa;;;iBAGjB,CAAC;AAEH,KAAK,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEvD,qBAAa,8BAA+B,YAAW,aAAa;IAClE,YAAY,IAAI,EAAE,CAAC,iBAAiB,EAAE;IAQtC,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,EAAE,CAAC,iBAAiB;CAqC/E"}
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import type { TypeBuilder } from '../interfaces/code-generator.js';
2
+ import type { TypeBuilder } from '../interfaces/code-generator';
3
3
  export declare class TypeScriptTypeBuilderService implements TypeBuilder {
4
4
  buildType(type: string): ts.TypeNode;
5
5
  createProperty(name: string, type: string, isReadonly?: boolean): ts.PropertyDeclaration;
@@ -1 +1 @@
1
- {"version":3,"file":"type-builder.service.d.ts","sourceRoot":"","sources":["../../../src/services/type-builder.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,iCAAiC,CAAC;AAEjE,qBAAa,4BAA6B,YAAW,WAAW;IAC9D,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,EAAE,CAAC,QAAQ;IA6BpC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,UAAQ,GAAG,EAAE,CAAC,mBAAmB;IAYtF,eAAe,CACb,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC,QAAQ,EAC3B,YAAY,CAAC,EAAE,EAAE,CAAC,UAAU,EAC5B,UAAU,UAAQ,GACjB,EAAE,CAAC,oBAAoB;IAW1B,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,EAAE,CAAC,wBAAwB;IAS5D,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAMxC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAIjC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAGnC"}
1
+ {"version":3,"file":"type-builder.service.d.ts","sourceRoot":"","sources":["../../../src/services/type-builder.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,8BAA8B,CAAC;AAE9D,qBAAa,4BAA6B,YAAW,WAAW;IAC9D,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,EAAE,CAAC,QAAQ;IA6BpC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,UAAQ,GAAG,EAAE,CAAC,mBAAmB;IAYtF,eAAe,CACb,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC,QAAQ,EAC3B,YAAY,CAAC,EAAE,EAAE,CAAC,UAAU,EAC5B,UAAU,UAAQ,GACjB,EAAE,CAAC,oBAAoB;IAW1B,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,EAAE,CAAC,wBAAwB;IAS5D,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAMxC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAIjC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAGnC"}
@@ -1,4 +1,4 @@
1
- import type { NamingConvention, OperationNameTransformer } from '../utils/naming-convention.js';
1
+ import type { NamingConvention, OperationNameTransformer } from '../utils/naming-convention';
2
2
  /**
3
3
  * Configuration options for the Generator class.
4
4
  *
@@ -1 +1 @@
1
- {"version":3,"file":"generator-options.d.ts","sourceRoot":"","sources":["../../../src/types/generator-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,gBAAgB,EAAE,wBAAwB,EAAC,MAAM,+BAA+B,CAAC;AAE9F;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;;;;OAYG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAEpC;;;;;;;;;;;;;;;;;;OAkBG;IACH,wBAAwB,CAAC,EAAE,wBAAwB,CAAC;CACrD"}
1
+ {"version":3,"file":"generator-options.d.ts","sourceRoot":"","sources":["../../../src/types/generator-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,gBAAgB,EAAE,wBAAwB,EAAC,MAAM,4BAA4B,CAAC;AAE3F;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;;;;OAYG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAEpC;;;;;;;;;;;;;;;;;;OAkBG;IACH,wBAAwB,CAAC,EAAE,wBAAwB,CAAC;CACrD"}
@@ -1,3 +1,4 @@
1
- export declare const errorReceived: (process: NodeJS.Process, startTime: bigint) => () => void;
2
- export declare const handleErrors: (process: NodeJS.Process, startTime: bigint) => void;
1
+ import type { Reporter } from './reporter';
2
+ export declare const errorReceived: (process: NodeJS.Process, startTime: bigint, reporter: Reporter) => () => void;
3
+ export declare const handleErrors: (process: NodeJS.Process, startTime: bigint, reporter: Reporter) => void;
3
4
  //# sourceMappingURL=error-handler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../../src/utils/error-handler.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,CAAC,OAAO,EAAE,WAAW,MAAM,WAAS,IAGhF,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,SAAS,MAAM,CAAC,OAAO,EAAE,WAAW,MAAM,KAAG,IAKzE,CAAC"}
1
+ {"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../../src/utils/error-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,YAAY,CAAC;AAEzC,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,CAAC,OAAO,EAAE,WAAW,MAAM,EAAE,UAAU,QAAQ,WAAS,IAGpG,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,SAAS,MAAM,CAAC,OAAO,EAAE,WAAW,MAAM,EAAE,UAAU,QAAQ,KAAG,IAK7F,CAAC"}
@@ -1,11 +1,11 @@
1
1
  import { getExecutionTime } from './execution-time.js';
2
- export const errorReceived = (process, startTime) => () => {
3
- console.log(`Done after ${String(getExecutionTime(startTime))}s`);
2
+ export const errorReceived = (process, startTime, reporter) => () => {
3
+ reporter.log(`Done after ${String(getExecutionTime(startTime))}s`);
4
4
  process.exit(1);
5
5
  };
6
- export const handleErrors = (process, startTime) => {
6
+ export const handleErrors = (process, startTime, reporter) => {
7
7
  const catchErrors = ['unhandledRejection', 'uncaughtException'];
8
8
  catchErrors.forEach((event) => {
9
- process.on(event, errorReceived(process, startTime));
9
+ process.on(event, errorReceived(process, startTime, reporter));
10
10
  });
11
11
  };
@@ -1,6 +1,7 @@
1
1
  export declare class Reporter {
2
- private readonly _stdout;
3
- constructor(_stdout: NodeJS.WriteStream);
2
+ private readonly stdout;
3
+ private readonly stderr;
4
+ constructor(stdout: NodeJS.WriteStream, stderr?: NodeJS.WriteStream);
4
5
  log(...args: readonly unknown[]): void;
5
6
  error(...args: readonly unknown[]): void;
6
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../../src/utils/reporter.ts"],"names":[],"mappings":"AAEA,qBAAa,QAAQ;IACP,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,MAAM,CAAC,WAAW;IAKxD,GAAG,CAAC,GAAG,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI;IAItC,KAAK,CAAC,GAAG,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI;CAGzC"}
1
+ {"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../../src/utils/reporter.ts"],"names":[],"mappings":"AAEA,qBAAa,QAAQ;IAEjB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,MAAM,EAAE,MAAM,CAAC,WAAW,EAC1B,MAAM,GAAE,MAAM,CAAC,WAAoB;IAMtD,GAAG,CAAC,GAAG,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI;IAItC,KAAK,CAAC,GAAG,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI;CAGzC"}
@@ -1,15 +1,17 @@
1
1
  import { format } from 'node:util';
2
2
  export class Reporter {
3
- _stdout;
4
- constructor(_stdout) {
5
- this._stdout = _stdout;
3
+ stdout;
4
+ stderr;
5
+ constructor(stdout, stderr = stdout) {
6
+ this.stdout = stdout;
7
+ this.stderr = stderr;
6
8
  this.log = this.log.bind(this);
7
9
  this.error = this.error.bind(this);
8
10
  }
9
11
  log(...args) {
10
- this._stdout.write(format(...args) + '\n');
12
+ this.stdout.write(format(...args) + '\n');
11
13
  }
12
14
  error(...args) {
13
- this._stdout.write(format(...args) + '\n');
15
+ this.stderr.write(format(...args) + '\n');
14
16
  }
15
17
  }
@@ -1,3 +1,4 @@
1
- export declare const signalReceived: (process: NodeJS.Process, startTime: bigint, event: NodeJS.Signals) => () => void;
2
- export declare const handleSignals: (process: NodeJS.Process, startTime: bigint) => void;
1
+ import type { Reporter } from './reporter';
2
+ export declare const signalReceived: (process: NodeJS.Process, startTime: bigint, event: NodeJS.Signals, reporter: Reporter) => () => void;
3
+ export declare const handleSignals: (process: NodeJS.Process, startTime: bigint, reporter: Reporter) => void;
3
4
  //# sourceMappingURL=signal-handler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"signal-handler.d.ts","sourceRoot":"","sources":["../../../src/utils/signal-handler.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,GAAI,SAAS,MAAM,CAAC,OAAO,EAAE,WAAW,MAAM,EAAE,OAAO,MAAM,CAAC,OAAO,WAAS,IAIxG,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,CAAC,OAAO,EAAE,WAAW,MAAM,KAAG,IAK1E,CAAC"}
1
+ {"version":3,"file":"signal-handler.d.ts","sourceRoot":"","sources":["../../../src/utils/signal-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,YAAY,CAAC;AAEzC,eAAO,MAAM,cAAc,GACxB,SAAS,MAAM,CAAC,OAAO,EAAE,WAAW,MAAM,EAAE,OAAO,MAAM,CAAC,OAAO,EAAE,UAAU,QAAQ,WAAS,IAI9F,CAAC;AAEJ,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,CAAC,OAAO,EAAE,WAAW,MAAM,EAAE,UAAU,QAAQ,KAAG,IAK9F,CAAC"}
@@ -1,12 +1,12 @@
1
1
  import { getExecutionTime } from './execution-time.js';
2
- export const signalReceived = (process, startTime, event) => () => {
3
- console.log(`Done after ${String(getExecutionTime(startTime))}s`);
2
+ export const signalReceived = (process, startTime, event, reporter) => () => {
3
+ reporter.log(`Done after ${String(getExecutionTime(startTime))}s`);
4
4
  process.kill(process.pid, event);
5
5
  process.exit(1);
6
6
  };
7
- export const handleSignals = (process, startTime) => {
7
+ export const handleSignals = (process, startTime, reporter) => {
8
8
  const catchSignals = ['SIGTERM', 'SIGINT', 'SIGUSR2'];
9
9
  catchSignals.forEach((event) => {
10
- process.once(event, signalReceived(process, startTime, event));
10
+ process.once(event, signalReceived(process, startTime, event, reporter));
11
11
  });
12
12
  };
@@ -24,8 +24,15 @@ zod-codegen --input ./samples/swagger-petstore.yaml --output ./examples/petstore
24
24
  ```bash
25
25
  npx ts-node examples/petstore/basic-usage.ts
26
26
  npx ts-node examples/petstore/authenticated-usage.ts
27
+ npx ts-node examples/petstore/server-variables-usage.ts
28
+ npx ts-node examples/petstore/retry-handler-usage.ts
27
29
  ```
28
30
 
31
+ **Additional examples:**
32
+
33
+ - `server-variables-usage.ts` - Using server variables for different environments
34
+ - `retry-handler-usage.ts` - Custom retry handler implementation
35
+
29
36
  ### ⚡ [PokéAPI](./pokeapi/)
30
37
 
31
38
  PokéAPI is a public RESTful API that provides data about Pokémon. This example demonstrates:
@@ -37,9 +44,11 @@ PokéAPI is a public RESTful API that provides data about Pokémon. This example
37
44
  **Generate the client:**
38
45
 
39
46
  ```bash
40
- zod-codegen --input https://pokeapi.co/api/v2/openapi.json --output ./examples/pokeapi
47
+ zod-codegen --input ./samples/pokeapi-openapi.json --output ./examples/pokeapi
41
48
  ```
42
49
 
50
+ **Note**: PokéAPI doesn't provide an official OpenAPI specification, so we use a simplified OpenAPI spec based on their API structure.
51
+
43
52
  ## Structure
44
53
 
45
54
  Each example directory contains:
@@ -11,7 +11,7 @@ After running `zod-codegen`, you'll get:
11
11
  ## Basic Usage
12
12
 
13
13
  ```typescript
14
- import {SwaggerPetstoreOpenAPI30} from './type.js';
14
+ import {SwaggerPetstoreOpenAPI30} from './type';
15
15
 
16
16
  // Create a client instance using default server from OpenAPI spec
17
17
  const client = new SwaggerPetstoreOpenAPI30({});
@@ -26,7 +26,7 @@ console.log('Available pets:', pets);
26
26
  The generated client supports flexible server configuration:
27
27
 
28
28
  ```typescript
29
- import {SwaggerPetstoreOpenAPI30, ClientOptions} from './type.js';
29
+ import {SwaggerPetstoreOpenAPI30, ClientOptions} from './type';
30
30
 
31
31
  // Option 1: Use default server (first server from OpenAPI spec)
32
32
  const defaultClient = new SwaggerPetstoreOpenAPI30({});
@@ -56,10 +56,10 @@ const variableClient = new SwaggerPetstoreOpenAPI30({
56
56
  ## Example: Finding Pets
57
57
 
58
58
  ```typescript
59
- import {SwaggerPetstoreOpenAPI30, defaultBaseUrl} from './type.js';
59
+ import {SwaggerPetstoreOpenAPI30} from './type';
60
60
 
61
61
  async function findAvailablePets() {
62
- const client = new SwaggerPetstoreOpenAPI30(defaultBaseUrl);
62
+ const client = new SwaggerPetstoreOpenAPI30({});
63
63
 
64
64
  try {
65
65
  // Find pets by status
@@ -84,11 +84,11 @@ findAvailablePets();
84
84
  ## Example: Adding a Pet
85
85
 
86
86
  ```typescript
87
- import {SwaggerPetstoreOpenAPI30, Pet, PetStatus, defaultBaseUrl} from './type.js';
87
+ import {SwaggerPetstoreOpenAPI30, Pet, PetStatus} from './type';
88
88
  import {z} from 'zod';
89
89
 
90
90
  async function addNewPet() {
91
- const client = new SwaggerPetstoreOpenAPI30(defaultBaseUrl);
91
+ const client = new SwaggerPetstoreOpenAPI30({});
92
92
 
93
93
  const newPet: z.infer<typeof Pet> = {
94
94
  id: 12345,
@@ -4,7 +4,7 @@
4
4
  * Run with: npx ts-node examples/petstore/authenticated-usage.ts
5
5
  */
6
6
 
7
- import {SwaggerPetstoreOpenAPI30, ClientOptions} from './type.js';
7
+ import {SwaggerPetstoreOpenAPI30, ClientOptions} from './type';
8
8
 
9
9
  class AuthenticatedPetstoreAPI extends SwaggerPetstoreOpenAPI30 {
10
10
  private apiKey: string | null = null;
@@ -4,7 +4,7 @@
4
4
  * Run with: npx ts-node examples/petstore/basic-usage.ts
5
5
  */
6
6
 
7
- import {SwaggerPetstoreOpenAPI30} from './type.js';
7
+ import {SwaggerPetstoreOpenAPI30} from './type';
8
8
 
9
9
  async function main() {
10
10
  // Use default server (first server from OpenAPI spec)
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Example showing how to handle special HTTP response cases (e.g., 429 retry logic)
3
+ * by extending the generated client and overriding the handleResponse method
4
+ *
5
+ * Run with: npx ts-node examples/petstore/retry-handler-usage.ts
6
+ */
7
+
8
+ import {SwaggerPetstoreOpenAPI30} from './type';
9
+
10
+ class PetstoreClientWithRetry extends SwaggerPetstoreOpenAPI30 {
11
+ private maxRetries = 3;
12
+ private retryDelay = 1000; // 1 second base delay
13
+ private retrying = false; // Track if we're currently in a retry to avoid infinite loops
14
+
15
+ /**
16
+ * Override handleResponse to intercept responses and handle special cases
17
+ * This method is called before error checking, allowing you to:
18
+ * - Retry requests on specific status codes (e.g., 429 Too Many Requests)
19
+ * - Modify responses before they're processed
20
+ * - Implement custom error handling logic
21
+ */
22
+ protected async handleResponse<T>(
23
+ response: Response,
24
+ method: string,
25
+ path: string,
26
+ options: {
27
+ params?: Record<string, string | number | boolean>;
28
+ data?: unknown;
29
+ contentType?: string;
30
+ headers?: Record<string, string>;
31
+ },
32
+ ): Promise<Response> {
33
+ // Skip retry logic if we're already retrying to avoid infinite loops
34
+ if (this.retrying) {
35
+ return response;
36
+ }
37
+
38
+ // Handle 429 Too Many Requests with exponential backoff retry
39
+ if (response.status === 429) {
40
+ const retryAfter = response.headers.get('Retry-After');
41
+ const delay = retryAfter ? parseInt(retryAfter, 10) * 1000 : this.retryDelay;
42
+
43
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
44
+ console.log(
45
+ `⚠️ Rate limited (429). Retrying in ${delay * (attempt + 1)}ms... (Attempt ${attempt + 1}/${this.maxRetries})`,
46
+ );
47
+
48
+ // Wait before retrying with exponential backoff
49
+ await new Promise((resolve) => setTimeout(resolve, delay * (attempt + 1)));
50
+
51
+ // Retry the request
52
+ // We use retryRequest helper to avoid going through handleResponse again
53
+ // (which would cause infinite loops). Alternatively, if makeRequest is protected,
54
+ // you could call it directly, but you'd need to ensure handleResponse doesn't
55
+ // retry again by checking the retrying flag.
56
+ try {
57
+ this.retrying = true;
58
+ const retryResponse = await this.retryRequest(method, path, options);
59
+ this.retrying = false;
60
+
61
+ if (retryResponse.ok) {
62
+ console.log(`✅ Retry successful after ${attempt + 1} attempt(s)`);
63
+ return retryResponse;
64
+ }
65
+ // If still rate limited, continue to next retry
66
+ if (retryResponse.status === 429 && attempt < this.maxRetries - 1) {
67
+ continue;
68
+ }
69
+ // Return the response even if it's an error (will be handled by normal error flow)
70
+ return retryResponse;
71
+ } catch (error) {
72
+ this.retrying = false;
73
+ // If retry fails and we're out of attempts, throw
74
+ if (attempt === this.maxRetries - 1) {
75
+ throw error;
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ // Handle other status codes if needed
82
+ // For example, you could handle 503 Service Unavailable similarly
83
+ if (response.status === 503) {
84
+ console.log('⚠️ Service unavailable (503). You could implement retry logic here too.');
85
+ }
86
+
87
+ // For all other responses, return as-is
88
+ return response;
89
+ }
90
+
91
+ /**
92
+ * Helper method to retry a request
93
+ * This reconstructs the request using the same parameters
94
+ * Note: If makeRequest is protected (not private), you could call it directly instead
95
+ */
96
+ private async retryRequest(
97
+ method: string,
98
+ path: string,
99
+ options: {
100
+ params?: Record<string, string | number | boolean>;
101
+ data?: unknown;
102
+ contentType?: string;
103
+ headers?: Record<string, string>;
104
+ },
105
+ ): Promise<Response> {
106
+ // Reconstruct the request - this duplicates logic from makeRequest
107
+ // but allows us to retry without going through handleResponse again
108
+ const baseUrl = `${(this as any)['#baseUrl']}${path}`;
109
+ const url =
110
+ options.params && Object.keys(options.params).length > 0
111
+ ? (() => {
112
+ const urlObj = new URL(baseUrl);
113
+ Object.entries(options.params).forEach(([key, value]) => {
114
+ urlObj.searchParams.set(key, String(value));
115
+ });
116
+ return urlObj.toString();
117
+ })()
118
+ : baseUrl;
119
+
120
+ const baseOptions = this.getBaseRequestOptions();
121
+ const contentType =
122
+ options.contentType === 'application/x-www-form-urlencoded'
123
+ ? 'application/x-www-form-urlencoded'
124
+ : 'application/json';
125
+ const baseHeaders = baseOptions.headers !== undefined ? baseOptions.headers : {};
126
+ const headers = Object.assign(
127
+ {},
128
+ baseHeaders,
129
+ {'Content-Type': contentType},
130
+ options.headers !== undefined ? options.headers : {},
131
+ );
132
+ const body =
133
+ options.data !== undefined
134
+ ? options.contentType === 'application/x-www-form-urlencoded'
135
+ ? (() => {
136
+ const params = new URLSearchParams();
137
+ Object.entries(options.data as Record<string, unknown>).forEach(([key, value]) => {
138
+ params.set(key, String(value));
139
+ });
140
+ return params.toString();
141
+ })()
142
+ : JSON.stringify(options.data)
143
+ : null;
144
+
145
+ return await fetch(url, Object.assign({}, baseOptions, {method, headers: headers, body: body}));
146
+ }
147
+ }
148
+
149
+ async function main() {
150
+ const client = new PetstoreClientWithRetry({});
151
+
152
+ try {
153
+ console.log('🔍 Fetching pets with retry handler...\n');
154
+
155
+ // This will use the handleResponse hook if a 429 is encountered
156
+ const availablePets = await client.findPetsByStatus('available');
157
+ console.log(`✅ Found ${availablePets.length} available pets`);
158
+
159
+ if (availablePets.length > 0) {
160
+ console.log('\n📋 First pet details:');
161
+ console.log(JSON.stringify(availablePets[0], null, 2));
162
+ }
163
+ } catch (error) {
164
+ if (error instanceof Error) {
165
+ console.error('❌ Error:', error.message);
166
+ } else {
167
+ console.error('❌ Unknown error:', error);
168
+ }
169
+ process.exit(1);
170
+ }
171
+ }
172
+
173
+ void main();
@@ -7,7 +7,7 @@
7
7
  * For a real example, generate a client from an OpenAPI spec with server variables.
8
8
  */
9
9
 
10
- import {SwaggerPetstoreOpenAPI30, ClientOptions} from './type.js';
10
+ import {SwaggerPetstoreOpenAPI30, ClientOptions} from './type';
11
11
 
12
12
  async function main() {
13
13
  // Example 1: Use default server (first server from OpenAPI spec)