ani-client 1.8.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,11 +1,9 @@
1
1
  # ani-client
2
2
 
3
- ![ani-client logo](docs/public/assets/logo.png)
4
-
5
3
  [![CI](https://github.com/gonzyui/ani-client/actions/workflows/ci.yml/badge.svg)](https://github.com/gonzyui/ani-client/actions/workflows/ci.yml)
6
4
  [![npm version](https://img.shields.io/npm/v/ani-client)](https://www.npmjs.com/package/ani-client)
7
5
  [![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)
6
+ [![codecov](https://codecov.io/gh/gonzyui/ani-client/graph/badge.svg)](https://codecov.io/gh/gonzyui/ani-client)
9
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)](https://www.typescriptlang.org/)
10
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
11
9
 
package/dist/index.d.mts 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;
@@ -155,6 +155,78 @@ interface AniListClientOptions {
155
155
  logger?: Logger;
156
156
  }
157
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;
228
+ }
229
+
158
230
  declare enum StaffSort {
159
231
  ID = "ID",
160
232
  ID_DESC = "ID_DESC",
@@ -259,139 +331,6 @@ interface VoiceActor extends Pick<Staff, "id" | "image" | "gender" | "primaryOcc
259
331
  languageV2: string | null;
260
332
  }
261
333
 
262
- declare enum CharacterSort {
263
- ID = "ID",
264
- ID_DESC = "ID_DESC",
265
- ROLE = "ROLE",
266
- ROLE_DESC = "ROLE_DESC",
267
- SEARCH_MATCH = "SEARCH_MATCH",
268
- FAVOURITES = "FAVOURITES",
269
- FAVOURITES_DESC = "FAVOURITES_DESC"
270
- }
271
- declare enum CharacterRole {
272
- MAIN = "MAIN",
273
- SUPPORTING = "SUPPORTING",
274
- BACKGROUND = "BACKGROUND"
275
- }
276
- interface CharacterName {
277
- first: string | null;
278
- middle: string | null;
279
- last: string | null;
280
- full: string | null;
281
- native: string | null;
282
- alternative: string[];
283
- }
284
- interface CharacterImage {
285
- large: string | null;
286
- medium: string | null;
287
- }
288
- type CharacterMediaNode = Pick<Media, "id" | "title" | "type" | "coverImage" | "siteUrl">;
289
- interface CharacterMediaEdge {
290
- node: CharacterMediaNode;
291
- voiceActors?: VoiceActor[];
292
- }
293
- interface Character {
294
- id: number;
295
- name: CharacterName;
296
- image: CharacterImage;
297
- description: string | null;
298
- gender: string | null;
299
- dateOfBirth: FuzzyDate | null;
300
- age: string | null;
301
- bloodType: string | null;
302
- favourites: number | null;
303
- siteUrl: string | null;
304
- media: {
305
- nodes?: CharacterMediaNode[];
306
- edges?: CharacterMediaEdge[];
307
- } | null;
308
- }
309
- /** Options for including extra data when fetching a character. */
310
- interface CharacterIncludeOptions {
311
- /** Include voice actors for each media the character appears in. */
312
- voiceActors?: boolean;
313
- }
314
- interface SearchCharacterOptions {
315
- query?: string;
316
- sort?: CharacterSort[];
317
- page?: number;
318
- perPage?: number;
319
- /** Include voice actors for each media the character appears in. */
320
- voiceActors?: boolean;
321
- }
322
-
323
- declare enum MediaListStatus {
324
- CURRENT = "CURRENT",
325
- PLANNING = "PLANNING",
326
- COMPLETED = "COMPLETED",
327
- DROPPED = "DROPPED",
328
- PAUSED = "PAUSED",
329
- REPEATING = "REPEATING"
330
- }
331
- declare enum MediaListSort {
332
- MEDIA_ID = "MEDIA_ID",
333
- MEDIA_ID_DESC = "MEDIA_ID_DESC",
334
- SCORE = "SCORE",
335
- SCORE_DESC = "SCORE_DESC",
336
- STATUS = "STATUS",
337
- STATUS_DESC = "STATUS_DESC",
338
- PROGRESS = "PROGRESS",
339
- PROGRESS_DESC = "PROGRESS_DESC",
340
- PROGRESS_VOLUMES = "PROGRESS_VOLUMES",
341
- PROGRESS_VOLUMES_DESC = "PROGRESS_VOLUMES_DESC",
342
- REPEAT = "REPEAT",
343
- REPEAT_DESC = "REPEAT_DESC",
344
- PRIORITY = "PRIORITY",
345
- PRIORITY_DESC = "PRIORITY_DESC",
346
- STARTED_ON = "STARTED_ON",
347
- STARTED_ON_DESC = "STARTED_ON_DESC",
348
- FINISHED_ON = "FINISHED_ON",
349
- FINISHED_ON_DESC = "FINISHED_ON_DESC",
350
- ADDED_TIME = "ADDED_TIME",
351
- ADDED_TIME_DESC = "ADDED_TIME_DESC",
352
- UPDATED_TIME = "UPDATED_TIME",
353
- UPDATED_TIME_DESC = "UPDATED_TIME_DESC",
354
- MEDIA_TITLE_ROMAJI = "MEDIA_TITLE_ROMAJI",
355
- MEDIA_TITLE_ROMAJI_DESC = "MEDIA_TITLE_ROMAJI_DESC",
356
- MEDIA_TITLE_ENGLISH = "MEDIA_TITLE_ENGLISH",
357
- MEDIA_TITLE_ENGLISH_DESC = "MEDIA_TITLE_ENGLISH_DESC",
358
- MEDIA_TITLE_NATIVE = "MEDIA_TITLE_NATIVE",
359
- MEDIA_TITLE_NATIVE_DESC = "MEDIA_TITLE_NATIVE_DESC",
360
- MEDIA_POPULARITY = "MEDIA_POPULARITY",
361
- MEDIA_POPULARITY_DESC = "MEDIA_POPULARITY_DESC"
362
- }
363
- interface MediaListEntry {
364
- id: number;
365
- mediaId: number;
366
- status: MediaListStatus;
367
- score: number | null;
368
- progress: number | null;
369
- progressVolumes: number | null;
370
- repeat: number | null;
371
- priority: number | null;
372
- private: boolean | null;
373
- notes: string | null;
374
- startedAt: FuzzyDate | null;
375
- completedAt: FuzzyDate | null;
376
- updatedAt: number | null;
377
- createdAt: number | null;
378
- media: Media;
379
- }
380
- interface GetUserMediaListOptions {
381
- /** User ID (provide either userId or userName) */
382
- userId?: number;
383
- /** Username (provide either userId or userName) */
384
- userName?: string;
385
- /** ANIME or MANGA */
386
- type: MediaType;
387
- /** Filter by list status (CURRENT, COMPLETED, etc.) */
388
- status?: MediaListStatus;
389
- /** Sort order */
390
- sort?: MediaListSort[];
391
- page?: number;
392
- perPage?: number;
393
- }
394
-
395
334
  interface Studio {
396
335
  id: number;
397
336
  name: string;
@@ -644,6 +583,7 @@ interface MediaTag {
644
583
  description: string | null;
645
584
  category: string | null;
646
585
  rank: number | null;
586
+ isAdult: boolean | null;
647
587
  isMediaSpoiler: boolean | null;
648
588
  }
649
589
  declare enum MediaRelationType {
@@ -885,57 +825,118 @@ interface AiringSchedule {
885
825
  type DayOfWeek = "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday" | "Sunday";
886
826
  type WeeklySchedule = Record<DayOfWeek, AiringSchedule[]>;
887
827
 
888
- /** Represents a forum thread on AniList. */
889
- interface Thread {
890
- id: number;
891
- title: string;
892
- body: string | null;
893
- userId: number;
894
- replyUserId: number | null;
895
- replyCommentId: number | null;
896
- replyCount: number;
897
- viewCount: number;
898
- isLocked: boolean;
899
- isSticky: boolean;
900
- isSubscribed: boolean;
901
- repliedAt: number | null;
902
- createdAt: number;
903
- updatedAt: number;
904
- siteUrl: string | null;
905
- user: {
906
- id: number;
907
- name: string;
908
- avatar: UserAvatar;
909
- } | null;
910
- replyUser: {
911
- id: number;
912
- name: string;
913
- avatar: UserAvatar;
914
- } | null;
915
- categories: ThreadCategory[] | null;
916
- mediaCategories: ThreadMediaCategory[] | null;
917
- likes: {
918
- id: number;
919
- name: string;
920
- }[] | null;
921
- }
922
- interface ThreadCategory {
923
- id: number;
924
- name: string;
925
- }
926
- interface ThreadMediaCategory {
927
- id: number;
928
- title: MediaTitle;
929
- type: MediaType;
930
- coverImage: Pick<MediaCoverImage, "large" | "medium"> | null;
931
- siteUrl: string | null;
932
- }
933
- /** Sort options for thread queries. */
934
- declare enum ThreadSort {
828
+ declare enum CharacterSort {
935
829
  ID = "ID",
936
830
  ID_DESC = "ID_DESC",
937
- TITLE = "TITLE",
938
- TITLE_DESC = "TITLE_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: {
919
+ id: number;
920
+ name: string;
921
+ }[] | null;
922
+ }
923
+ interface ThreadCategory {
924
+ id: number;
925
+ name: string;
926
+ }
927
+ interface ThreadMediaCategory {
928
+ id: number;
929
+ title: MediaTitle;
930
+ type: MediaType;
931
+ coverImage: Pick<MediaCoverImage, "large" | "medium"> | null;
932
+ siteUrl: string | null;
933
+ }
934
+ /** Sort options for thread queries. */
935
+ declare enum ThreadSort {
936
+ ID = "ID",
937
+ ID_DESC = "ID_DESC",
938
+ TITLE = "TITLE",
939
+ TITLE_DESC = "TITLE_DESC",
939
940
  CREATED_AT = "CREATED_AT",
940
941
  CREATED_AT_DESC = "CREATED_AT_DESC",
941
942
  UPDATED_AT = "UPDATED_AT",
@@ -965,6 +966,145 @@ interface SearchThreadOptions {
965
966
  perPage?: number;
966
967
  }
967
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
+
968
1108
  /**
969
1109
  * Lightweight AniList GraphQL client with built-in caching and rate limiting.
970
1110
  *
@@ -1167,142 +1307,6 @@ declare class AniListError extends Error {
1167
1307
  constructor(message: string, status: number, errors?: unknown[]);
1168
1308
  }
1169
1309
 
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
- }
1181
- declare class MemoryCache implements CacheAdapter {
1182
- private readonly ttl;
1183
- private readonly maxSize;
1184
- private readonly enabled;
1185
- private readonly swrMs;
1186
- private readonly store;
1187
- private _hits;
1188
- private _misses;
1189
- private _stales;
1190
- constructor(options?: CacheOptions);
1191
- /** Build a deterministic cache key from a query + variables pair. */
1192
- static key(query: string, variables: Record<string, unknown>): string;
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
- */
1198
- get<T>(key: string): T | undefined;
1199
- /** Store a value in the cache. */
1200
- set<T>(key: string, data: T): void;
1201
- /** Remove a specific entry. */
1202
- delete(key: string): boolean;
1203
- /** Clear the entire cache and reset statistics. */
1204
- clear(): void;
1205
- /** Number of entries currently stored. */
1206
- get size(): number | Promise<number>;
1207
- /** Return all cache keys. */
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;
1223
- /**
1224
- * Remove all entries whose key matches the given pattern.
1225
- *
1226
- * - **String**: treated as a substring match (e.g. `"Media"` removes all keys containing `"Media"`).
1227
- * - **RegExp**: tested against each key directly.
1228
- *
1229
- * @param pattern — A string (substring match) or RegExp.
1230
- * @returns Number of entries removed.
1231
- */
1232
- invalidate(pattern: string | RegExp): number;
1233
- }
1234
-
1235
- /**
1236
- * Minimal interface representing a Redis client.
1237
- * Compatible with both `ioredis` and `redis` (node-redis v4+).
1238
- */
1239
- interface RedisLikeClient {
1240
- get(key: string): Promise<string | null>;
1241
- set(key: string, value: string, ...args: unknown[]): Promise<unknown>;
1242
- del(...keys: (string | string[])[]): Promise<number>;
1243
- keys(pattern: string): Promise<string[]>;
1244
- /** Optional SCAN-based iteration — used when available to avoid blocking the server. */
1245
- scanIterator?(options: {
1246
- MATCH: string;
1247
- COUNT?: number;
1248
- }): AsyncIterable<string>;
1249
- }
1250
- interface RedisCacheOptions {
1251
- /** A Redis client instance (ioredis or node-redis). */
1252
- client: RedisLikeClient;
1253
- /** Key prefix to namespace ani-client entries (default: `"ani:"`) */
1254
- prefix?: string;
1255
- /** TTL in seconds (default: 86 400 = 24 h) */
1256
- ttl?: number;
1257
- }
1258
- /**
1259
- * Redis-backed cache adapter for AniListClient.
1260
- *
1261
- * @example
1262
- * ```ts
1263
- * import Redis from "ioredis";
1264
- * import { AniListClient, RedisCache } from "ani-client";
1265
- *
1266
- * const redis = new Redis();
1267
- * const client = new AniListClient({
1268
- * cacheAdapter: new RedisCache({ client: redis }),
1269
- * });
1270
- * ```
1271
- */
1272
- declare class RedisCache implements CacheAdapter {
1273
- private readonly client;
1274
- private readonly prefix;
1275
- private readonly ttl;
1276
- constructor(options: RedisCacheOptions);
1277
- private prefixedKey;
1278
- get<T>(key: string): Promise<T | undefined>;
1279
- set<T>(key: string, data: T): Promise<void>;
1280
- delete(key: string): Promise<boolean>;
1281
- /**
1282
- * Collect keys matching a pattern. Uses SCAN when available, falls back to KEYS.
1283
- *
1284
- * **Warning:** The `KEYS` fallback is O(N) and blocks the Redis server.
1285
- * Provide a client with `scanIterator` support for production use.
1286
- * @internal
1287
- */
1288
- private collectKeys;
1289
- clear(): Promise<void>;
1290
- /**
1291
- * Get the actual number of keys with this prefix in Redis.
1292
- */
1293
- get size(): Promise<number>;
1294
- /** @internal */
1295
- private getSize;
1296
- keys(): Promise<string[]>;
1297
- /**
1298
- * Remove all entries whose key matches the given glob pattern.
1299
- *
1300
- * @param pattern — A glob pattern (e.g. `"*Media*"`)
1301
- * @returns Number of entries removed.
1302
- */
1303
- invalidate(pattern: string | RegExp): Promise<number>;
1304
- }
1305
-
1306
1310
  /**
1307
1311
  * Rate limiter with automatic retry for AniList API.
1308
1312
  *