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

oci-distribution

v0.2.1

Published

A zero-dependency TypeScript client for the OCI Distribution Specification.

Downloads

485

Readme

oci-distribution

A zero-dependency TypeScript client for the OCI Distribution Specification. It supports both read and write operations against any standards-compliant OCI registry, with the standard authentication mechanisms (bearer-token challenge, HTTP basic, and Docker config files).

  • No production dependencies. Built entirely on the Node.js standard library (fetch, node:crypto, node:http).
  • Full lifecycle. Pull, push, content discovery (tags & referrers), and content management (deletion).
  • Standard auth. Token challenge (WWW-Authenticate), OAuth2 refresh-token grant, HTTP basic, and ~/.docker/config.json (including credential helpers).
  • Resilient. Automatic retry/backoff for transient failures, Docker Hub host canonicalization, and referrers handling that copes with registries (like AWS ECR) that report a subject but don't implement the referrers API.
  • OCI 1.1 artifacts. First-class subject/referrers support, so it can be used as the foundation for artifact specs such as the Sigstore cosign bundle spec (see below).

Requirements

Node.js ^22.22.2 || ^24.15.0 || >=26.0.0. The package is published as ESM.

Install

npm install oci-distribution

Quick start

import { Registry } from "oci-distribution";

const registry = new Registry("ghcr.io");
const repo = registry.repository("owner/app");

// Resolve a tag to a descriptor (HEAD).
const descriptor = await repo.resolve("latest");

// Fetch and parse a manifest.
const { manifest } = await repo.getManifest("latest");

// Pull the first layer blob (digest-verified).
const firstLayer = (manifest as import("oci-distribution").ImageManifest).layers[0];
const bytes = await repo.blobs.get(firstLayer.digest);

Authentication

Pass a credential provider — a function from a registry host to a credential — via the credentials option. Several are built in.

HTTP basic (username / password)

import { Registry, basicCredential } from "oci-distribution";

const registry = new Registry("ghcr.io", {
  credentials: basicCredential("ghcr.io", "octocat", process.env.GITHUB_TOKEN!),
});

Docker config file

Reads ~/.docker/config.json (or $DOCKER_CONFIG/config.json), honoring inline auths, credsStore, and credHelpers (credential helper binaries):

import { Registry, dockerConfigCredential } from "oci-distribution";

const registry = new Registry("ghcr.io", {
  credentials: dockerConfigCredential(),
});

Some registries and proxies also require custom HTTP headers (the Docker config's top-level HttpHeaders). Read them with dockerConfigHeaders() and pass them as headers — they are scoped to the registry host and never sent to a host named by a server response:

import { Registry, dockerConfigCredential, dockerConfigHeaders } from "oci-distribution";

const registry = new Registry("ghcr.io", {
  credentials: dockerConfigCredential(),
  headers: dockerConfigHeaders(),
});

Combining sources

import { Registry, chainCredentials, basicCredential, dockerConfigCredential } from "oci-distribution";

const registry = new Registry("ghcr.io", {
  credentials: chainCredentials(
    basicCredential("ghcr.io", "octocat", process.env.GITHUB_TOKEN ?? ""),
    dockerConfigCredential(),
  ),
});

Custom provider

A provider is just a function; it may be async:

const registry = new Registry("registry.example.com", {
  credentials: async (host) => ({ refreshToken: await fetchIdentityToken(host) }),
});

The token-challenge flow (parsing WWW-Authenticate, fetching a token from the authorization server via the distribution GET or OAuth2 POST grant, caching it per host and scope, and retrying) is handled automatically. Set forceOAuth2: true to always use the OAuth2 grant when credentials are present.

Pushing content

Push blobs first, then the manifest that references them:

import { Registry, MEDIA_TYPE_OCI_IMAGE_MANIFEST, calculateDigest } from "oci-distribution";

const repo = new Registry("ghcr.io", { credentials }).repository("owner/app");

const layer = new TextEncoder().encode("layer contents");
const layerDescriptor = await repo.blobs.push(
  { mediaType: "application/vnd.oci.image.layer.v1.tar" },
  layer,
);

const manifest = new TextEncoder().encode(JSON.stringify({
  schemaVersion: 2,
  mediaType: MEDIA_TYPE_OCI_IMAGE_MANIFEST,
  config: repo.emptyConfigDescriptor(),
  layers: [layerDescriptor],
}));

const manifestDescriptor = await repo.manifests.push("v1", manifest, {
  mediaType: MEDIA_TYPE_OCI_IMAGE_MANIFEST,
});

packManifest builds and pushes an OCI artifact manifest for you (pushing the empty {} config automatically):

const descriptor = await repo.packManifest({
  artifactType: "application/vnd.example.thing",
  layers: [layerDescriptor],
  annotations: { "org.opencontainers.image.created": new Date().toISOString() },
  tag: "v1",
});

Other write operations: repo.blobs.mount(descriptor, "other/repo") (cross-repo mount), repo.manifests.delete(ref), and repo.blobs.delete(digest).

Discovery

// Tags (with automatic Link-header pagination).
const tags = await repo.tags.listAll();

// Referrers of a subject (referrers API, with tag-schema fallback).
const index = await repo.referrers.list(subjectDigest, {
  artifactType: "application/vnd.example.thing",
});

API overview

| Area | Entry points | | --- | --- | | Client | Registry, Registry#repository, Registry#ping | | Repository | Repository#{blobs,manifests,tags,referrers}, resolve, fetchManifest, getManifest, pushManifest, pushBlob, packManifest, attachArtifact | | Blobs | exists, get, fetchResponse, push, mount, delete | | Manifests | exists, resolve, fetch, get, push, delete | | Auth | AuthClient, AuthCache, parseChallenge, credential providers | | Digests | calculateDigest, verifyDigest, parseDigest, Digester | | References | parseReference, stringifyReference, referrersTag |

Development

npm run build       # compile to dist/ (tsc)
npm run typecheck   # type-check src + tests
npm test            # run the test suite (node:test)

Downloaded blobs and digest-referenced manifests are verified against their digests; a mismatch throws DigestMismatchError. Non-2xx responses throw ResponseError, which parses the OCI structured error body.

License

Apache-2.0