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

@get-air/media-mappings

v0.1.0

Published

Promise and Effect APIs for unified media identifier and episode mappings

Readme

@get-air/media-mappings

CI npm

An object-oriented TypeScript library for resolving media identifiers and episode mappings across public datasets and mapping APIs. The default API returns Promises and does not require callers to use Effect; an Effect-native API is available from @get-air/media-mappings/effect.

This is an embeddable library, not a web server. It never scrapes HTML: it only calls JSON APIs and downloads maintained JSON artifacts.

Basic usage

import { MediaMappings } from "@get-air/media-mappings"

const mappings = new MediaMappings()

try {
  const result = await mappings.resolve({
    provider: "anilist",
    id: 1,
    episode: 3,
    target: "tvdb",
  })

  for (const match of result.matches) {
    console.log(match.ref, match.evidence)
  }
} finally {
  await mappings.dispose()
}

resolve rejects when no match exists. lookup has the same input and returns an empty matches array instead. IDs may be strings or numbers and are normalized to strings.

Custom fetch transport

Networking is represented by the MappingFetch interface, so the library is not tied to the browser or Node implementation of fetch:

export interface MappingFetch {
  readonly fetch: (url: string, init?: RequestInit) => Promise<Response>
}

Pass a Tauri-compatible fetch function (or any other Fetch API-compatible transport) at construction:

import { fetch as tauriFetch } from "@tauri-apps/plugin-http"
import { FunctionMappingFetch, MediaMappings } from "@get-air/media-mappings"

const mappings = new MediaMappings({
  fetch: FunctionMappingFetch.from(tauriFetch),
})

Tauri's HTTP plugin requires its capability allowlist to include the mapping API and JSON-host URLs you intend to use.

You can also implement the interface directly when an adapter needs extra behavior:

import type { MappingFetch } from "@get-air/media-mappings"

class TauriMappingFetch implements MappingFetch {
  fetch(url: string, init?: RequestInit): Promise<Response> {
    return tauriFetch(url, init)
  }
}

When omitted, the library uses globalThis.fetch.

Air applications can instead pass the shared @get-air/http transport used by the other libraries:

const mappings = new MediaMappings({ transport })

Custom cache

Caching is also an injected interface. Entries are serialized JSON plus an absolute expiry time, which makes adapters for Tauri Store, SQLite, IndexedDB, Redis, or a filesystem straightforward.

MappingCache is the shared @get-air/cache CacheStore contract, so the same Tauri or application cache can be injected here and into the Stremio and Simkl clients.

import type {
  MappingCache,
  MappingCacheEntry,
} from "@get-air/media-mappings"

class MyCache implements MappingCache {
  async get(key: string): Promise<MappingCacheEntry | undefined> {
    // Read from your cache backend.
    return undefined
  }

  async set(key: string, entry: MappingCacheEntry): Promise<void> {
    // Persist entry.value and entry.expiresAtMillis.
  }

  async remove(key: string): Promise<void> {
    // Delete the expired or invalid entry.
  }
}

const mappings = new MediaMappings({ cache: new MyCache() })

MemoryMappingCache is used by default. Cache read/write failures are logged through Effect and treated as cache misses, so mapping lookups can continue. Configure expiry durations in milliseconds:

const mappings = new MediaMappings({
  cache: new MyCache(),
  config: {
    bulkCacheTtlMillis: 6 * 60 * 60 * 1_000,
    liveCacheTtlMillis: 5 * 60 * 1_000,
  },
})

The same cache instance may be shared across multiple MediaMappings objects.

Sources

| Source | Access | Coverage | | --- | --- | --- | | AniBridge v3 | Release JSON | AniDB, AniList, MAL, IMDb, TMDB, TVDB, and episode ranges | | AniZip | JSON API | Anime IDs and AniDB/TVDB episode data | | Shinkro community mapping | Raw generated JSON | MAL, AniDB, TVDB, and TMDB community mappings | | Fribb anime-lists | Raw generated JSON | Broad anime identifier mappings, including Simkl | | Anime-Lists | Fribb's documented JSON conversion | AniDB, IMDb, TMDB, TVDB, season, and offset data | | Yuna Relations | JSON API | Anime identifier expansion used by the referenced Stremio addon | | Simkl | Optional JSON API | General movie/series IMDb and TMDB resolution | | TMDB | Optional JSON API | General movie/series IMDb and TMDB resolution |

Anime-Lists publishes XML, but the library does not download it. It uses Fribb's published JSON conversion as the JSON-only ingestion path. The AnimeFLV-style extraction routes from the referenced addon are deliberately excluded because those routes scrape websites; its AniZip and Yuna JSON mapping integrations are included.

Simkl and TMDB expand coverage beyond anime when credentials are supplied:

const mappings = new MediaMappings({
  config: {
    simklClientId: "your-client-id",
    tmdbApiKey: "your-api-key",
  },
})

Supported providers are anidb, anilist, anime-planet, animecountdown, animenewsnetwork, anisearch, imdb, kitsu, livechart, mal, notifymoe, simkl, tmdb, and tvdb.

Bulk datasets load lazily. refresh() reloads them in parallel and retains the last good copy when one source temporarily fails. API failures appear as typed per-source warnings alongside results from healthy sources. sources() returns source configuration and load status.

Effect integration

Import the Effect-native surface from the explicit /effect entrypoint. It uses the same implementation as the Promise API while preserving typed errors and layer composition:

import { Effect } from "effect"
import {
  MediaMappingService,
  makeMediaMappingsLayer,
} from "@get-air/media-mappings/effect"

const program = MediaMappingService.resolve({
  provider: "anilist",
  id: "1",
}).pipe(Effect.provide(makeMediaMappingsLayer()))

Use @get-air/media-mappings for Promise-based applications and @get-air/media-mappings/effect when the application already uses Effect. Do not mix the two surfaces for the same operation.

Development

pnpm check
pnpm test
pnpm build
pnpm ci:act