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

@aahoughton/oav-core

v1.1.2

Published

HTTP-aware OpenAPI request/response validator with a JSON Schema codegen compiler. Zero runtime dependencies; install @aahoughton/oav for the batteries-included experience (YAML readers + CLI).

Downloads

574

Readme

oav

OpenAPI 3.0 / 3.1 / 3.2 HTTP request and response validator. Two primary drivers:

  • Tenant overrides over a base spec. When tenants extend a shared API — adding a required header on one route, refining a schema, requiring auth where the base spec didn't — they need to document those changes in the spec they ship, not as application-side patches. applyOverlays rewrites the document at load time. Custom keywords, formats, and dialects plug into the compiler the same way, so per-tenant validation rules don't require forking. See OVERLAYS.md.
  • Validators that fit in microservice runners. oav compile-spec openapi.yaml emits a single zero-dependency ES module exposing the full validator surface. Targets Cloudflare Workers, Vercel Edge, Lambda@Edge, Deno Deploy — runtimes where new Function() is unavailable, or where dependency footprint matters. --only "POST /pets" (repeatable) scopes the output to specific operations without touching the source spec.

Errors come back as a typed tree (code, path, message, params, children). One validator call covers the full HTTP frame: method, path, parameters, body, content type, status, and headers.

Install

oav ships in two core packages, plus framework adapter packages that build on either oav or oav-core -- if you don't need YAML support, you can skip oav entirely — the lean path for zero-dependency / edge targets:

| Package | When to use | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | oav | Batteries-included: YAML readers + the oav CLI. Depends on yaml; pulls in commander + esbuild for the CLI only (never imported from the library entry points, so bundlers tree-shake them out of application bundles; Node server runs load them only when the oav binary is invoked). | | oav-core | Lean. Zero runtime dependencies. Same programmatic surface as oav, minus the YAML readers and CLI. Feed it JSON specs (or pre-parsed objects via the memory reader). | | oav-express4 | Express 4 framework adapter. Thin: imports the validator from oav-core, exports a middleware factory plus standalone helpers. See INTEGRATION.md. | | oav-express5 | Express 5 framework adapter. Same exports as oav-express4; promise-native middleware shape. | | oav-fastify | Fastify framework adapter. Same exports as the Express adapters; ships a preValidation hook instead of middleware. |

npm install @aahoughton/oav            # YAML + CLI
npm install @aahoughton/oav-core       # JSON only, zero runtime deps
npm install @aahoughton/oav-express4   # Express 4 adapter (transitively pulls oav-core)
npm install @aahoughton/oav-express5   # Express 5 adapter
npm install @aahoughton/oav-fastify    # Fastify adapter

oav re-exports oav-core at matching subpaths. Samples below use oav; on the lean package, substitute oav-core in imports that don't touch the YAML readers (createYamlFileReader, createSmartHttpReader) or the CLI.

The commander + esbuild deps oav pulls in are reachable only from the oav CLI binary (dist/cli.js). Application code importing from oav hits dist/index.js, which doesn't reference them — bundlers tree-shake them out of the output, and Node servers load them only when the CLI is invoked. Consumers who want to skip the ~10 MB of esbuild's native binary on disk can install oav-core instead (zero runtime deps, no CLI).

Quick start

import { createValidator, createYamlFileReader, formatText } from "@aahoughton/oav";
import { loadSpec } from "@aahoughton/oav/spec";

const { document } = await loadSpec({
  reader: createYamlFileReader(),
  entry: "openapi.yaml",
});
const validator = createValidator(document);

const err = validator.validateRequest({
  method: "POST",
  path: "/pets",
  contentType: "application/json",
  headers: { "x-tenant": "acme" },
  body: { name: "Fido" },
});

if (err !== null) console.error(formatText(err));

For a multi-file spec or a spec hosted over HTTP, compose readers: composeReaders([createYamlFileReader(), createSmartHttpReader(), createFileReader()]) handles local YAML, remote JSON / YAML, and local JSON transparently.

validateRequest / validateResponse return null on success or a ValidationError tree on failure. Every error carries a stable code (e.g. "type", "required", "content-type", "oneOf"), a path rooted at the HTTP frame (e.g. ["body", "pets", 3, "name"]), a human-readable message, and a machine-readable params object whose shape per code is documented in BuiltInErrorParams.

How it compares

oav's primary alternative is Ajv — directly for compileSchema, or via express-openapi-validator for HTTP validation. (Migrating from EOV specifically: MIGRATION-FROM-EOV.md.)

Numbers below are from the performance/ benchmark on AWS c7i.large (Intel Sapphire Rapids, Node 22). Your hardware will vary.

Compile: oav is meaningfully faster.

| | Ajv | oav | | ------------------------------------------ | ----- | --------- | | Single synthetic schema (varies by shape) | ~6 ms | 25–200 µs | | Real-world spec (petstore-31, ~10 schemas) | 27 ms | 1.6 ms |

Ajv compile is essentially constant overhead per schema; oav scales with shape. The advantage shows up wherever validator construction sits in the hot path — per-request, per-tenant, per-test, edge cold-start, AOT module emit.

Validate: roughly tied on simple shapes; Ajv wins on complex.

Both libraries are sub-microsecond per check on typical OpenAPI bodies. On complex oneOf/allOf or large arrays, Ajv leads by 2–4× (say 100 ns → 400 ns per call, or 1.7 µs → 4 µs). oav's predicate mode (compileSchema(..., { predicate: true })) closes most of that gap for yes/no use cases.

For typical HTTP workloads — 1k–10k req/sec × ~1 validation per request — the difference is invisible at any of those numbers. For validation-heavy code (millions of validations per second), Ajv wins.

Full per-shape breakdown: COMPARISON.md. Raw benchmark data and methodology: performance/README.md.

Conformance

The conformance/ sub-package drives the compiler and CLI against the upstream JSON Schema 2020-12 Test Suite, a set of OpenAPI 3.0 / 3.1 / 3.2 petstore scenarios, and a handful of real-world specs (Stripe, GitHub, DigitalOcean, Twilio, Asana, Box, Adyen) that have to load and compile without error. See conformance/REPORT.md for pass / fail counts by category.

Categories oav does not aim to cover:

  • $dynamicRef with runtime dynamic-scope rebinding. oav resolves statically against the anchor map.
  • The optional/format/* subtree. Those tests target strict-assertion behaviour; format is annotation-only by default per JSON Schema 2020-12 §6.3.
  • A small tail of isolated optional cases (float-overflow handling, external-ref loading tied to the dynamic-scope category above).

In practice, OpenAPI specs generated or hand-authored by application developers rarely touch any of these. $dynamicRef / $dynamicAnchor are concentrated in meta-schemas and extensible-type libraries (JSON Schema's own meta-schema, Hyperjump's type system); a spec that describes "POST /pets takes a Pet" doesn't declare them. The strict format-assertion gap only surfaces if you rely on RFC-edge behaviour in iri / iri-reference or non-BMP regex content — date-time, email, uuid, and the common URI formats pass. Float overflow concerns numbers beyond Number.MAX_SAFE_INTEGER (~9 × 10¹⁵); outside that range, JavaScript's own Number precision is the limiting factor regardless of validator. If any of these corners matter for your use case, the report lays out which tests fail and why.

CLI

oav resolve openapi.yaml
oav validate openapi.yaml --request req.http
oav validate openapi.yaml --path "POST /pets" --body payload.json
oav validate openapi.yaml --path "GET /pets" --response --status 200 --body resp.json
oav compile-schema schema.json -o validator.mjs             # JSON Schema → standalone validator
oav compile-spec openapi.yaml  -o validator.mjs             # OpenAPI   → standalone HTTP validator (edge / Lambda)

Flags: --format text|json|flat, --depth n, --overlay file (repeatable), -o file, --quiet, --dialect (compile-schema / compile-spec), --requests-only (compile-spec), --only METHOD PATH (compile-spec, repeatable). See packages/cli/README.md for the full surface, the .http file format, and both compile commands' output contracts.

compile-schema and compile-spec emit ES modules with zero imports after the esbuild bundle step. compile-spec's output exposes the same validateRequest / validateResponse / getOperation surface as createValidator(document) but with every schema already compiled into the file — suited for Cloudflare Workers / Vercel Edge / Lambda@Edge where runtime ajv.compile() is forbidden, or for Lambda cold-start latency where skipping 10–50 ms of spec parse + compile pays back.

Versions

createValidator reads the spec's openapi string once at construction and picks the matching dialect. No per-request branching.

| Spec | Dialect | Notes | | ----- | --------------------- | ----------------------------------------------------------- | | 3.0.x | OAS 3.0 Schema Object | nullable, boolean exclusiveMin/Max, sibling-$ref drop | | 3.1.x | JSON Schema 2020-12 | Assertive format | | 3.2.x | JSON Schema 2020-12 | Same as 3.1 + the QUERY HTTP method |

Override via createValidator(spec, { dialect }) to force or customise one of the built-in dialects (jsonSchemaDialect, openapi31Dialect, oas30Dialect). Unknown / missing openapi strings fall back to the 3.1 dialect by default; configure with onUnknownVersion: "throw" | "warn" | "fallback31".

Swagger 2.0 specs aren't supported directly — createValidator throws on swagger: "2.0" documents. Convert to OpenAPI 3.0 first with swagger2openapi and pass the 3.0 output to createValidator:

npx swagger2openapi swagger.json -o openapi.json

Configuring the validator

| Option | Effect | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------- | | dialect | Force a specific schema dialect, bypassing version detection. | | formats | Extra string format validators merged on top of the built-ins. | | keywords | Register user-defined schema keywords (see below). | | maxErrors | Cap on leaf errors; 1 is fast-fail, default is uncapped. | | strictQueryParameters | Reject undeclared query parameters. Default false. | | validateSecurity | Shape-only security check (bearer / basic / apiKey). Default false (auth middleware runs upstream); set true to opt in. | | ignoreUndocumented | Return null on requests whose path the router can't match. Default false. | | ignorePaths | Predicate (path) => boolean; returning true short-circuits validation to null before routing. | | onUnknownVersion | Policy for specs with missing/unsupported openapi: "fallback31" (default), "warn", or "throw". |

Custom keywords

const validator = createValidator(spec, {
  keywords: {
    activeTenant: (data) =>
      typeof data !== "string" || tenantCache.has(data)
        ? true
        : { message: `tenant "${data}" is not active` },
  },
});

Custom keywords plug into generated code alongside the built-ins. See examples/custom-keywords.ts.

Bounded error collection

createValidator(spec, { maxErrors: 1 }); // fast-fail
createValidator(spec, { maxErrors: 10 }); // bound CPU/memory on huge payloads

Hot loops (array items, object properties, allOf/anyOf branches) short-circuit once the budget is exhausted. Results carry truncated: true so callers know the tree was capped.

Framework integration

oav is a validator, not a middleware package: you write a short adapter between your framework and validateRequest / validateResponse. An Express 5 adapter is about this long:

import { allowHeaderFor, httpStatusFor, toProblemDetails } from "@aahoughton/oav";

app.use(async (req, res, next) => {
  const err = validator.validateRequest({
    method: req.method,
    path: req.path,
    query: req.query as Record<string, string | string[]>,
    headers: req.headers as Record<string, string | string[]>,
    contentType: req.get("content-type") ?? undefined,
    body: req.body,
  });
  if (err === null) return next();
  const allow = allowHeaderFor(err);
  if (allow !== undefined) res.setHeader("Allow", allow);
  res
    .status(httpStatusFor(err))
    .type("application/problem+json")
    .json(toProblemDetails(err, { instance: req.originalUrl }));
});

See INTEGRATION.md for:

  • Adapter packages for Express 4, Express 5, and Fastify; recipes for Next.js, Hono, Bun, and Deno via the Web Standards adapter.
  • Recipes for file uploads (multer), response validation, security, ignoring paths, and the full status-code switch.
  • A migration table from express-openapi-validator, including where oav is stricter or more conformant and where you'll do more wiring by hand.

Companion adapter packages cover the common framework wiring: oav-express4, oav-express5, oav-fastify. They share the same export names and option shapes; only the framework-typed argument differs.

oav is not a drop-in for express-openapi-validator: the adapters cover the request-validation middleware, but you own the error → HTTP mapping if you customise it, you wire up multer if you need file uploads, and you run your own auth middleware. The error tree is structured (code/path/message/params/children), the OpenAPI 3.0 dialect is built in rather than translated to 2020-12, and the validator does not mutate req or res.

Modules

The package publishes a small root and four subpath entrypoints. oav-core exposes the same five entrypoints; substitute oav-core/... to import from the lean package.

| Import | Surface | | ------------------------- | -------------------------------------------------------- | | @aahoughton/oav | createValidator, error helpers, formatters, types | | @aahoughton/oav/schema | compileSchema, dialects, vocabularies, custom keywords | | @aahoughton/oav/spec | loadSpec, resolveSpec, applyOverlays, readers | | @aahoughton/oav/formats | Built-in string format validators | | @aahoughton/oav/core | Error tree model, shared OpenAPI / HTTP types |

oav also exports createYamlFileReader, createSmartHttpReader (HTTP reader that handles both JSON and YAML by inspecting Content-Type), and parseYamlString at the root entry, and ships the oav CLI as a bin.

Examples

Runnable, self-contained TypeScript examples in examples/:

| File | Shows | | ------------------------------ | -------------------------------------------------------------------- | | basic-validation.ts | Load a spec → createValidator → request and response checks | | custom-formats.ts | Register a user string format (E.164 phone) | | custom-keywords.ts | Register a schema keyword that reads dynamic runtime state | | cross-field-validation.ts | Cross-field constraint (max >= min) via an object-level keyword | | max-errors.ts | Fast-fail and bounded error collection on a bulk-invalid payload | | versions.ts | 3.0, 3.1, 3.2 side by side (nullable, QUERY method) | | overlay.ts | Merge a gateway header requirement into one operation | | overlay-petstore-schema.ts | Extend the Pet component with a deployment-required field | | overlay-petstore-endpoint.ts | Require an X-Tenant header on POST /pets via an endpoint overlay | | spec-digest.ts | Derive middleware config (multer limits, required headers) at boot |

Known limitations

Runtime-behaviour corners. For a feature-scope comparison against Ajv (draft versions, $data, async validation, etc.) see COMPARISON.md.

  • $dynamicRef behaves like $ref with anchor lookup — no runtime dynamic-scope traversal.
  • style: deepObject query parameters support only single-level nesting (obj[key]=value). OpenAPI 3.0–3.2 do not define nested semantics; specs that rely on obj[a][b]=value should model the flattened shape explicitly or use a different parameter style.
  • pattern keywords and format: "regex" compile to the JavaScript built-in RegExp, which has no execution timeout. If your OpenAPI spec is attacker-controlled (e.g. multi-tenant upload), a catastrophic pattern like (a+)+$ is a ReDoS vector against any string the validator checks. Vet spec sources before loading them. A pluggable regexCompiler option for plugging in re2 or a complexity-checking engine is tracked in #146.

Contributing

See CONTRIBUTING.md for branch / PR / release flow. Development workflow (lint / typecheck / test / build) and the conformance and performance sub-packages are described there and in CLAUDE.md.

License

MIT — see LICENSE.