@get-air/media-mappings
v0.1.0
Published
Promise and Effect APIs for unified media identifier and episode mappings
Maintainers
Readme
@get-air/media-mappings
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