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

@ts-http/openapi

v0.0.5

Published

OpenAPI 3.0 spec generator for @ts-http contracts

Readme

@ts-http/openapi

npm GitHub

OpenAPI 3.0 spec generator for ts-http contracts.

Reads your existing ApiDescription variable and TypeScript interface — no decorators, no schema files, no separate annotation layer. Types are extracted at build time via the TypeScript compiler API.

Installation

npm install --save-dev @ts-http/openapi
# or
pnpm add -D @ts-http/openapi

Quick start

1. Add an openapi.config.json to your project:

{
    "outputPath": "./openapi.json",
    "info": { "title": "My API", "version": "1.0.0" },
    "contracts": [{ "variablePattern": "*Api" }]
}

2. Run the CLI:

npx ts-http-openapi
# or, if installed locally:
ts-http-openapi openapi.config.json

That's it. The CLI finds your tsconfig.json automatically, scans every TypeScript file it includes, discovers all exported variables annotated as ApiDescription<X> whose name matches *ApiuserApi, orderApi, paymentApi, all of them — and writes the spec.

Note: Variables must have an explicit type annotation to be discovered:

export const userApi: ApiDescription<UserApi> = { … }  // ✅ found
export const userApi = { … }                           // ❌ skipped (inferred type)

End-to-end example

Given this contract:

// src/contract.ts
import { ApiDescription } from '@ts-http/core';

export interface UserApi {
    getAll(): Promise<User[]>;
    getById(id: string): Promise<User>;
    create(data: { name: string; email: string }): Promise<User>;
    update(id: string, data: { name?: string; email?: string }): Promise<User>;
    remove(id: string): Promise<void>;
    streamAll(): Promise<ReadableStream<Uint8Array>>;
}

export const userApi: ApiDescription<UserApi> = {
    subRoute: '/api/users',
    mapping: {
        getAll:    { method: 'GET',    path: '',       tags: ['Users'],   summary: 'List all users' },
        getById:   { method: 'GET',    path: ':id',    tags: ['Users'],   summary: 'Get a user by ID' },
        create:    { method: 'POST',   path: '',       tags: ['Users'],   summary: 'Create a new user' },
        update:    { method: 'PUT',    path: ':id',    tags: ['Users'],   summary: 'Update a user' },
        remove:    { method: 'DELETE', path: ':id',    tags: ['Users'],   summary: 'Delete a user',        resultType: 'NONE' },
        streamAll: { method: 'GET',    path: 'stream', tags: ['Streams'], summary: 'Stream all users as NDJSON', resultType: 'STREAM' },
    },
};

And this config:

{
    "outputPath": "./openapi.json",
    "tsconfigPath": "./tsconfig.json",
    "serverUrl": "http://localhost:3000",
    "info": {
        "title": "User API",
        "description": "CRUD and streaming endpoints for user management.",
        "version": "0.0.1"
    },
    "tags": [
        { "name": "Users",   "description": "User resource operations" },
        { "name": "Streams", "description": "Streaming endpoints" }
    ],
    "contracts": [{ "variablePattern": "*Api" }]
}

Running ts-http-openapi openapi.config.json produces a complete OpenAPI 3.0.3 spec with:

  • All six paths under /api/users
  • User extracted as a reusable schema in components/schemas
  • Tag groupings, summaries, and correct response types per resultType
  • The binary stream endpoint mapped to application/octet-stream

Minimal setup

No tags, summary, or metadata at all — just types and routes:

export interface TaskApi {
    getAll(): Promise<Task[]>;
    create(data: { title: string }): Promise<Task>;
    remove(id: string): Promise<void>;
}

export const taskApi: ApiDescription<TaskApi> = {
    subRoute: '/tasks',
    mapping: {
        getAll: { method: 'GET',    path: '' },
        create: { method: 'POST',   path: '' },
        remove: { method: 'DELETE', path: ':id', resultType: 'NONE' },
    },
};
{
    "outputPath": "./openapi.json",
    "info": { "title": "Task API", "version": "0.0.1" },
    "contracts": [{ "variablePattern": "*Api" }]
}

Swagger UI will show the three endpoints under /tasks grouped as one block, using method names as operation IDs (getAll, create, remove), with request/response schemas inferred from the TypeScript types. No grouping sidebar, no summaries — just a working, explorable spec.

Usage

Option A — JSON config + CLI (recommended)

The quickest path. Put the config next to your tsconfig.json and run:

ts-http-openapi                         # reads openapi.config.json in cwd
ts-http-openapi path/to/openapi.config.json  # explicit path

All paths in the config are resolved relative to the config file itself, so the config is portable.

Option B — TypeScript script

Useful when you need to import the contract at runtime (e.g. to reuse the same ApiDescription object in tests or tooling):

// scripts/generate-openapi.ts
import * as path from 'node:path';
import { userApi } from '../src/contract';
import { writeOpenApi } from '@ts-http/openapi';

writeOpenApi({
    contracts: [{ api: userApi, variableName: 'userApi' }],
    outputPath: path.resolve(__dirname, '../openapi.json'),
    tsconfigPath: path.resolve(__dirname, '../tsconfig.json'),
    serverUrl: 'http://localhost:3000',
    info: { title: 'User API', version: '0.0.1' },
});

Run with tsx:

tsx scripts/generate-openapi.ts

Route metadata

All fields are optional. Add them directly to the mapping entries:

const userApi: ApiDescription<UserApi> = {
    subRoute: '/api/users',
    mapping: {
        getAll: {
            method: 'GET',
            path: '',
            summary: 'List all users',          // → operation.summary
            description: 'Returns all users.',  // → operation.description
            tags: ['Users'],                    // → operation.tags
            operationId: 'listUsers',           // → operation.operationId (defaults to method name)
            deprecated: true,                   // → operation.deprecated
        },
    },
};

API reference

generateOpenApi(options): OpenApiDocument

Generates and returns an OpenAPI 3.0 document object.

writeOpenApi(options): void

Generates and writes the document to options.outputPath as formatted JSON.

Options

| Field | Type | Description | |---|---|---| | contracts | ContractSource[] | One entry per route group (see below) | | outputPath | string | Where to write the JSON file (writeOpenApi only) | | tsconfigPath | string? | Path to tsconfig.json. Defaults to nearest from cwd | | serverUrl | string? | Base URL added to servers[0].url | | info | object? | { title, description, version } for the spec info block | | tags | object[]? | Top-level tag definitions [{ name, description }] |

ContractSource

Each contract source must have exactly one of:

| Field | Description | |---|---| | api + variableName | Pass the runtime object and the variable name so the compiler can find its type | | variableName | Static-only: the compiler reads both the mapping and the types from AST — no import required | | variablePattern | Glob: auto-discover all matching exported ApiDescription variables (e.g. "*Api") |

resultType mapping

| resultType | HTTP response | |---|---| | (default) | 200 with JSON schema inferred from the return type | | 'NONE' | 204 No content | | 'STREAM' | 200 with { type: 'string', format: 'binary' } |

License

MIT © 2026 Clemens Meier