@zyno-io/openapi-client-codegen
v2.7.3
Published
OpenAPI client generator for TypeScript apps
Readme
@zyno-io/openapi-client-codegen
A TypeScript library that wraps @hey-api/openapi-ts to provide a batteries-included workflow for generating and consuming OpenAPI clients. It adds runtime client configuration, error handling, request middleware, and file upload support on top of the generated SDK.
Table of Contents
- Why not just use @hey-api/openapi-ts directly?
- Installation
- Generating a Client
- Configuring the Generated Client
- File Uploads
- Response Utilities
- Error Handling
Why not just use @hey-api/openapi-ts directly?
@hey-api/openapi-ts is a powerful codegen tool, but it only generates code — it doesn't provide runtime utilities for configuring the generated client in an application. This library fills that gap:
- Client configuration —
configureOpenApiClient()sets up error handling, dynamic headers, and request middleware in one call. - Structured errors — Responses are wrapped in
OpenApiErrorwith access to the request, response, and parsed body, rather than raw fetch errors. - Request middleware — A wrapper function lets you intercept every request for logging, auth token injection, retry logic, etc.
- File uploads — Drop-in classes for browser (
FileUploadRequest) and React Native (ReactNativeFileUploadRequest) file uploads that automatically convert to multipart/form-data. - CLI and watch mode — A CLI tool and programmatic API for generating clients, with file watching for automatic regeneration during development.
Installation
npm install @zyno-io/openapi-client-codegen
# or
yarn add @zyno-io/openapi-client-codegenGenerating a Client
CLI
# Generate from a single spec
npx generate-openapi-client ./api.openapi.yaml ./src/generated
# Generate from config file (openapi-specs.json)
npx generate-openapi-client
# Watch mode — regenerates on file changes
npx generate-openapi-client -w ./api.openapi.yaml ./src/generated
npx generate-openapi-client -w # watch all configured specsConfig file
Create openapi-specs.json in your project root:
{
"./specs/api.yaml": "./src/generated/api",
"./specs/admin.yaml": {
"path": "./src/generated/admin",
"prefix": "Admin"
}
}The prefix option prepends to generated SDK class names (e.g. AdminUsersApi instead of UsersApi).
For development, create openapi-specs.dev.json to redirect spec paths to local files without modifying the main config:
{
"./specs/api.yaml": "../backend/api.openapi.yaml"
}The keys match the paths in openapi-specs.json; the values point to where the spec actually lives during development (e.g. a file generated by a locally running backend). When using watch mode, the generator watches the dev path instead of the committed path. After each successful generation, it automatically copies the dev spec back to the committed path so that spec changes show up in git diff and stay in sync with version control.
Programmatic API
import {
generateOpenapiClient,
generateConfiguredOpenapiClients,
createWatchfulOpenapiClientGenerator
} from '@zyno-io/openapi-client-codegen/generator';
// Generate a single client
await generateOpenapiClient('./api.yaml', './src/generated');
// Generate all clients from openapi-specs.json
await generateConfiguredOpenapiClients();
// Watch a single spec and regenerate on changes
const watcher = createWatchfulOpenapiClientGenerator('./api.yaml', './src/generated');
// watcher.generate() — manually trigger
// watcher.close() — stop watchingConfiguring the Generated Client
The generated SDK exports a client instance. Pass it to configureOpenApiClient() to set up error handling, headers, and middleware:
import { configureOpenApiClient } from '@zyno-io/openapi-client-codegen';
import { client } from './generated/client.gen';
configureOpenApiClient(client, {
headers: {
Authorization: `Bearer ${token}`
},
onError: (err, options) => {
if (err instanceof OpenApiError && err.response.status === 401) {
redirectToLogin();
return null; // the request promise will never settle
}
}
});Options
headers
Static headers, or a (sync/async) function that returns headers per-request:
// Static
configureOpenApiClient(client, {
headers: { 'X-API-Key': 'abc123' }
});
// Dynamic
configureOpenApiClient(client, {
headers: async request => ({
Authorization: `Bearer ${await getToken()}`
})
});Set a header to null to remove it from the request.
onError
Called when the server returns an error response. Receives an OpenApiError and the request options:
configureOpenApiClient(client, {
onError: (err, options) => {
// Throw a different error
if (err.response.status === 403) {
throw new ForbiddenError(err.message);
}
// Return null to silently discard the error — the request promise
// will never resolve or reject, so the caller hangs and does nothing.
// Useful when you're redirecting the user away from the page entirely.
if (err.response.status === 401) {
redirectToLogin();
return null;
}
// Return void to throw the original OpenApiError
}
});wrapper
A middleware function that wraps every request. Receives the request options and the original request function, and must return the result. Useful for logging, retry logic, or request queuing:
configureOpenApiClient(client, {
wrapper: async (options, request) => {
const start = performance.now();
try {
const result = await request(options);
console.log(`${options.method} ${options.url} — ${Math.round(performance.now() - start)}ms`);
return result;
} catch (err) {
console.error(`${options.method} ${options.url} failed`, err);
throw err;
}
}
});File Uploads
The upload classes provide client-side compatibility with Deepkit's UploadedFile type, in conjunction with OpenAPI specs generated by deepkit-openapi. When a Deepkit backend exposes UploadedFile parameters, these classes satisfy the generated TypeScript types and automatically convert the request to multipart/form-data:
import { FileUploadRequest, ReactNativeFileUploadRequest } from '@zyno-io/openapi-client-codegen';
// Browser — wrap a File or Blob
const pdfFile = new File([pdfBlob], 'report.pdf', { type: 'application/pdf' });
const result = dataFrom(
await ReportsApi.postReportsUpload({
body: {
title: 'Q4 Report',
pdf: new FileUploadRequest(pdfFile)
}
})
);
// React Native — pass a file URI
await ProfileApi.postProfileUploadPhoto({
body: {
photo: new ReactNativeFileUploadRequest({
uri: photo.uri,
name: 'photo.jpg',
type: 'image/jpeg'
})
}
});Response Utilities
import { dataFrom, dataFromAsync, type OpenApiDataType } from '@zyno-io/openapi-client-codegen';
// Extract .data from a response
const response = await PetsApi.listPets();
const pets = dataFrom(response);
// Extract .data from a promise
const pets = await dataFromAsync(PetsApi.listPets());
// Extract the data type from a response type
type Pet = OpenApiDataType<Awaited<ReturnType<typeof PetsApi.getPet>>>;Error Handling
configureOpenApiClient sets throwOnError: true on the client. Failed requests throw an OpenApiError:
import { OpenApiError } from '@zyno-io/openapi-client-codegen';
try {
await PetsApi.getPet({ path: { petId: '123' } });
} catch (err) {
if (err instanceof OpenApiError) {
console.error(err.message); // "Not Found (404)"
console.error(err.response); // Response object
console.error(err.body); // Parsed response body
console.error(err.request); // Request object
}
}License
MIT
