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

@docspack/lapis

v0.2.0

Published

Convert an OpenAPI 3 document to LAPIS, the compact notation for describing an API to a language model. About 90% fewer tokens than the same document as JSON.

Readme

@docspack/lapis

OpenAPI 3 in, LAPIS out. About 90% fewer tokens than the same document as JSON.

LAPIS is an open notation for describing an API to a language model rather than to a code generator: a signature per operation, compact types, and the sections OpenAPI spends thousands of lines on collapsed into a few. There was no JavaScript converter for it, so this is one.

npx @docspack/lapis ./openapi.yaml
npx @docspack/lapis ./openapi.json -o api.lapis --stats
curl -s https://example.com/openapi.json | npx @docspack/lapis - > api.lapis
[meta]
api: Invoice Service
base: https://api.example.com/v2
auth: bearer header:Authorization

[types]
InvoiceCreate:
  customer_id: str
  due_date?: date

[ops]
create_invoice POST /invoices
  Creates an invoice for a customer.
  > body: InvoiceCreate
  < Invoice  # 201 The created invoice.
  @auth bearerAuth

[errors]
401 ApiError
  The token is missing, expired or invalid.

Measured against Stripe's published document — 594 operations, 1,454 schemas, 8.0 MB:

594 operations, 1454 types, read as json
~193,593 tokens of LAPIS, against ~2,163,446 of JSON (91% smaller)

As a library

import { parseOpenApi, toLapis } from "@docspack/lapis";

const document = parseOpenApi(await someOpenApiObject);
toLapis(document);

parseOpenApi resolves every $ref, merges allOf, and keeps named schemas by name so a recursive document terminates. It takes an already-parsed object, so this entry point has no dependencies and runs anywhere — including a browser.

Reading a file, or text that might be YAML, is a separate entry point:

import { readApiFile, readApiText } from "@docspack/lapis/read";

const { document, format } = await readApiFile("./openapi.yaml"); // format: "json" | "yaml"

That split is the point of the package's shape. JSON.parse is built into every JavaScript runtime at zero bytes; YAML needs a parser, and that parser is ~57 KB gzipped. Putting it behind /read means a browser bundling parseOpenApi and toLapis ships none of it, and tests/entry.test.ts asserts that over the built output rather than trusting the comment.

The model

Both renderings come from one shape — ApiDocument, with operations, types, servers, securitySchemes and tags. It is exported, so it is also usable on its own if you want a normalized OpenAPI document and not LAPIS at all. toGroups(document) buckets operations by tag for a sidebar.

Three limits, and the measurement behind each

A faithful renderer produces unusable output on real documents. Each of these exists because its absence was measured against Stripe's spec:

| Limit | Without it | | --- | --- | | Type closure stops at a token budget | Every Stripe object has an expandable field pointing at another, so one operation reaches most of the API: 66,500 tokens for one endpoint | | Enumerations list 8 members | The merchant-category enum has 297, four times in one operation: 25,637 characters on one line | | Inline objects expand 2 levels, 12 fields | A document that names no schemas renders its whole body tree inline |

Everything elided is counted — +32 more, {...} — because "there is more to this shape" and "this is the whole shape" are different claims, and only one of them is true.

Conformance

One thing here is not in LAPIS's grammar: an @auth <scheme> line on an operation. LAPIS declares authentication in [meta] and has no per-operation form, so a document where some endpoints are public and others are not — which is most of them — cannot say so in conformant LAPIS. It is emitted by default because that is worth more to a model than conformance is, and turned off by --strict (or toLapis(document, { strict: true })) for anything feeding a parser that would reject it.

Everything else is legal LAPIS: # comments carry the success status, and [errors] is the format's own section — declared once per document, with @ops: bindings where an error is not universal.

What it refuses

Each of these stops with the command to run:

  • Swagger 2.0 — npx swagger2openapi openapi.json -o openapi3.json
  • An external $ref — npx @redocly/cli bundle openapi.yaml -o openapi.json. Refused rather than dropped: a schema silently replaced by "unknown" is a description claiming an endpoint takes no body.
  • A $ref that points at nothing — named, with the pointer.

Who uses it

This package is the shared half of docspack's OpenAPI support. @docspack/openapi builds on it — per-operation digests, request building, code samples, a try-it console — and docspack build --openapi turns a document into a searchable documentation package. None of that is needed to use this; it converts one format to another and stops.

Licence

MIT. The LAPIS specification is CC BY 4.0 and belongs to its authors; this package implements a renderer for it.