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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@sdk-it/generic

v0.34.0

Published

<p align="center">A TypeScript analysis tool for generating OpenAPI specifications from TypeScript code</p>

Readme

@sdk-it/generic

This package provides tools to analyze TypeScript code and generate OpenAPI specifications from it. It can extract route information, parameter types, and response schemas from your TypeScript codebase.

Frameworks specific integrations

Installation

npm install @sdk-it/generic

Usage

Consider the following example:

  • Create a route using your API framework of choice with the @openapi tag and validate middleware.
import z from 'zod';

import { validate } from '@sdk-it/express/runtime';

const app = express();

/**
 * @openapi getAuthor
 * @tags authors
 */
app.get(
  '/authors/:id',
  validate((payload) => ({
    id: {
      select: payload.param.id,
      against: z.string(),
    },
  })),
  async (req, res) => {
    const author = [{ name: 'John Doe' }];
    return res.json(author);
  },
);
  • Use the generate fn to create an OpenAPI spec from your routes.
import { join } from 'node:path';

import { analyze, responseAnalyzer } from '@sdk-it/generic';
import { generate } from '@sdk-it/typescript';

const { paths, components } = await analyze('path/to/tsconfig.json', {
  responseAnalyzer,
});

const spec = {
  info: {
    title: 'My API',
    version: '1.0.0',
  },
  paths,
  components,
};

await writeFile(
  join(process.cwd(), 'openapi.json'),
  JSON.stringify(spec, null, 2),
);

[!TIP] See typescript to create fully functional SDKs from the generated OpenAPI specification.

Customizing Operations

You can customize the operations as well as add more through the onOperation fn.

Use file name as tag

Assuming your projects structurd like the following where routes are grouped by representivie file names.

apps/
  backend/
    tsconfig.app.json
    src/
      routes/
        authors.ts

Then you can consider the file name as the tag for the operation which means you don't need to specify the tag in the JSDoc comment or only specify the tag if you want to override the default behavior.

import { basename } from 'node:path';
import { camelcase } from 'stringcase';

import { analyze, responseAnalyzer } from '@sdk-it/generic';

const { paths, components } = await analyze('apps/backend/tsconfig.app.json', {
  responseAnalyzer,
  onOperation(sourceFile, method, path, operation) {
    const fileName = basename(sourceFile.split('/').at(-1), '.ts');
    return {
      [method]: {
        [path]: {
          ...operation,
          tags: [fileName],
        },
      },
    };
  },
});

Customizing Type Mappings

The type analysis can be customized to handle types that are not standard in TypeScript, such as Decimal from Prisma. The typesMap option in the analyze function allows you to provide your own type mappings.

The example below shows how to map a Decimal type to a string.

import { writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { cwd } from 'node:process';

import { defaultTypesMap } from '@sdk-it/core';
import { analyze } from '@sdk-it/generic';
import { responseAnalyzer } from '@sdk-it/hono';

// Define custom type mappings
const customTypeMappings = {
  Decimal: 'string',
};

const { paths, components } = await analyze('path/to/tsconfig.json', {
  responseAnalyzer,
  typesMap: {
    ...defaultTypesMap,
    Decimal: 'string',
  },
});

const spec = {
  openapi: '3.1.0',
  info: {
    title: 'My API',
    version: '1.0.0',
  },
  paths,
  components,
};

await writeFile('openapi.json', JSON.stringify(spec, null, 2));

This configuration ensures that any property with the Decimal type is represented as a string in the generated OpenAPI specification.

Referencing external schemas

By default, the analyzer doesn't see beyond the validation schema defined in the validate middleware route handler and expects the schemas to be defined inline.

For instance the following route handler is perfectly valid and will be analyzed correctly.

/**
 * @openapi getAuthor
 * @tags authors
 */
app.get(
  '/authors/:id',
  validate((payload) => ({
    id: {
      select: payload.param.id,
      against: z.string(),
    },
  })),
  async (req, res) => {
    const { id } = req.input;
    return res.json({ id, name: 'John Doe' });
  },
);

However, if you want to reference external schemas as shown below, you need to provide a way for the analyzer to resolve the schema.

// filename: schemas.ts
import { z } from 'zod';

export const authorSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(2).max(100),
});
import crypto from 'node:crypto';
import { z } from 'zod';

import { validate } from '@sdk-it/express/runtime';

import { authorSchema } from './schemas';

/**
 * @openapi createBook
 * @tags books
 */
app.post(
  '/books',
  validate((payload) => ({
    title: {
      select: payload.body.title,
      against: z.string().min(2).max(100),
    },
    author: {
      select: payload.body.author,
      against: authorSchema, // <-- Referencing external schema
    },
  })),
  async (req, res) => {
    const { title, author } = req.input;
    return res.json({ id: crypto.randomUUID(), title, author });
  },
);

In this case the analyzer needs to be able to resolve the authorSchema reference to generate the correct OpenAPI schema otherwise it will fail.

Luckily, the analyzer provides an imports option that allows you to specify additional files to be included in the analysis.

import { join } from 'node:path';

import { analyze } from '@sdk-it/generic';

const { paths, components } = await analyze('path/to/tsconfig.json', {
  responseAnalyzer,
  imports: [
    {
      import: 'schemas',
      from: join(process.cwd(), 'path/to/schemas.ts'), // <-- Path to the file containing the external schema
    },
  ],
});

Now you need to update the import to namespace imports in the route handler where the schemas variable is used.

import * as schemas from './schemas';

/**
 * @openapi createBook
 * @tags books
 */
app.post(
  '/books',
  validate((payload) => ({
    title: {
      select: payload.body.title,
      against: z.string().min(2).max(100),
    },
    author: {
      select: payload.body.author,
      against: schemas.authorSchema,
    },
  })),
  async (req, res) => {
    const { title, author } = req.input;
    return res.json({ id: crypto.randomUUID(), title, author });
  },
);

Control endpoint/operation visibility

You can control the visibility of endpoints and operations in the generated OpenAPI specification by using the @access tag in your JSDoc comments. for now only private is supported.

/**
 * @openapi getAuthor
 * @tags authors
 * @access private
 */
app.get('/authors/:id', async (req, res) => {
  const author = [{ name: 'John Doe' }];
  return res.json(author);
});

In this example, the getAuthor operation will be hidden from the generated OpenAPI specification.