# animedb
`animedb` is a Rust-first metadata project for local media servers.
> **Advisory**: `animedb` stores and normalizes public metadata, but it does not override
> provider Terms of Service, attribution requirements, authentication rules, rate limits or
> mature-content restrictions. Before enabling bulk sync for a source, verify that your
> intended usage is allowed by that source and configure conservative sync budgets.
It has two consumption modes that can be used separately or together:
- **local-first**: manage a local SQLite catalog with schema, downloads, sync, FTS5 search and
JSON source payloads
- **remote-first**: query normalized metadata from remote providers without forcing client
applications to manage persistence or provider-specific normalization
The project also ships a Rust GraphQL API on top of the same crate.
See [REFERENCE.md](/home/kokoro/btw/projects/animedb/REFERENCE.md) for the current library
and API reference.
## Supported providers
| AniList | Anime, Manga | GraphQL API | [AniList Terms](https://anilist.co/terms) |
| Jikan (MyAnimeList) | Anime, Manga | REST API | [Jikan MIT](https://gitlab.com/moritz-k/jikan/-/blob/master/LICENSE) |
| Kitsu | Anime, Manga | REST API | [Kitsu API Policy](https://kitsu.io/terms) |
| TVmaze | Shows | REST API (`api.tvmaze.com`) | CC BY-SA 4.0 |
| IMDb | Movies, Shows | Official TSV datasets (`datasets.imdb.com`) | [IMDb Conditions](https://www.imdb.com/conditions) |
## Workspace
- `crates/animedb` — library crate with SQLite schema management, sync and query APIs
- `crates/animedb-api` — GraphQL API binary built on top of `animedb`
## Feature flags
```toml
# Full featured (local SQLite + all providers) — default
animedb = "0.2"
# Remote-only, no SQLite dependency (safe for sqlx-based projects)
animedb = { version = "0.2", default-features = false, features = ["remote"] }
```
- `local-db` (default): local SQLite storage, sync state persistence, and the [`AnimeDb`] type.
This feature pulls in `rusqlite` with a bundled SQLite.
- `remote` (default): remote provider clients and the normalized data model. Zero native
dependencies.
**Why feature gates?** `local-db` requires `rusqlite` (bundled SQLite). Many Rust projects
already use `sqlx` with its own SQLite linkage, and Cargo rejects putting both in the same
dependency graph. If your project uses `sqlx`, depend on animedb with only `features = ["remote"]`
to get all provider clients, normalization types, and sync data structures without any SQLite
conflict.
## Current Rust surface
### Local-first
```rust
use animedb::{AnimeDb, SourceName};
let (db, report) = AnimeDb::generate_database_with_report("/tmp/animedb.sqlite")?;
println!("downloaded {} records", report.total_upserted_records);
let updated = AnimeDb::sync_database("/tmp/animedb.sqlite")?;
println!("synced {} records", updated.total_upserted_records);
let monster = db.anime_metadata().by_external_id(SourceName::AniList, "19")?;
println!("{}", monster.name());
let show = db.show_metadata().search("breaking bad")?;
let show = db.get_by_external_id(SourceName::Imdb, "tt0903747")?;
let movies = db.movie_metadata().search("spirited away")?;
println!("movie hits: {}", movies.len());
# Ok::<(), animedb::Error>(())
```
### Remote-first
```rust
use animedb::AnimeDb;
let remote = AnimeDb::remote_anilist();
let results = remote.anime_metadata().search("monster")?;
let media = remote.anime_metadata().by_id("19")?;
# Ok::<(), animedb::Error>(())
```
Or choose the provider dynamically:
```rust
use animedb::{AnimeDb, RemoteSource};
let remote = AnimeDb::remote(RemoteSource::Tvmaze);
let results = remote.show_metadata().search("breaking bad")?;
# Ok::<(), animedb::Error>(())
```
## Media kinds
All providers map to one of four supported kinds:
```rust
pub enum MediaKind {
Anime, // Japanese animation
Manga, // Japanese comics
Show, // TV series (from TVmaze / IMDb)
Movie, // Films (from IMDb)
}
```
## SQLite schema
The SQLite catalog is created and migrated by the crate itself. The current schema includes:
- `media` — canonical normalized records
- `media_alias` — normalized aliases and synonyms
- `media_external_id` — source-specific identifiers
- `source_record` — raw per-source JSON payloads and source update metadata
- `field_provenance` — winner-per-field audit trail for canonical merge decisions
- `sync_state` — persisted sync checkpoints/cursors
- `media_fts` — `FTS5` index for title, alias and synopsis search
The connection is configured with:
- `PRAGMA journal_mode=WAL`
- `PRAGMA synchronous=NORMAL`
- `PRAGMA foreign_keys=ON`
- `PRAGMA busy_timeout=5000`
- `PRAGMA temp_store=MEMORY`
## GraphQL API
The GraphQL API is provided by `animedb-api`. Run it locally:
```bash
cargo run -p animedb-api
```
Environment variables:
- `ANIMEDB_DATABASE_PATH` — SQLite file path, default `/data/animedb.sqlite`
- `ANIMEDB_LISTEN_ADDR` — bind address, default `0.0.0.0:8080`
Query example (search shows and movies):
```graphql
{
search(query: "breaking bad", options: { limit: 5, mediaKind: SHOW }) {
mediaId
titleDisplay
mediaKind
genres
externalIds { source sourceId }
}
}
```
## Docker
Build and run the Rust GraphQL API:
```bash
docker build -t animedb .
docker run --rm -p 8080:8080 -v $(pwd)/data:/data animedb
```
## Make targets
The repository includes a `Makefile` for common workflows:
- `make build` — compile the workspace
- `make test` — run the Rust test suite
- `make test-e2e` — run the end-to-end integration test (`scripts/e2e_test.sh`)
- `make docker-build` — build the API image
- `make docker-run` — run the API image locally
- `make debug-api` — run the GraphQL API directly with `cargo run`