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/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  interface PageInfo {
2
2
  total: number | null;
3
- perPage: number | null;
4
- currentPage: number | null;
5
- lastPage: number | null;
6
- hasNextPage: boolean | null;
3
+ perPage: number;
4
+ currentPage: number;
5
+ lastPage: number;
6
+ hasNextPage: boolean;
7
7
  }
8
8
  interface PagedResult<T> {
9
9
  pageInfo: PageInfo;
@@ -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,89 @@ 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;
156
+ }
157
+
158
+ declare enum MediaListStatus {
159
+ CURRENT = "CURRENT",
160
+ PLANNING = "PLANNING",
161
+ COMPLETED = "COMPLETED",
162
+ DROPPED = "DROPPED",
163
+ PAUSED = "PAUSED",
164
+ REPEATING = "REPEATING"
165
+ }
166
+ declare enum MediaListSort {
167
+ MEDIA_ID = "MEDIA_ID",
168
+ MEDIA_ID_DESC = "MEDIA_ID_DESC",
169
+ SCORE = "SCORE",
170
+ SCORE_DESC = "SCORE_DESC",
171
+ STATUS = "STATUS",
172
+ STATUS_DESC = "STATUS_DESC",
173
+ PROGRESS = "PROGRESS",
174
+ PROGRESS_DESC = "PROGRESS_DESC",
175
+ PROGRESS_VOLUMES = "PROGRESS_VOLUMES",
176
+ PROGRESS_VOLUMES_DESC = "PROGRESS_VOLUMES_DESC",
177
+ REPEAT = "REPEAT",
178
+ REPEAT_DESC = "REPEAT_DESC",
179
+ PRIORITY = "PRIORITY",
180
+ PRIORITY_DESC = "PRIORITY_DESC",
181
+ STARTED_ON = "STARTED_ON",
182
+ STARTED_ON_DESC = "STARTED_ON_DESC",
183
+ FINISHED_ON = "FINISHED_ON",
184
+ FINISHED_ON_DESC = "FINISHED_ON_DESC",
185
+ ADDED_TIME = "ADDED_TIME",
186
+ ADDED_TIME_DESC = "ADDED_TIME_DESC",
187
+ UPDATED_TIME = "UPDATED_TIME",
188
+ UPDATED_TIME_DESC = "UPDATED_TIME_DESC",
189
+ MEDIA_TITLE_ROMAJI = "MEDIA_TITLE_ROMAJI",
190
+ MEDIA_TITLE_ROMAJI_DESC = "MEDIA_TITLE_ROMAJI_DESC",
191
+ MEDIA_TITLE_ENGLISH = "MEDIA_TITLE_ENGLISH",
192
+ MEDIA_TITLE_ENGLISH_DESC = "MEDIA_TITLE_ENGLISH_DESC",
193
+ MEDIA_TITLE_NATIVE = "MEDIA_TITLE_NATIVE",
194
+ MEDIA_TITLE_NATIVE_DESC = "MEDIA_TITLE_NATIVE_DESC",
195
+ MEDIA_POPULARITY = "MEDIA_POPULARITY",
196
+ MEDIA_POPULARITY_DESC = "MEDIA_POPULARITY_DESC"
197
+ }
198
+ interface MediaListEntry {
199
+ id: number;
200
+ mediaId: number;
201
+ status: MediaListStatus;
202
+ score: number | null;
203
+ progress: number | null;
204
+ progressVolumes: number | null;
205
+ repeat: number | null;
206
+ priority: number | null;
207
+ private: boolean | null;
208
+ notes: string | null;
209
+ startedAt: FuzzyDate | null;
210
+ completedAt: FuzzyDate | null;
211
+ updatedAt: number | null;
212
+ createdAt: number | null;
213
+ media: Media;
214
+ }
215
+ interface GetUserMediaListOptions {
216
+ /** User ID (provide either userId or userName) */
217
+ userId?: number;
218
+ /** Username (provide either userId or userName) */
219
+ userName?: string;
220
+ /** ANIME or MANGA */
221
+ type: MediaType;
222
+ /** Filter by list status (CURRENT, COMPLETED, etc.) */
223
+ status?: MediaListStatus;
224
+ /** Sort order */
225
+ sort?: MediaListSort[];
226
+ page?: number;
227
+ perPage?: number;
129
228
  }
130
229
 
131
230
  declare enum StaffSort {
@@ -232,139 +331,6 @@ interface VoiceActor extends Pick<Staff, "id" | "image" | "gender" | "primaryOcc
232
331
  languageV2: string | null;
233
332
  }
234
333
 
235
- declare enum CharacterSort {
236
- ID = "ID",
237
- ID_DESC = "ID_DESC",
238
- ROLE = "ROLE",
239
- ROLE_DESC = "ROLE_DESC",
240
- SEARCH_MATCH = "SEARCH_MATCH",
241
- FAVOURITES = "FAVOURITES",
242
- FAVOURITES_DESC = "FAVOURITES_DESC"
243
- }
244
- declare enum CharacterRole {
245
- MAIN = "MAIN",
246
- SUPPORTING = "SUPPORTING",
247
- BACKGROUND = "BACKGROUND"
248
- }
249
- interface CharacterName {
250
- first: string | null;
251
- middle: string | null;
252
- last: string | null;
253
- full: string | null;
254
- native: string | null;
255
- alternative: string[];
256
- }
257
- interface CharacterImage {
258
- large: string | null;
259
- medium: string | null;
260
- }
261
- type CharacterMediaNode = Pick<Media, "id" | "title" | "type" | "coverImage" | "siteUrl">;
262
- interface CharacterMediaEdge {
263
- node: CharacterMediaNode;
264
- voiceActors?: VoiceActor[];
265
- }
266
- interface Character {
267
- id: number;
268
- name: CharacterName;
269
- image: CharacterImage;
270
- description: string | null;
271
- gender: string | null;
272
- dateOfBirth: FuzzyDate | null;
273
- age: string | null;
274
- bloodType: string | null;
275
- favourites: number | null;
276
- siteUrl: string | null;
277
- media: {
278
- nodes?: CharacterMediaNode[];
279
- edges?: CharacterMediaEdge[];
280
- } | null;
281
- }
282
- /** Options for including extra data when fetching a character. */
283
- interface CharacterIncludeOptions {
284
- /** Include voice actors for each media the character appears in. */
285
- voiceActors?: boolean;
286
- }
287
- interface SearchCharacterOptions {
288
- query?: string;
289
- sort?: CharacterSort[];
290
- page?: number;
291
- perPage?: number;
292
- /** Include voice actors for each media the character appears in. */
293
- voiceActors?: boolean;
294
- }
295
-
296
- declare enum MediaListStatus {
297
- CURRENT = "CURRENT",
298
- PLANNING = "PLANNING",
299
- COMPLETED = "COMPLETED",
300
- DROPPED = "DROPPED",
301
- PAUSED = "PAUSED",
302
- REPEATING = "REPEATING"
303
- }
304
- declare enum MediaListSort {
305
- MEDIA_ID = "MEDIA_ID",
306
- MEDIA_ID_DESC = "MEDIA_ID_DESC",
307
- SCORE = "SCORE",
308
- SCORE_DESC = "SCORE_DESC",
309
- STATUS = "STATUS",
310
- STATUS_DESC = "STATUS_DESC",
311
- PROGRESS = "PROGRESS",
312
- PROGRESS_DESC = "PROGRESS_DESC",
313
- PROGRESS_VOLUMES = "PROGRESS_VOLUMES",
314
- PROGRESS_VOLUMES_DESC = "PROGRESS_VOLUMES_DESC",
315
- REPEAT = "REPEAT",
316
- REPEAT_DESC = "REPEAT_DESC",
317
- PRIORITY = "PRIORITY",
318
- PRIORITY_DESC = "PRIORITY_DESC",
319
- STARTED_ON = "STARTED_ON",
320
- STARTED_ON_DESC = "STARTED_ON_DESC",
321
- FINISHED_ON = "FINISHED_ON",
322
- FINISHED_ON_DESC = "FINISHED_ON_DESC",
323
- ADDED_TIME = "ADDED_TIME",
324
- ADDED_TIME_DESC = "ADDED_TIME_DESC",
325
- UPDATED_TIME = "UPDATED_TIME",
326
- UPDATED_TIME_DESC = "UPDATED_TIME_DESC",
327
- MEDIA_TITLE_ROMAJI = "MEDIA_TITLE_ROMAJI",
328
- MEDIA_TITLE_ROMAJI_DESC = "MEDIA_TITLE_ROMAJI_DESC",
329
- MEDIA_TITLE_ENGLISH = "MEDIA_TITLE_ENGLISH",
330
- MEDIA_TITLE_ENGLISH_DESC = "MEDIA_TITLE_ENGLISH_DESC",
331
- MEDIA_TITLE_NATIVE = "MEDIA_TITLE_NATIVE",
332
- MEDIA_TITLE_NATIVE_DESC = "MEDIA_TITLE_NATIVE_DESC",
333
- MEDIA_POPULARITY = "MEDIA_POPULARITY",
334
- MEDIA_POPULARITY_DESC = "MEDIA_POPULARITY_DESC"
335
- }
336
- interface MediaListEntry {
337
- id: number;
338
- mediaId: number;
339
- status: MediaListStatus;
340
- score: number | null;
341
- progress: number | null;
342
- progressVolumes: number | null;
343
- repeat: number | null;
344
- priority: number | null;
345
- private: boolean | null;
346
- notes: string | null;
347
- startedAt: FuzzyDate | null;
348
- completedAt: FuzzyDate | null;
349
- updatedAt: number | null;
350
- createdAt: number | null;
351
- media: Media;
352
- }
353
- interface GetUserMediaListOptions {
354
- /** User ID (provide either userId or userName) */
355
- userId?: number;
356
- /** Username (provide either userId or userName) */
357
- userName?: string;
358
- /** ANIME or MANGA */
359
- type: MediaType;
360
- /** Filter by list status (CURRENT, COMPLETED, etc.) */
361
- status?: MediaListStatus;
362
- /** Sort order */
363
- sort?: MediaListSort[];
364
- page?: number;
365
- perPage?: number;
366
- }
367
-
368
334
  interface Studio {
369
335
  id: number;
370
336
  name: string;
@@ -394,6 +360,16 @@ interface SearchStudioOptions {
394
360
  page?: number;
395
361
  perPage?: number;
396
362
  }
363
+ /**
364
+ * Options for controlling embedded media when fetching a single studio.
365
+ * Pass `{ media: { perPage: 50 } }` to fetch more media per studio.
366
+ */
367
+ interface StudioIncludeOptions {
368
+ /** Customize the number of media returned. `true` = 25 (default), or `{ perPage }`. */
369
+ media?: boolean | {
370
+ perPage?: number;
371
+ };
372
+ }
397
373
 
398
374
  declare enum UserSort {
399
375
  ID = "ID",
@@ -485,6 +461,13 @@ interface UserFavorites {
485
461
  staff: FavoriteStaffNode[];
486
462
  studios: FavoriteStudioNode[];
487
463
  }
464
+ /**
465
+ * Options for controlling the number of results when fetching user favorites.
466
+ */
467
+ interface UserFavoritesOptions {
468
+ /** Number of items per category (default: 25, max: 50). */
469
+ perPage?: number;
470
+ }
488
471
 
489
472
  declare enum MediaType {
490
473
  ANIME = "ANIME",
@@ -600,6 +583,7 @@ interface MediaTag {
600
583
  description: string | null;
601
584
  category: string | null;
602
585
  rank: number | null;
586
+ isAdult: boolean | null;
603
587
  isMediaSpoiler: boolean | null;
604
588
  }
605
589
  declare enum MediaRelationType {
@@ -841,36 +825,97 @@ interface AiringSchedule {
841
825
  type DayOfWeek = "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday" | "Sunday";
842
826
  type WeeklySchedule = Record<DayOfWeek, AiringSchedule[]>;
843
827
 
844
- /** Represents a forum thread on AniList. */
845
- interface Thread {
846
- id: number;
847
- title: string;
848
- body: string | null;
849
- userId: number;
850
- replyUserId: number | null;
851
- replyCommentId: number | null;
852
- replyCount: number;
853
- viewCount: number;
854
- isLocked: boolean;
855
- isSticky: boolean;
856
- isSubscribed: boolean;
857
- repliedAt: number | null;
858
- createdAt: number;
859
- updatedAt: number;
860
- siteUrl: string | null;
861
- user: {
862
- id: number;
863
- name: string;
864
- avatar: UserAvatar;
865
- } | null;
866
- replyUser: {
867
- id: number;
868
- name: string;
869
- avatar: UserAvatar;
870
- } | null;
871
- categories: ThreadCategory[] | null;
872
- mediaCategories: ThreadMediaCategory[] | null;
873
- likes: {
828
+ declare enum CharacterSort {
829
+ ID = "ID",
830
+ ID_DESC = "ID_DESC",
831
+ ROLE = "ROLE",
832
+ ROLE_DESC = "ROLE_DESC",
833
+ SEARCH_MATCH = "SEARCH_MATCH",
834
+ FAVOURITES = "FAVOURITES",
835
+ FAVOURITES_DESC = "FAVOURITES_DESC"
836
+ }
837
+ declare enum CharacterRole {
838
+ MAIN = "MAIN",
839
+ SUPPORTING = "SUPPORTING",
840
+ BACKGROUND = "BACKGROUND"
841
+ }
842
+ interface CharacterName {
843
+ first: string | null;
844
+ middle: string | null;
845
+ last: string | null;
846
+ full: string | null;
847
+ native: string | null;
848
+ alternative: string[];
849
+ }
850
+ interface CharacterImage {
851
+ large: string | null;
852
+ medium: string | null;
853
+ }
854
+ type CharacterMediaNode = Pick<Media, "id" | "title" | "type" | "coverImage" | "siteUrl">;
855
+ interface CharacterMediaEdge {
856
+ node: CharacterMediaNode;
857
+ voiceActors?: VoiceActor[];
858
+ }
859
+ interface Character {
860
+ id: number;
861
+ name: CharacterName;
862
+ image: CharacterImage;
863
+ description: string | null;
864
+ gender: string | null;
865
+ dateOfBirth: FuzzyDate | null;
866
+ age: string | null;
867
+ bloodType: string | null;
868
+ favourites: number | null;
869
+ siteUrl: string | null;
870
+ media: {
871
+ nodes?: CharacterMediaNode[];
872
+ edges?: CharacterMediaEdge[];
873
+ } | null;
874
+ }
875
+ /** Options for including extra data when fetching a character. */
876
+ interface CharacterIncludeOptions {
877
+ /** Include voice actors for each media the character appears in. */
878
+ voiceActors?: boolean;
879
+ }
880
+ interface SearchCharacterOptions {
881
+ query?: string;
882
+ sort?: CharacterSort[];
883
+ page?: number;
884
+ perPage?: number;
885
+ /** Include voice actors for each media the character appears in. */
886
+ voiceActors?: boolean;
887
+ }
888
+
889
+ /** Represents a forum thread on AniList. */
890
+ interface Thread {
891
+ id: number;
892
+ title: string;
893
+ body: string | null;
894
+ userId: number;
895
+ replyUserId: number | null;
896
+ replyCommentId: number | null;
897
+ replyCount: number;
898
+ viewCount: number;
899
+ isLocked: boolean;
900
+ isSticky: boolean;
901
+ isSubscribed: boolean;
902
+ repliedAt: number | null;
903
+ createdAt: number;
904
+ updatedAt: number;
905
+ siteUrl: string | null;
906
+ user: {
907
+ id: number;
908
+ name: string;
909
+ avatar: UserAvatar;
910
+ } | null;
911
+ replyUser: {
912
+ id: number;
913
+ name: string;
914
+ avatar: UserAvatar;
915
+ } | null;
916
+ categories: ThreadCategory[] | null;
917
+ mediaCategories: ThreadMediaCategory[] | null;
918
+ likes: {
874
919
  id: number;
875
920
  name: string;
876
921
  }[] | null;
@@ -921,6 +966,145 @@ interface SearchThreadOptions {
921
966
  perPage?: number;
922
967
  }
923
968
 
969
+ /** Cache performance statistics. */
970
+ interface CacheStats {
971
+ /** Total cache hits. */
972
+ hits: number;
973
+ /** Total cache misses. */
974
+ misses: number;
975
+ /** Stale entries returned (only with stale-while-revalidate). */
976
+ stales: number;
977
+ /** Hit rate as a ratio 0–1 (NaN if no requests yet). */
978
+ hitRate: number;
979
+ }
980
+ declare class MemoryCache implements CacheAdapter {
981
+ private readonly ttl;
982
+ private readonly maxSize;
983
+ private readonly enabled;
984
+ private readonly swrMs;
985
+ private readonly store;
986
+ private _hits;
987
+ private _misses;
988
+ private _stales;
989
+ constructor(options?: CacheOptions);
990
+ /** Build a deterministic cache key from a query + variables pair. */
991
+ static key(query: string, variables: Record<string, unknown>): string;
992
+ /**
993
+ * Retrieve a cached value, or `undefined` if missing / expired.
994
+ * With stale-while-revalidate enabled, returns stale data within the grace window
995
+ * and flags it so the caller can refresh in the background.
996
+ */
997
+ get<T>(key: string): T | undefined;
998
+ /** Store a value in the cache. */
999
+ set<T>(key: string, data: T): void;
1000
+ /** Remove a specific entry. */
1001
+ delete(key: string): boolean;
1002
+ /** Clear the entire cache and reset statistics. */
1003
+ clear(): void;
1004
+ /** Number of entries currently stored. */
1005
+ get size(): number | Promise<number>;
1006
+ /** Return all cache keys. */
1007
+ keys(): string[];
1008
+ /**
1009
+ * Get cache performance statistics.
1010
+ *
1011
+ * @example
1012
+ * ```ts
1013
+ * const cache = new MemoryCache();
1014
+ * // ... after some usage ...
1015
+ * console.log(cache.stats);
1016
+ * // { hits: 42, misses: 8, stales: 0, hitRate: 0.84 }
1017
+ * ```
1018
+ */
1019
+ get stats(): CacheStats;
1020
+ /** Reset cache statistics without clearing stored data. */
1021
+ resetStats(): void;
1022
+ /**
1023
+ * Remove all entries whose key matches the given pattern.
1024
+ *
1025
+ * - **String**: treated as a substring match (e.g. `"Media"` removes all keys containing `"Media"`).
1026
+ * - **RegExp**: tested against each key directly.
1027
+ *
1028
+ * @param pattern — A string (substring match) or RegExp.
1029
+ * @returns Number of entries removed.
1030
+ */
1031
+ invalidate(pattern: string | RegExp): number;
1032
+ }
1033
+
1034
+ /**
1035
+ * Minimal interface representing a Redis client.
1036
+ * Compatible with both `ioredis` and `redis` (node-redis v4+).
1037
+ */
1038
+ interface RedisLikeClient {
1039
+ get(key: string): Promise<string | null>;
1040
+ set(key: string, value: string, ...args: unknown[]): Promise<unknown>;
1041
+ del(...keys: (string | string[])[]): Promise<number>;
1042
+ keys(pattern: string): Promise<string[]>;
1043
+ /** Optional SCAN-based iteration — used when available to avoid blocking the server. */
1044
+ scanIterator?(options: {
1045
+ MATCH: string;
1046
+ COUNT?: number;
1047
+ }): AsyncIterable<string>;
1048
+ }
1049
+ interface RedisCacheOptions {
1050
+ /** A Redis client instance (ioredis or node-redis). */
1051
+ client: RedisLikeClient;
1052
+ /** Key prefix to namespace ani-client entries (default: `"ani:"`) */
1053
+ prefix?: string;
1054
+ /** TTL in seconds (default: 86 400 = 24 h) */
1055
+ ttl?: number;
1056
+ }
1057
+ /**
1058
+ * Redis-backed cache adapter for AniListClient.
1059
+ *
1060
+ * @example
1061
+ * ```ts
1062
+ * import Redis from "ioredis";
1063
+ * import { AniListClient, RedisCache } from "ani-client";
1064
+ *
1065
+ * const redis = new Redis();
1066
+ * const client = new AniListClient({
1067
+ * cacheAdapter: new RedisCache({ client: redis }),
1068
+ * });
1069
+ * ```
1070
+ */
1071
+ declare class RedisCache implements CacheAdapter {
1072
+ private readonly client;
1073
+ private readonly prefix;
1074
+ private readonly ttl;
1075
+ constructor(options: RedisCacheOptions);
1076
+ private prefixedKey;
1077
+ get<T>(key: string): Promise<T | undefined>;
1078
+ set<T>(key: string, data: T): Promise<void>;
1079
+ delete(key: string): Promise<boolean>;
1080
+ /**
1081
+ * Collect keys matching a pattern. Uses SCAN when available, falls back to KEYS.
1082
+ *
1083
+ * **Warning:** The `KEYS` fallback is O(N) and blocks the Redis server.
1084
+ * Provide a client with `scanIterator` support for production use.
1085
+ * @internal
1086
+ */
1087
+ private collectKeys;
1088
+ clear(): Promise<void>;
1089
+ /**
1090
+ * Get the actual number of keys with this prefix in Redis.
1091
+ */
1092
+ get size(): Promise<number>;
1093
+ /** @internal */
1094
+ private getSize;
1095
+ keys(): Promise<string[]>;
1096
+ /**
1097
+ * Remove all entries whose key matches the given pattern.
1098
+ *
1099
+ * - **String**: treated as a substring match (e.g. `"Media"` removes all keys containing `"Media"`).
1100
+ * - **RegExp**: tested against each key directly.
1101
+ *
1102
+ * @param pattern — A string (substring match) or RegExp.
1103
+ * @returns Number of entries removed.
1104
+ */
1105
+ invalidate(pattern: string | RegExp): Promise<number>;
1106
+ }
1107
+
924
1108
  /**
925
1109
  * Lightweight AniList GraphQL client with built-in caching and rate limiting.
926
1110
  *
@@ -945,6 +1129,7 @@ declare class AniListClient {
945
1129
  private readonly cacheAdapter;
946
1130
  private readonly rateLimiter;
947
1131
  private readonly hooks;
1132
+ private readonly logger?;
948
1133
  private readonly signal?;
949
1134
  private readonly inFlight;
950
1135
  private _rateLimitInfo?;
@@ -1000,6 +1185,13 @@ declare class AniListClient {
1000
1185
  * @deprecated Use `getRecentlyUpdatedManga` instead. This alias will be removed in v2.
1001
1186
  */
1002
1187
  getAiredChapters(options?: GetRecentChaptersOptions): Promise<PagedResult<Media>>;
1188
+ /**
1189
+ * Fetch a media entry by its MyAnimeList (MAL) ID.
1190
+ *
1191
+ * @param malId - The MyAnimeList ID
1192
+ * @param type - Optional media type to disambiguate (some MAL IDs map to both ANIME and MANGA)
1193
+ */
1194
+ getMediaByMalId(malId: number, type?: MediaType): Promise<Media>;
1003
1195
  /** Get the detailed schedule for the current week, sorted by day. */
1004
1196
  getWeeklySchedule(date?: Date): Promise<WeeklySchedule>;
1005
1197
  /** Get upcoming (not yet released) media. */
@@ -1030,17 +1222,29 @@ declare class AniListClient {
1030
1222
  * Fetch a user's favorite anime, manga, characters, staff, and studios.
1031
1223
  *
1032
1224
  * @param idOrName - AniList user ID (number) or username (string)
1225
+ * @param options - Optional pagination options (perPage per category)
1033
1226
  * @returns The user's favorites grouped by category
1034
1227
  *
1035
1228
  * @example
1036
1229
  * ```typescript
1037
1230
  * const favs = await client.getUserFavorites("AniList");
1038
1231
  * favs.anime.forEach(a => console.log(a.title.romaji));
1232
+ *
1233
+ * // Fetch more results per category
1234
+ * const moreResults = await client.getUserFavorites(1, { perPage: 50 });
1235
+ * ```
1236
+ */
1237
+ getUserFavorites(idOrName: number | string, options?: UserFavoritesOptions): Promise<UserFavorites>;
1238
+ /**
1239
+ * Fetch a studio by its AniList ID.
1240
+ * Pass `include` to customise the number of media returned.
1241
+ *
1242
+ * @example
1243
+ * ```typescript
1244
+ * const studio = await client.getStudio(21, { media: { perPage: 50 } });
1039
1245
  * ```
1040
1246
  */
1041
- getUserFavorites(idOrName: number | string): Promise<UserFavorites>;
1042
- /** Fetch a studio by its AniList ID. */
1043
- getStudio(id: number): Promise<Studio>;
1247
+ getStudio(id: number, include?: StudioIncludeOptions): Promise<Studio>;
1044
1248
  /** Search for studios by name. */
1045
1249
  searchStudios(options?: SearchStudioOptions): Promise<PagedResult<Studio>>;
1046
1250
  /** Fetch a forum thread by its AniList ID. */
@@ -1076,6 +1280,20 @@ declare class AniListClient {
1076
1280
  invalidateCache(pattern: string | RegExp): Promise<number>;
1077
1281
  /** Clean up resources held by the client. */
1078
1282
  destroy(): Promise<void>;
1283
+ /**
1284
+ * Return a scoped view of this client where every request uses the given `AbortSignal`.
1285
+ * The returned object shares the same cache, rate limiter, and hooks.
1286
+ *
1287
+ * @example
1288
+ * ```ts
1289
+ * const controller = new AbortController();
1290
+ * const media = await client.withSignal(controller.signal).getMedia(1);
1291
+ *
1292
+ * // Cancel all in-flight requests made through the scoped view
1293
+ * controller.abort();
1294
+ * ```
1295
+ */
1296
+ withSignal(signal: AbortSignal): AniListClient;
1079
1297
  }
1080
1298
 
1081
1299
  /**
@@ -1089,109 +1307,6 @@ declare class AniListError extends Error {
1089
1307
  constructor(message: string, status: number, errors?: unknown[]);
1090
1308
  }
1091
1309
 
1092
- declare class MemoryCache implements CacheAdapter {
1093
- private readonly ttl;
1094
- private readonly maxSize;
1095
- private readonly enabled;
1096
- private readonly store;
1097
- constructor(options?: CacheOptions);
1098
- /** Build a deterministic cache key from a query + variables pair. */
1099
- static key(query: string, variables: Record<string, unknown>): string;
1100
- /** Retrieve a cached value, or `undefined` if missing / expired. */
1101
- get<T>(key: string): T | undefined;
1102
- /** Store a value in the cache. */
1103
- set<T>(key: string, data: T): void;
1104
- /** Remove a specific entry. */
1105
- delete(key: string): boolean;
1106
- /** Clear the entire cache. */
1107
- clear(): void;
1108
- /** Number of entries currently stored. */
1109
- get size(): number | Promise<number>;
1110
- /** Return all cache keys. */
1111
- keys(): string[];
1112
- /**
1113
- * Remove all entries whose key matches the given pattern.
1114
- *
1115
- * - **String**: treated as a substring match (e.g. `"Media"` removes all keys containing `"Media"`).
1116
- * - **RegExp**: tested against each key directly.
1117
- *
1118
- * @param pattern — A string (substring match) or RegExp.
1119
- * @returns Number of entries removed.
1120
- */
1121
- invalidate(pattern: string | RegExp): number;
1122
- }
1123
-
1124
- /**
1125
- * Minimal interface representing a Redis client.
1126
- * Compatible with both `ioredis` and `redis` (node-redis v4+).
1127
- */
1128
- interface RedisLikeClient {
1129
- get(key: string): Promise<string | null>;
1130
- set(key: string, value: string, ...args: unknown[]): Promise<unknown>;
1131
- del(...keys: (string | string[])[]): Promise<number>;
1132
- keys(pattern: string): Promise<string[]>;
1133
- /** Optional SCAN-based iteration — used when available to avoid blocking the server. */
1134
- scanIterator?(options: {
1135
- MATCH: string;
1136
- COUNT?: number;
1137
- }): AsyncIterable<string>;
1138
- }
1139
- interface RedisCacheOptions {
1140
- /** A Redis client instance (ioredis or node-redis). */
1141
- client: RedisLikeClient;
1142
- /** Key prefix to namespace ani-client entries (default: `"ani:"`) */
1143
- prefix?: string;
1144
- /** TTL in seconds (default: 86 400 = 24 h) */
1145
- ttl?: number;
1146
- }
1147
- /**
1148
- * Redis-backed cache adapter for AniListClient.
1149
- *
1150
- * @example
1151
- * ```ts
1152
- * import Redis from "ioredis";
1153
- * import { AniListClient, RedisCache } from "ani-client";
1154
- *
1155
- * const redis = new Redis();
1156
- * const client = new AniListClient({
1157
- * cacheAdapter: new RedisCache({ client: redis }),
1158
- * });
1159
- * ```
1160
- */
1161
- declare class RedisCache implements CacheAdapter {
1162
- private readonly client;
1163
- private readonly prefix;
1164
- private readonly ttl;
1165
- constructor(options: RedisCacheOptions);
1166
- private prefixedKey;
1167
- get<T>(key: string): Promise<T | undefined>;
1168
- set<T>(key: string, data: T): Promise<void>;
1169
- delete(key: string): Promise<boolean>;
1170
- /**
1171
- * Collect keys matching a pattern. Uses SCAN when available, falls back to KEYS.
1172
- *
1173
- * **Warning:** The `KEYS` fallback is O(N) and blocks the Redis server.
1174
- * Provide a client with `scanIterator` support for production use.
1175
- * @internal
1176
- */
1177
- private collectKeys;
1178
- clear(): Promise<void>;
1179
- /**
1180
- * Get the actual number of keys with this prefix in Redis.
1181
- */
1182
- get size(): Promise<number>;
1183
- /** @internal */
1184
- private getSize;
1185
- keys(): Promise<string[]>;
1186
- /**
1187
- * Remove all entries whose key matches the given glob pattern.
1188
- *
1189
- * @param pattern — A glob pattern (e.g. `"*Media*"`)
1190
- * @returns Number of entries removed.
1191
- */
1192
- invalidate(pattern: string | RegExp): Promise<number>;
1193
- }
1194
-
1195
1310
  /**
1196
1311
  * Rate limiter with automatic retry for AniList API.
1197
1312
  *
@@ -1239,4 +1354,4 @@ declare class RateLimiter {
1239
1354
 
1240
1355
  declare function parseAniListMarkdown(text: string): string;
1241
1356
 
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 };
1357
+ 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 };