npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dreampulse/openapi-to-typescript

v1.2.0

Published

CLI tool that generates TypeScript type definitions from OpenAPI 3.x and Swagger 2.0 specs

Readme

openapi-to-typescript

Generates TypeScript type definitions and JSON Schemas from OpenAPI 3.x specs — one file per schema, one file per endpoint. The output is optimized for LLM consumption: small, self-contained files with all metadata preserved as JSDoc comments.

Why?

When working with LLMs on codebases that consume APIs, you want the AI to understand your API types without needing to parse a massive OpenAPI spec. This tool generates compact, individual .ts files that an LLM can read and reason about independently. Each file contains everything it needs: the type definition, JSDoc comments with descriptions, constraints, formats, and deprecation markers.

Design decisions:

  • type instead of interface — Union types, intersections, and mapped types compose better with type aliases. This matters for discriminated unions and allOf/oneOf schemas.
  • One file per schema/endpoint — LLMs work best with small, focused context. A single file per type means you only feed the AI what it needs.
  • All metadata as JSDoc — Descriptions, constraints (@minimum, @maxLength, @pattern), formats (@format date-time), and deprecation are preserved as JSDoc comments so they're visible in IDE tooltips and to LLMs reading the code.
  • Unsupported features throw errors — Instead of silently generating wrong types, unsupported constructs (like if/then/else) throw descriptive errors so you know what needs attention.
  • JSON Schema alongside TypeScript — Each schema also generates a .schema.json file (JSON Schema Draft 2020-12) with all transitive dependencies embedded in $defs, useful for runtime validation.

Installation

pnpm add openapi-to-typescript

CLI Usage

npx openapi-to-ts --input <spec> --output <dir>

| Option | Description | |---|---| | -i, --input <path> | Path to the OpenAPI spec (YAML or JSON) | | -o, --output <dir> | Output directory for generated files |

Example:

npx openapi-to-ts -i ./api/openapi.yaml -o ./generated

This reads the spec, resolves all internal $ref references, and writes the generated files:

generated/
  schemas/
    Kundenangaben.ts            # export type Kundenangaben = { ... }
    Kundenangaben.schema.json   # JSON Schema with embedded $defs
    Anschrift.ts
    Anschrift.schema.json
    ...
  endpoints/
    getKundenangaben.ts         # path params + header params + response types
    importKundenangaben.ts      # header params + request body + response types
    ...

The output directory is cleaned before each run.

Output

Schema files

Each schema from components/schemas becomes a .ts file with a single export type:

// schemas/Anschrift.ts
export type Anschrift = {
  hausnummer?: string;
  ort?: string;
  /**
   * Postleitzahlen, die nicht aus fünf Ziffern bestehen werden ignoriert.
   * @pattern \d{5}
   * @example "10557"
   */
  plz?: string;
  strasse?: string;
};

Properties from the OpenAPI spec are preserved:

  • description → JSDoc comment
  • format@format tag
  • deprecated@deprecated tag
  • Constraints (minimum, maxLength, pattern, minItems, ...) → corresponding @tag
  • readOnlyreadonly modifier
  • required fields are non-optional, everything else gets ?

Discriminated unions are handled automatically. A base schema with a discriminator mapping becomes a union type, and each subtype gets the discriminator property as a string literal:

// schemas/Finanzierungsbaustein.ts  (base — union of subtypes)
export type Finanzierungsbaustein =
  Annuitaetendarlehen | Bauspardarlehen | Forwarddarlehen | ...;

// schemas/Annuitaetendarlehen.ts  (subtype — literal discriminator)
export type Annuitaetendarlehen = {
  "@type": "ANNUITAETENDARLEHEN";
} & {
  darlehensbetrag?: number;
  annuitaetendetails?: Annuitaetendetails;
  ...
};

Endpoint files

Each operation (identified by operationId) becomes a .ts file with types for parameters, request body, and responses:

// endpoints/getKundenangaben.ts
import type { Kundenangaben } from "../schemas/Kundenangaben";
import type { Problem } from "../schemas/Problem";

/**
 * Kundenangaben für einen bestehenden Vorgang laden
 * @http GET /kundenangaben/{vorgangsnummer}
 */

export type GetKundenangabenPathParams = {
  vorgangsnummer: string;
};

export type GetKundenangabenHeaderParams = {
  Authorization: string;
  "X-TraceId"?: string;
};

export type GetKundenangabenResponse200 = Kundenangaben;
export type GetKundenangabenResponse401 = void;
export type GetKundenangabenResponse404 = Problem;

Naming conventions:

  • Schema files: PascalCase (matching the schema name from the spec)
  • Endpoint files: camelCase (matching the operationId)
  • Type names: {PascalOperationId}PathParams, {PascalOperationId}QueryParams, {PascalOperationId}HeaderParams, {PascalOperationId}RequestBody, {PascalOperationId}Response{StatusCode}
  • Responses without a body become void

JSON Schema files

Each schema also generates a .schema.json file following JSON Schema Draft 2020-12. All referenced types are embedded as $defs, making each file self-contained:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "plz": {
      "type": "string",
      "pattern": "\\d{5}",
      "description": "Postleitzahlen, die nicht aus fünf Ziffern bestehen werden ignoriert.",
      "examples": ["10557"]
    }
  }
}

This is useful for runtime validation with libraries like Ajv.

Library Usage

You can also use the tool programmatically:

import {
  parseSpec,
  generateFiles,
  extractEndpoints,
  schemaToType,
} from "openapi-to-typescript";

Generate all files

import { parseSpec, generateFiles } from "openapi-to-typescript";

const spec = await parseSpec("./api/openapi.yaml");
await generateFiles(spec, "./generated");

This is equivalent to the CLI — it parses the spec, generates all .ts and .schema.json files, and writes them to the output directory.

Extract endpoint metadata

import { parseSpec, extractEndpoints } from "openapi-to-typescript";

const spec = await parseSpec("./api/openapi.yaml");
const endpoints = extractEndpoints(spec);

for (const endpoint of endpoints) {
  console.log(`${endpoint.method.toUpperCase()} ${endpoint.path}`);
  console.log(`  operationId: ${endpoint.operationId}`);
  console.log(`  parameters: ${endpoint.parameters.length}`);
  console.log(`  responses: ${endpoint.responses.map((r) => r.statusCode)}`);
}

Each EndpointInfo contains:

  • operationId, method, path, summary, description
  • parameters — array of { name, in, required, schema, description }
  • requestBody — the request body schema (or null)
  • responses — array of { statusCode, schema, description }

Convert a single schema to TypeScript

import { schemaToType } from "openapi-to-typescript";

schemaToType({ type: "string" });
// → "string"

schemaToType({ type: "object", properties: { name: { type: "string" } }, required: ["name"] });
// → '{\n  name: string;\n}'

schemaToType({ enum: ["A", "B", "C"] });
// → '"A" | "B" | "C"'

schemaToType({ $ref: "#/components/schemas/Foo" });
// → "Foo"

schemaToType handles: primitives, enums, const, objects (nested, with required/optional), arrays, tuples, allOf/oneOf/anyOf, nullable, readOnly, $ref, additionalProperties, and all JSDoc metadata. Unsupported constructs like if/then/else throw an error.

Supported OpenAPI Features

| Feature | TypeScript Output | |---|---| | type: "string" / "number" / "integer" / "boolean" | string / number / number / boolean | | type: "null" (OAS 3.1) | null | | nullable: true (OAS 3.0) | Type \| null | | enum | "A" \| "B" \| "C" | | const | "value" (literal type) | | type: "object" with properties | { key: type; ... } | | type: "array" with items | Type[] | | prefixItems (tuples) | [Type1, Type2] | | allOf | A & B (intersection) | | oneOf / anyOf | A \| B (union) | | discriminator with mapping | Discriminated union with string literal property | | $ref | Named type import | | additionalProperties | Record<string, Type> or intersection with object | | readOnly | readonly modifier | | description, deprecated, format, constraints | JSDoc comments |