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

@buddy-works/docs-schema

v0.1.3

Published

Buddy's YAML and REST API schemas, fetched, cached and resolved into documented shapes

Readme

@buddy-works/docs-schema

Fetches Buddy's YAML and REST API schemas and resolves them into shapes you can render or search. It follows $ref pointers, flattens allOf, and keeps enums, constraints and examples.

Installation

npm install @buddy-works/docs-schema

Usage

import { BuddySchema } from "@buddy-works/docs-schema";

const parser = await BuddySchema.load();

await parser.actions.list();                  // every action a pipeline can run
await parser.actions.get("AMAZON_S3");        // one action, in full
await parser.actions.search("notify");        // the ones that send notifications
await parser.search("deploy to s3");          // across YAML and the REST API
await parser.api.endpoint("GET", "/workspaces/{workspace_domain}/projects");

How it gets the schemas

BuddySchema.load() reads from a local cache and revalidates over HTTP with an ETag once the cache is 3 hours old. A 304 keeps the cached copy and starts the window again. A 200 replaces the document. Nothing is fetched until a call needs it, so a process that only asks about actions never downloads the OpenAPI document.

In a long-running process such as an MCP server, the check runs in the background and the call answers from memory. A query never waits on the network. When a 200 lands, the new document takes over and the shaped results derived from the old one are dropped. A query already in flight finishes against the graph it started with.

A response that does not parse, or that lacks the key its scope depends on, is refused. The previous good copy stays in place rather than being overwritten by an error page. If the server cannot be reached at all, the cached copy keeps being served and stats() reports staleSince for that document.

It reads production unless environment says otherwise. The other names are "stage", "master" and "dev". Each one caches separately, so a process can hold two at once:

const live = await BuddySchema.load();
const stage = await BuddySchema.load({ environment: "stage" });

Supplying the documents yourself

If you already have the schemas, hand them over instead of letting the package fetch. This covers a pinned version, a file on disk, or a schema that is not published. Nothing is fetched and nothing is cached:

import { readFileSync } from "node:fs";

const parser = BuddySchema.from({
  pipelines: JSON.parse(readFileSync("yaml-schema.json", "utf8")),
});

The keys are documents, not scopes, and pipelines backs two of them:

| Key | Scopes it serves | |---|---| | pipelines | parser.pipelines, parser.actions | | targets | parser.targets | | environments | parser.environments | | sandboxes | parser.sandboxes | | agents | parser.agents | | domains | parser.domains | | customActions | parser.customActions | | distributions | parser.distributions | | api | parser.api |

Reaching a scope whose document you did not supply throws and names what was given.

Methods

Ten scopes over those nine documents. What a scope offers depends on what its schema declares, and the types enforce it. parser.agents.list() does not compile, because the agents schema has no union.

Every YAML scope

| Method | What it does | |---|---| | names(options?) | Definition names in the document, minus the ones the schema retired. { includeDeprecated: true } lists those too.await parser.pipelines.names() → ["PipelineYaml", "TransferActionYaml", …] | | def(name) | One definition as a document: fields, types it refers to, examples.await parser.pipelines.def("VariableYaml") | | properties(name) | The same definition flattened into property rows, the shape a table renders. Empty for a name the document does not declare.await parser.pipelines.properties("VariableYaml") | | search(query, options?) | Ranked hits. The query is split into words and results rank by how many they match, so a whole question works.await parser.actions.search("notify") → ROLLBAR, SMS, HONEYBADGER…await parser.search("deploy to s3") → AMAZON_S3… | | search(query, { fuzzy: true }) | The same, tolerating typos. Off by default, because a loose match out-ranks the exact one.await parser.actions.search("varibles", { fuzzy: true }) → SET_VARIABLES… | | search(query, { includeDeprecated: true }) | Retired members and definitions too. Off by default, like list(): searching is how a caller finds what to use.await parser.actions.search("sandbox") → the live ones only |

Scopes with a root

pipelines, targets, environments, sandboxes, agents, domains, customActions, distributions

root() gives the document's entry type: what a YAML file at that path may contain. await parser.pipelines.root() returns the PipelineYaml document.

Scopes with a union

actions, targets, environments

| Method | What it does | |---|---| | list(options?) | The discriminator values the union accepts, minus the ones the schema retired.await parser.targets.list() → ["SSH", "FTP", "MATCH", …]{ includeDeprecated: true } lists those too. get() and search() answer for a retired member either way. | | get(value) | One member in full: name, docs slug, tags, required fields, examples, properties, and the shaped document.await parser.actions.get("BUILD") | | commonFields() | Fields every member of the union declares.await parser.actions.commonFields() → ["type", "action", "trigger_time", …] |

api

| Method | What it does | |---|---| | operations() | Every operation the document declares, with its summary, operationId, tags, deprecated and security. Nothing is resolved, so listing every endpoint costs one walk rather than one per operation.await parser.api.operations() | | endpoint(method, path, variant?) | The operation as a document: parameters, request body, responses. Pass variant to pick one branch of a union body.await parser.api.endpoint("POST", "/oauth2/register")Bodies are a list: request.contents and each response's contents hold one entry per media type, in the document's order, so a download offering a byte stream and a JSON envelope reports both. A status that declares no content has none. | | variants(method, path) | The discriminator values you can pass as variant. Empty when the body branches by named type instead. Those shapes are on the document, under request.contents[0].body.variants. | | type(name) | One named schema as a document, for a type name you read off a field. Saves refetching the endpoint it came from.await parser.api.type("ExecutionView") | | typeNames(options?) | Every schema name the document declares, minus the retired ones. | | search(query, options?) | As above, over operation summaries, paths and tags. |

On the client

| Method | What it does | |---|---| | search(query, options?) | One index across every scope, YAML and REST together. Narrow it with { scopes: ["actions"] }. Twenty hits unless limit says otherwise.Each hit carries kind, scope, key, name, tags, score. | | raw(document) | The unresolved document, $ref pointers intact. For a validator compiling against them, or for reading $defs directly. | | prefetch(documents?) | Load now rather than on the first request. Under from() this reports { refreshed: [], reason: "documents were supplied" } rather than throwing. | | release(document) | Drop a fetched document and its resolution memo. The shaped-result cache is shared, so it is emptied whole rather than per document. Returns false for a supplied document, which cannot be reacquired. | | stats() | Which documents are held, their ETag and fetch time, which deployment they came from, whether a write to the cache failed, and the result cache's entries, hits and misses. |

Options

await BuddySchema.load({ environment, cacheDir, ttlMs, results });
BuddySchema.from(documents, { results });

environment is one of the names above. cacheDir and ttlMs fall back to the variables below. results bounds how many shaped documents are held. Pass false to shape every call afresh.

Errors

  • DocumentUnavailableError: nothing fetched and no cached copy. Names the URL and points at BuddySchema.from().
  • MissingDocumentError: a scope was reached whose document from() was not given. Lists what was.
  • ScopeUnsupportedError: a supplied document is not the schema that scope needs. Says what was expected, what was found, and the nearest existing name.

Requirements

Node 20.19 or newer, or Bun.

The cache lives in ~/.buddy/docs-schema, one pair of files per document: the body and its ETag. Two environment variables override the defaults:

  • BUDDY_DOCS_SCHEMA_CACHE: cache directory.
  • BUDDY_DOCS_SCHEMA_TTL: milliseconds a cached copy stays fresh. 0 revalidates on every access. Infinity pins a run to the version it started with.