Skip to content

TheAsda/genoc

Repository files navigation

genoc

Generate TypeScript HTTP clients from OpenAPI 3.0 / 3.1 specifications. The generated code has zero runtime dependencies. Full type safety. Bring your own HTTP client.

npm version Node.js TypeScript License: MIT

Features

  • Full OpenAPI 3.0 and 3.1 specification support with automatic version detection
  • End-to-end type safety — requests, responses, and errors are fully typed
  • HTTP-client agnostic — adapter pattern lets you plug in fetch, axios, or anything else
  • Error types with per-status-code narrowing and type guards
  • File and binary upload/download with stream handling
  • Flexible method naming strategies (path-based, operationId, operationId-with-fallback)
  • CLI and programmatic API

Quick Start

Install:

npm install -D genoc

Generate:

genoc ./path/to/spec.yaml --output-dir ./src/api

This creates two files in ./src/api:

  • contracts.ts — Type definitions, error classes, and helper types
  • client.ts — Typed client with createClient(requester) factory

Usage

The generated client requires a Requester implementation — a function that performs the actual HTTP call and returns the result. This is the type your implementation must satisfy:

type Requester = <TResponse>(
  method: string,
  path: string,
  options: {
    query?: Record<string, unknown>;
    body?: unknown;
    headers?: Record<string, string>;
    expectStream?: true;
  }
) => Promise<TResponse | StreamResponse | ErrorResponse>;

Basic Example with fetch

import { createClient } from './client.js';
import { ApiError, RequesterFailError, ErrorResponse } from './contracts.js';

const baseUrl = 'https://api.example.com';

const requester: Requester = async (method, path, options) => {
  const url = new URL(path, baseUrl);
  if (options.query) {
    Object.entries(options.query).forEach(([key, value]) => {
      url.searchParams.set(key, String(value));
    });
  }

  const response = await fetch(url, {
    method,
    headers: {
      'Content-Type': 'application/json',
      ...options.headers,
    },
    body: options.body ? JSON.stringify(options.body) : undefined,
  });

  if (!response.ok) {
    return new ErrorResponse(
      response.status,
      await response.json(),
      response.headers,
      response.statusText
    );
  }

  return response.json();
};

const client = createClient(requester);

// Typed call — response type is inferred from the spec
const pets = await client.getPets({ limit: 10 });

See Binary / File Responses for handling expectStream: true.

Binary / File Responses

When your spec defines binary responses (e.g. format: binary, application/octet-stream, image/*), the generated client sends expectStream: true in options. Your Requester should return a StreamResponse in that case:

import { StreamResponse } from './contracts.js';

// Inside your Requester implementation:
if (options.expectStream === true) {
  return new StreamResponse(
    response.body as ReadableStream<Uint8Array>,
    getFilename(response.headers), // extract from Content-Disposition
    response.headers
  );
}

StreamResponse is a simple container:

class StreamResponse {
  data: ReadableStream<Uint8Array>;
  filename?: string;
  headers: Record<string, string>;
}

Response Helpers

The generated contracts.ts includes helper functions for constructing responses in your Requester implementation:

  • streamResponse(data, filename?, headers?) — Creates a StreamResponse instance
  • errorResponse(status, data, headers?, message?) — Creates an ErrorResponse instance

These are convenience wrappers around the StreamResponse and ErrorResponse constructors.

CLI Reference

genoc <spec> [flags]

<spec> — Path or URL to an OpenAPI 3.0 / 3.1 spec (JSON or YAML).

Flag Default Description
--output-dir (required) Output directory for generated files
--method-name-strategy path-based Method naming strategy
--spec-version auto-detect Override version detection ("3.0" or "3.1")
--strict-version true Warn if --spec-version mismatches detected version

Method Naming Strategies

  • path-based (default) — HTTP method + path segments in PascalCase. GET /petsgetPets, GET /api/v1/productsgetApiV1Products

  • operationId — Use the operationId field from the spec. GET /petsfindPets (if operationId is "findPets")

  • operationId-with-fallback — Use operationId if present, otherwise fall back to path-based naming.

Programmatic API

import { generateClient } from 'genoc';

await generateClient({
  input: './openapi.yaml',
  outputDir: './src/api',
  methodNameStrategy: 'path-based',
  specVersion: '3.1',
  strictVersion: true,
});

Error Handling

The generated client throws typed errors. Each method carries its own error union, and isDefinedError narrows a caught error to that union:

  • ApiError<TStatus, TData> — Error for a specific status code defined in the spec
  • UnspecifiedApiError — Error for a status code not defined in the spec
  • RequesterFailError — Wraps unexpected failures in your Requester
  • isDefinedError(err, client.method) — Type guard that narrows to the method's defined error union
import { UnspecifiedApiError, RequesterFailError } from './contracts.js';
import { isDefinedError } from './client.js';

try {
  const result = await client.getPets();
} catch (error) {
  if (isDefinedError(error, client.getPets)) {
    // error is narrowed to GetPetsErrors (ApiError<400, ...> | ApiError<500, ...>)
    if (error.status === 400) {
      console.error('Bad request:', error.data);
    }
  }

  if (error instanceof UnspecifiedApiError) {
    console.error('Unexpected status:', error.status, error.data);
  }

  if (error instanceof RequesterFailError) {
    console.error('Requester failed:', error.cause);
  }
}

Feature Support

Check the detailed feature support tables to see if your OpenAPI spec features are covered:

  • OpenAPI 3.0 Support — Data types, schema keywords, parameters, request bodies, file uploads, responses, error handling, $ref resolution, components, security schemes, servers, and path operations.
  • OpenAPI 3.1 Support — All 3.0 features plus type arrays, $ref siblings, webhooks, JSON Schema 2020-12 alignment, and a 3.0 → 3.1 diff.

Requirements

  • Node.js >= 18
  • OpenAPI 3.0.x or 3.1.x specification (JSON or YAML, file path or URL)

License

MIT — Copyright © Andrey Kiselev

About

Generate type-safe HTTP clients from OpenAPI 3.0 / 3.1 specs — zero runtime deps, any HTTP client.

Topics

Resources

License

Stars

Watchers

Forks

Contributors