ani-client 1.3.0 → 1.4.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
@@ -7,7 +7,7 @@
7
7
  > A simple, typed client to fetch anime, manga, character, staff and user data from [AniList](https://anilist.co).
8
8
 
9
9
  - **Zero dependencies** — uses the native `fetch` API
10
- - **Universal** — Node.js ≥ 18, Bun, Deno and modern browsers
10
+ - **Universal** — Node.js ≥ 20, Bun, Deno and modern browsers
11
11
  - **Dual format** — ships ESM + CJS with full TypeScript declarations
12
12
  - **Built-in caching** — in-memory LRU with optional Redis adapter
13
13
  - **Rate-limit aware** — auto-retry on 429, configurable timeout & network error retry
@@ -22,6 +22,7 @@
22
22
  - [Client options](#client-options)
23
23
  - [API reference](#api-reference)
24
24
  - [Media](#media)
25
+ - [Include options](#include-options)
25
26
  - [Characters](#characters)
26
27
  - [Staff](#staff)
27
28
  - [Users](#users)
@@ -145,15 +146,27 @@ const client = new AniListClient({
145
146
 
146
147
  | Method | Description |
147
148
  | --- | --- |
148
- | `getMedia(id)` | Fetch a single anime / manga by ID |
149
+ | `getMedia(id, include?)` | Fetch a single anime / manga by ID with optional extra data |
149
150
  | `searchMedia(options?)` | Search & filter anime / manga |
150
151
  | `getTrending(type?, page?, perPage?)` | Currently trending entries |
151
152
  | `getMediaBySeason(options)` | Anime/manga for a given season & year |
152
153
  | `getRecommendations(mediaId, options?)` | User recommendations for a media |
153
154
 
154
155
  ```ts
156
+ // Simple — same as before
155
157
  const anime = await client.getMedia(1);
156
158
 
159
+ // With extra data
160
+ const anime = await client.getMedia(1, {
161
+ characters: { perPage: 25, sort: true },
162
+ staff: true,
163
+ relations: true,
164
+ streamingEpisodes: true,
165
+ externalLinks: true,
166
+ stats: true,
167
+ recommendations: { perPage: 5 },
168
+ });
169
+
157
170
  const results = await client.searchMedia({
158
171
  query: "Naruto",
159
172
  type: MediaType.ANIME,
@@ -163,6 +176,56 @@ const results = await client.searchMedia({
163
176
  });
164
177
  ```
165
178
 
179
+ ### Include options
180
+
181
+ The second parameter of `getMedia()` lets you opt-in to additional data. By default, only `relations` are included for backward compatibility.
182
+
183
+ | Option | Type | Default | Description |
184
+ | --- | --- | --- | --- |
185
+ | `characters` | `boolean \| { perPage?, sort? }` | — | Characters with their roles (MAIN, SUPPORTING, BACKGROUND) |
186
+ | `staff` | `boolean \| { perPage?, sort? }` | — | Staff members with their roles |
187
+ | `relations` | `boolean` | `true` | Sequels, prequels, adaptations, etc. Set `false` to exclude |
188
+ | `streamingEpisodes` | `boolean` | — | Streaming links (Crunchyroll, Funimation, etc.) |
189
+ | `externalLinks` | `boolean` | — | External links (MAL, official site, etc.) |
190
+ | `stats` | `boolean` | — | Score & status distribution |
191
+ | `recommendations` | `boolean \| { perPage? }` | — | User recommendations |
192
+
193
+ ```ts
194
+ // Include characters sorted by role (25 per page by default)
195
+ const anime = await client.getMedia(1, { characters: true });
196
+ anime.characters?.edges.forEach((e) =>
197
+ console.log(`${e.node.name.full} (${e.role})`)
198
+ );
199
+
200
+ // 50 characters, no sorting
201
+ const anime = await client.getMedia(1, {
202
+ characters: { perPage: 50, sort: false },
203
+ });
204
+
205
+ // Staff members
206
+ const anime = await client.getMedia(1, { staff: true });
207
+ anime.staff?.edges.forEach((e) =>
208
+ console.log(`${e.node.name.full} — ${e.role}`)
209
+ );
210
+
211
+ // Everything at once
212
+ const anime = await client.getMedia(1, {
213
+ characters: { perPage: 50 },
214
+ staff: { perPage: 25 },
215
+ relations: true,
216
+ streamingEpisodes: true,
217
+ externalLinks: true,
218
+ stats: true,
219
+ recommendations: { perPage: 10 },
220
+ });
221
+
222
+ // Lightweight — exclude relations
223
+ const anime = await client.getMedia(1, {
224
+ characters: true,
225
+ relations: false,
226
+ });
227
+ ```
228
+
166
229
  ### Characters
167
230
 
168
231
  | Method | Description |
@@ -266,13 +329,16 @@ recs.results.forEach((r) =>
266
329
 
267
330
  ### Relations
268
331
 
269
- Every media object includes a `relations` field with sequels, prequels, spin-offs, etc.
332
+ Relations (sequels, prequels, spin-offs, etc.) are included by default when using `getMedia()`. You can also explicitly request them via the `include` parameter, or exclude them with `relations: false`.
270
333
 
271
334
  ```ts
272
335
  const anime = await client.getMedia(1);
273
336
  anime.relations?.edges.forEach((edge) =>
274
337
  console.log(`${edge.relationType}: ${edge.node.title.romaji}`)
275
338
  );
339
+
340
+ // Exclude relations for a lighter response
341
+ const anime = await client.getMedia(1, { relations: false });
276
342
  ```
277
343
 
278
344
  Available types: `ADAPTATION`, `PREQUEL`, `SEQUEL`, `PARENT`, `SIDE_STORY`, `CHARACTER`, `SUMMARY`, `ALTERNATIVE`, `SPIN_OFF`, `OTHER`, `SOURCE`, `COMPILATION`, `CONTAINS`.
@@ -474,7 +540,10 @@ All types and enums are exported:
474
540
  import type {
475
541
  Media, Character, Staff, User,
476
542
  AiringSchedule, MediaListEntry, Recommendation, StudioDetail,
477
- MediaEdge, MediaConnection, PageInfo, PagedResult,
543
+ MediaEdge, MediaConnection, MediaCharacterEdge, MediaCharacterConnection,
544
+ MediaStaffEdge, MediaStaffConnection, MediaIncludeOptions,
545
+ StreamingEpisode, ExternalLink, MediaStats, MediaRecommendationNode,
546
+ PageInfo, PagedResult,
478
547
  CacheAdapter, AniListHooks, AniListClientOptions,
479
548
  SearchMediaOptions, SearchCharacterOptions, SearchStaffOptions,
480
549
  SearchStudioOptions, GetAiringOptions, GetRecentChaptersOptions,
@@ -484,7 +553,7 @@ import type {
484
553
 
485
554
  import {
486
555
  MediaType, MediaFormat, MediaStatus, MediaSeason, MediaSort,
487
- CharacterSort, AiringSort, RecommendationSort,
556
+ CharacterSort, CharacterRole, AiringSort, RecommendationSort,
488
557
  MediaRelationType, MediaListStatus, MediaListSort,
489
558
  } from "ani-client";
490
559
  ```
package/dist/index.d.mts CHANGED
@@ -29,21 +29,37 @@ declare enum MediaSeason {
29
29
  }
30
30
  declare enum MediaSort {
31
31
  ID = "ID",
32
+ ID_DESC = "ID_DESC",
32
33
  TITLE_ROMAJI = "TITLE_ROMAJI",
34
+ TITLE_ROMAJI_DESC = "TITLE_ROMAJI_DESC",
33
35
  TITLE_ENGLISH = "TITLE_ENGLISH",
36
+ TITLE_ENGLISH_DESC = "TITLE_ENGLISH_DESC",
34
37
  TITLE_NATIVE = "TITLE_NATIVE",
38
+ TITLE_NATIVE_DESC = "TITLE_NATIVE_DESC",
35
39
  TYPE = "TYPE",
40
+ TYPE_DESC = "TYPE_DESC",
36
41
  FORMAT = "FORMAT",
42
+ FORMAT_DESC = "FORMAT_DESC",
37
43
  START_DATE = "START_DATE",
44
+ START_DATE_DESC = "START_DATE_DESC",
38
45
  END_DATE = "END_DATE",
46
+ END_DATE_DESC = "END_DATE_DESC",
39
47
  SCORE = "SCORE",
48
+ SCORE_DESC = "SCORE_DESC",
40
49
  POPULARITY = "POPULARITY",
50
+ POPULARITY_DESC = "POPULARITY_DESC",
41
51
  TRENDING = "TRENDING",
52
+ TRENDING_DESC = "TRENDING_DESC",
42
53
  EPISODES = "EPISODES",
54
+ EPISODES_DESC = "EPISODES_DESC",
43
55
  DURATION = "DURATION",
56
+ DURATION_DESC = "DURATION_DESC",
44
57
  STATUS = "STATUS",
58
+ STATUS_DESC = "STATUS_DESC",
45
59
  FAVOURITES = "FAVOURITES",
60
+ FAVOURITES_DESC = "FAVOURITES_DESC",
46
61
  UPDATED_AT = "UPDATED_AT",
62
+ UPDATED_AT_DESC = "UPDATED_AT_DESC",
47
63
  SEARCH_MATCH = "SEARCH_MATCH"
48
64
  }
49
65
  declare enum AiringSort {
@@ -58,9 +74,17 @@ declare enum AiringSort {
58
74
  }
59
75
  declare enum CharacterSort {
60
76
  ID = "ID",
77
+ ID_DESC = "ID_DESC",
61
78
  ROLE = "ROLE",
79
+ ROLE_DESC = "ROLE_DESC",
62
80
  SEARCH_MATCH = "SEARCH_MATCH",
63
- FAVOURITES = "FAVOURITES"
81
+ FAVOURITES = "FAVOURITES",
82
+ FAVOURITES_DESC = "FAVOURITES_DESC"
83
+ }
84
+ declare enum CharacterRole {
85
+ MAIN = "MAIN",
86
+ SUPPORTING = "SUPPORTING",
87
+ BACKGROUND = "BACKGROUND"
64
88
  }
65
89
  interface MediaTitle {
66
90
  romaji: string | null;
@@ -135,6 +159,51 @@ interface CharacterImage {
135
159
  large: string | null;
136
160
  medium: string | null;
137
161
  }
162
+ interface MediaCharacterEdge {
163
+ role: CharacterRole;
164
+ node: Omit<Character, "media">;
165
+ }
166
+ interface MediaCharacterConnection {
167
+ edges: MediaCharacterEdge[];
168
+ }
169
+ interface MediaStaffEdge {
170
+ role: string;
171
+ node: Staff;
172
+ }
173
+ interface MediaStaffConnection {
174
+ edges: MediaStaffEdge[];
175
+ }
176
+ interface StreamingEpisode {
177
+ title: string | null;
178
+ thumbnail: string | null;
179
+ url: string | null;
180
+ site: string | null;
181
+ }
182
+ interface ExternalLink {
183
+ id: number;
184
+ url: string | null;
185
+ site: string;
186
+ type: string | null;
187
+ icon: string | null;
188
+ color: string | null;
189
+ }
190
+ interface ScoreDistribution {
191
+ score: number;
192
+ amount: number;
193
+ }
194
+ interface StatusDistribution {
195
+ status: MediaListStatus | string;
196
+ amount: number;
197
+ }
198
+ interface MediaStats {
199
+ scoreDistribution: ScoreDistribution[];
200
+ statusDistribution: StatusDistribution[];
201
+ }
202
+ interface MediaRecommendationNode {
203
+ id: number;
204
+ rating: number | null;
205
+ mediaRecommendation: Pick<Media, "id" | "title" | "type" | "format" | "coverImage" | "averageScore" | "siteUrl">;
206
+ }
138
207
  interface Media {
139
208
  id: number;
140
209
  idMal: number | null;
@@ -168,6 +237,14 @@ interface Media {
168
237
  tags: MediaTag[];
169
238
  studios: StudioConnection;
170
239
  relations: MediaConnection | null;
240
+ characters?: MediaCharacterConnection;
241
+ staff?: MediaStaffConnection;
242
+ streamingEpisodes?: StreamingEpisode[];
243
+ externalLinks?: ExternalLink[];
244
+ stats?: MediaStats;
245
+ recommendations?: {
246
+ nodes: MediaRecommendationNode[];
247
+ };
171
248
  isAdult: boolean | null;
172
249
  siteUrl: string | null;
173
250
  }
@@ -207,7 +284,7 @@ interface Staff {
207
284
  gender: string | null;
208
285
  dateOfBirth: FuzzyDate | null;
209
286
  dateOfDeath: FuzzyDate | null;
210
- age: number | null;
287
+ age: string | null;
211
288
  yearsActive: number[];
212
289
  homeTown: string | null;
213
290
  bloodType: string | null;
@@ -280,6 +357,7 @@ interface SearchCharacterOptions {
280
357
  }
281
358
  interface SearchStaffOptions {
282
359
  query?: string;
360
+ sort?: CharacterSort[];
283
361
  page?: number;
284
362
  perPage?: number;
285
363
  }
@@ -435,6 +513,36 @@ interface SearchStudioOptions {
435
513
  page?: number;
436
514
  perPage?: number;
437
515
  }
516
+ /**
517
+ * Options to include additional related data when fetching a media entry.
518
+ * Pass `true` to include with defaults, or an object to customize.
519
+ */
520
+ interface MediaIncludeOptions {
521
+ /** Include characters with their roles (MAIN, SUPPORTING, BACKGROUND).
522
+ * `true` = 25 results sorted by role. Object form to customize. */
523
+ characters?: boolean | {
524
+ perPage?: number;
525
+ sort?: boolean;
526
+ };
527
+ /** Include staff members with their roles.
528
+ * `true` = 25 results sorted by relevance. Object form to customize. */
529
+ staff?: boolean | {
530
+ perPage?: number;
531
+ sort?: boolean;
532
+ };
533
+ /** Include relations (default: `true` for backward compat). Set to `false` to exclude. */
534
+ relations?: boolean;
535
+ /** Include streaming episode links (Crunchyroll, Funimation, etc.) */
536
+ streamingEpisodes?: boolean;
537
+ /** Include external links (MAL, official site, etc.) */
538
+ externalLinks?: boolean;
539
+ /** Include score & status distribution stats */
540
+ stats?: boolean;
541
+ /** Include user recommendations. `true` = 10 results, or customize with `{ perPage }`. */
542
+ recommendations?: boolean | {
543
+ perPage?: number;
544
+ };
545
+ }
438
546
  /**
439
547
  * Interface that all cache adapters must implement.
440
548
  * Methods may return sync values or Promises — the client awaits all calls.
@@ -451,10 +559,36 @@ interface CacheAdapter {
451
559
  /** Number of entries currently stored (sync). Returns -1 if unknown. */
452
560
  readonly size: number;
453
561
  /** Return all cache keys. */
454
- keys(): IterableIterator<string> | string[] | Promise<string[]>;
562
+ keys(): string[] | Promise<string[]>;
455
563
  /** Bulk-remove entries matching a pattern. Optional — the client provides a fallback. */
456
564
  invalidate?(pattern: string | RegExp): number | Promise<number>;
457
565
  }
566
+ /** Cache configuration options. */
567
+ interface CacheOptions {
568
+ /** Time-to-live in milliseconds (default: 86 400 000 = 24h) */
569
+ ttl?: number;
570
+ /** Maximum number of cached entries (default: 500, 0 = unlimited) */
571
+ maxSize?: number;
572
+ /** Set to false to disable caching entirely */
573
+ enabled?: boolean;
574
+ }
575
+ /** Rate limiter configuration options. */
576
+ interface RateLimitOptions {
577
+ /** Max requests per window (default: 85) */
578
+ maxRequests?: number;
579
+ /** Window size in ms (default: 60 000) */
580
+ windowMs?: number;
581
+ /** Max retries on 429 (default: 3) */
582
+ maxRetries?: number;
583
+ /** Retry delay in ms when Retry-After header is absent (default: 2000) */
584
+ retryDelayMs?: number;
585
+ /** Set to false to disable rate limiting entirely */
586
+ enabled?: boolean;
587
+ /** Timeout per request in ms (default: 30 000). 0 = no timeout. */
588
+ timeoutMs?: number;
589
+ /** Retry on network errors like ECONNRESET / ETIMEDOUT (default: true) */
590
+ retryOnNetworkError?: boolean;
591
+ }
458
592
  /** Event hooks for logging, debugging, and monitoring. */
459
593
  interface AniListHooks {
460
594
  /** Called before every API request. */
@@ -474,33 +608,11 @@ interface AniListClientOptions {
474
608
  /** Custom API endpoint (defaults to https://graphql.anilist.co) */
475
609
  apiUrl?: string;
476
610
  /** Cache configuration (enabled by default, 24h TTL) */
477
- cache?: {
478
- /** Time-to-live in milliseconds (default: 86 400 000 = 24h) */
479
- ttl?: number;
480
- /** Maximum number of cached entries (default: 500, 0 = unlimited) */
481
- maxSize?: number;
482
- /** Set to false to disable caching entirely */
483
- enabled?: boolean;
484
- };
611
+ cache?: CacheOptions;
485
612
  /** Custom cache adapter (e.g. RedisCache). Takes precedence over `cache`. */
486
613
  cacheAdapter?: CacheAdapter;
487
614
  /** Rate limiter configuration (enabled by default, 85 req/min) */
488
- rateLimit?: {
489
- /** Max requests per window (default: 85) */
490
- maxRequests?: number;
491
- /** Window size in ms (default: 60 000) */
492
- windowMs?: number;
493
- /** Max retries on 429 (default: 3) */
494
- maxRetries?: number;
495
- /** Retry delay in ms when Retry-After header is absent (default: 2000) */
496
- retryDelayMs?: number;
497
- /** Set to false to disable rate limiting entirely */
498
- enabled?: boolean;
499
- /** Timeout per request in ms (default: 30 000). 0 = no timeout. */
500
- timeoutMs?: number;
501
- /** Retry on network errors like ECONNRESET / ETIMEDOUT (default: true) */
502
- retryOnNetworkError?: boolean;
503
- };
615
+ rateLimit?: RateLimitOptions;
504
616
  /** Event hooks for logging, debugging, and monitoring */
505
617
  hooks?: AniListHooks;
506
618
  }
@@ -542,13 +654,44 @@ declare class AniListClient {
542
654
  * Shorthand for paginated queries that follow the `Page { pageInfo, <field>[] }` pattern.
543
655
  */
544
656
  private pagedRequest;
657
+ /**
658
+ * @internal
659
+ * Clamp perPage to AniList's maximum of 50.
660
+ */
661
+ private clampPerPage;
545
662
  /**
546
663
  * Fetch a single media entry by its AniList ID.
547
664
  *
665
+ * Optionally include related data (characters, staff, relations, etc.) via the `include` parameter.
666
+ *
548
667
  * @param id - The AniList media ID
668
+ * @param include - Optional related data to include
549
669
  * @returns The media object
670
+ *
671
+ * @example
672
+ * ```ts
673
+ * // Basic usage — same as before (includes relations by default)
674
+ * const anime = await client.getMedia(1);
675
+ *
676
+ * // Include characters sorted by role, 25 results
677
+ * const anime = await client.getMedia(1, { characters: true });
678
+ *
679
+ * // Full control
680
+ * const anime = await client.getMedia(1, {
681
+ * characters: { perPage: 50, sort: true },
682
+ * staff: true,
683
+ * relations: true,
684
+ * streamingEpisodes: true,
685
+ * externalLinks: true,
686
+ * stats: true,
687
+ * recommendations: { perPage: 5 },
688
+ * });
689
+ *
690
+ * // Exclude relations for a lighter response
691
+ * const anime = await client.getMedia(1, { characters: true, relations: false });
692
+ * ```
550
693
  */
551
- getMedia(id: number): Promise<Media>;
694
+ getMedia(id: number, include?: MediaIncludeOptions): Promise<Media>;
552
695
  /**
553
696
  * Search for anime or manga.
554
697
  *
@@ -575,26 +718,78 @@ declare class AniListClient {
575
718
  getTrending(type?: MediaType, page?: number, perPage?: number): Promise<PagedResult<Media>>;
576
719
  /**
577
720
  * Fetch a character by AniList ID.
721
+ *
722
+ * @param id - The AniList character ID
723
+ * @returns The character object
724
+ *
725
+ * @example
726
+ * ```ts
727
+ * const spike = await client.getCharacter(1);
728
+ * console.log(spike.name.full); // "Spike Spiegel"
729
+ * ```
578
730
  */
579
731
  getCharacter(id: number): Promise<Character>;
580
732
  /**
581
733
  * Search for characters by name.
734
+ *
735
+ * @param options - Search / pagination parameters
736
+ * @returns Paginated results with matching characters
737
+ *
738
+ * @example
739
+ * ```ts
740
+ * const result = await client.searchCharacters({ query: "Luffy", perPage: 5 });
741
+ * ```
582
742
  */
583
743
  searchCharacters(options?: SearchCharacterOptions): Promise<PagedResult<Character>>;
584
744
  /**
585
745
  * Fetch a staff member by AniList ID.
746
+ *
747
+ * @param id - The AniList staff ID
748
+ * @returns The staff object
749
+ *
750
+ * @example
751
+ * ```ts
752
+ * const staff = await client.getStaff(95001);
753
+ * console.log(staff.name.full);
754
+ * ```
586
755
  */
587
756
  getStaff(id: number): Promise<Staff>;
588
757
  /**
589
758
  * Search for staff (voice actors, directors, etc.).
759
+ *
760
+ * @param options - Search / pagination parameters
761
+ * @returns Paginated results with matching staff
762
+ *
763
+ * @example
764
+ * ```ts
765
+ * const result = await client.searchStaff({ query: "Miyazaki", perPage: 5 });
766
+ * ```
590
767
  */
591
768
  searchStaff(options?: SearchStaffOptions): Promise<PagedResult<Staff>>;
592
769
  /**
593
770
  * Fetch a user by AniList ID.
771
+ *
772
+ * @param id - The AniList user ID
773
+ * @returns The user object
774
+ *
775
+ * @example
776
+ * ```ts
777
+ * const user = await client.getUser(1);
778
+ * console.log(user.name);
779
+ * ```
594
780
  */
595
781
  getUser(id: number): Promise<User>;
596
782
  /**
597
783
  * Fetch a user by username.
784
+ *
785
+ * @param name - The AniList username
786
+ * @returns The user object
787
+ *
788
+ * @example
789
+ * ```ts
790
+ * const user = await client.getUserByName("AniList");
791
+ * console.log(user.statistics);
792
+ * ```
598
793
  */
599
794
  getUserByName(name: string): Promise<User>;
600
795
  /**
@@ -830,18 +1025,6 @@ declare class AniListError extends Error {
830
1025
  constructor(message: string, status: number, errors?: unknown[]);
831
1026
  }
832
1027
 
833
- /**
834
- * Simple in-memory cache with configurable TTL.
835
- * Used internally by AniListClient to avoid redundant API calls.
836
- */
837
- interface CacheOptions {
838
- /** Time-to-live in milliseconds (default: 24 hours) */
839
- ttl?: number;
840
- /** Maximum number of entries to keep (default: 500, 0 = unlimited) */
841
- maxSize?: number;
842
- /** Disable caching entirely (default: false) */
843
- enabled?: boolean;
844
- }
845
1028
  declare class MemoryCache implements CacheAdapter {
846
1029
  private readonly ttl;
847
1030
  private readonly maxSize;
@@ -860,8 +1043,8 @@ declare class MemoryCache implements CacheAdapter {
860
1043
  clear(): void;
861
1044
  /** Number of entries currently stored. */
862
1045
  get size(): number;
863
- /** Return an iterator over all cache keys. */
864
- keys(): IterableIterator<string>;
1046
+ /** Return all cache keys. */
1047
+ keys(): string[];
865
1048
  /**
866
1049
  * Remove all entries whose key matches the given pattern.
867
1050
  *
@@ -880,6 +1063,11 @@ interface RedisLikeClient {
880
1063
  set(key: string, value: string, ...args: unknown[]): Promise<unknown>;
881
1064
  del(...keys: (string | string[])[]): Promise<number>;
882
1065
  keys(pattern: string): Promise<string[]>;
1066
+ /** Optional SCAN-based iteration — used when available to avoid blocking the server. */
1067
+ scanIterator?(options: {
1068
+ MATCH: string;
1069
+ COUNT?: number;
1070
+ }): AsyncIterable<string>;
883
1071
  }
884
1072
  interface RedisCacheOptions {
885
1073
  /** A Redis client instance (ioredis or node-redis). */
@@ -912,6 +1100,14 @@ declare class RedisCache implements CacheAdapter {
912
1100
  get<T>(key: string): Promise<T | undefined>;
913
1101
  set<T>(key: string, data: T): Promise<void>;
914
1102
  delete(key: string): Promise<boolean>;
1103
+ /**
1104
+ * Collect keys matching a pattern. Uses SCAN when available, falls back to KEYS.
1105
+ *
1106
+ * **Warning:** The `KEYS` fallback is O(N) and blocks the Redis server.
1107
+ * Provide a client with `scanIterator` support for production use.
1108
+ * @internal
1109
+ */
1110
+ private collectKeys;
915
1111
  clear(): Promise<void>;
916
1112
  /**
917
1113
  * Returns -1 because Redis keys can expire silently via TTL.
@@ -927,7 +1123,7 @@ declare class RedisCache implements CacheAdapter {
927
1123
  * @param pattern — A glob pattern (e.g. `"*Media*"`)
928
1124
  * @returns Number of entries removed.
929
1125
  */
930
- invalidate(pattern: string): Promise<number>;
1126
+ invalidate(pattern: string | RegExp): Promise<number>;
931
1127
  }
932
1128
 
933
1129
  /**
@@ -937,22 +1133,7 @@ declare class RedisCache implements CacheAdapter {
937
1133
  * When a 429 (Too Many Requests) is received, the client
938
1134
  * waits for the Retry-After header and retries automatically.
939
1135
  */
940
- interface RateLimitOptions {
941
- /** Max requests per window (default: 85, conservative under AniList's 90/min) */
942
- maxRequests?: number;
943
- /** Window size in milliseconds (default: 60 000 = 1 minute) */
944
- windowMs?: number;
945
- /** Max number of retries on 429 responses (default: 3) */
946
- maxRetries?: number;
947
- /** Default retry delay in ms when Retry-After header is missing (default: 2000) */
948
- retryDelayMs?: number;
949
- /** Disable rate limiting entirely (default: false) */
950
- enabled?: boolean;
951
- /** Timeout per request in milliseconds (default: 30 000). 0 = no timeout. */
952
- timeoutMs?: number;
953
- /** Retry on network errors like ECONNRESET / ETIMEDOUT (default: true) */
954
- retryOnNetworkError?: boolean;
955
- }
1136
+
956
1137
  declare class RateLimiter {
957
1138
  private readonly maxRequests;
958
1139
  private readonly windowMs;
@@ -980,4 +1161,4 @@ declare class RateLimiter {
980
1161
  private sleep;
981
1162
  }
982
1163
 
983
- export { type AiringSchedule, AiringSort, AniListClient, type AniListClientOptions, AniListError, type AniListHooks, type CacheAdapter, type CacheOptions, type Character, type CharacterImage, type CharacterName, CharacterSort, type FuzzyDate, type GetAiringOptions, type GetPlanningOptions, type GetRecentChaptersOptions, type GetRecommendationsOptions, type GetSeasonOptions, type GetUserMediaListOptions, type Media, type MediaConnection, type MediaCoverImage, type MediaEdge, MediaFormat, type MediaListEntry, MediaListSort, MediaListStatus, MediaRelationType, MediaSeason, MediaSort, MediaStatus, type MediaTag, type MediaTitle, type MediaTrailer, MediaType, MemoryCache, type PageInfo, type PagedResult, type RateLimitOptions, RateLimiter, type Recommendation, RecommendationSort, RedisCache, type RedisCacheOptions, type RedisLikeClient, type SearchCharacterOptions, type SearchMediaOptions, type SearchStaffOptions, type SearchStudioOptions, type Staff, type StaffImage, type StaffName, type Studio, type StudioConnection, type StudioDetail, type User, type UserAvatar, type UserStatistics };
1164
+ export { type AiringSchedule, AiringSort, AniListClient, type AniListClientOptions, AniListError, type AniListHooks, type CacheAdapter, type CacheOptions, type Character, type CharacterImage, type CharacterName, CharacterRole, CharacterSort, type ExternalLink, 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, type MediaStaffConnection, type MediaStaffEdge, type MediaStats, MediaStatus, type MediaTag, type MediaTitle, type MediaTrailer, MediaType, MemoryCache, type PageInfo, type PagedResult, type RateLimitOptions, RateLimiter, type Recommendation, RecommendationSort, RedisCache, type RedisCacheOptions, type RedisLikeClient, type ScoreDistribution, type SearchCharacterOptions, type SearchMediaOptions, type SearchStaffOptions, type SearchStudioOptions, type Staff, type StaffImage, type StaffName, type StatusDistribution, type StreamingEpisode, type Studio, type StudioConnection, type StudioDetail, type User, type UserAvatar, type UserStatistics };