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

@mad-core/sdk

v0.1.1

Published

Typed TypeScript SDK for the MAD media server management API — upload, assets, exports and galleries, with full OpenAPI-contract coverage via a typed escape hatch. Isomorphic (Node 20+ and browser).

Readme

@mad-core/sdk

Typed TypeScript client for the MAD media server's management plane — upload, list, edit, export and organise assets programmatically. It is the counterpart to @mad-core/react (which builds delivery URLs and renders media): this package talks to the API.

  • Typed against the contract. Types are generated from the committed openapi.json, so every one of the API's operations is reachable and typed via the mad.raw escape hatch.
  • Ergonomic where it counts. A thin hand-written layer covers the integration happy-path — upload / uploadMany, assets, exports, galleries — adding auth injection, a typed error (MadError), the 409-as-result upload semantics, multipart, and pagination as an async-iterator.
  • Isomorphic. Runs in Node 20+ and the browser. The only Node-specific piece (upload from a file path) lives in the @mad-core/sdk/node subpath; the main entry never imports node:fs.
npm install @mad-core/sdk

Quick start

import { createMadClient } from "@mad-core/sdk";

const mad = createMadClient({
  baseUrl: "https://mad.example.com", // omit for same-origin (browser)
  apiKey: process.env.MAD_API_KEY!,
});

// Upload a File/Blob (e.g. from an <input type="file">)
const result = await mad.upload({ file, namespace: "marketing", tags: ["hero"] });
if (result.duplicate) {
  console.log("already exists:", result.existing.slug);
} else {
  console.log("uploaded:", result.asset.slug);
}

// List with typed filters
const { images, pagination } = await mad.assets.list({ type: "image", liked: "true" });

// Iterate every matching asset across all pages
for await (const asset of mad.assets.listAll({ namespace: "marketing" })) {
  console.log(asset.slug);
}

Uploading

mad.upload(input) POSTs one File/Blob as multipart. A 409 duplicate is returned, not thrown (the global content-hash dedup); any other non-ok status throws a MadError.

const r = await mad.upload({
  file,                 // File | Blob
  filename: "photo.png",// required for a bare Blob (the server derives the slug from it)
  name: "Hero photo",   // asset display name
  namespace: "marketing",
  tags: ["hero", "banner"],
  altText: "A hero banner",
});

mad.uploadMany(files, opts) orchestrates many single uploads client-side with bounded concurrency, resilient per file — a duplicate or a single failure never aborts the batch — and is resumable (re-running reports already-uploaded files as duplicates).

const summary = await mad.uploadMany(files, {
  concurrency: 4,
  namespace: "marketing",
  onProgress: ({ completed, total }) => console.log(`${completed}/${total}`),
});
// summary: { uploaded, duplicates, failed, outcomes }

From disk (Node)

import { uploadFile, uploadFiles } from "@mad-core/sdk/node";

await uploadFile(mad, "./photo.png", { namespace: "marketing" });
await uploadFiles(mad, ["./a.png", "./b.jpg"], { concurrency: 4 });

Files are streamed from disk (fs.openAsBlob) — they are never read fully into memory.

Assets, exports, galleries

const detail = await mad.assets.get(id);
await mad.assets.update(id, { name: "Renamed", tags: ["x"] });
await mad.assets.delete(id);                  // soft-delete (trash)
await mad.assets.delete(id, { permanent: true });
const restored = await mad.assets.restore(id); // 409 conflict returned as { restored: false, conflict }

const job = await mad.exports.create({ namespace: "marketing", type: "image" });
const status = await mad.exports.get(job.jobId);
const zip = await mad.exports.download(job.jobId, token); // public token link, no auth header

const gallery = await mad.galleries.create({ name: "Spring 2026" });
await mad.galleries.setItems(gallery.id, [id1, id2]);     // idempotent PUT
const fit = await mad.galleries.suggestions(gallery.id, [id1, id2]); // needs the semantic_search feature

Errors

Every operation (except the documented 409-as-result cases) throws a MadError on a non-ok response. Branch on the typed code, never the message:

import { MadError } from "@mad-core/sdk";

try {
  await mad.assets.get("missing");
} catch (err) {
  if (err instanceof MadError) {
    console.log(err.status, err.code, err.message, err.details);
    if (err.code === "VALIDATION_ERROR") console.log(err.issues); // [{ path, message }]
    if (err.retryable) {/* 503 / STORAGE_UNAVAILABLE / SERVER_BUSY — safe to retry */}
  }
}

A non-JSON error body (e.g. an ALB HTML 502/504) yields a generic MadError carrying the status, never a parse crash.

The escape hatch: mad.raw

Any operation the ergonomic layer doesn't wrap (folders, workflows, trash, features, watermark, OG templates, API keys, LUTs, namespaces…) is reachable, fully typed, via the underlying openapi-fetch client:

const { data, error } = await mad.raw.GET("/api/folders");
await mad.raw.POST("/api/folders", { body: { name: "Campaigns" } });

mad.raw uses the same auth and base URL as the ergonomic methods. Unlike them, it follows the openapi-fetch convention of returning { data, error } rather than throwing.

Cancellation

Every operation accepts an AbortSignal:

const controller = new AbortController();
const p = mad.assets.list({ type: "image" }, { signal: controller.signal });
controller.abort();