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

@openmirai/typeforge

v0.3.2

Published

Typeforge: headless OpenAPI to TypeScript codegen CLI and HTTPFetch runtime

Readme

@openmirai/typeforge

Headless OpenAPI / Swagger → TypeScript codegen. The CLI is typeforge. It reads a spec, writes typed route enums, request types, and HTTP caller functions, and never talks to a network.

You own http.ts (the HTTPFetch adapter). Generated files import that adapter — they do not invent axios/fetch calls inline.

What it generates

For each source (a named API, e.g. atlas), under <apiRoot>/<source>/generated/:

| Output | Role | | --- | --- | | types/**/*.d.ts | Params, body, and response types per operation | | functions/**/*.ts | Typed callers (getWidgets, …) | | routes.ts | Routes string map + RouteTargets enum (name configurable) | | runtime.ts | Re-exports HTTPFetch, httpFetch (if singleton), routes | | base.ts | BaseResponse<T> when the spec uses a response envelope (or base.d.ts for split declaration output) |

Optional:

  • TanStack Query — set tanstackQuery: true in source.ts and add <apiRoot>/query-scope.ts.
  • Zod — wrap a schema with createZodValidator from @openmirai/typeforge/validation/zod and pass it as config.validateResponse.

Install

Requires Node.js 24+ (LTS). Use any package manager.

| Package manager | Install | | --- | --- | | npm | npm install --save-dev @openmirai/typeforge | | pnpm | pnpm add -D @openmirai/typeforge | | yarn | yarn add -D @openmirai/typeforge | | bun | bun add -d @openmirai/typeforge |

Axios is an optional peer. Install axios only if you use --client axios.

Add a script so every package manager resolves the CLI from node_modules/.bin:

{
  "scripts": {
    "generate:types": "typeforge generate --all"
  }
}

Then run npm run generate:types, pnpm run generate:types, yarn generate:types, or bun run generate:types.

CLI usage

Prefer the package.json script above. To invoke the binary directly:

| Command | npm | pnpm | yarn | bun | | --- | --- | --- | --- | --- | | Init a source | npx typeforge init --source atlas --client axios | pnpm exec typeforge init --source atlas --client axios | yarn typeforge init --source atlas --client axios | bunx typeforge init --source atlas --client axios | | Generate one source | npx typeforge generate --source atlas | pnpm exec typeforge generate --source atlas | yarn typeforge generate --source atlas | bunx typeforge generate --source atlas | | Generate all sources | npx typeforge generate --all | pnpm exec typeforge generate --all | yarn typeforge generate --all | bunx typeforge generate --all | | Drift check (CI) | npx typeforge generate --all --check | pnpm exec typeforge generate --all --check | yarn typeforge generate --all --check | bunx typeforge generate --all --check |

| Subcommand | Purpose | | --- | --- | | init | Scaffold http.ts, source.ts, known-types.ts | | generate | Write generated files | | check | Same as generate --check — exit 1 if output would change | | accept-base | Update generated base.ts (base.d.ts for split declaration output) and patch models.ts BaseResponse |

--check and --accept-base cannot be combined. See docs/cli.md for the full command reference.

How the flow works

init  →  source.ts + http.ts  →  resolve spec  →  generate  →  typed callers

1. Init a source

typeforge init --source atlas --client axios
typeforge init --source orbit --client fetch --layout packages

--client is axios | fetch | custom. --layout is monolith (default, apiRoot = src/api) or packages (apiRoot = packages/utils/src/api).

Init creates (if missing):

  • typeforge.json with apiRoot
  • <apiRoot>/http.ts — your HTTPFetch implementation
  • <apiRoot>/known-types.ts — optional schema → local type mapping
  • <apiRoot>/<source>/source.ts — per-API config (type-safe template)
  • <apiRoot>/<source>/generated/ directory

Existing files are skipped.

2. Configure source.ts

Use defineSourceConfig for autocomplete and compile-time checks:

import { defineSourceConfig } from "@openmirai/typeforge";

export default defineSourceConfig({
  spec: "./specs/acme.json",
  functionsDir: "packages/utils/src/api/routes/atlas",
  typesDir: "packages/types/src/api/atlas",
  pathPrefix: "/api/acme/v3",
  stripApiPrefix: true,
  routeEnumName: "RouteTargets",
  generationMode: "authoritative",
  naming: "path",
  ignorePaths: [],
  maxRenderDepth: 50,
  resolveMapKeyRefs: true,
  tanstackQuery: false,
  queryExtends: {
    page: "page",
    limit: "limit",
    sortBy: "sortBy",
    sortOrder: "sortOrder",
    paginationTypeName: "OffsetLimitQuery",
    paginationImportPath: "./pagination",
    sortTypeName: "SortParams",
    sortImportPath: "./pagination",
  },
});

Plain export default { ... } still works; the CLI reads config fields from the file at generate time.

Re-exported types from the package root:

  • SourceConfig, QueryExtendsConfig, GenerationMode, NamingStrategy
  • defineSourceConfig(config) — identity helper for typed source.ts

| Field | Meaning | | --- | --- | | spec | Project-relative spec path (used when no --spec / env override) | | functionsDir | Project-relative function output directory (defaults to the source's generated/functions) | | typesDir | Project-relative type output directory (defaults to the source's generated/types; a generated base.d.ts is placed beside this directory when customized) | | pathPrefix | Only generate operations under this prefix (e.g. /api/acme/v3) | | ignorePaths | Extra paths to skip | | stripApiPrefix | Strip a leading /api segment from route enum member names | | routeEnumName | Enum name (default RouteTargets) | | generationMode | authoritative (overwrite routes) or merge (keep extra enum members) | | naming | path or operationId for function names | | queryExtends | Fold page/limit/sort query params into shared pagination types | | tanstackQuery | Emit Query helpers when query-scope.ts exists | | importBase | Force import prefix for generated function files (overrides tsconfig aliases) | | maxRenderDepth / resolveMapKeyRefs | Schema renderer limits | | unwrapResponseData | Emit an envelope's data schema as the operation response type when the project's HTTPFetch already unwraps envelopes |

3. Spec resolution (first match wins)

  1. --spec <path>
  2. Env OPENAPI_SPEC_<KEY> — source key uppercased, hyphens → underscores
  3. spec in that source’s source.ts
  4. typeforge.local.json (gitignored) map of { "<source>": "<path>" }
  5. Committed snapshot <apiRoot>/<source>/spec.json

4. Envelope modes

Inferred from success response schemas. Details: docs/envelope.md.

| Mode | When | Types | | --- | --- | --- | | shared | One envelope shape (data / success / message) | BaseResponse<Unwrapped> | | raw | No shared envelope | Spec schema as-is | | mixed | Some ops have data, others do not | Unwrap per operation when data exists |

Set unwrapResponseData: true when the project's injected HTTPFetch normalizes successful envelope bodies before returning { data }. Every operation whose success schema is recognized as an API envelope then receives its data payload type. Data-only objects and business payloads that also contain success remain raw. Metadata-only envelopes without a data field receive the null type, matching clients that normalize an omitted payload to null. The default remains envelope-preserving and is compatible with the bundled Axios and Fetch adapters.

5. HTTPFetch (http.ts)

Adapters implement HTTPFetch from @openmirai/typeforge/http (or the axios/fetch adapter packages). Methods return Promise<{ data: TResponse }>.

  • If http.ts exports httpFetch, generated functions call that singleton.
  • Otherwise they take props.http: HTTPFetch (injected).

6. Path-alias aware imports

Generated function files import types and runtime, and generated response types import the generated base declaration (base.ts in monolith output, or base.d.ts for split declaration output), using:

  1. importBase in source.ts, if set
  2. Else compilerOptions.paths from the nearest ancestor tsconfig.json with path aliases, starting at the corresponding functionsDir or typesDir
  3. Else relative paths (../../runtime)

Where files go

typeforge.json:

{ "apiRoot": "packages/utils/src/api" }

You can also set "typeforge": { "apiRoot": "..." } in package.json. The JSON file wins.

Monolith (--layout monolith, default):

src/api/http.ts
src/api/known-types.ts
src/api/models.ts                 # optional BaseResponse drift check
src/api/query-scope.ts            # optional TanStack
src/api/atlas/source.ts
src/api/atlas/spec.json           # optional snapshot
src/api/atlas/generated/…

Packages layout (--layout packages): typical placement is packages/utils/src/api/<source>/.

Set functionsDir and typesDir when callers and declarations belong in different packages. Relative imports continue to work without aliases; when a nearby tsconfig.json maps both output roots, deep generated imports use those aliases automatically.

Zod (optional)

import { createZodValidator } from "@openmirai/typeforge/validation/zod";
import { widgetListSchema } from "./widget-list";

await getWidgets({
  params: { page: 1, limit: 20 },
  config: { validateResponse: createZodValidator(widgetListSchema) },
});

Releasing

Publishes go through npm Trusted Publishing (GitHub Actions OIDC). Do not npm publish from a laptop.

| | Value | | --- | --- | | npm package | @openmirai/typeforge | | GitHub repo | openmirai/typeforge | | Workflow | .github/workflows/publish.yml | | Tag | v* (e.g. v0.1.3) |

Develop this repo

This repository uses pnpm for its own CI. Consumers are not required to use pnpm.

pnpm install
pnpm verify   # format, lint, typecheck, build, coverage

Test layout: test/README.md.