ani-client 1.7.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,39 +1,38 @@
1
- # ani-client
1
+ # ani-client
2
2
 
3
- ![ani-client logo](docs/public/assets/logo.png)
4
3
  [![CI](https://github.com/gonzyui/ani-client/actions/workflows/ci.yml/badge.svg)](https://github.com/gonzyui/ani-client/actions/workflows/ci.yml)
5
- [![npm](https://img.shields.io/npm/v/ani-client)](https://www.npmjs.com/package/ani-client)
4
+ [![npm version](https://img.shields.io/npm/v/ani-client)](https://www.npmjs.com/package/ani-client)
5
+ [![npm downloads](https://img.shields.io/npm/dm/ani-client)](https://www.npmjs.com/package/ani-client)
6
+ [![codecov](https://codecov.io/gh/gonzyui/ani-client/graph/badge.svg)](https://codecov.io/gh/gonzyui/ani-client)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)](https://www.typescriptlang.org/)
6
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
9
 
8
- > A simple, typed client to fetch anime, manga, character, staff and user data from [AniList](https://anilist.co).
10
+ > A fully typed, zero-dependency client for the [AniList](https://anilist.co) GraphQL API.
9
11
 
10
- ✨ **Showcase**: [Check here](https://ani-client.js.org/showcase) to see which projects use this package!
12
+ ✨ **Showcase**: [See who's using ani-client](https://ani-client.js.org/showcase)
11
13
 
12
- - **Zero dependencies** — uses the native `fetch` API
13
- - **Universal** — Node.js ≥ 20, Bun, Deno and modern browsers
14
- - **Dual format** — ships ESM + CJS with full TypeScript declarations
15
- - **Reliable** — Built-in caching, Rate-limit protections with exponential backoff, automatic retries & request deduplication!
16
-
17
- ## 📖 Documentation
14
+ ## Highlights
18
15
 
19
- The full API reference, usage guide, and configuration examples are available on our official documentation website!
20
-
21
- **[👉 View the full documentation here](https://ani-client.js.org)**
16
+ - **Zero dependencies** uses the native `fetch` API
17
+ - **Universal** — Node.js ≥ 20, Bun, Deno, and modern browsers
18
+ - **Dual format** ships ESM + CJS with full `.d.ts` declarations
19
+ - **LRU cache** with TTL, stale-while-revalidate, and hit/miss stats
20
+ - **Rate-limit protection** with exponential backoff, retries, and custom strategies
21
+ - **Request deduplication** — concurrent identical queries share a single in-flight request
22
+ - **Batch queries** — fetch up to 50 media/characters/staff in one API call
23
+ - **Auto-pagination** — async iterator that yields items across pages
24
+ - **AbortSignal support** — cancel globally or per-request with `withSignal()`
25
+ - **Injectable logger** — plug in `console`, pino, winston, or any compatible logger
26
+ - **Redis-ready** — swap the cache adapter with the built-in `RedisCache` for distributed setups
22
27
 
23
28
  ## Install
24
29
 
25
30
  ```bash
26
- # npm
27
31
  npm install ani-client
28
-
29
- # pnpm
32
+ # or
30
33
  pnpm add ani-client
31
-
32
- # yarn
34
+ # or
33
35
  yarn add ani-client
34
-
35
- # bun
36
- bun add ani-client
37
36
  ```
38
37
 
39
38
  ## Quick start
@@ -43,59 +42,118 @@ import { AniListClient, MediaType } from "ani-client";
43
42
 
44
43
  const client = new AniListClient();
45
44
 
46
- // Get an anime by ID
47
- const cowboyBebop = await client.getMedia(1);
48
- console.log(cowboyBebop.title.romaji); // "Cowboy Bebop"
45
+ // Fetch an anime by AniList ID
46
+ const bebop = await client.getMedia(1);
47
+ console.log(bebop.title.romaji); // "Cowboy Bebop"
49
48
 
50
- // Search for anime
49
+ // Search with filters
51
50
  const results = await client.searchMedia({
52
51
  query: "Naruto",
53
52
  type: MediaType.ANIME,
54
- perPage: 5,
53
+ genres: ["Action"],
54
+ perPage: 10,
55
55
  });
56
- console.log(results.results.map((m) => m.title.english));
56
+
57
+ // Cross-platform lookup by MyAnimeList ID
58
+ const fma = await client.getMediaByMalId(5114);
57
59
  ```
58
60
 
59
- ### Fetch user favorites
61
+ ## Features at a glance
62
+
63
+ ### Caching & stale-while-revalidate
60
64
 
61
65
  ```ts
62
- const favs = await client.getUserFavorites("AniList");
66
+ const client = new AniListClient({
67
+ cache: {
68
+ ttl: 1000 * 60 * 5, // 5 min TTL
69
+ maxSize: 200, // LRU capacity
70
+ staleWhileRevalidateMs: 60_000, // serve stale for 1 min after expiry
71
+ },
72
+ });
63
73
 
64
- favs.anime.forEach((a) => console.log(a.title.romaji));
65
- favs.characters.forEach((c) => console.log(c.name.full));
74
+ // Check cache performance
75
+ console.log(client.cacheStats);
76
+ // { hits: 42, misses: 8, stales: 2, hitRate: 0.84 }
66
77
  ```
67
78
 
68
- ### Monitor rate limits
79
+ ### Per-request cancellation
80
+
81
+ ```ts
82
+ const controller = new AbortController();
83
+ const scoped = client.withSignal(controller.signal);
84
+
85
+ setTimeout(() => controller.abort(), 3_000);
86
+ const anime = await scoped.getMedia(1);
87
+ ```
88
+
89
+ ### Structured logging
90
+
91
+ ```ts
92
+ const client = new AniListClient({ logger: console });
93
+ // debug: "API request" { query: "query { Media(id: 1) { ... } }" }
94
+ // debug: "Request complete" { durationMs: 120, status: 200 }
95
+ ```
96
+
97
+ ### Rate limiting & retries
69
98
 
70
99
  ```ts
71
100
  const client = new AniListClient({
72
101
  rateLimit: {
102
+ maxRequests: 85,
103
+ windowMs: 60_000,
104
+ maxRetries: 3,
105
+ retryOnNetworkError: true,
73
106
  retryStrategy: (attempt) => (attempt + 1) * 1000, // linear backoff
74
107
  },
75
108
  });
76
109
 
77
- await client.getMedia(1);
110
+ console.log(client.rateLimitInfo);
111
+ // { remaining: 82, limit: 85, reset: 1741104000 }
112
+ ```
78
113
 
79
- const info = client.rateLimitInfo;
80
- console.log(`${info?.remaining}/${info?.limit} requests remaining`);
114
+ ### Batch & pagination
81
115
 
82
- const meta = client.lastRequestMeta;
83
- console.log(`${meta?.durationMs}ms, cache: ${meta?.fromCache}`);
116
+ ```ts
117
+ // Fetch 100 anime in 2 API calls (50 per batch)
118
+ const batch = await client.getMediaBatch([1, 2, 3, /* ...up to 100 IDs */]);
119
+
120
+ // Auto-paginate through all results
121
+ for await (const anime of client.paginate(
122
+ (page) => client.searchMedia({ query: "Gundam", page, perPage: 50 }),
123
+ 5, // max 5 pages
124
+ )) {
125
+ console.log(anime.title.romaji);
126
+ }
84
127
  ```
85
128
 
86
- ### Cancel requests
129
+ ### Users, characters, studios & more
87
130
 
88
131
  ```ts
89
- const controller = new AbortController();
90
- const client = new AniListClient({ signal: controller.signal });
91
-
92
- setTimeout(() => controller.abort(), 5_000);
93
- await client.getMedia(1); // aborted after 5s
132
+ const user = await client.getUser("AniList");
133
+ const favs = await client.getUserFavorites("AniList", { perPage: 50 });
134
+ const char = await client.getCharacter(1, { voiceActors: true });
135
+ const studio = await client.getStudio(21, { media: { perPage: 50 } });
136
+ const schedule = await client.getWeeklySchedule();
94
137
  ```
95
138
 
139
+ ## Documentation
140
+
141
+ Full API reference, guides (caching, pagination, includes, hooks, etc.) and configuration examples:
142
+
143
+ **[ani-client.js.org](https://ani-client.js.org)**
144
+
145
+ ## Requirements
146
+
147
+ | Runtime | Version |
148
+ | --- | --- |
149
+ | Node.js | ≥ 20 |
150
+ | Bun | ≥ 1.0 |
151
+ | Deno | ≥ 1.28 |
152
+ | Browsers | Any with `fetch` + `AbortController` |
153
+
96
154
  ## Contributing
97
155
 
98
- See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, coding standards, and how to submit changes.
156
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, coding standards, and PR guidelines.
99
157
 
100
158
  ## License
101
159