ani-client 1.7.0 → 1.8.0

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,40 @@
1
- # ani-client
1
+ # ani-client
2
2
 
3
3
  ![ani-client logo](docs/public/assets/logo.png)
4
+
4
5
  [![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)
6
+ [![npm version](https://img.shields.io/npm/v/ani-client)](https://www.npmjs.com/package/ani-client)
7
+ [![npm downloads](https://img.shields.io/npm/dm/ani-client)](https://www.npmjs.com/package/ani-client)
8
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/ani-client)](https://bundlephobia.com/package/ani-client)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)](https://www.typescriptlang.org/)
6
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
11
 
8
- > A simple, typed client to fetch anime, manga, character, staff and user data from [AniList](https://anilist.co).
9
-
10
- ✨ **Showcase**: [Check here](https://ani-client.js.org/showcase) to see which projects use this package!
11
-
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!
12
+ > A fully typed, zero-dependency client for the [AniList](https://anilist.co) GraphQL API.
16
13
 
17
- ## 📖 Documentation
14
+ **Showcase**: [See who's using ani-client](https://ani-client.js.org/showcase)
18
15
 
19
- The full API reference, usage guide, and configuration examples are available on our official documentation website!
16
+ ## Highlights
20
17
 
21
- **[👉 View the full documentation here](https://ani-client.js.org)**
18
+ - **Zero dependencies** — uses the native `fetch` API
19
+ - **Universal** — Node.js ≥ 20, Bun, Deno, and modern browsers
20
+ - **Dual format** — ships ESM + CJS with full `.d.ts` declarations
21
+ - **LRU cache** with TTL, stale-while-revalidate, and hit/miss stats
22
+ - **Rate-limit protection** with exponential backoff, retries, and custom strategies
23
+ - **Request deduplication** — concurrent identical queries share a single in-flight request
24
+ - **Batch queries** — fetch up to 50 media/characters/staff in one API call
25
+ - **Auto-pagination** — async iterator that yields items across pages
26
+ - **AbortSignal support** — cancel globally or per-request with `withSignal()`
27
+ - **Injectable logger** — plug in `console`, pino, winston, or any compatible logger
28
+ - **Redis-ready** — swap the cache adapter with the built-in `RedisCache` for distributed setups
22
29
 
23
30
  ## Install
24
31
 
25
32
  ```bash
26
- # npm
27
33
  npm install ani-client
28
-
29
- # pnpm
34
+ # or
30
35
  pnpm add ani-client
31
-
32
- # yarn
36
+ # or
33
37
  yarn add ani-client
34
-
35
- # bun
36
- bun add ani-client
37
38
  ```
38
39
 
39
40
  ## Quick start
@@ -43,59 +44,118 @@ import { AniListClient, MediaType } from "ani-client";
43
44
 
44
45
  const client = new AniListClient();
45
46
 
46
- // Get an anime by ID
47
- const cowboyBebop = await client.getMedia(1);
48
- console.log(cowboyBebop.title.romaji); // "Cowboy Bebop"
47
+ // Fetch an anime by AniList ID
48
+ const bebop = await client.getMedia(1);
49
+ console.log(bebop.title.romaji); // "Cowboy Bebop"
49
50
 
50
- // Search for anime
51
+ // Search with filters
51
52
  const results = await client.searchMedia({
52
53
  query: "Naruto",
53
54
  type: MediaType.ANIME,
54
- perPage: 5,
55
+ genres: ["Action"],
56
+ perPage: 10,
57
+ });
58
+
59
+ // Cross-platform lookup by MyAnimeList ID
60
+ const fma = await client.getMediaByMalId(5114);
61
+ ```
62
+
63
+ ## Features at a glance
64
+
65
+ ### Caching & stale-while-revalidate
66
+
67
+ ```ts
68
+ const client = new AniListClient({
69
+ cache: {
70
+ ttl: 1000 * 60 * 5, // 5 min TTL
71
+ maxSize: 200, // LRU capacity
72
+ staleWhileRevalidateMs: 60_000, // serve stale for 1 min after expiry
73
+ },
55
74
  });
56
- console.log(results.results.map((m) => m.title.english));
75
+
76
+ // Check cache performance
77
+ console.log(client.cacheStats);
78
+ // { hits: 42, misses: 8, stales: 2, hitRate: 0.84 }
57
79
  ```
58
80
 
59
- ### Fetch user favorites
81
+ ### Per-request cancellation
60
82
 
61
83
  ```ts
62
- const favs = await client.getUserFavorites("AniList");
84
+ const controller = new AbortController();
85
+ const scoped = client.withSignal(controller.signal);
86
+
87
+ setTimeout(() => controller.abort(), 3_000);
88
+ const anime = await scoped.getMedia(1);
89
+ ```
90
+
91
+ ### Structured logging
63
92
 
64
- favs.anime.forEach((a) => console.log(a.title.romaji));
65
- favs.characters.forEach((c) => console.log(c.name.full));
93
+ ```ts
94
+ const client = new AniListClient({ logger: console });
95
+ // debug: "API request" { query: "query { Media(id: 1) { ... } }" }
96
+ // debug: "Request complete" { durationMs: 120, status: 200 }
66
97
  ```
67
98
 
68
- ### Monitor rate limits
99
+ ### Rate limiting & retries
69
100
 
70
101
  ```ts
71
102
  const client = new AniListClient({
72
103
  rateLimit: {
104
+ maxRequests: 85,
105
+ windowMs: 60_000,
106
+ maxRetries: 3,
107
+ retryOnNetworkError: true,
73
108
  retryStrategy: (attempt) => (attempt + 1) * 1000, // linear backoff
74
109
  },
75
110
  });
76
111
 
77
- await client.getMedia(1);
112
+ console.log(client.rateLimitInfo);
113
+ // { remaining: 82, limit: 85, reset: 1741104000 }
114
+ ```
78
115
 
79
- const info = client.rateLimitInfo;
80
- console.log(`${info?.remaining}/${info?.limit} requests remaining`);
116
+ ### Batch & pagination
81
117
 
82
- const meta = client.lastRequestMeta;
83
- console.log(`${meta?.durationMs}ms, cache: ${meta?.fromCache}`);
118
+ ```ts
119
+ // Fetch 100 anime in 2 API calls (50 per batch)
120
+ const batch = await client.getMediaBatch([1, 2, 3, /* ...up to 100 IDs */]);
121
+
122
+ // Auto-paginate through all results
123
+ for await (const anime of client.paginate(
124
+ (page) => client.searchMedia({ query: "Gundam", page, perPage: 50 }),
125
+ 5, // max 5 pages
126
+ )) {
127
+ console.log(anime.title.romaji);
128
+ }
84
129
  ```
85
130
 
86
- ### Cancel requests
131
+ ### Users, characters, studios & more
87
132
 
88
133
  ```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
134
+ const user = await client.getUser("AniList");
135
+ const favs = await client.getUserFavorites("AniList", { perPage: 50 });
136
+ const char = await client.getCharacter(1, { voiceActors: true });
137
+ const studio = await client.getStudio(21, { media: { perPage: 50 } });
138
+ const schedule = await client.getWeeklySchedule();
94
139
  ```
95
140
 
141
+ ## Documentation
142
+
143
+ Full API reference, guides (caching, pagination, includes, hooks, etc.) and configuration examples:
144
+
145
+ **[ani-client.js.org](https://ani-client.js.org)**
146
+
147
+ ## Requirements
148
+
149
+ | Runtime | Version |
150
+ | --- | --- |
151
+ | Node.js | ≥ 20 |
152
+ | Bun | ≥ 1.0 |
153
+ | Deno | ≥ 1.28 |
154
+ | Browsers | Any with `fetch` + `AbortController` |
155
+
96
156
  ## Contributing
97
157
 
98
- See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, coding standards, and how to submit changes.
158
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, coding standards, and PR guidelines.
99
159
 
100
160
  ## License
101
161
 
package/dist/index.d.mts CHANGED
@@ -50,6 +50,12 @@ interface CacheOptions {
50
50
  maxSize?: number;
51
51
  /** Set to false to disable caching entirely */
52
52
  enabled?: boolean;
53
+ /**
54
+ * Stale-while-revalidate grace period in milliseconds (default: 0 = disabled).
55
+ * When set, expired entries are still returned within the grace window,
56
+ * allowing the caller to refresh in the background.
57
+ */
58
+ staleWhileRevalidateMs?: number;
53
59
  }
54
60
  /** Rate limiter configuration options. */
55
61
  interface RateLimitOptions {
@@ -111,6 +117,16 @@ interface ResponseMeta {
111
117
  /** Rate limit information from the API response headers (not present for cached responses). */
112
118
  rateLimitInfo?: RateLimitInfo;
113
119
  }
120
+ /**
121
+ * Minimal logger interface for structured log output.
122
+ * Compatible with `console`, `pino`, `winston`, etc.
123
+ */
124
+ interface Logger {
125
+ debug(message: string, ...args: unknown[]): void;
126
+ info(message: string, ...args: unknown[]): void;
127
+ warn(message: string, ...args: unknown[]): void;
128
+ error(message: string, ...args: unknown[]): void;
129
+ }
114
130
  interface AniListClientOptions {
115
131
  /** Optional AniList OAuth token for authenticated requests */
116
132
  token?: string;
@@ -126,6 +142,17 @@ interface AniListClientOptions {
126
142
  hooks?: AniListHooks;
127
143
  /** Optional AbortSignal to cancel all requests made by this client */
128
144
  signal?: AbortSignal;
145
+ /**
146
+ * Optional logger for structured log output.
147
+ * Accepts any object with `debug`, `info`, `warn`, `error` methods.
148
+ * Compatible with `console`, `pino`, `winston`, etc.
149
+ *
150
+ * @example
151
+ * ```ts
152
+ * const client = new AniListClient({ logger: console });
153
+ * ```
154
+ */
155
+ logger?: Logger;
129
156
  }
130
157
 
131
158
  declare enum StaffSort {
@@ -394,6 +421,16 @@ interface SearchStudioOptions {
394
421
  page?: number;
395
422
  perPage?: number;
396
423
  }
424
+ /**
425
+ * Options for controlling embedded media when fetching a single studio.
426
+ * Pass `{ media: { perPage: 50 } }` to fetch more media per studio.
427
+ */
428
+ interface StudioIncludeOptions {
429
+ /** Customize the number of media returned. `true` = 25 (default), or `{ perPage }`. */
430
+ media?: boolean | {
431
+ perPage?: number;
432
+ };
433
+ }
397
434
 
398
435
  declare enum UserSort {
399
436
  ID = "ID",
@@ -485,6 +522,13 @@ interface UserFavorites {
485
522
  staff: FavoriteStaffNode[];
486
523
  studios: FavoriteStudioNode[];
487
524
  }
525
+ /**
526
+ * Options for controlling the number of results when fetching user favorites.
527
+ */
528
+ interface UserFavoritesOptions {
529
+ /** Number of items per category (default: 25, max: 50). */
530
+ perPage?: number;
531
+ }
488
532
 
489
533
  declare enum MediaType {
490
534
  ANIME = "ANIME",
@@ -945,6 +989,7 @@ declare class AniListClient {
945
989
  private readonly cacheAdapter;
946
990
  private readonly rateLimiter;
947
991
  private readonly hooks;
992
+ private readonly logger?;
948
993
  private readonly signal?;
949
994
  private readonly inFlight;
950
995
  private _rateLimitInfo?;
@@ -1000,6 +1045,13 @@ declare class AniListClient {
1000
1045
  * @deprecated Use `getRecentlyUpdatedManga` instead. This alias will be removed in v2.
1001
1046
  */
1002
1047
  getAiredChapters(options?: GetRecentChaptersOptions): Promise<PagedResult<Media>>;
1048
+ /**
1049
+ * Fetch a media entry by its MyAnimeList (MAL) ID.
1050
+ *
1051
+ * @param malId - The MyAnimeList ID
1052
+ * @param type - Optional media type to disambiguate (some MAL IDs map to both ANIME and MANGA)
1053
+ */
1054
+ getMediaByMalId(malId: number, type?: MediaType): Promise<Media>;
1003
1055
  /** Get the detailed schedule for the current week, sorted by day. */
1004
1056
  getWeeklySchedule(date?: Date): Promise<WeeklySchedule>;
1005
1057
  /** Get upcoming (not yet released) media. */
@@ -1030,17 +1082,29 @@ declare class AniListClient {
1030
1082
  * Fetch a user's favorite anime, manga, characters, staff, and studios.
1031
1083
  *
1032
1084
  * @param idOrName - AniList user ID (number) or username (string)
1085
+ * @param options - Optional pagination options (perPage per category)
1033
1086
  * @returns The user's favorites grouped by category
1034
1087
  *
1035
1088
  * @example
1036
1089
  * ```typescript
1037
1090
  * const favs = await client.getUserFavorites("AniList");
1038
1091
  * favs.anime.forEach(a => console.log(a.title.romaji));
1092
+ *
1093
+ * // Fetch more results per category
1094
+ * const moreResults = await client.getUserFavorites(1, { perPage: 50 });
1095
+ * ```
1096
+ */
1097
+ getUserFavorites(idOrName: number | string, options?: UserFavoritesOptions): Promise<UserFavorites>;
1098
+ /**
1099
+ * Fetch a studio by its AniList ID.
1100
+ * Pass `include` to customise the number of media returned.
1101
+ *
1102
+ * @example
1103
+ * ```typescript
1104
+ * const studio = await client.getStudio(21, { media: { perPage: 50 } });
1039
1105
  * ```
1040
1106
  */
1041
- getUserFavorites(idOrName: number | string): Promise<UserFavorites>;
1042
- /** Fetch a studio by its AniList ID. */
1043
- getStudio(id: number): Promise<Studio>;
1107
+ getStudio(id: number, include?: StudioIncludeOptions): Promise<Studio>;
1044
1108
  /** Search for studios by name. */
1045
1109
  searchStudios(options?: SearchStudioOptions): Promise<PagedResult<Studio>>;
1046
1110
  /** Fetch a forum thread by its AniList ID. */
@@ -1076,6 +1140,20 @@ declare class AniListClient {
1076
1140
  invalidateCache(pattern: string | RegExp): Promise<number>;
1077
1141
  /** Clean up resources held by the client. */
1078
1142
  destroy(): Promise<void>;
1143
+ /**
1144
+ * Return a scoped view of this client where every request uses the given `AbortSignal`.
1145
+ * The returned object shares the same cache, rate limiter, and hooks.
1146
+ *
1147
+ * @example
1148
+ * ```ts
1149
+ * const controller = new AbortController();
1150
+ * const media = await client.withSignal(controller.signal).getMedia(1);
1151
+ *
1152
+ * // Cancel all in-flight requests made through the scoped view
1153
+ * controller.abort();
1154
+ * ```
1155
+ */
1156
+ withSignal(signal: AbortSignal): AniListClient;
1079
1157
  }
1080
1158
 
1081
1159
  /**
@@ -1089,26 +1167,59 @@ declare class AniListError extends Error {
1089
1167
  constructor(message: string, status: number, errors?: unknown[]);
1090
1168
  }
1091
1169
 
1170
+ /** Cache performance statistics. */
1171
+ interface CacheStats {
1172
+ /** Total cache hits. */
1173
+ hits: number;
1174
+ /** Total cache misses. */
1175
+ misses: number;
1176
+ /** Stale entries returned (only with stale-while-revalidate). */
1177
+ stales: number;
1178
+ /** Hit rate as a ratio 0–1 (NaN if no requests yet). */
1179
+ hitRate: number;
1180
+ }
1092
1181
  declare class MemoryCache implements CacheAdapter {
1093
1182
  private readonly ttl;
1094
1183
  private readonly maxSize;
1095
1184
  private readonly enabled;
1185
+ private readonly swrMs;
1096
1186
  private readonly store;
1187
+ private _hits;
1188
+ private _misses;
1189
+ private _stales;
1097
1190
  constructor(options?: CacheOptions);
1098
1191
  /** Build a deterministic cache key from a query + variables pair. */
1099
1192
  static key(query: string, variables: Record<string, unknown>): string;
1100
- /** Retrieve a cached value, or `undefined` if missing / expired. */
1193
+ /**
1194
+ * Retrieve a cached value, or `undefined` if missing / expired.
1195
+ * With stale-while-revalidate enabled, returns stale data within the grace window
1196
+ * and flags it so the caller can refresh in the background.
1197
+ */
1101
1198
  get<T>(key: string): T | undefined;
1102
1199
  /** Store a value in the cache. */
1103
1200
  set<T>(key: string, data: T): void;
1104
1201
  /** Remove a specific entry. */
1105
1202
  delete(key: string): boolean;
1106
- /** Clear the entire cache. */
1203
+ /** Clear the entire cache and reset statistics. */
1107
1204
  clear(): void;
1108
1205
  /** Number of entries currently stored. */
1109
1206
  get size(): number | Promise<number>;
1110
1207
  /** Return all cache keys. */
1111
1208
  keys(): string[];
1209
+ /**
1210
+ * Get cache performance statistics.
1211
+ *
1212
+ * @example
1213
+ * ```ts
1214
+ * const cache = new MemoryCache();
1215
+ * // ... after some usage ...
1216
+ * console.log(cache.stats);
1217
+ * // { hits: 42, misses: 8, stales: 0, hitRate: 0.84 }
1218
+ * ```
1219
+ */
1220
+ get stats(): CacheStats;
1221
+ /** Reset cache statistics without clearing stored data. */
1222
+ resetStats(): void;
1112
1223
  /**
1113
1224
  * Remove all entries whose key matches the given pattern.
1114
1225
  *
@@ -1239,4 +1350,4 @@ declare class RateLimiter {
1239
1350
 
1240
1351
  declare function parseAniListMarkdown(text: string): string;
1241
1352
 
1242
- export { type AiringSchedule, AiringSort, AniListClient, type AniListClientOptions, AniListError, type AniListHooks, type CacheAdapter, type CacheOptions, type Character, type CharacterImage, type CharacterIncludeOptions, type CharacterMediaEdge, type CharacterName, CharacterRole, CharacterSort, type DayOfWeek, type ExternalLink, type FavoriteCharacterNode, type FavoriteMediaNode, type FavoriteStaffNode, type FavoriteStudioNode, type FuzzyDate, type GetAiringOptions, type GetPlanningOptions, type GetRecentChaptersOptions, type GetRecommendationsOptions, type GetSeasonOptions, type GetUserMediaListOptions, type Media, type MediaCharacterConnection, type MediaCharacterEdge, type MediaConnection, type MediaCoverImage, type MediaEdge, MediaFormat, type MediaIncludeOptions, type MediaListEntry, MediaListSort, MediaListStatus, type MediaRecommendationNode, MediaRelationType, MediaSeason, MediaSort, MediaSource, type MediaStaffConnection, type MediaStaffEdge, type MediaStats, MediaStatus, type MediaTag, type MediaTitle, type MediaTrailer, MediaType, MemoryCache, type NextAiringEpisode, type PageInfo, type PagedResult, type RateLimitInfo, type RateLimitOptions, RateLimiter, type Recommendation, RecommendationSort, RedisCache, type RedisCacheOptions, type RedisLikeClient, type ResponseMeta, type ScoreDistribution, type SearchCharacterOptions, type SearchMediaOptions, type SearchStaffOptions, type SearchStudioOptions, type SearchThreadOptions, type SearchUserOptions, type Staff, type StaffImage, type StaffIncludeOptions, type StaffMediaNode, type StaffName, StaffSort, type StatusDistribution, type StreamingEpisode, type Studio, type StudioConnection, StudioSort, type Thread, type ThreadCategory, type ThreadMediaCategory, ThreadSort, type User, type UserAvatar, type UserFavorites, UserSort, type UserStatistics, type VoiceActor, type WeeklySchedule, parseAniListMarkdown };
1353
+ export { type AiringSchedule, AiringSort, AniListClient, type AniListClientOptions, AniListError, type AniListHooks, type CacheAdapter, type CacheOptions, type CacheStats, type Character, type CharacterImage, type CharacterIncludeOptions, type CharacterMediaEdge, type CharacterName, CharacterRole, CharacterSort, type DayOfWeek, type ExternalLink, type FavoriteCharacterNode, type FavoriteMediaNode, type FavoriteStaffNode, type FavoriteStudioNode, type FuzzyDate, type GetAiringOptions, type GetPlanningOptions, type GetRecentChaptersOptions, type GetRecommendationsOptions, type GetSeasonOptions, type GetUserMediaListOptions, type Logger, type Media, type MediaCharacterConnection, type MediaCharacterEdge, type MediaConnection, type MediaCoverImage, type MediaEdge, MediaFormat, type MediaIncludeOptions, type MediaListEntry, MediaListSort, MediaListStatus, type MediaRecommendationNode, MediaRelationType, MediaSeason, MediaSort, MediaSource, type MediaStaffConnection, type MediaStaffEdge, type MediaStats, MediaStatus, type MediaTag, type MediaTitle, type MediaTrailer, MediaType, MemoryCache, type NextAiringEpisode, type PageInfo, type PagedResult, type RateLimitInfo, type RateLimitOptions, RateLimiter, type Recommendation, RecommendationSort, RedisCache, type RedisCacheOptions, type RedisLikeClient, type ResponseMeta, type ScoreDistribution, type SearchCharacterOptions, type SearchMediaOptions, type SearchStaffOptions, type SearchStudioOptions, type SearchThreadOptions, type SearchUserOptions, type Staff, type StaffImage, type StaffIncludeOptions, type StaffMediaNode, type StaffName, StaffSort, type StatusDistribution, type StreamingEpisode, type Studio, type StudioConnection, type StudioIncludeOptions, StudioSort, type Thread, type ThreadCategory, type ThreadMediaCategory, ThreadSort, type User, type UserAvatar, type UserFavorites, type UserFavoritesOptions, UserSort, type UserStatistics, type VoiceActor, type WeeklySchedule, parseAniListMarkdown };
package/dist/index.d.ts CHANGED
@@ -50,6 +50,12 @@ interface CacheOptions {
50
50
  maxSize?: number;
51
51
  /** Set to false to disable caching entirely */
52
52
  enabled?: boolean;
53
+ /**
54
+ * Stale-while-revalidate grace period in milliseconds (default: 0 = disabled).
55
+ * When set, expired entries are still returned within the grace window,
56
+ * allowing the caller to refresh in the background.
57
+ */
58
+ staleWhileRevalidateMs?: number;
53
59
  }
54
60
  /** Rate limiter configuration options. */
55
61
  interface RateLimitOptions {
@@ -111,6 +117,16 @@ interface ResponseMeta {
111
117
  /** Rate limit information from the API response headers (not present for cached responses). */
112
118
  rateLimitInfo?: RateLimitInfo;
113
119
  }
120
+ /**
121
+ * Minimal logger interface for structured log output.
122
+ * Compatible with `console`, `pino`, `winston`, etc.
123
+ */
124
+ interface Logger {
125
+ debug(message: string, ...args: unknown[]): void;
126
+ info(message: string, ...args: unknown[]): void;
127
+ warn(message: string, ...args: unknown[]): void;
128
+ error(message: string, ...args: unknown[]): void;
129
+ }
114
130
  interface AniListClientOptions {
115
131
  /** Optional AniList OAuth token for authenticated requests */
116
132
  token?: string;
@@ -126,6 +142,17 @@ interface AniListClientOptions {
126
142
  hooks?: AniListHooks;
127
143
  /** Optional AbortSignal to cancel all requests made by this client */
128
144
  signal?: AbortSignal;
145
+ /**
146
+ * Optional logger for structured log output.
147
+ * Accepts any object with `debug`, `info`, `warn`, `error` methods.
148
+ * Compatible with `console`, `pino`, `winston`, etc.
149
+ *
150
+ * @example
151
+ * ```ts
152
+ * const client = new AniListClient({ logger: console });
153
+ * ```
154
+ */
155
+ logger?: Logger;
129
156
  }
130
157
 
131
158
  declare enum StaffSort {
@@ -394,6 +421,16 @@ interface SearchStudioOptions {
394
421
  page?: number;
395
422
  perPage?: number;
396
423
  }
424
+ /**
425
+ * Options for controlling embedded media when fetching a single studio.
426
+ * Pass `{ media: { perPage: 50 } }` to fetch more media per studio.
427
+ */
428
+ interface StudioIncludeOptions {
429
+ /** Customize the number of media returned. `true` = 25 (default), or `{ perPage }`. */
430
+ media?: boolean | {
431
+ perPage?: number;
432
+ };
433
+ }
397
434
 
398
435
  declare enum UserSort {
399
436
  ID = "ID",
@@ -485,6 +522,13 @@ interface UserFavorites {
485
522
  staff: FavoriteStaffNode[];
486
523
  studios: FavoriteStudioNode[];
487
524
  }
525
+ /**
526
+ * Options for controlling the number of results when fetching user favorites.
527
+ */
528
+ interface UserFavoritesOptions {
529
+ /** Number of items per category (default: 25, max: 50). */
530
+ perPage?: number;
531
+ }
488
532
 
489
533
  declare enum MediaType {
490
534
  ANIME = "ANIME",
@@ -945,6 +989,7 @@ declare class AniListClient {
945
989
  private readonly cacheAdapter;
946
990
  private readonly rateLimiter;
947
991
  private readonly hooks;
992
+ private readonly logger?;
948
993
  private readonly signal?;
949
994
  private readonly inFlight;
950
995
  private _rateLimitInfo?;
@@ -1000,6 +1045,13 @@ declare class AniListClient {
1000
1045
  * @deprecated Use `getRecentlyUpdatedManga` instead. This alias will be removed in v2.
1001
1046
  */
1002
1047
  getAiredChapters(options?: GetRecentChaptersOptions): Promise<PagedResult<Media>>;
1048
+ /**
1049
+ * Fetch a media entry by its MyAnimeList (MAL) ID.
1050
+ *
1051
+ * @param malId - The MyAnimeList ID
1052
+ * @param type - Optional media type to disambiguate (some MAL IDs map to both ANIME and MANGA)
1053
+ */
1054
+ getMediaByMalId(malId: number, type?: MediaType): Promise<Media>;
1003
1055
  /** Get the detailed schedule for the current week, sorted by day. */
1004
1056
  getWeeklySchedule(date?: Date): Promise<WeeklySchedule>;
1005
1057
  /** Get upcoming (not yet released) media. */
@@ -1030,17 +1082,29 @@ declare class AniListClient {
1030
1082
  * Fetch a user's favorite anime, manga, characters, staff, and studios.
1031
1083
  *
1032
1084
  * @param idOrName - AniList user ID (number) or username (string)
1085
+ * @param options - Optional pagination options (perPage per category)
1033
1086
  * @returns The user's favorites grouped by category
1034
1087
  *
1035
1088
  * @example
1036
1089
  * ```typescript
1037
1090
  * const favs = await client.getUserFavorites("AniList");
1038
1091
  * favs.anime.forEach(a => console.log(a.title.romaji));
1092
+ *
1093
+ * // Fetch more results per category
1094
+ * const moreResults = await client.getUserFavorites(1, { perPage: 50 });
1095
+ * ```
1096
+ */
1097
+ getUserFavorites(idOrName: number | string, options?: UserFavoritesOptions): Promise<UserFavorites>;
1098
+ /**
1099
+ * Fetch a studio by its AniList ID.
1100
+ * Pass `include` to customise the number of media returned.
1101
+ *
1102
+ * @example
1103
+ * ```typescript
1104
+ * const studio = await client.getStudio(21, { media: { perPage: 50 } });
1039
1105
  * ```
1040
1106
  */
1041
- getUserFavorites(idOrName: number | string): Promise<UserFavorites>;
1042
- /** Fetch a studio by its AniList ID. */
1043
- getStudio(id: number): Promise<Studio>;
1107
+ getStudio(id: number, include?: StudioIncludeOptions): Promise<Studio>;
1044
1108
  /** Search for studios by name. */
1045
1109
  searchStudios(options?: SearchStudioOptions): Promise<PagedResult<Studio>>;
1046
1110
  /** Fetch a forum thread by its AniList ID. */
@@ -1076,6 +1140,20 @@ declare class AniListClient {
1076
1140
  invalidateCache(pattern: string | RegExp): Promise<number>;
1077
1141
  /** Clean up resources held by the client. */
1078
1142
  destroy(): Promise<void>;
1143
+ /**
1144
+ * Return a scoped view of this client where every request uses the given `AbortSignal`.
1145
+ * The returned object shares the same cache, rate limiter, and hooks.
1146
+ *
1147
+ * @example
1148
+ * ```ts
1149
+ * const controller = new AbortController();
1150
+ * const media = await client.withSignal(controller.signal).getMedia(1);
1151
+ *
1152
+ * // Cancel all in-flight requests made through the scoped view
1153
+ * controller.abort();
1154
+ * ```
1155
+ */
1156
+ withSignal(signal: AbortSignal): AniListClient;
1079
1157
  }
1080
1158
 
1081
1159
  /**
@@ -1089,26 +1167,59 @@ declare class AniListError extends Error {
1089
1167
  constructor(message: string, status: number, errors?: unknown[]);
1090
1168
  }
1091
1169
 
1170
+ /** Cache performance statistics. */
1171
+ interface CacheStats {
1172
+ /** Total cache hits. */
1173
+ hits: number;
1174
+ /** Total cache misses. */
1175
+ misses: number;
1176
+ /** Stale entries returned (only with stale-while-revalidate). */
1177
+ stales: number;
1178
+ /** Hit rate as a ratio 0–1 (NaN if no requests yet). */
1179
+ hitRate: number;
1180
+ }
1092
1181
  declare class MemoryCache implements CacheAdapter {
1093
1182
  private readonly ttl;
1094
1183
  private readonly maxSize;
1095
1184
  private readonly enabled;
1185
+ private readonly swrMs;
1096
1186
  private readonly store;
1187
+ private _hits;
1188
+ private _misses;
1189
+ private _stales;
1097
1190
  constructor(options?: CacheOptions);
1098
1191
  /** Build a deterministic cache key from a query + variables pair. */
1099
1192
  static key(query: string, variables: Record<string, unknown>): string;
1100
- /** Retrieve a cached value, or `undefined` if missing / expired. */
1193
+ /**
1194
+ * Retrieve a cached value, or `undefined` if missing / expired.
1195
+ * With stale-while-revalidate enabled, returns stale data within the grace window
1196
+ * and flags it so the caller can refresh in the background.
1197
+ */
1101
1198
  get<T>(key: string): T | undefined;
1102
1199
  /** Store a value in the cache. */
1103
1200
  set<T>(key: string, data: T): void;
1104
1201
  /** Remove a specific entry. */
1105
1202
  delete(key: string): boolean;
1106
- /** Clear the entire cache. */
1203
+ /** Clear the entire cache and reset statistics. */
1107
1204
  clear(): void;
1108
1205
  /** Number of entries currently stored. */
1109
1206
  get size(): number | Promise<number>;
1110
1207
  /** Return all cache keys. */
1111
1208
  keys(): string[];
1209
+ /**
1210
+ * Get cache performance statistics.
1211
+ *
1212
+ * @example
1213
+ * ```ts
1214
+ * const cache = new MemoryCache();
1215
+ * // ... after some usage ...
1216
+ * console.log(cache.stats);
1217
+ * // { hits: 42, misses: 8, stales: 0, hitRate: 0.84 }
1218
+ * ```
1219
+ */
1220
+ get stats(): CacheStats;
1221
+ /** Reset cache statistics without clearing stored data. */
1222
+ resetStats(): void;
1112
1223
  /**
1113
1224
  * Remove all entries whose key matches the given pattern.
1114
1225
  *
@@ -1239,4 +1350,4 @@ declare class RateLimiter {
1239
1350
 
1240
1351
  declare function parseAniListMarkdown(text: string): string;
1241
1352
 
1242
- export { type AiringSchedule, AiringSort, AniListClient, type AniListClientOptions, AniListError, type AniListHooks, type CacheAdapter, type CacheOptions, type Character, type CharacterImage, type CharacterIncludeOptions, type CharacterMediaEdge, type CharacterName, CharacterRole, CharacterSort, type DayOfWeek, type ExternalLink, type FavoriteCharacterNode, type FavoriteMediaNode, type FavoriteStaffNode, type FavoriteStudioNode, type FuzzyDate, type GetAiringOptions, type GetPlanningOptions, type GetRecentChaptersOptions, type GetRecommendationsOptions, type GetSeasonOptions, type GetUserMediaListOptions, type Media, type MediaCharacterConnection, type MediaCharacterEdge, type MediaConnection, type MediaCoverImage, type MediaEdge, MediaFormat, type MediaIncludeOptions, type MediaListEntry, MediaListSort, MediaListStatus, type MediaRecommendationNode, MediaRelationType, MediaSeason, MediaSort, MediaSource, type MediaStaffConnection, type MediaStaffEdge, type MediaStats, MediaStatus, type MediaTag, type MediaTitle, type MediaTrailer, MediaType, MemoryCache, type NextAiringEpisode, type PageInfo, type PagedResult, type RateLimitInfo, type RateLimitOptions, RateLimiter, type Recommendation, RecommendationSort, RedisCache, type RedisCacheOptions, type RedisLikeClient, type ResponseMeta, type ScoreDistribution, type SearchCharacterOptions, type SearchMediaOptions, type SearchStaffOptions, type SearchStudioOptions, type SearchThreadOptions, type SearchUserOptions, type Staff, type StaffImage, type StaffIncludeOptions, type StaffMediaNode, type StaffName, StaffSort, type StatusDistribution, type StreamingEpisode, type Studio, type StudioConnection, StudioSort, type Thread, type ThreadCategory, type ThreadMediaCategory, ThreadSort, type User, type UserAvatar, type UserFavorites, UserSort, type UserStatistics, type VoiceActor, type WeeklySchedule, parseAniListMarkdown };
1353
+ export { type AiringSchedule, AiringSort, AniListClient, type AniListClientOptions, AniListError, type AniListHooks, type CacheAdapter, type CacheOptions, type CacheStats, type Character, type CharacterImage, type CharacterIncludeOptions, type CharacterMediaEdge, type CharacterName, CharacterRole, CharacterSort, type DayOfWeek, type ExternalLink, type FavoriteCharacterNode, type FavoriteMediaNode, type FavoriteStaffNode, type FavoriteStudioNode, type FuzzyDate, type GetAiringOptions, type GetPlanningOptions, type GetRecentChaptersOptions, type GetRecommendationsOptions, type GetSeasonOptions, type GetUserMediaListOptions, type Logger, type Media, type MediaCharacterConnection, type MediaCharacterEdge, type MediaConnection, type MediaCoverImage, type MediaEdge, MediaFormat, type MediaIncludeOptions, type MediaListEntry, MediaListSort, MediaListStatus, type MediaRecommendationNode, MediaRelationType, MediaSeason, MediaSort, MediaSource, type MediaStaffConnection, type MediaStaffEdge, type MediaStats, MediaStatus, type MediaTag, type MediaTitle, type MediaTrailer, MediaType, MemoryCache, type NextAiringEpisode, type PageInfo, type PagedResult, type RateLimitInfo, type RateLimitOptions, RateLimiter, type Recommendation, RecommendationSort, RedisCache, type RedisCacheOptions, type RedisLikeClient, type ResponseMeta, type ScoreDistribution, type SearchCharacterOptions, type SearchMediaOptions, type SearchStaffOptions, type SearchStudioOptions, type SearchThreadOptions, type SearchUserOptions, type Staff, type StaffImage, type StaffIncludeOptions, type StaffMediaNode, type StaffName, StaffSort, type StatusDistribution, type StreamingEpisode, type Studio, type StudioConnection, type StudioIncludeOptions, StudioSort, type Thread, type ThreadCategory, type ThreadMediaCategory, ThreadSort, type User, type UserAvatar, type UserFavorites, type UserFavoritesOptions, UserSort, type UserStatistics, type VoiceActor, type WeeklySchedule, parseAniListMarkdown };