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

multi-spec-parser

v0.5.0

Published

Parse OpenAPI 3.x, Swagger 2.0, and Google Discovery documents into normalized operation-level JSON Schemas and Standard Schema adapters.

Readme

multi-spec-parser

Parse OpenAPI 3.0/3.1, Swagger 2.0, and Google Discovery documents into one normalized collection of operation-level schema handles.

The package parses and projects API descriptions. It does not build HTTP requests, execute operations, manage authentication, or run consumer workflows.

⚠️ WIP — expect breaking changes. This package is pre-1.0. Pin an exact version before upgrading.

Why

  • One operation model: all supported source formats produce the same shape.
  • Self-contained schemas: each input and output handle carries only its reachable TypeScript definitions.
  • Bounded presentation: generated TypeScript types have explicit depth, branch, node, property, and output-length limits.
  • Standard Schema: operation schemas can be adapted to Standard Schema and Standard JSON Schema.
  • Memory safety: eager parsing normalizes shared definitions once; lazy URL loading keeps the source on disk and materializes one operation closure at a time.

Install

npm install multi-spec-parser

YAML is decoded by the bundled dependency-free Node-API addon. Installation uses a matching prebuild when available and otherwise compiles the bundled C source.

Quick start

import { MultiSpecParser } from "multi-spec-parser";

const parser = new MultiSpecParser({
  spec: {
    text: `
openapi: 3.0.3
info: { title: Pets, version: "1" }
paths:
  /pets/{petId}:
    get:
      operationId: getPet
      parameters:
        - name: petId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: ok
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Pet" }
components:
  schemas:
    Pet:
      type: object
      properties:
        id: { type: string }
`,
  },
});

const operations = await parser.parse();

console.log(parser.format); // openapi3
console.log(operations.length); // 1

const operation = await parser.getOperation("getPet");
console.log(operation?.method);       // GET
console.log(operation?.path);         // /pets/{petId}
console.log(operation?.input.type);   // TypeScript input presentation
console.log(operation?.output?.type); // TypeScript success-response presentation

const result = await operation?.input.validate({ petId: "123" });
console.log(result?.status); // ok or error

How it works

                         ┌─ OpenAPI 3 adapter ───────┐
source → bounded loader ─┼─ Swagger 2 adapter ───────┼→ normalized model
                         └─ Discovery adapter ───────┘         │
                                                               ▼
                                                    operation compiler
                                                               │
                                      ┌────────────────────────┴──────────┐
                                      ▼                                   ▼
                             operation schema handles           Standard Schema adapter

There are two loading paths:

  • Eager: URL, text, or object → complete parsed document → every operation.
  • Lazy: URL → bounded temporary file → structural byte-range index → one requested operation plus its transitive references.

Both paths use the same format adapters and operation compiler, so a materialized lazy operation has the same shape as its eager counterpart. See docs/architecture.md and docs/lazy-loading.md.

Sources

The parser accepts a URL, JSON/YAML text, or a pre-parsed object:

new MultiSpecParser({ spec: { url: "https://example.com/openapi.yaml" } });
new MultiSpecParser({ spec: { text: sourceText } });
new MultiSpecParser({ spec: { spec: sourceObject } });

| Source | Eager parse() | Lazy load() | |---|---:|---:| | HTTP(S) URL | Yes | Yes | | Caller-owned text | Yes | No | | Caller-owned object | Yes | No |

Text and object sources are intentionally eager because the complete source is already resident in caller-owned memory.

URL loading accepts HTTP(S), enforces a 200 MiB response limit and a 60-second timeout, and supports cancellation:

const operations = await parser.parse({ signal: abortController.signal });

For large URL sources, lazy mode streams the response to a bounded temporary file, structurally indexes OpenAPI 3.x, Swagger 2.0, or Google Discovery JSON (and OpenAPI 3.x block YAML), and materializes only requested operations and their transitive schema references:

const parser = new MultiSpecParser({
  spec: { url: "https://example.com/openapi.yaml" },
  options: { lazy: true },
});

try {
  await parser.load({ signal: abortController.signal });
  console.log(parser.operationNames());
  const operation = await parser.getOperation("getPet");
} finally {
  await parser.close();
}

Lazy mode rejects text and object sources, unknown formats, and unsupported layouts instead of falling back to eager parsing. parse() is unavailable in lazy mode. close() is idempotent and removes the temporary file.

Authentication and custom request policy remain caller-owned. Fetch protected sources first, then pass their text to the parser. Object sources remain caller-owned and are not mutated.

Operation projections

parse() returns a copy of the operation collection. Every operation has:

interface CompiledOperation {
  name: string;
  operationKey: string; // stable METHOD + path identity
  description: string;
  method: string;
  path: string;
  input: OperationSchema;
  output: OperationSchema | undefined;
  operation: ExtractedOperation;
  unresolvedRefs?: string[];
}

name comes from the source operation identifier, with deterministic fallback and deduplication. operationKey remains independent of that generated name.

Each schema handle has exactly three public members:

interface OperationSchema {
  readonly type: string;
  readonly definitions: Readonly<Record<string, string>>;
  validate(value: unknown): Promise<ValidationResult>;
}

type is a bounded TypeScript presentation. definitions contains only named types reachable from that input or output root. The canonical JSON Schema stays private so consumers cannot accidentally couple workflows to the compiler's intermediate representation.

Validation is dependency-free, does not mutate or coerce values, and never inserts defaults. Expected outcomes use a discriminated result:

type ValidationResult =
  | { status: "ok"; value: unknown }
  | { status: "error"; error: ValidationError };

ValidationError is tagged as ValidationFailed, UnsupportedValidationKeyword, or ValidationLimitExceeded. Unsupported assertions reject instead of being silently ignored. Validation plans are compiled lazily and memoized per handle.

Standard Schema

After eager parsing, the parser can adapt an operation to Standard Schema and Standard JSON Schema:

const schema = parser.toStandardSchema("getPet");

const validation = await schema.validate({ petId: "123" });
const input = schema.input("draft-07");
const output = schema.output(); // draft-2020-12 by default

Input and output projections independently include only definitions reachable from their respective roots. The underlying schema["~standard"] property is available for framework interoperability, but normal application code does not need to access it. The adapter uses the same dependency-free validator as the operation handle. A direct adapter is available from the subpath. This is also the lazy-mode route: materialize the operation first, then pass the operation object.

import { toStandardSchema } from "multi-spec-parser/standard-schema";

const operation = await parser.getOperation("getPet");
const schema = toStandardSchema(operation!);

Formats

| Format | Detection | Eager | Lazy URL | Notable normalization | |---|---|---:|---:|---| | OpenAPI 3.0.x | openapi: "3.0.x" | JSON/YAML | JSON/block YAML | nullable and boolean exclusive bounds | | OpenAPI 3.1.x | openapi: "3.1.x" | JSON/YAML | JSON/block YAML | type arrays and numeric exclusive bounds | | Swagger 2.0 | swagger: "2.0" | JSON/YAML | JSON | body/formData and collectionFormat conversion | | Google Discovery | kind: "discovery#restDescription" | JSON/YAML | JSON | resources, repeated parameters, and global parameters |

Detection uses document content, not URLs or file extensions.

Options

| Option | Default | Purpose | |---|---:|---| | maxDefsBytes | 1,000,000 | Use the shared definition map when an operation closure exceeds this UTF-8 budget | | lazy | false | Index a supported JSON or OpenAPI block-YAML URL on disk and materialize operations on demand |

parse() accepts only an optional cancellation signal. Unknown options throw immediately so removed functionality cannot silently become a no-op.

Examples and verification

npm run examples
npm test
npm run battle

The battle suite checks large real and synthetic descriptions in bounded child processes, including cyclic references, dense definition graphs, missing references, and thousands of operations.

License

MIT