# ani-client

[![CI](https://github.com/gonzyui/ani-client/actions/workflows/ci.yml/badge.svg)](https://github.com/gonzyui/ani-client/actions/workflows/ci.yml)
[![npm version](https://img.shields.io/npm/v/ani-client)](https://www.npmjs.com/package/ani-client)
[![npm downloads](https://img.shields.io/npm/dm/ani-client)](https://www.npmjs.com/package/ani-client)
[![codecov](https://codecov.io/gh/gonzyui/ani-client/graph/badge.svg)](https://codecov.io/gh/gonzyui/ani-client)
[![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)](https://www.typescriptlang.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

> A fully typed, zero-dependency client for the [AniList](https://anilist.co) GraphQL API.  
> Supports Node.js and modern browsers.

## Features

- **Zero dependencies** — uses the native `fetch` API
- **Universal** — Node.js ≥ 20, and modern browsers
- **Dual format** — ships ESM + CJS with full `.d.ts` declarations
- **LRU cache** with TTL, stale-while-revalidate, and hit/miss stats
- **Rate-limit protection** with exponential backoff, retries, and custom strategies
- **Request deduplication** — concurrent identical queries share a single in-flight request
- **Automatic batching (DataLoader)** — groups rapid, individual `getMedia(id)` calls within a 50ms window into a single batch query, saving API limits.
- **Batch queries** — manually fetch up to 50 media, characters, or staff in a single API call
- **Auto-pagination** — async iterator that yields items across all pages
- **AbortSignal support** — cancel globally or per-request via `withSignal()`
- **Injectable logger** — plug in `console`, pino, winston, or any compatible logger
- **Redis-ready** — swap the in-memory cache with the built-in `RedisCache` adapter

## Installation

```bash
npm install ani-client
# pnpm
pnpm add ani-client
# yarn
yarn add ani-client
```

## Quick Start

```ts
import { AniListClient, MediaType } from "ani-client";

const client = new AniListClient();

// Fetch an anime by AniList ID
const bebop = await client.getMedia(1);
console.log(bebop.title.romaji); // "Cowboy Bebop"

// Search with filters
const results = await client.searchMedia({
  query: "Naruto",
  type: MediaType.ANIME,
  genres: ["Action"],
  perPage: 10,
});
```

## Documentation

For full API reference, configuration options, caching, rate limiting, and advanced usage guides, please visit the official documentation:

**[📚 ani-client.js.org](https://ani-client.js.org)**

## Requirements

| Runtime  | Version            |
|----------|--------------------|
| Node.js  | ≥ 22.13.0          |
| Browsers | `fetch` + `AbortController` required |

## Community

- 💬 [Discord server](https://discord.gg/3P7twDurUD)
- ✨ [Showcase — see who's using ani-client](https://ani-client.js.org/showcase)

## Contributing

Contributions are welcome! Before opening an issue or a pull request, please read:
- [CONTRIBUTING.md](CONTRIBUTING.md)
- [SECURITY.md](SECURITY.md)

## Disclaimer

> **Note:** `ani-client` is an unofficial, community-driven tool. It is not affiliated with, endorsed by, or associated with AniList or AniChart in any way.

## License

[MIT](LICENSE) © [gonzyui](https://gonzyuidev.xyz)
