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

@n0safe/openapi-orpc-contracts

v1.3.1

Published

Generate tree-shakable oRPC contracts from OpenAPI 3.x specs (URL or file) via typed-openapi.

Readme

oapi — OpenAPI → oRPC contract generator

Production-ready CLI + programmatic API for generating tree-shakable oRPC contracts from any OpenAPI 3.x spec (URL or file), built with NestJS + nest-commander. Spec-agnostic: there is no bundled/default spec — you always pass --spec <url|file>.

Features

  • Spec from URL or file--spec https://... or --spec ./my-spec.json (validated, cached). No default spec; never Cloudflare-aware.
  • In-memory zod generation — typed-openapi's programmatic API, no intermediate .zod.ts file
  • Per-endpoint contract files — only the schemas each endpoint uses (transitive closure, tree-shaken)
  • Route-folder tree with oc.router() — every directory has an index.ts router carrying its path via oc.prefix; each contract declares path: "/"
  • Per-directory common.ts — LCA placement: a schema lives in the lowest directory whose subtree contains every endpoint that needs it
  • Detailed input/output + status codes — real path params (params), query, headers, body; every 2xx/3xx → status-typed output, every ≥400 → .errors() with oRPC standard codes
  • Runtime defect repair — circular schemas (z.lazy), .extend() on non-objects (.and()), z.iso (zod/v4)
  • Fast IntelliSense — router type-alias memoization (RootRouter, ZonesRouter, …) so the editor resolves each level as a cached named type
  • CLI + programmatic API — nest-commander commands and a typed createGenerator() facade

Structure

src/
  main.ts                    # CLI bootstrap (nest-commander CommandFactory)
  app.module.ts              # root NestJS module
  index.ts                   # public programmatic API (createGenerator)
  commands/                  # nest-commander @Command classes
    extract.command.ts       #   oapi extract <selector>
    extract-many.command.ts  #   oapi extract-many [selectors...]
    shrink.command.ts        #   oapi shrink
  contracts/                 # command/API option contracts (interfaces + mappers)
    extract.contract.ts
    extract-many.contract.ts
    shrink.contract.ts
  services/                  # @Injectable() services (DI)
    spec.service.ts          #   load + validate spec (URL/file, cached)
    zod-generator.service.ts #   in-memory zod/type generation + ctx building
    extract.service.ts       #   orchestrate extractOne / extractMany + write files
    shrink.service.ts        #   shrink schema subset
    output.service.ts        #   filesystem writes + summaries
  core/                      # pure, framework-agnostic logic (no NestJS)
    schema-file.ts           #   schema module indexing, closure, emit (z.lazy/.and repairs)
    types-file.ts            #   type sidecar indexing + emit
    generate.ts              #   typed-openapi programmatic generation
    openapi.ts               #   spec helpers, JSON→zod compiler, operation compilation
    router.ts                #   route tree, LCA common placement, router/endpoint emission
  types/                     # shared TypeScript interfaces
  constants/                 # defaults + HTTP/status constants

Install & build

bun install
bun run build

bun run build produces a single self-contained monofile bin plus the programmatic API bundle and type declarations:

| artifact | purpose | | --- | --- | | dist/main.js | monofile CLI (~9.5 MB): all real deps (NestJS, typed-openapi, …) bundled; only NestJS optional peers are external | | dist/index.js | bundled programmatic API (createGenerator) | | dist/**/*.d.ts | TypeScript declarations (tsc --emitDeclarationOnly) |

Only the NestJS optional peers (@nestjs/microservices, @nestjs/platform-express, @nestjs/websockets, class-transformer, class-validator) are kept external — our code never uses them, and inlining them would just bloat the monofile.

CLI

# dev (bun) — --spec is REQUIRED (URL or file path)
bun src/main.ts extract-many --spec https://example.com/openapi.json --all --out-dir out
bun src/main.ts extract-many --spec ./my-spec.json --tag Zone
bun src/main.ts extract-many --path-prefix /zones
bun src/main.ts extract-many zones-0-get "GET /zones"
bun src/main.ts extract "GET /zones/{zone_id}" --out endpoints/zone-details.ts
bun src/main.ts shrink --prefix zones_ --out slim.ts

# production (node) — the bin is a single self-contained file
node dist/main.js extract-many --spec ./my-spec.json --all --out-dir out

Options:

| command | flags | | --- | --- | | extract <selector> | -s, --spec, -o, --out, --validation, --no-tree-shake, --no-descriptions | | extract-many [selectors...] | -s, --spec, -o, --out-dir, --all, --tag, --path-prefix, --id-prefix, --threshold, --flat, --no-common, --no-barrel, --validation, --no-tree-shake, --no-descriptions | | shrink | -s, --spec, -o, --out, -k, --keep, -p, --prefix (repeatable), --validation, --no-tree-shake, --no-descriptions |

Programmatic API

import { createGenerator } from "@n0safe/openapi-orpc-contracts";

const gen = await createGenerator();
try {
  const result = await gen.extract("https://example.com/openapi.json", "out", {
    filters: { all: true },
  });
  console.log(result.stats.endpoints, "endpoints,", result.files.length, "files");

  const one = await gen.extractOne("openapi.json", "zones-0-get", { out: "zone.ts" });
  const slim = await gen.shrink("openapi.json", { prefixes: ["zones_"], out: "zones.ts" });
} finally {
  await gen.appContext.close();
}

Advanced users can also use the pure core/ functions directly (re-exported from the package root): buildSchemaIndex, buildTypeIndex, compileOperation, extractMany, extractOne, shrinkSchemas, …

Generated output shape

out/
  index.ts            export const router: RootRouter = oc.router({ accounts, zones, ... })
  common.ts           schemas shared across top-level routers (root LCA)
  zones/
    index.ts          export const zones: ZonesRouter = oc.prefix("/zones").router({...})
    common.ts         schemas shared by 2+ endpoints under /zones
    {zone_id}/
      index.ts        export const zone_id: ZoneIdRouter = oc.prefix("/{zone_id}").router({...})
      common.ts
      zones_0_get.ts  contract for GET /zones/{zone_id} (path: "/")
      purge_cache/
        index.ts
        common.ts
        zone_purge.ts contract for POST /zones/{zone_id}/purge_cache (path: "/")

Each leaf's baked route.path equals the exact full path:

import { router } from "./out";
router.zones.zone_id.purge_cache.post["~orpc"].route.path;  // "/zones/{zone_id}/purge_cache/"

Testing

The suite is vitest, split into unit, service, and e2e layers:

test/
  fixtures/spec.json              tiny spec exercising params, bodies, multi-status, enums, refs
  unit/schema-file.spec.ts        schema indexer, closures, z.lazy cycle repair, .extend() rewrite
  unit/types-file.spec.ts         type indexer, closures, emission
  unit/openapi.spec.ts            spec validation, name map, JSON→zod compiler, operation resolution/compilation
  unit/router.spec.ts             extractOne / extractMany (tree, flat, noCommon, noBarrel) / shrink
  unit/spec.service.spec.ts       SpecService: file + URL loading (fetch mocked), caching, validation
  unit/extract.service.spec.ts    ExtractService / ShrinkService with real DI + disk writes
  e2e/programmatic.spec.ts        createGenerator() booting the real NestJS context
  e2e/cli.spec.ts                 compiled dist/main.js (help, extract, extract-many, shrink, error exits)

Run:

bun run test          # one-shot
bun run test:watch    # watch mode

The e2e CLI tests build dist/ automatically if missing, and assert that command errors exit non-zero (the CLI sets process.exitCode = 1 on errors). Tests run in CI on every push/PR and before every npm publish.

Scripts

| command | action | | --- | --- | | bun run dev | run the CLI with bun (source) | | bun run build | monofile bin + API bundle + tsc declarations → dist/ | | bun run start | run the compiled CLI with node | | bun run extract:all | all endpoints → out/ (bun dev mode, needs --spec) | | bun run test | run the vitest suite | | bun run test:watch | vitest watch mode | | bun run typecheck | typecheck src/ | | bun run typecheck:out | typecheck generated out/ |