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

@vllnt/convex-music

v0.1.0

Published

Provider-neutral, cached music catalog as a Convex component — Spotify, Apple Music, and more behind one API

Readme

Convex Component npm CI license

@vllnt/convex-music

A provider-neutral, cached music catalog for Convex apps — look up tracks, artists, and albums from Spotify, Apple Music, and more through one typed API, with results cached in the component's own sandboxed tables.

const music = new Music(components.music);

// Cache a provider's normalized facts for an entity (TTL-bounded)…
await music.put(ctx, { kind: "track", provider: "spotify", externalId: id, isrc, value });
// …then read it back fast on the next request.
const hit = await music.get(ctx, { kind: "track", provider: "spotify", externalId: id });

Features

  • One typed API across providers — Spotify and Apple Music today; Deezer, MusicBrainz, and Wikidata as drop-in adapters.
  • Sandboxed cache tables with a per-entry TTL — dedupe provider calls and ease rate limits.
  • Tracks, artists, and albums, cached by opaque provider id and cross-referenced by ISRC.
  • getByIsrc resolves the same recording across every provider you've cached.
  • pruneExpired deletes an indexed 100-row batch and reschedules full batches until the backlog drains.
  • Stores only public catalog facts — no secrets or credentials.
  • [planned] Owns a durable music catalog (artists / tracks / playlists) populated from providers and read via API; your app keeps gameplay + editorial, referencing catalog rows by id / ISRC.
  • [planned] In-component import / sync / repair engine, driven by mount policy.
  • [planned] Provider fetch/search adapters (search, getTrack / getArtist / getAlbum).
  • [planned] Field-source policy — configure what comes back from search + catalog reads: which entity kinds, which fields, and — for every field independently — which provider(s) supply it: one, an ordered pick, an explicit subset (e.g. 3 of 4 preview URLs), or all. Multi-select returns a provider-keyed map, so adding providers never changes a field's type. Default at mount, override per call.
  • [planned] Multiple catalogs per mount — one app.use holds many catalogs (e.g. artists, tracks), each with its own providers + field-source policy; a default catalog keeps single use zero-config. Named mounts remain available for hard isolation.
  • [planned] Pluggable providers — add a provider as one adapter (its raw schema + a mapper to the internal normalized schema) registered in the registry; no core changes.
  • [planned] Artist-image auto-sync with a configurable provider-selection policy.

Installation

npm install @vllnt/convex-music

Requires convex@^1.41.0 as a peer dependency.

Environment variables [planned]

Provider credentials are supplied as Convex environment variables on your deployment (used once the provider adapters ship):

| Provider | Variables | | --- | --- | | Spotify | SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET | | Apple Music | APPLE_MUSIC_ISSUER, APPLE_MUSIC_KID, APPLE_MUSIC_PRIVATE_KEY |

Which providers are enabled (and their preference order) is set in the mount policy, not via env flags.

Usage

Mount the component in your app's convex.config.ts:

import { defineApp } from "convex/server";
import music from "@vllnt/convex-music/convex.config";

const app = defineApp();
app.use(music);
export default app;

Then use the client from your own queries and mutations. The host owns auth — gate the write methods behind your own authorized functions, and persist results into your own domain tables:

import { Music } from "@vllnt/convex-music";
import { components } from "./_generated/api";

const music = new Music(components.music);

export const cacheTrack = internalMutation({
  args: { externalId: v.string(), isrc: v.string(), value: trackValue },
  handler: (ctx, args) =>
    music.put(ctx, {
      kind: "track",
      provider: "spotify",
      externalId: args.externalId,
      isrc: args.isrc,
      value: args.value,
      ttlMs: 1000 * 60 * 60 * 24, // refresh daily
    }),
});

API Reference

| Method | Kind | Description | | --- | --- | --- | | put(ctx, input) | mutation | Cache (insert or refresh) one provider's facts; returns the entry id. | | get(ctx, key) | query | Fetch one cached entry, or null if missing or expired. | | getByIsrc(ctx, isrc) | query | Every fresh cached track for an ISRC, across providers. | | invalidate(ctx, key) | mutation | Drop one cached entry; returns whether a row was deleted. | | pruneExpired(ctx) | mutation | Delete one bounded expired-entry batch, rescheduling until drained; returns this batch's count. | | stats(ctx) | query | Maintained count of cached entries (no table scan). |

Full reference — signatures, value shapes, and error codes: docs/API.md.

React [planned]

Shipped in the ./react entry; wraps the planned catalog query surface (not in 0.1.0).

Optional, tree-shakeable hooks over convex/react. The host re-exports its own catalog query refs and passes them in — the component never owns the host's api. react + convex/react are optional peer deps, so a backend-only consumer pulls in zero React.

import { useArtist, useSearchTracks } from "@vllnt/convex-music/react";
import { api } from "../convex/_generated/api"; // your wrappers re-exporting the component queries

const artist = useArtist(api.music.getArtist, artistId);
const tracks = useSearchTracks(api.music.searchTracks, query, 20);

| Hook | Returns | Description | | --- | --- | --- | | useArtist(ref, id) | CatalogArtist \| null | Reactively read one unified artist by id. | | useTrack(ref, id) | CatalogTrack \| null | Reactively read one unified track by id. | | useArtistImage(ref, provider, id, policy?) | string \| null | Project an artist image per a field-source policy. | | useTrackPreview(ref, provider, id, policy?) | string \| null | Project a track preview URL per a field-source policy. | | useSearchArtists(ref, query, limit?) | CatalogArtist[] | Reactively search artists by name. | | useSearchTracks(ref, query, limit?) | CatalogTrack[] | Reactively search tracks by title. |

Security

  • The component caches only public catalog facts — no secrets or credentials are stored.
  • Provider credentials live in the host's environment; they never reach the client.
  • The host gates every write method behind its own authorized functions.

See SECURITY.md for vulnerability reporting.

Testing

pnpm test            # vitest + convex-test + @edge-runtime/vm
pnpm test:coverage   # 100% coverage gate

Contributing

See CONTRIBUTING.md. Issues and pull requests welcome.

Author

Built by bntvllnt · bntvllnt.com · X @bntvllnt

Part of the @vllnt Convex component fleet — vllnt.com

If this is useful, sponsor the work.

License

MIT — see LICENSE.