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

@diffusionstudio/api-contract

v0.5.0

Published

API contract for the Diffusion Studio backend: zod schemas and tRPC AppRouter types.

Readme

@diffusionstudio/api-contract

The API contract for the Diffusion Studio backend: zod input schemas, shared types (Job, Asset, GenerateRequest, …) and the tRPC AppRouter type used to create a fully typed client.

The AppRouter type is generated from the backend router, so this package always reflects the deployed API surface. The runtime exports are limited to zod schemas and constants; no server code is included.

Client

v2 is served at /api/v2/trpc. Queries and mutations go over HTTP; jobs.watch is a server-sent events subscription, so route subscriptions to httpSubscriptionLink with a splitLink:

import { createTRPCClient, httpBatchLink, httpSubscriptionLink, splitLink } from "@trpc/client";
import type { AppRouter } from "@diffusionstudio/api-contract";

const url = "https://api.diffusion.studio/api/v2/trpc";
const authorization = async () => `Bearer ${await getAccessToken()}`; // Supabase access token

export const api = createTRPCClient<AppRouter>({
  links: [
    splitLink({
      condition: (op) => op.type === "subscription",
      // EventSource can't send headers, so the token travels as a connection param.
      true: httpSubscriptionLink({ url, connectionParams: async () => ({ authorization: await authorization() }) }),
      false: httpBatchLink({ url, headers: async () => ({ authorization: await authorization() }) }),
    }),
  ],
});

Every procedure requires auth. Request bodies are limited to 1 MB; files are uploaded directly to storage (see below).

Generating

All generation goes through generate, typed by the model discriminator. Generative models share one schema per kind (imageRequest, videoRequest, audioRequest, voiceRequest) with the widest bounds any model of the kind accepts; a request outside the chosen model's own bounds (e.g. a duration Veo doesn't support) fails with BAD_REQUEST. Tools have a schema each. It returns a Job immediately; follow it with jobs.watch:

import type { Job } from "@diffusionstudio/api-contract";

const job = await api.generate.mutate({
  request: { model: "minimax-h3", prompt: "A red fox at dawn", duration: 6 },
  idempotencyKey: crypto.randomUUID(), // same key → same job, no double charge
  maxCredits: 100,                     // optional: reject if the price is higher
});

const sub = api.jobs.watch.subscribe(
  { id: job.id },
  {
    onData: ({ data }: { data: Job }) => {
      if (data.status === "succeeded") console.log(data.assets.map((a) => a.url));
      if (data.status === "failed") console.error(data.error);
    },
    onError: console.error,
  },
);
  • Each event is tracked by the job's version, so the link resumes from the last event after a reconnect. The server closes the stream once the job is terminal (or after 5 minutes; the link reconnects).
  • jobs.get({ id }) is the polling alternative for scripts and the CLI.
  • Asset URLs are signed for 1 hour on every read; read the job again for fresh ones.
  • estimate(request) returns { credits, etaSeconds } without creating a job.
  • Rerun: generate({ request: job.request, idempotencyKey: crypto.randomUUID() }).
  • jobs.cancel({ id }) cancels and refunds.

Uploading inputs

Model inputs reference files as assetRef: { kind: "asset", id: sha256 } for uploaded files or { kind: "url", url } for public https URLs. Files are stored by content hash, so the same file is uploaded once:

const bytes = new Uint8Array(await file.arrayBuffer());
const digest = await crypto.subtle.digest("SHA-256", bytes);
const sha256 = [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");

const { asset, uploadUrl } = await api.uploads.create.mutate({ sha256, contentType: file.type, size: file.size });

if (uploadUrl) {
  // Signed resumable upload (valid 15 min): start a session, then upload to it.
  const start = await fetch(uploadUrl, {
    method: "POST",
    headers: { "x-goog-resumable": "start", "content-type": file.type },
  });
  await fetch(start.headers.get("location")!, { method: "PUT", body: file });
}
// uploadUrl === null: the file is already stored.

await api.generate.mutate({
  request: { model: "minimax-h3", prompt: "Bring this to life", startFrame: asset },
  idempotencyKey: crypto.randomUUID(),
});

Uploads expire 30 days after their last uploads.create. If generate fails with "asset not found — re-upload", call uploads.create again and upload.

v1

The v1 API (/api/trpc) stays available while clients migrate; its router type is exported as AppRouterV1.