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

@nitida/asset-client

v0.24.1

Published

nitida URL builders — construct image, video and HLS URLs for the nitida CDN. No network, no key, no config beyond a tenant id.

Downloads

4,231

Readme

@nitida/asset-client

URL builders for assets stored on the nitida platform and served from https://8ok.uk.

No network calls, no API key, no side effects — it turns a stored sha into a URL. If you also need to upload, that is @nitida/sdk (which re-exports everything here).

bun add @nitida/asset-client

Where these symbols live

Everything documented here is exported by @nitida/asset-client, and every one of those exports is re-exported by the root @nitida/sdk — if you already installed the SDK, importing from its root works and needs no second dependency.

@nitida/sdk/server and @nitida/sdk/web carry the full set as well, so any of the three works. ⚠️ Not so before 2026-08-21 — /server was short 28 of the root's exports and /web short 37. On an older SDK, import these from the root or from this package.

Configure once, at module load

The builders read process-global config, so pin it where your helpers live — every Server and Client Component that imports a builder is then configured.

import { setCdnBase, setTenantId } from "@nitida/asset-client";

setCdnBase("https://8ok.uk");   // already the default
setTenantId(12);                // REQUIRED for video/variant URLs — see below

Images — on-the-fly transforms

import { getTransformSrcSet, getTransformUrl, type TransformWidth } from "@nitida/asset-client";

getTransformUrl({ sha }, { format: "webp", width: 640 });
// → https://8ok.uk/t/format=webp,width=640/<sha16>.webp

getTransformSrcSet({ sha }, [640, 960, 1280], { format: "webp" });

⚠️ Widths must be on the unsigned ladder TRANSFORM_WIDTHS — generated from the source, do not edit by hand:

96, 128, 160, 180, 240, 256, 320, 400, 480, 600, 640, 800, 960, 1080, 1200, 1280, 1440, 1600, 1920, 2560, 3840 — 21 widths.

Anything else is HTTP 400 at the edge — it is a DoS guard, not a bug. Import the TransformWidth type and an off-ladder number becomes a compile error instead of a runtime 400.

Video — the stored variant, not a transform

import { getAssetUrl, setTenantId } from "@nitida/asset-client";

setTenantId(tenantId);
getAssetUrl({ sha }, "video");   // → https://8ok.uk/<tenantId base36>/v/<sha16>-v.mp4

⚠️ The tenant segment is base36, not decimal. Tenant 10 is /a/, tenant 12 is /c/; /12/v/ 404s. This is invisible for tenants ≤ 9 and bit a real migration exactly once — so always let getAssetUrl build the path.

⚠️ Never getVideoTransformUrl for a stored video. That builds a /t/… transform URL, which does NOT give you the video. On a video sha it transforms the poster frame and answers 200 image/webp (x-transform-source: poster) — useful, but an image. If that video has no poster it answers 410. Either way you never get playable video out of /t/; use getAssetUrl(asset, "video").

The original variant

Pass the asset's mime or you get the "bin" sentinel:

getAssetUrl({ sha }, "original");              // → …-o.bin  ❌ 404, always
getAssetUrl({ sha, mime }, "original");        // → …-o.webp ✓

⚠️ And the extension the SERVER stored comes from the uploaded filename, not the mime. For JPEG those disagree (image/jpegjpeg, but a camera writes .jpg) — measured, -o.jpeg 404s while -o.jpg is 200. If the URL must be right, verify it with a HEAD before relying on it.

Asking for a preset vs. a preset existing

VariantPreset is what a variant can be. RequestablePreset is what you can ask for. They overlap in nine values and differ in three:

  • hls and mp3 exist but cannot be ordered — the ladder is built when a video transcodes, the mp3 is emitted alongside any audio original.
  • probe can be ordered but never reaches the compact presets string, so it is not a VariantPreset.

Sending either of the first two answers HTTP 400; the write methods now take RequestablePreset[], so it is a compile error instead. A CI check reads the server's own schemas and asserts the type still matches them.

Which presets exist

Read dto.presets — the compact code string ("oq" = original + thumb, "pv" = poster + video) — via hasPreset, not the variants array:

import { hasPreset } from "@nitida/asset-client";
if (hasPreset(asset, "thumb")) { /* … */ }

GET /assets/:id sends the full variants array (URLs included) as of the 2026-08-17 deploy, but the slim list and slot-resolver shapes never carry it, and an older server deploy returns it empty on every shape. presets is populated everywhere, on every version — so read existence from presets and use variants only for URLs.

| preset | code | ext | max dim | |---|---|---|---| | thumb | q | webp | 256 (square cover) | | sm / md / lg / xl | s m l x | webp | 640 / 1280 / 1920 / 3840 | | original | o | source ext | — | | poster / video / aiproxy | p v a | webp / mp4 / mp4 | — | | mp3 | — | mp3 | — |

Troubleshooting

| Symptom | Cause | |---|---| | 400 on an image URL | width not on TRANSFORM_WIDTHS | | 410 on a video /t/ URL | that video has no poster variant. With one, /t/ returns the poster as an image — never the video. Use getAssetUrl(asset, "video") | | 404 on a video URL | decimal tenant prefix instead of base36 | | original 404s | no mime passed, or the stored ext came from the filename (-o.jpg vs -o.jpeg), or it was never written |

Deeper guidance — including uploads, tenant onboarding and the CORS boundary — lives in @nitida/sdk's skills/nitida-sdk/SKILL.md, which ships inside that package.

License

MIT — see LICENSE.

⚠️ The licence covers this client code, not the service. MIT lets you use, modify and redistribute the SDK; it does not grant access to nitida. The service is governed by its own terms and plan tiers, and every call still needs credentials issued to your project. Same shape as every other API client you already depend on.