Skip to content

zyno-io/openapi-client-codegen-ts

Repository files navigation

@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?

@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 configurationconfigureOpenApiClient() sets up error handling, dynamic headers, and request middleware in one call.
  • Structured errors — Responses are wrapped in OpenApiError with 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 — Browser Blob/File values and React Native upload helpers 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-codegen

Generating 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 specs

Config 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).

To generate a client with only a subset of operations (and their referenced schemas), add operations:

{
    "./specs/api.yaml": {
        "path": "./src/generated/api",
        "operations": ["listPets", "getPet"]
    }
}

This filters the spec to only include the named operations (matched by operationId) and any schemas they reference, including transitive references. This is useful for keeping generated clients small when you only need a few endpoints from a large API.

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.

To override the operations filter during development, use the object form with operations. This takes precedence over any operations defined in openapi-specs.json:

{
    "./specs/api.yaml": {
        "src": "../backend/api.openapi.yaml",
        "operations": ["listPets", "getPet"]
    }
}

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 watching

Configuring 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

Generated multipart upload fields are represented as Blob | File | null. When a Deepkit backend exposes UploadedFile parameters, passing a Blob or File automatically converts the request to multipart/form-data, sends file fields as file parts, and moves non-file fields into the _payload JSON part expected by Deepkit:

import { ReactNativeFileUploadRequest } from '@zyno-io/openapi-client-codegen';

// Browser — pass a File or Blob directly
const pdfFile = new File([pdfBlob], 'report.pdf', { type: 'application/pdf' });
const result = dataFrom(
    await ReportsApi.postReportsUpload({
        body: {
            title: 'Q4 Report',
            pdf: pdfFile
        }
    })
);

// React Native — pass a file URI helper
await ProfileApi.postProfileUploadPhoto({
    body: {
        photo: new ReactNativeFileUploadRequest({
            uri: photo.uri,
            name: 'photo.jpg',
            type: 'image/jpeg'
        }) as unknown as Blob
    }
});

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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages