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

@zivue/zuuid

v0.2.8

Published

TypeScript helpers for fetching, searching, and transforming Zuuid datasets.

Readme

@zivue/zuuid

Search, fetch, and normalize media metadata from external providers into a shared Zuuid data shape.

This package is meant to be used by apps that need provider-backed lookup and transformation, but do not want provider-specific response shapes leaking through the app.

TMDB, Open Library, ComicVine, GamesDB, MusicBrainz, OpenFoodFacts, and OpenStreetMap include live search/fetch clients. IMDb includes a fetch-by-ID title-page scraper for movie and TV titles. Additional providers currently expose source-record transformers: you provide the raw provider payload, and this package normalizes it into ZuuidData.

Storage, caching, indexing, review state, object-store keys, and persistence belong in a layer outside this package.

Install

npm install @zivue/zuuid

Quick Start

import { createZuuidClient } from "@zivue/zuuid";

const zuuid = createZuuidClient({
  providers: {
    tmdb: {
      bearerToken: process.env.TMDB_BEARER_TOKEN!
    },
    openlibrary: {
      // Open Library does not require credentials.
    }
  }
});

const search = await zuuid.movie.tmdb?.search({ query: "Fight Club" });
const selected = search?.results[0];
const movie = selected ? await zuuid.movie.tmdb?.fetch({ id: selected.source.value }) : undefined;

console.log(movie?.primaryTitle);
// Fight Club

What You Get

The package has two main output shapes:

  • SearchResponse<ZuuidSearchResult> for search/list screens.
  • ZuuidData for full fetched and transformed datasets.

Search is lightweight and paginated. Fetch returns the full normalized dataset for a selected provider ID.

const movies = await zuuid.movie.tmdb?.search({ query: "Fight Club" });

console.log(movies?.pagination);
// { page: 1, totalPages: 10, totalResults: 190 }

console.log(movies?.results[0]);
// {
//   id: "1706d641-d381-5618-9425-d8cd8b35f898",
//   zuuid: "1706d641-d381-5618-9425-d8cd8b35f898",
//   category: "movie",
//   title: "Fight Club",
//   date: "1999-10-15",
//   cover: "https://image.tmdb.org/t/p/w500/...",
//   rating: 4.2,
//   weight: 20.0,
//   relationType: null,
//   attribute: null,
//   order: null,
//   source: { source: "tmdb", category: "movie", value: "550" }
// }

The full fetched dataset is flat and provider-normalized:

const movie = await zuuid.movie.tmdb?.fetch({ id: "550" });

console.log(movie);
// {
//   zuuid,
//   kind: "watch",
//   category: "movie",
//   primaryTitle: "Fight Club",
//   primaryDate: "1999-10-15",
//   rating,
//   cover,
//   aliases,
//   descriptions,
//   details,
//   media,
//   relations,
//   recommendations,
//   tags,
//   externalIds,
//   provenance
// }

Search results, relations, and recommendations share the same lightweight list item fields: id, zuuid, category, title, date, cover, rating, weight, relationType, attribute, and order.

Ratings are normalized to a 0-5 scale when the provider exposes a compatible numeric score. The original provider score is preserved in details as provider_rating for providers whose native scale differs.

Supported Providers

| Provider | Category | Search | Fetch | Transform | | --- | --- | --- | --- | --- | | TMDB | movie | yes | yes | yes | | TMDB | tv | yes | yes | yes | | TMDB | person | yes | yes | yes | | Open Library | book | yes | yes | yes | | Open Library | author | yes | yes | yes | | ComicVine | volume, issue, story_arc, character, person, publisher | yes | yes | yes | | IMDb | movie, tv | no | yes | yes | | GamesDB | game | yes | yes | yes | | GamesDB | platform | no | yes | yes | | Jikan | anime, manga, producer, magazine, character, person | no | no | yes | | MusicBrainz | release, release-group, recording, artist, label, work | yes | yes | yes | | OpenFoodFacts | product | yes | yes | yes | | OpenStreetMap | city, country, place, venue | yes | yes | yes | | Podcast / iTunes | podcast | no | no | yes | | Setlist.fm | setlist, artist, venue | no | no | yes | | Ticketmaster | event, attraction, venue | no | no | yes | | Wger | exercise, equipment | no | no | yes |

For IMDb, search is marked no because the scraper fetches known title IDs such as tt0137523; it does not implement IMDb search. ComicVine and GamesDB require API keys for live API calls. GamesDB platform search is marked no because only game search and ID fetches are currently implemented. For transformer-only providers, "Search" and "Fetch" are marked no because this package does not perform those HTTP requests yet. Use your own provider client or stored payloads, wrap the payload in a SourceRecord, and call the category transformer.

GamesDB Credentials

GamesDbProvider uses TheGamesDB v1 API and requires an API key:

import { GamesDbProvider } from "@zivue/zuuid/providers/gamesdb";

const gamesdb = new GamesDbProvider({
  apiKey: process.env.GAMESDB_API_KEY!
});

const game = await gamesdb.fetchGame({ id: 17444 });
const search = await gamesdb.searchGames({ query: "Chrono Trigger" });
const platform = await gamesdb.fetchPlatform({ id: 6 });

The category-first client exposes GamesDB under play.gamesdb:

const zuuid = createZuuidClient({
  providers: { gamesdb: { apiKey: process.env.GAMESDB_API_KEY! } }
});

const game = await zuuid.play.gamesdb?.game.fetch({ id: 17444 });

GamesDB transforms accept both simple flat payloads and native TheGamesDB API envelopes with data.games, data.platforms, lookup maps, and boxart image metadata.

OpenFoodFacts

OpenFoodFactsProvider uses the public Open Food Facts API for product lookup and search. Fetch IDs are product barcodes.

import { OpenFoodFactsProvider } from "@zivue/zuuid/providers/openfoodfacts";

const off = new OpenFoodFactsProvider();

const product = await off.fetchProduct({ id: "3017620422003" });
const products = await off.searchProducts({ query: "Nutella" });

The category-first client exposes OpenFoodFacts under product.openfoodfacts:

const zuuid = createZuuidClient({ providers: { openfoodfacts: {} } });

const products = await zuuid.product.openfoodfacts?.search({ query: "oat bar" });
const product = await zuuid.product.openfoodfacts?.fetch({ id: "3017620422003" });

OpenStreetMap

OpenStreetMapProvider uses the public Nominatim API for OSM lookup and search. It sends a descriptive User-Agent by default and lets you override it. Fetch IDs must be OSM object IDs with a type prefix: N for node, W for way, or R for relation.

import { OpenStreetMapProvider } from "@zivue/zuuid/providers/openstreetmap";

const osm = new OpenStreetMapProvider({
  userAgent: "your-app/1.0 ([email protected])"
});

const city = await osm.fetchCity({ id: "R406091" });
const venues = await osm.searchVenues({ query: "Blue Note Oslo" });

The category-first client exposes OpenStreetMap under visit.openstreetmap:

const zuuid = createZuuidClient({ providers: { openstreetmap: {} } });

const places = await zuuid.visit.openstreetmap?.place.search({ query: "Eiffel Tower" });
const products = await zuuid.product.openfoodfacts?.search({ query: "Nutella" });
const country = await zuuid.visit.openstreetmap?.country.fetch({ id: "R2978650" });

ComicVine Credentials

ComicVineProvider uses the ComicVine API and requires an API key:

import { ComicVineProvider } from "@zivue/zuuid/providers/comicvine";

const comicvine = new ComicVineProvider({
  apiKey: process.env.COMICVINE_API_KEY!
});

const volume = await comicvine.fetchVolume({ id: 1 });
const issues = await comicvine.searchIssues({ query: "Saga" });
const publisher = await comicvine.fetchPublisher({ id: 10 });

The category-first client exposes ComicVine under read.comicvine and people.comicvine:

const zuuid = createZuuidClient({
  providers: { comicvine: { apiKey: process.env.COMICVINE_API_KEY! } }
});

const volumes = await zuuid.read.comicvine?.volume.search({ query: "Saga" });
const character = await zuuid.people.comicvine?.character.search({ query: "Spider-Man" });

MusicBrainz

MusicBrainzProvider uses the public MusicBrainz JSON web service. No API key is required, but the provider sends a descriptive User-Agent by default and lets you override it.

import { MusicBrainzProvider } from "@zivue/zuuid/providers/musicbrainz";

const musicbrainz = new MusicBrainzProvider({
  userAgent: "your-app/1.0 ([email protected])"
});

const release = await musicbrainz.fetchRelease({ id: "f5093c06-23e3-404f-aeaa-40f72885ee3a" });
const releaseGroups = await musicbrainz.searchReleaseGroups({ query: "Kind of Blue" });
const artist = await musicbrainz.fetchArtist({ id: "561d854a-6a28-4aa7-8c99-323e6ce46c2a" });

The category-first client exposes MusicBrainz under listen.musicbrainz and people.musicbrainz:

const zuuid = createZuuidClient({ providers: { musicbrainz: {} } });

const album = await zuuid.listen.musicbrainz?.releaseGroup.search({ query: "Kind of Blue" });
const artist = await zuuid.people.musicbrainz?.artist.search({ query: "Miles Davis" });

IMDb Scraper

IMDb can be used as a credential-free alternative source when you already have an IMDb title ID. It scrapes the title page, preserves the fetched HTML and extracted JSON-LD in the raw SourceRecord, and transforms the JSON-LD into normalized ZuuidData. If IMDb serves a challenge page instead of title HTML, the provider falls back to IMDb suggestion data for core fields such as title, year, poster, type, rank, and cast summary.

import { ImdbProvider } from "@zivue/zuuid/providers/imdb";

const imdb = new ImdbProvider();
const movie = await imdb.fetchMovie({ id: "tt0137523" });
const tv = await imdb.fetchTv({ id: "tt0944947" });

The category-first client exposes the same fetch-only provider under movie.imdb and tv.imdb:

const zuuid = createZuuidClient({ providers: { imdb: {} } });
const movie = await zuuid.movie.imdb?.fetch({ id: "tt0137523" });

TMDB Credentials

TmdbProvider accepts either a TMDB API Read Access Token or a v3 API key:

import { TmdbProvider } from "@zivue/zuuid/providers/tmdb";

const tmdb = new TmdbProvider({
  bearerToken: process.env.TMDB_BEARER_TOKEN!
});

// or
const tmdbWithApiKey = new TmdbProvider({
  apiKey: process.env.TMDB_API_KEY!
});

TMDB bearer tokens usually start with eyJ...; v3 API keys are shorter hex-like strings.

Search

Use the category-first client facade when your app may have several providers:

const movies = await zuuid.movie.tmdb?.search({ query: "Fight Club" });
const tvShows = await zuuid.tv.tmdb?.search({ query: "Game of Thrones" });
const people = await zuuid.people.tmdb?.search({ query: "Brad Pitt" });
const books = await zuuid.read.openlibrary?.search({ query: "The Lord of the Rings" });
const authors = await zuuid.people.openlibrary?.search({ query: "J. K. Rowling" });
const comics = await zuuid.read.comicvine?.volume.search({ query: "Saga" });
const places = await zuuid.visit.openstreetmap?.place.search({ query: "Eiffel Tower" });
const products = await zuuid.product.openfoodfacts?.search({ query: "Nutella" });

Provider methods are also available directly:

const movies = await tmdb.searchMovies({ query: "Fight Club" });
const tvShows = await tmdb.searchTv({ query: "Game of Thrones" });
const people = await tmdb.searchPeople({ query: "Brad Pitt" });
import { OpenLibraryProvider } from "@zivue/zuuid/providers/openlibrary";

const openlibrary = new OpenLibraryProvider();
const books = await openlibrary.searchBooks({ query: "The Lord of the Rings" });
const authors = await openlibrary.searchAuthors({ query: "J. K. Rowling" });
import { ComicVineProvider } from "@zivue/zuuid/providers/comicvine";

const comicvine = new ComicVineProvider({ apiKey: process.env.COMICVINE_API_KEY! });
const volumes = await comicvine.searchVolumes({ query: "Saga" });
const issues = await comicvine.searchIssues({ query: "Saga" });
import { OpenStreetMapProvider } from "@zivue/zuuid/providers/openstreetmap";

const osm = new OpenStreetMapProvider();
const places = await osm.searchPlaces({ query: "Eiffel Tower" });
const city = await osm.fetchCity({ id: "R406091" });
import { OpenFoodFactsProvider } from "@zivue/zuuid/providers/openfoodfacts";

const off = new OpenFoodFactsProvider();
const products = await off.searchProducts({ query: "Nutella" });
const product = await off.fetchProduct({ id: "3017620422003" });

Search options include pagination and common TMDB filters:

const movies = await tmdb.searchMovies({
  query: "Fight Club",
  page: 2,
  includeAdult: false,
  primaryReleaseYear: 1999
});

If you need the raw TMDB search payload wrapped as source records:

const rawMovies = await tmdb.searchMovieSourceRecords({ query: "Fight Club" });

Fetch

Fetch returns transformed ZuuidData:

const movie = await zuuid.movie.tmdb?.fetch({ id: 550 });
const tv = await zuuid.tv.tmdb?.fetch({ id: 1399 });
const person = await zuuid.people.tmdb?.fetch({ id: 287 });
const book = await zuuid.read.openlibrary?.fetch({ id: "OL82563W" });
const author = await zuuid.people.openlibrary?.fetch({ id: "OL23919A" });

Direct provider methods are equivalent:

const movie = await tmdb.fetchMovie({ id: 550 });
const tv = await tmdb.fetchTv({ id: 1399 });
const person = await tmdb.fetchPerson({ id: 287 });

const book = await openlibrary.fetchBook({ id: "OL82563W" });
const author = await openlibrary.fetchAuthor({ id: "OL23919A" });

Raw Source Records And Transform

For debugging, caching in your own layer, or custom transform timing, split fetch from transform:

import { transformTmdbMovie } from "@zivue/zuuid/providers/tmdb";

const source = await tmdb.fetchMovieSourceRecord({ id: 550 });
const movie = source ? await transformTmdbMovie(source, tmdb.transformOptions()) : undefined;

The source record contains the raw payload:

console.log(source?.payload);

The transform result is normalized ZuuidData:

console.log(movie?.primaryTitle);
console.log(movie?.externalIds);
console.log(movie?.provenance);

For transformer-only providers:

import { createSourceRecord, transformGamesDbGame } from "@zivue/zuuid";

const source = await createSourceRecord({
  source: { provider: "gamesdb", category: "game", externalId: "17444" },
  payload: rawGamesDbPayload
});

const game = await transformGamesDbGame(source);

Category-specific imports are available:

import { transformTmdbMovie } from "@zivue/zuuid/providers/tmdb/movie";
import { transformTmdbTv } from "@zivue/zuuid/providers/tmdb/tv";
import { transformTmdbPerson } from "@zivue/zuuid/providers/tmdb/person";
import { transformOpenLibraryBook } from "@zivue/zuuid/providers/openlibrary/book";
import { transformOpenLibraryAuthor } from "@zivue/zuuid/providers/openlibrary/author";
import { transformMusicBrainzReleaseGroup } from "@zivue/zuuid/providers/musicbrainz/release-group";
import { transformComicVineIssue } from "@zivue/zuuid/providers/comicvine/issue";
import { transformJikanProducer } from "@zivue/zuuid/providers/jikan/producer";
import { transformOpenStreetMapVenue } from "@zivue/zuuid/providers/openstreetmap/venue";
import { transformWgerEquipment } from "@zivue/zuuid/providers/wger/equipment";

Data Model

ZuuidData is the full normalized dataset:

type ZuuidData = {
  zuuid: string;
  kind: EntityKind;
  category: string;
  primaryTitle: string;
  primaryDate?: string;
  rating?: number;
  cover?: string;
  aliases: Alias[];
  descriptions: Description[];
  details: Detail[];
  media: MediaAsset[];
  relations: EntityRelation[];
  recommendations: RecommendationEdge[];
  tags: string[];
  externalIds: ExternalId[];
  provenance: Provenance[];
};

Detail.value can be any JSON value, so details can hold strings, numbers, booleans, arrays, or structured objects without duplicating value and data fields.

ZUUIDs

Every fetched dataset and unified search result includes a zuuid. This is a deterministic UUID v5 generated from the provider namespace, category, and external ID. It gives your app a stable cross-provider identifier while the provider's own ID remains available in source or externalIds.

import { providerZuuid } from "@zivue/zuuid";

const zuuid = await providerZuuid({
  provider: "tmdb",
  category: "movie",
  externalId: "550"
});

Most applications do not need to call providerZuuid directly; search and fetch do it internally.

Client Design

The client is stateless. It does not cache, persist, schedule, read environment variables, or write files. It only closes over provider configuration and exposes category/provider methods:

zuuid.movie.tmdb?.search({ query: "Fight Club" });
zuuid.movie.tmdb?.fetch({ id: 550 });

zuuid.tv.tmdb?.search({ query: "Game of Thrones" });
zuuid.tv.tmdb?.fetch({ id: 1399 });

zuuid.people.tmdb?.search({ query: "Brad Pitt" });
zuuid.people.tmdb?.fetch({ id: 287 });
zuuid.people.openlibrary?.search({ query: "J. K. Rowling" });
zuuid.people.openlibrary?.fetch({ id: "OL23919A" });

zuuid.read.openlibrary?.search({ query: "The Lord of the Rings" });
zuuid.read.openlibrary?.fetch({ id: "OL82563W" });

zuuid.listen.musicbrainz?.release.search({ query: "Kind of Blue" });
zuuid.listen.musicbrainz?.releaseGroup.fetch({ id: "aaa50249-1e6b-3910-b830-7e2fb622a8c4" });
zuuid.listen.musicbrainz?.recording.search({ query: "So What" });
zuuid.people.musicbrainz?.artist.search({ query: "Miles Davis" });

Example Scripts

The examples read .env from the repo root:

TMDB_BEARER_TOKEN=...
# or
TMDB_READ_ACCESS_TOKEN=...
# or
TMDB_API_KEY=...

Search a provider and print unified search results:

npm run example:search -- movie "Fight Club"
npm run example:search -- tv "Game of Thrones"
npm run example:search -- people "Brad Pitt"
npm run example:search -- book "The Lord of the Rings"
npm run example:search -- author "J. K. Rowling"
npm run example:search -- release-group "Kind of Blue"
npm run example:search -- recording "So What"
npm run example:search -- artist "Miles Davis"
COMICVINE_API_KEY=... npm run example:search -- comicvine:volume "Saga"
COMICVINE_API_KEY=... npm run example:search -- comicvine:issue "Saga"
npm run example:search -- openstreetmap:place "Eiffel Tower"
npm run example:search -- openstreetmap:venue "Blue Note Oslo"
npm run example:search -- openfoodfacts:product "Nutella"

Fetch and transform a selected provider ID:

npm run example:fetch -- movie 550
npm run example:fetch -- tv 1399
npm run example:fetch -- people 287
npm run example:fetch -- book OL82563W
npm run example:fetch -- author OL23919A

Fetch live provider examples:

npm run example:fetch -- imdb:movie tt0137523
npm run example:fetch -- imdb:tv tt0944947
COMICVINE_API_KEY=... npm run example:fetch -- comicvine:volume 1
COMICVINE_API_KEY=... npm run example:fetch -- comicvine:issue 101
GAMESDB_API_KEY=... npm run example:fetch -- gamesdb:game 17444
GAMESDB_API_KEY=... npm run example:fetch -- gamesdb:platform 6
npm run example:fetch -- musicbrainz:release f5093c06-23e3-404f-aeaa-40f72885ee3a
npm run example:fetch -- musicbrainz:release-group aaa50249-1e6b-3910-b830-7e2fb622a8c4
npm run example:fetch -- musicbrainz:artist 561d854a-6a28-4aa7-8c99-323e6ce46c2a
npm run example:fetch -- openstreetmap:city R406091
npm run example:fetch -- openstreetmap:country R2978650
npm run example:fetch -- openfoodfacts:product 3017620422003

Transformer-only providers do not have fetch examples. Wrap a real payload from your own provider client in a SourceRecord and call the transformer directly.

example:fetch writes both raw and transformed JSON:

data/<provider>/<category>/<id>.raw.json
data/<provider>/<category>/<id>.zuuid.json

example:search writes raw and transformed search JSON:

data/<provider>/search/<category>/<query>.raw-search.json
data/<provider>/search/<category>/<query>.zuuid-search.json

Live provider smoke checks can be run with:

npm run provider:smoke

The script skips credentialed providers when their API keys are not present.

API Reference

Core exports:

  • createZuuidClient(config)
  • providerZuuid(input)
  • providerNamespace(provider)
  • categoryFor(value)
  • kindForCategory(category)
  • createSourceRecord(input)
  • attachSourceMetadata(dataset, sourceRecord, confidence?)

Provider exports:

  • TmdbProvider
  • OpenFoodFactsProvider
  • OpenLibraryProvider
  • ImdbProvider
  • ComicVineProvider
  • GamesDbProvider
  • MusicBrainzProvider
  • OpenStreetMapProvider
  • transformTmdbMovie(sourceRecord, options?)
  • transformTmdbTv(sourceRecord, options?)
  • transformTmdbPerson(sourceRecord, options?)
  • transformImdbMovie(sourceRecord, options?)
  • transformImdbTv(sourceRecord, options?)
  • transformOpenLibraryBook(sourceRecord, options?)
  • transformOpenLibraryAuthor(sourceRecord, options?)
  • transformComicVine(sourceRecord)
  • transformGamesDbGame(sourceRecord, options?)
  • transformGamesDbPlatform(sourceRecord, options?)
  • transformJikan(sourceRecord)
  • transformMusicBrainzRelease(sourceRecord, options?)
  • transformMusicBrainzReleaseGroup(sourceRecord, options?)
  • transformMusicBrainzRecording(sourceRecord)
  • transformMusicBrainzArtist(sourceRecord)
  • transformMusicBrainzLabel(sourceRecord)
  • transformMusicBrainzWork(sourceRecord)
  • transformOpenFoodFactsProduct(sourceRecord)
  • transformOpenStreetMapPlace(sourceRecord)
  • transformPodcast(sourceRecord)
  • transformSetlistFm(sourceRecord)
  • transformTicketmaster(sourceRecord)
  • transformWgerExercise(sourceRecord)
  • transformWgerEquipment(sourceRecord)
  • searchTmdbMovies(provider, input, options?)
  • searchTmdbTv(provider, input, options?)
  • searchTmdbPeople(provider, input, options?)
  • searchOpenLibraryBooks(provider, input, options?)
  • searchOpenLibraryAuthors(provider, input, options?)

Development

npm install
npm test
npm pack --dry-run

Package Boundary

This package intentionally does not include storage, caching, object-store metadata, record versions, review state, index state, or backend flags. Those concerns should live in the consuming application or service.