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

@prdb/sdk

v0.14.0

Published

TypeScript SDK for the prdb Public API

Readme

@prdb/sdk (TypeScript)

TypeScript client for the prdb Public API.

Install

npm install @prdb/sdk

Requires Node 22 or newer. The package is ESM-only and ships type declarations.

Usage

import { createClient } from "@prdb/sdk";

const client = createClient({ apiKey: "..." });

// GET /videos
const page = await client.videos.get();
for (const video of page?.items ?? []) {
  console.log(video.title);
}

// GET /videos/{id}
const video = await client.videos.byId(videoId).get();

// Query parameters are typed, including the closed-set ones.
const pageTwo = await client.videos.get({
  queryParameters: { page: 2, pageSize: 50, search: "..." },
});

The request builders mirror the API's URL structure, so GET /videos/{id}/filehashes is client.videos.byId(videoId).filehashes.get().

Authentication

createClient sends the key in the X-Api-Key header, and keeps it on the API host: a redirect to a different origin throws CrossOriginRedirectError rather than handing your credential to whoever answers there. Redirects that stay on the same origin are followed normally.

baseUrl must use https, so the key is never sent in cleartext. A loopback address (localhost, 127.0.0.1 or [::1]) is the exception: the request never leaves the machine, so plain http is accepted there and a local stand-in for the API needs no certificate.

GET /health is the only endpoint that works without a key; use createAnonymousClient() for health probes. That one has no credential to protect, so it accepts a plain http base URL.

Options

import { RETRY_DISABLED, createClient } from "@prdb/sdk";

const client = createClient({
  apiKey: "...",
  baseUrl: "https://api.prdb.net", // override for a staging deployment
  customFetch: myFetch,            // control timeouts, proxies, agents
  retry: RETRY_DISABLED,           // see below
});

customFetch is wrapped in the SDK's middleware, so the redirect rule above applies to it too.

Retrying

By default the SDK retries a 429, 503 or 504 up to three times, honouring Retry-After.

Turn that off if your application already retries prdb calls:

const client = createClient({ apiKey: "...", retry: RETRY_DISABLED });

Otherwise the two policies multiply — one logical call becomes up to n×m requests against an API that rate limits, and an outer circuit breaker never sees a stable failure to open on. The built-in policy also retries writes, so an application that must not repeat one should own the retry itself.

To keep it but change it:

const client = createClient({
  apiKey: "...",
  retry: { maxRetries: 5, delay: 1 },
});

Reading the response status

A typed call returns the deserialised body but not the response status. Pass a ResponseStatusOption when the status itself matters; the conditional-request example below uses it to distinguish a 304 Not Modified response from other responses with no body.

Pass a ResponseStatusOption to read it:

import { ResponseStatusOption } from "@prdb/sdk";

const status = new ResponseStatusOption();

const health = await client.health.get({
  options: [status],
});

console.assert(health?.status === "healthy");
console.assert(status.statusCode === 200);

Kiota's own native response handler surfaces the raw Response but suppresses deserialisation while doing so. This option keeps the typed result and records the status alongside it.

Reading the rate limit

Every metered response carries the rate limit it was counted against, so you can pace off the answers you are already getting instead of spending a request on GET /rate-limit to ask.

import { RateLimitOption } from "@prdb/sdk";

const limits = new RateLimitOption();

const sites = await client.sites.get({ options: [limits] });

if (limits.hour && limits.hour.remaining < 50) {
  // Slow down; limits.hour.resetInSeconds until a slot frees up.
}

hour and month are each a RateLimitWindow with limit, remaining and resetInSeconds, or undefined.

resetInSeconds is the wait until the oldest request leaves the sliding window and frees one slot — not a timestamp, and not the time until the whole window resets. It is the same quantity resetsInSeconds carries on GET /rate-limit.

undefined is an answer rather than a gap. A response the API did not meter — 401, 403, 503, and GET /rate-limit itself — carries no headers at all, and a 429 carries only the window that refused the request, so exactly one of the two being set is normal. A rejected call records too, so the reading is there for a caller that catches the error.

Kiota can also surface response headers itself, through HeadersInspectionOptions, as raw multi-valued strings. This option is the typed reading of the six that matter.

Conditional requests

GET /sites returns a weak ETag covering the matched rows and the paging, sorting and search parameters. Send it back as If-None-Match and the endpoint answers 304 Not Modified with no body while nothing has changed — the whole site list fits in one request at pageSize: 1000, so this is worth doing.

import { HeadersInspectionOptions } from "@microsoft/kiota-http-fetchlibrary";
import { ResponseStatusOption } from "@prdb/sdk";

// First call: read the validator off the response.
const inspect = new HeadersInspectionOptions({ inspectResponseHeaders: true });
await client.sites.get({ options: [inspect] });
const [etag] = inspect.getResponseHeaders().get("etag") ?? [];

// Later: ask only for what changed.
const status = new ResponseStatusOption();
const sites = await client.sites.get({
  headers: { "If-None-Match": etag },
  options: [status],
});

if (status.statusCode === 304) {
  // Nothing changed; keep the copy you already have.
}

A 304 returns undefined from the typed call rather than rejecting. undefined alone does not distinguish "not modified" from "no rows", so pass a ResponseStatusOption when you need to tell them apart.

One wrinkle from the API side: the shared read-only cache does not vary by If-None-Match, so a request that hits it is answered 200 with a body even when your validator still matches. That is expected rather than an error.

Use one instance per call. It is written when the response arrives, so sharing one across concurrent calls means whichever finishes last wins.

The status recorded is the one the result was built from: after a redirect the SDK followed, and after the last retry. A call that rejects records too, so an error caught from a 403 still has its status alongside. It stays undefined when no response was reached at all — a failed connection, a timeout, or a refused cross-origin redirect.

Generated code

Everything under src/generated/ is produced by Kiota from spec/openapi.json in the repository root and is overwritten on every regeneration. Do not edit it — see the root README.