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/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  // src/queries/index.ts
4
- var MEDIA_FIELDS = `
4
+ var MEDIA_FIELDS_BASE = `
5
5
  id
6
6
  idMal
7
7
  title { romaji english native userPreferred }
@@ -33,6 +33,10 @@ var MEDIA_FIELDS = `
33
33
  trending
34
34
  tags { id name description category rank isMediaSpoiler }
35
35
  studios { nodes { id name isAnimationStudio siteUrl } }
36
+ isAdult
37
+ siteUrl
38
+ `;
39
+ var RELATIONS_FIELDS = `
36
40
  relations {
37
41
  edges {
38
42
  relationType(version: 2)
@@ -47,10 +51,12 @@ var MEDIA_FIELDS = `
47
51
  }
48
52
  }
49
53
  }
50
- isAdult
51
- siteUrl
52
54
  `;
53
- var CHARACTER_FIELDS = `
55
+ var MEDIA_FIELDS = `
56
+ ${MEDIA_FIELDS_BASE}
57
+ ${RELATIONS_FIELDS}
58
+ `;
59
+ var CHARACTER_FIELDS_COMPACT = `
54
60
  id
55
61
  name { first middle last full native alternative }
56
62
  image { large medium }
@@ -61,6 +67,9 @@ var CHARACTER_FIELDS = `
61
67
  bloodType
62
68
  favourites
63
69
  siteUrl
70
+ `;
71
+ var CHARACTER_FIELDS = `
72
+ ${CHARACTER_FIELDS_COMPACT}
64
73
  media(perPage: 10) {
65
74
  nodes {
66
75
  id
@@ -140,7 +149,7 @@ query (
140
149
  isAdult: $isAdult,
141
150
  sort: $sort
142
151
  ) {
143
- ${MEDIA_FIELDS}
152
+ ${MEDIA_FIELDS_BASE}
144
153
  }
145
154
  }
146
155
  }`;
@@ -149,7 +158,7 @@ query ($type: MediaType, $page: Int, $perPage: Int) {
149
158
  Page(page: $page, perPage: $perPage) {
150
159
  pageInfo { total perPage currentPage lastPage hasNextPage }
151
160
  media(type: $type, sort: TRENDING_DESC) {
152
- ${MEDIA_FIELDS}
161
+ ${MEDIA_FIELDS_BASE}
153
162
  }
154
163
  }
155
164
  }`;
@@ -175,10 +184,10 @@ query ($id: Int!) {
175
184
  }
176
185
  }`;
177
186
  var QUERY_STAFF_SEARCH = `
178
- query ($search: String, $page: Int, $perPage: Int) {
187
+ query ($search: String, $sort: [CharacterSort], $page: Int, $perPage: Int) {
179
188
  Page(page: $page, perPage: $perPage) {
180
189
  pageInfo { total perPage currentPage lastPage hasNextPage }
181
- staff(search: $search) {
190
+ staff(search: $search, sort: $sort) {
182
191
  ${STAFF_FIELDS}
183
192
  }
184
193
  }
@@ -206,7 +215,7 @@ query ($airingAt_greater: Int, $airingAt_lesser: Int, $sort: [AiringSort], $page
206
215
  episode
207
216
  mediaId
208
217
  media {
209
- ${MEDIA_FIELDS}
218
+ ${MEDIA_FIELDS_BASE}
210
219
  }
211
220
  }
212
221
  }
@@ -216,7 +225,7 @@ query ($page: Int, $perPage: Int) {
216
225
  Page(page: $page, perPage: $perPage) {
217
226
  pageInfo { total perPage currentPage lastPage hasNextPage }
218
227
  media(type: MANGA, status: RELEASING, sort: UPDATED_AT_DESC) {
219
- ${MEDIA_FIELDS}
228
+ ${MEDIA_FIELDS_BASE}
220
229
  }
221
230
  }
222
231
  }`;
@@ -225,7 +234,7 @@ query ($type: MediaType, $sort: [MediaSort], $page: Int, $perPage: Int) {
225
234
  Page(page: $page, perPage: $perPage) {
226
235
  pageInfo { total perPage currentPage lastPage hasNextPage }
227
236
  media(type: $type, status: NOT_YET_RELEASED, sort: $sort) {
228
- ${MEDIA_FIELDS}
237
+ ${MEDIA_FIELDS_BASE}
229
238
  }
230
239
  }
231
240
  }`;
@@ -234,7 +243,7 @@ query ($season: MediaSeason!, $seasonYear: Int!, $type: MediaType, $sort: [Media
234
243
  Page(page: $page, perPage: $perPage) {
235
244
  pageInfo { total perPage currentPage lastPage hasNextPage }
236
245
  media(season: $season, seasonYear: $seasonYear, type: $type, sort: $sort) {
237
- ${MEDIA_FIELDS}
246
+ ${MEDIA_FIELDS_BASE}
238
247
  }
239
248
  }
240
249
  }`;
@@ -254,7 +263,7 @@ var MEDIA_LIST_FIELDS = `
254
263
  updatedAt
255
264
  createdAt
256
265
  media {
257
- ${MEDIA_FIELDS}
266
+ ${MEDIA_FIELDS_BASE}
258
267
  }
259
268
  `;
260
269
  var QUERY_RECOMMENDATIONS = `
@@ -347,13 +356,101 @@ query {
347
356
  isAdult
348
357
  }
349
358
  }`;
359
+ function buildMediaByIdQuery(include) {
360
+ if (!include) return QUERY_MEDIA_BY_ID;
361
+ const extra = [];
362
+ if (include.relations !== false) {
363
+ extra.push(RELATIONS_FIELDS);
364
+ }
365
+ if (include.characters) {
366
+ const opts = typeof include.characters === "object" ? include.characters : {};
367
+ const perPage = opts.perPage ?? 25;
368
+ const sortClause = opts.sort !== false ? ", sort: [ROLE, RELEVANCE, ID]" : "";
369
+ extra.push(`
370
+ characters(perPage: ${perPage}${sortClause}) {
371
+ edges {
372
+ role
373
+ node {
374
+ ${CHARACTER_FIELDS_COMPACT}
375
+ }
376
+ }
377
+ }`);
378
+ }
379
+ if (include.staff) {
380
+ const opts = typeof include.staff === "object" ? include.staff : {};
381
+ const perPage = opts.perPage ?? 25;
382
+ const sortClause = opts.sort !== false ? ", sort: [RELEVANCE, ID]" : "";
383
+ extra.push(`
384
+ staff(perPage: ${perPage}${sortClause}) {
385
+ edges {
386
+ role
387
+ node {
388
+ ${STAFF_FIELDS}
389
+ }
390
+ }
391
+ }`);
392
+ }
393
+ if (include.recommendations) {
394
+ const perPage = typeof include.recommendations === "object" ? include.recommendations.perPage ?? 10 : 10;
395
+ extra.push(`
396
+ recommendations(perPage: ${perPage}, sort: [RATING_DESC]) {
397
+ nodes {
398
+ id
399
+ rating
400
+ mediaRecommendation {
401
+ id
402
+ title { romaji english native userPreferred }
403
+ type
404
+ format
405
+ coverImage { large medium }
406
+ averageScore
407
+ siteUrl
408
+ }
409
+ }
410
+ }`);
411
+ }
412
+ if (include.streamingEpisodes) {
413
+ extra.push(`
414
+ streamingEpisodes {
415
+ title
416
+ thumbnail
417
+ url
418
+ site
419
+ }`);
420
+ }
421
+ if (include.externalLinks) {
422
+ extra.push(`
423
+ externalLinks {
424
+ id
425
+ url
426
+ site
427
+ type
428
+ icon
429
+ color
430
+ }`);
431
+ }
432
+ if (include.stats) {
433
+ extra.push(`
434
+ stats {
435
+ scoreDistribution { score amount }
436
+ statusDistribution { status amount }
437
+ }`);
438
+ }
439
+ return `
440
+ query ($id: Int!) {
441
+ Media(id: $id) {
442
+ ${MEDIA_FIELDS_BASE}
443
+ ${extra.join("\n")}
444
+ }
445
+ }`;
446
+ }
350
447
  function buildBatchQuery(ids, typeName, fields, prefix) {
351
448
  const aliases = ids.map((id, i) => `${prefix}${i}: ${typeName}(id: ${id}) { ${fields} }`).join("\n ");
352
449
  return `query {
353
450
  ${aliases}
354
451
  }`;
355
452
  }
356
- var buildBatchMediaQuery = (ids) => buildBatchQuery(ids, "Media", MEDIA_FIELDS, "m");
453
+ var buildBatchMediaQuery = (ids) => buildBatchQuery(ids, "Media", MEDIA_FIELDS_BASE, "m");
357
454
  var buildBatchCharacterQuery = (ids) => buildBatchQuery(ids, "Character", CHARACTER_FIELDS, "c");
358
455
  var buildBatchStaffQuery = (ids) => buildBatchQuery(ids, "Staff", STAFF_FIELDS, "s");
359
456
 
@@ -368,7 +465,8 @@ var MemoryCache = class {
368
465
  }
369
466
  /** Build a deterministic cache key from a query + variables pair. */
370
467
  static key(query, variables) {
371
- return `${query.trim()}|${JSON.stringify(variables, Object.keys(variables).sort())}`;
468
+ const normalized = query.replace(/\s+/g, " ").trim();
469
+ return `${normalized}|${JSON.stringify(variables, Object.keys(variables).sort())}`;
372
470
  }
373
471
  /** Retrieve a cached value, or `undefined` if missing / expired. */
374
472
  get(key) {
@@ -405,9 +503,9 @@ var MemoryCache = class {
405
503
  get size() {
406
504
  return this.store.size;
407
505
  }
408
- /** Return an iterator over all cache keys. */
506
+ /** Return all cache keys. */
409
507
  keys() {
410
- return this.store.keys();
508
+ return [...this.store.keys()];
411
509
  }
412
510
  /**
413
511
  * Remove all entries whose key matches the given pattern.
@@ -416,25 +514,26 @@ var MemoryCache = class {
416
514
  * @returns Number of entries removed.
417
515
  */
418
516
  invalidate(pattern) {
419
- const regex = typeof pattern === "string" ? new RegExp(pattern) : pattern;
420
- let count = 0;
421
- for (const key of [...this.store.keys()]) {
422
- if (regex.test(key)) {
423
- this.store.delete(key);
424
- count++;
425
- }
517
+ const regex = typeof pattern === "string" ? new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")) : pattern;
518
+ const toDelete = [];
519
+ for (const key of this.store.keys()) {
520
+ if (regex.test(key)) toDelete.push(key);
426
521
  }
427
- return count;
522
+ for (const key of toDelete) this.store.delete(key);
523
+ return toDelete.length;
428
524
  }
429
525
  };
430
526
 
431
527
  // src/errors/index.ts
432
- var AniListError = class extends Error {
528
+ var AniListError = class _AniListError extends Error {
433
529
  constructor(message, status, errors = []) {
434
530
  super(message);
435
531
  this.name = "AniListError";
436
532
  this.status = status;
437
533
  this.errors = errors;
534
+ if (Error.captureStackTrace) {
535
+ Error.captureStackTrace(this, _AniListError);
536
+ }
438
537
  }
439
538
  };
440
539
 
@@ -533,6 +632,169 @@ function isNetworkError(err) {
533
632
  return false;
534
633
  }
535
634
 
635
+ // src/types/index.ts
636
+ var MediaType = /* @__PURE__ */ ((MediaType2) => {
637
+ MediaType2["ANIME"] = "ANIME";
638
+ MediaType2["MANGA"] = "MANGA";
639
+ return MediaType2;
640
+ })(MediaType || {});
641
+ var MediaFormat = /* @__PURE__ */ ((MediaFormat2) => {
642
+ MediaFormat2["TV"] = "TV";
643
+ MediaFormat2["TV_SHORT"] = "TV_SHORT";
644
+ MediaFormat2["MOVIE"] = "MOVIE";
645
+ MediaFormat2["SPECIAL"] = "SPECIAL";
646
+ MediaFormat2["OVA"] = "OVA";
647
+ MediaFormat2["ONA"] = "ONA";
648
+ MediaFormat2["MUSIC"] = "MUSIC";
649
+ MediaFormat2["MANGA"] = "MANGA";
650
+ MediaFormat2["NOVEL"] = "NOVEL";
651
+ MediaFormat2["ONE_SHOT"] = "ONE_SHOT";
652
+ return MediaFormat2;
653
+ })(MediaFormat || {});
654
+ var MediaStatus = /* @__PURE__ */ ((MediaStatus2) => {
655
+ MediaStatus2["FINISHED"] = "FINISHED";
656
+ MediaStatus2["RELEASING"] = "RELEASING";
657
+ MediaStatus2["NOT_YET_RELEASED"] = "NOT_YET_RELEASED";
658
+ MediaStatus2["CANCELLED"] = "CANCELLED";
659
+ MediaStatus2["HIATUS"] = "HIATUS";
660
+ return MediaStatus2;
661
+ })(MediaStatus || {});
662
+ var MediaSeason = /* @__PURE__ */ ((MediaSeason2) => {
663
+ MediaSeason2["WINTER"] = "WINTER";
664
+ MediaSeason2["SPRING"] = "SPRING";
665
+ MediaSeason2["SUMMER"] = "SUMMER";
666
+ MediaSeason2["FALL"] = "FALL";
667
+ return MediaSeason2;
668
+ })(MediaSeason || {});
669
+ var MediaSort = /* @__PURE__ */ ((MediaSort2) => {
670
+ MediaSort2["ID"] = "ID";
671
+ MediaSort2["ID_DESC"] = "ID_DESC";
672
+ MediaSort2["TITLE_ROMAJI"] = "TITLE_ROMAJI";
673
+ MediaSort2["TITLE_ROMAJI_DESC"] = "TITLE_ROMAJI_DESC";
674
+ MediaSort2["TITLE_ENGLISH"] = "TITLE_ENGLISH";
675
+ MediaSort2["TITLE_ENGLISH_DESC"] = "TITLE_ENGLISH_DESC";
676
+ MediaSort2["TITLE_NATIVE"] = "TITLE_NATIVE";
677
+ MediaSort2["TITLE_NATIVE_DESC"] = "TITLE_NATIVE_DESC";
678
+ MediaSort2["TYPE"] = "TYPE";
679
+ MediaSort2["TYPE_DESC"] = "TYPE_DESC";
680
+ MediaSort2["FORMAT"] = "FORMAT";
681
+ MediaSort2["FORMAT_DESC"] = "FORMAT_DESC";
682
+ MediaSort2["START_DATE"] = "START_DATE";
683
+ MediaSort2["START_DATE_DESC"] = "START_DATE_DESC";
684
+ MediaSort2["END_DATE"] = "END_DATE";
685
+ MediaSort2["END_DATE_DESC"] = "END_DATE_DESC";
686
+ MediaSort2["SCORE"] = "SCORE";
687
+ MediaSort2["SCORE_DESC"] = "SCORE_DESC";
688
+ MediaSort2["POPULARITY"] = "POPULARITY";
689
+ MediaSort2["POPULARITY_DESC"] = "POPULARITY_DESC";
690
+ MediaSort2["TRENDING"] = "TRENDING";
691
+ MediaSort2["TRENDING_DESC"] = "TRENDING_DESC";
692
+ MediaSort2["EPISODES"] = "EPISODES";
693
+ MediaSort2["EPISODES_DESC"] = "EPISODES_DESC";
694
+ MediaSort2["DURATION"] = "DURATION";
695
+ MediaSort2["DURATION_DESC"] = "DURATION_DESC";
696
+ MediaSort2["STATUS"] = "STATUS";
697
+ MediaSort2["STATUS_DESC"] = "STATUS_DESC";
698
+ MediaSort2["FAVOURITES"] = "FAVOURITES";
699
+ MediaSort2["FAVOURITES_DESC"] = "FAVOURITES_DESC";
700
+ MediaSort2["UPDATED_AT"] = "UPDATED_AT";
701
+ MediaSort2["UPDATED_AT_DESC"] = "UPDATED_AT_DESC";
702
+ MediaSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
703
+ return MediaSort2;
704
+ })(MediaSort || {});
705
+ var AiringSort = /* @__PURE__ */ ((AiringSort2) => {
706
+ AiringSort2["ID"] = "ID";
707
+ AiringSort2["ID_DESC"] = "ID_DESC";
708
+ AiringSort2["MEDIA_ID"] = "MEDIA_ID";
709
+ AiringSort2["MEDIA_ID_DESC"] = "MEDIA_ID_DESC";
710
+ AiringSort2["TIME"] = "TIME";
711
+ AiringSort2["TIME_DESC"] = "TIME_DESC";
712
+ AiringSort2["EPISODE"] = "EPISODE";
713
+ AiringSort2["EPISODE_DESC"] = "EPISODE_DESC";
714
+ return AiringSort2;
715
+ })(AiringSort || {});
716
+ var CharacterSort = /* @__PURE__ */ ((CharacterSort2) => {
717
+ CharacterSort2["ID"] = "ID";
718
+ CharacterSort2["ID_DESC"] = "ID_DESC";
719
+ CharacterSort2["ROLE"] = "ROLE";
720
+ CharacterSort2["ROLE_DESC"] = "ROLE_DESC";
721
+ CharacterSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
722
+ CharacterSort2["FAVOURITES"] = "FAVOURITES";
723
+ CharacterSort2["FAVOURITES_DESC"] = "FAVOURITES_DESC";
724
+ return CharacterSort2;
725
+ })(CharacterSort || {});
726
+ var CharacterRole = /* @__PURE__ */ ((CharacterRole2) => {
727
+ CharacterRole2["MAIN"] = "MAIN";
728
+ CharacterRole2["SUPPORTING"] = "SUPPORTING";
729
+ CharacterRole2["BACKGROUND"] = "BACKGROUND";
730
+ return CharacterRole2;
731
+ })(CharacterRole || {});
732
+ var MediaRelationType = /* @__PURE__ */ ((MediaRelationType2) => {
733
+ MediaRelationType2["ADAPTATION"] = "ADAPTATION";
734
+ MediaRelationType2["PREQUEL"] = "PREQUEL";
735
+ MediaRelationType2["SEQUEL"] = "SEQUEL";
736
+ MediaRelationType2["PARENT"] = "PARENT";
737
+ MediaRelationType2["SIDE_STORY"] = "SIDE_STORY";
738
+ MediaRelationType2["CHARACTER"] = "CHARACTER";
739
+ MediaRelationType2["SUMMARY"] = "SUMMARY";
740
+ MediaRelationType2["ALTERNATIVE"] = "ALTERNATIVE";
741
+ MediaRelationType2["SPIN_OFF"] = "SPIN_OFF";
742
+ MediaRelationType2["OTHER"] = "OTHER";
743
+ MediaRelationType2["SOURCE"] = "SOURCE";
744
+ MediaRelationType2["COMPILATION"] = "COMPILATION";
745
+ MediaRelationType2["CONTAINS"] = "CONTAINS";
746
+ return MediaRelationType2;
747
+ })(MediaRelationType || {});
748
+ var RecommendationSort = /* @__PURE__ */ ((RecommendationSort2) => {
749
+ RecommendationSort2["ID"] = "ID";
750
+ RecommendationSort2["ID_DESC"] = "ID_DESC";
751
+ RecommendationSort2["RATING"] = "RATING";
752
+ RecommendationSort2["RATING_DESC"] = "RATING_DESC";
753
+ return RecommendationSort2;
754
+ })(RecommendationSort || {});
755
+ var MediaListStatus = /* @__PURE__ */ ((MediaListStatus2) => {
756
+ MediaListStatus2["CURRENT"] = "CURRENT";
757
+ MediaListStatus2["PLANNING"] = "PLANNING";
758
+ MediaListStatus2["COMPLETED"] = "COMPLETED";
759
+ MediaListStatus2["DROPPED"] = "DROPPED";
760
+ MediaListStatus2["PAUSED"] = "PAUSED";
761
+ MediaListStatus2["REPEATING"] = "REPEATING";
762
+ return MediaListStatus2;
763
+ })(MediaListStatus || {});
764
+ var MediaListSort = /* @__PURE__ */ ((MediaListSort2) => {
765
+ MediaListSort2["MEDIA_ID"] = "MEDIA_ID";
766
+ MediaListSort2["MEDIA_ID_DESC"] = "MEDIA_ID_DESC";
767
+ MediaListSort2["SCORE"] = "SCORE";
768
+ MediaListSort2["SCORE_DESC"] = "SCORE_DESC";
769
+ MediaListSort2["STATUS"] = "STATUS";
770
+ MediaListSort2["STATUS_DESC"] = "STATUS_DESC";
771
+ MediaListSort2["PROGRESS"] = "PROGRESS";
772
+ MediaListSort2["PROGRESS_DESC"] = "PROGRESS_DESC";
773
+ MediaListSort2["PROGRESS_VOLUMES"] = "PROGRESS_VOLUMES";
774
+ MediaListSort2["PROGRESS_VOLUMES_DESC"] = "PROGRESS_VOLUMES_DESC";
775
+ MediaListSort2["REPEAT"] = "REPEAT";
776
+ MediaListSort2["REPEAT_DESC"] = "REPEAT_DESC";
777
+ MediaListSort2["PRIORITY"] = "PRIORITY";
778
+ MediaListSort2["PRIORITY_DESC"] = "PRIORITY_DESC";
779
+ MediaListSort2["STARTED_ON"] = "STARTED_ON";
780
+ MediaListSort2["STARTED_ON_DESC"] = "STARTED_ON_DESC";
781
+ MediaListSort2["FINISHED_ON"] = "FINISHED_ON";
782
+ MediaListSort2["FINISHED_ON_DESC"] = "FINISHED_ON_DESC";
783
+ MediaListSort2["ADDED_TIME"] = "ADDED_TIME";
784
+ MediaListSort2["ADDED_TIME_DESC"] = "ADDED_TIME_DESC";
785
+ MediaListSort2["UPDATED_TIME"] = "UPDATED_TIME";
786
+ MediaListSort2["UPDATED_TIME_DESC"] = "UPDATED_TIME_DESC";
787
+ MediaListSort2["MEDIA_TITLE_ROMAJI"] = "MEDIA_TITLE_ROMAJI";
788
+ MediaListSort2["MEDIA_TITLE_ROMAJI_DESC"] = "MEDIA_TITLE_ROMAJI_DESC";
789
+ MediaListSort2["MEDIA_TITLE_ENGLISH"] = "MEDIA_TITLE_ENGLISH";
790
+ MediaListSort2["MEDIA_TITLE_ENGLISH_DESC"] = "MEDIA_TITLE_ENGLISH_DESC";
791
+ MediaListSort2["MEDIA_TITLE_NATIVE"] = "MEDIA_TITLE_NATIVE";
792
+ MediaListSort2["MEDIA_TITLE_NATIVE_DESC"] = "MEDIA_TITLE_NATIVE_DESC";
793
+ MediaListSort2["MEDIA_POPULARITY"] = "MEDIA_POPULARITY";
794
+ MediaListSort2["MEDIA_POPULARITY_DESC"] = "MEDIA_POPULARITY_DESC";
795
+ return MediaListSort2;
796
+ })(MediaListSort || {});
797
+
536
798
  // src/client/index.ts
537
799
  var DEFAULT_API_URL = "https://graphql.anilist.co";
538
800
  var AniListClient = class {
@@ -600,16 +862,54 @@ var AniListClient = class {
600
862
  */
601
863
  async pagedRequest(query, variables, field) {
602
864
  const data = await this.request(query, variables);
603
- return { pageInfo: data.Page.pageInfo, results: data.Page[field] };
865
+ const results = data.Page[field];
866
+ if (!Array.isArray(results)) {
867
+ throw new AniListError(`Unexpected response: missing field "${field}" in Page`, 0, []);
868
+ }
869
+ return { pageInfo: data.Page.pageInfo, results };
870
+ }
871
+ /**
872
+ * @internal
873
+ * Clamp perPage to AniList's maximum of 50.
874
+ */
875
+ clampPerPage(value) {
876
+ return Math.min(Math.max(value, 1), 50);
604
877
  }
605
878
  /**
606
879
  * Fetch a single media entry by its AniList ID.
607
880
  *
881
+ * Optionally include related data (characters, staff, relations, etc.) via the `include` parameter.
882
+ *
608
883
  * @param id - The AniList media ID
884
+ * @param include - Optional related data to include
609
885
  * @returns The media object
886
+ *
887
+ * @example
888
+ * ```ts
889
+ * // Basic usage — same as before (includes relations by default)
890
+ * const anime = await client.getMedia(1);
891
+ *
892
+ * // Include characters sorted by role, 25 results
893
+ * const anime = await client.getMedia(1, { characters: true });
894
+ *
895
+ * // Full control
896
+ * const anime = await client.getMedia(1, {
897
+ * characters: { perPage: 50, sort: true },
898
+ * staff: true,
899
+ * relations: true,
900
+ * streamingEpisodes: true,
901
+ * externalLinks: true,
902
+ * stats: true,
903
+ * recommendations: { perPage: 5 },
904
+ * });
905
+ *
906
+ * // Exclude relations for a lighter response
907
+ * const anime = await client.getMedia(1, { characters: true, relations: false });
908
+ * ```
610
909
  */
611
- async getMedia(id) {
612
- const data = await this.request(QUERY_MEDIA_BY_ID, { id });
910
+ async getMedia(id, include) {
911
+ const query = include ? buildMediaByIdQuery(include) : QUERY_MEDIA_BY_ID;
912
+ const data = await this.request(query, { id });
613
913
  return data.Media;
614
914
  }
615
915
  /**
@@ -629,7 +929,7 @@ var AniListClient = class {
629
929
  */
630
930
  async searchMedia(options = {}) {
631
931
  const { query: search, page = 1, perPage = 20, ...filters } = options;
632
- return this.pagedRequest(QUERY_MEDIA_SEARCH, { search, ...filters, page, perPage }, "media");
932
+ return this.pagedRequest(QUERY_MEDIA_SEARCH, { search, ...filters, page, perPage: this.clampPerPage(perPage) }, "media");
633
933
  }
634
934
  /**
635
935
  * Get currently trending anime or manga.
@@ -638,11 +938,20 @@ var AniListClient = class {
638
938
  * @param page - Page number (default 1)
639
939
  * @param perPage - Results per page (default 20, max 50)
640
940
  */
641
- async getTrending(type = "ANIME", page = 1, perPage = 20) {
642
- return this.pagedRequest(QUERY_TRENDING, { type, page, perPage }, "media");
941
+ async getTrending(type = "ANIME" /* ANIME */, page = 1, perPage = 20) {
942
+ return this.pagedRequest(QUERY_TRENDING, { type, page, perPage: this.clampPerPage(perPage) }, "media");
643
943
  }
644
944
  /**
645
945
  * Fetch a character by AniList ID.
946
+ *
947
+ * @param id - The AniList character ID
948
+ * @returns The character object
949
+ *
950
+ * @example
951
+ * ```ts
952
+ * const spike = await client.getCharacter(1);
953
+ * console.log(spike.name.full); // "Spike Spiegel"
954
+ * ```
646
955
  */
647
956
  async getCharacter(id) {
648
957
  const data = await this.request(QUERY_CHARACTER_BY_ID, { id });
@@ -650,13 +959,30 @@ var AniListClient = class {
650
959
  }
651
960
  /**
652
961
  * Search for characters by name.
962
+ *
963
+ * @param options - Search / pagination parameters
964
+ * @returns Paginated results with matching characters
965
+ *
966
+ * @example
967
+ * ```ts
968
+ * const result = await client.searchCharacters({ query: "Luffy", perPage: 5 });
969
+ * ```
653
970
  */
654
971
  async searchCharacters(options = {}) {
655
972
  const { query: search, page = 1, perPage = 20, ...rest } = options;
656
- return this.pagedRequest(QUERY_CHARACTER_SEARCH, { search, ...rest, page, perPage }, "characters");
973
+ return this.pagedRequest(QUERY_CHARACTER_SEARCH, { search, ...rest, page, perPage: this.clampPerPage(perPage) }, "characters");
657
974
  }
658
975
  /**
659
976
  * Fetch a staff member by AniList ID.
977
+ *
978
+ * @param id - The AniList staff ID
979
+ * @returns The staff object
980
+ *
981
+ * @example
982
+ * ```ts
983
+ * const staff = await client.getStaff(95001);
984
+ * console.log(staff.name.full);
985
+ * ```
660
986
  */
661
987
  async getStaff(id) {
662
988
  const data = await this.request(QUERY_STAFF_BY_ID, { id });
@@ -664,13 +990,30 @@ var AniListClient = class {
664
990
  }
665
991
  /**
666
992
  * Search for staff (voice actors, directors, etc.).
993
+ *
994
+ * @param options - Search / pagination parameters
995
+ * @returns Paginated results with matching staff
996
+ *
997
+ * @example
998
+ * ```ts
999
+ * const result = await client.searchStaff({ query: "Miyazaki", perPage: 5 });
1000
+ * ```
667
1001
  */
668
1002
  async searchStaff(options = {}) {
669
- const { query: search, page = 1, perPage = 20 } = options;
670
- return this.pagedRequest(QUERY_STAFF_SEARCH, { search, page, perPage }, "staff");
1003
+ const { query: search, page = 1, perPage = 20, sort } = options;
1004
+ return this.pagedRequest(QUERY_STAFF_SEARCH, { search, sort, page, perPage: this.clampPerPage(perPage) }, "staff");
671
1005
  }
672
1006
  /**
673
1007
  * Fetch a user by AniList ID.
1008
+ *
1009
+ * @param id - The AniList user ID
1010
+ * @returns The user object
1011
+ *
1012
+ * @example
1013
+ * ```ts
1014
+ * const user = await client.getUser(1);
1015
+ * console.log(user.name);
1016
+ * ```
674
1017
  */
675
1018
  async getUser(id) {
676
1019
  const data = await this.request(QUERY_USER_BY_ID, { id });
@@ -678,6 +1021,15 @@ var AniListClient = class {
678
1021
  }
679
1022
  /**
680
1023
  * Fetch a user by username.
1024
+ *
1025
+ * @param name - The AniList username
1026
+ * @returns The user object
1027
+ *
1028
+ * @example
1029
+ * ```ts
1030
+ * const user = await client.getUserByName("AniList");
1031
+ * console.log(user.statistics);
1032
+ * ```
681
1033
  */
682
1034
  async getUserByName(name) {
683
1035
  const data = await this.request(QUERY_USER_BY_NAME, { name });
@@ -716,7 +1068,7 @@ var AniListClient = class {
716
1068
  airingAt_lesser: options.airingAtLesser ?? now,
717
1069
  sort: options.sort ?? ["TIME_DESC"],
718
1070
  page: options.page ?? 1,
719
- perPage: options.perPage ?? 20
1071
+ perPage: this.clampPerPage(options.perPage ?? 20)
720
1072
  };
721
1073
  return this.pagedRequest(QUERY_AIRING_SCHEDULE, variables, "airingSchedules");
722
1074
  }
@@ -739,7 +1091,7 @@ var AniListClient = class {
739
1091
  QUERY_RECENT_CHAPTERS,
740
1092
  {
741
1093
  page: options.page ?? 1,
742
- perPage: options.perPage ?? 20
1094
+ perPage: this.clampPerPage(options.perPage ?? 20)
743
1095
  },
744
1096
  "media"
745
1097
  );
@@ -765,7 +1117,7 @@ var AniListClient = class {
765
1117
  type: options.type,
766
1118
  sort: options.sort ?? ["POPULARITY_DESC"],
767
1119
  page: options.page ?? 1,
768
- perPage: options.perPage ?? 20
1120
+ perPage: this.clampPerPage(options.perPage ?? 20)
769
1121
  },
770
1122
  "media"
771
1123
  );
@@ -827,7 +1179,7 @@ var AniListClient = class {
827
1179
  type: options.type ?? "ANIME",
828
1180
  sort: options.sort ?? ["POPULARITY_DESC"],
829
1181
  page: options.page ?? 1,
830
- perPage: options.perPage ?? 20
1182
+ perPage: this.clampPerPage(options.perPage ?? 20)
831
1183
  },
832
1184
  "media"
833
1185
  );
@@ -869,7 +1221,7 @@ var AniListClient = class {
869
1221
  status: options.status,
870
1222
  sort: options.sort,
871
1223
  page: options.page ?? 1,
872
- perPage: options.perPage ?? 20
1224
+ perPage: this.clampPerPage(options.perPage ?? 20)
873
1225
  },
874
1226
  "mediaList"
875
1227
  );
@@ -902,7 +1254,7 @@ var AniListClient = class {
902
1254
  {
903
1255
  search: options.query,
904
1256
  page: options.page ?? 1,
905
- perPage: options.perPage ?? 20
1257
+ perPage: this.clampPerPage(options.perPage ?? 20)
906
1258
  },
907
1259
  "studios"
908
1260
  );
@@ -1003,13 +1355,14 @@ var AniListClient = class {
1003
1355
  /** @internal */
1004
1356
  async executeBatch(ids, buildQuery, prefix) {
1005
1357
  const chunks = this.chunk(ids, 50);
1006
- const results = [];
1007
- for (const chunk of chunks) {
1008
- const query = buildQuery(chunk);
1009
- const data = await this.request(query);
1010
- results.push(...chunk.map((_, i) => data[`${prefix}${i}`]));
1011
- }
1012
- return results;
1358
+ const chunkResults = await Promise.all(
1359
+ chunks.map(async (chunk) => {
1360
+ const query = buildQuery(chunk);
1361
+ const data = await this.request(query);
1362
+ return chunk.map((_, i) => data[`${prefix}${i}`]);
1363
+ })
1364
+ );
1365
+ return chunkResults.flat();
1013
1366
  }
1014
1367
  /** @internal */
1015
1368
  chunk(arr, size) {
@@ -1082,8 +1435,25 @@ var RedisCache = class {
1082
1435
  const count = await this.client.del(this.prefixedKey(key));
1083
1436
  return count > 0;
1084
1437
  }
1438
+ /**
1439
+ * Collect keys matching a pattern. Uses SCAN when available, falls back to KEYS.
1440
+ *
1441
+ * **Warning:** The `KEYS` fallback is O(N) and blocks the Redis server.
1442
+ * Provide a client with `scanIterator` support for production use.
1443
+ * @internal
1444
+ */
1445
+ async collectKeys(pattern) {
1446
+ if (this.client.scanIterator) {
1447
+ const keys = [];
1448
+ for await (const key of this.client.scanIterator({ MATCH: pattern, COUNT: 100 })) {
1449
+ keys.push(key);
1450
+ }
1451
+ return keys;
1452
+ }
1453
+ return this.client.keys(pattern);
1454
+ }
1085
1455
  async clear() {
1086
- const keys = await this.client.keys(`${this.prefix}*`);
1456
+ const keys = await this.collectKeys(`${this.prefix}*`);
1087
1457
  if (keys.length > 0) {
1088
1458
  await this.client.del(...keys);
1089
1459
  }
@@ -1101,7 +1471,7 @@ var RedisCache = class {
1101
1471
  return keys.length;
1102
1472
  }
1103
1473
  async keys() {
1104
- const raw = await this.client.keys(`${this.prefix}*`);
1474
+ const raw = await this.collectKeys(`${this.prefix}*`);
1105
1475
  return raw.map((k) => k.slice(this.prefix.length));
1106
1476
  }
1107
1477
  /**
@@ -1111,153 +1481,22 @@ var RedisCache = class {
1111
1481
  * @returns Number of entries removed.
1112
1482
  */
1113
1483
  async invalidate(pattern) {
1114
- const keys = await this.client.keys(`${this.prefix}${pattern}`);
1115
- if (keys.length === 0) return 0;
1116
- return this.client.del(...keys);
1484
+ if (typeof pattern === "string") {
1485
+ const keys = await this.collectKeys(`${this.prefix}${pattern}`);
1486
+ if (keys.length === 0) return 0;
1487
+ return this.client.del(...keys);
1488
+ }
1489
+ const allKeys = await this.collectKeys(`${this.prefix}*`);
1490
+ const matching = allKeys.filter((k) => pattern.test(k.slice(this.prefix.length)));
1491
+ if (matching.length === 0) return 0;
1492
+ return this.client.del(...matching);
1117
1493
  }
1118
1494
  };
1119
1495
 
1120
- // src/types/index.ts
1121
- var MediaType = /* @__PURE__ */ ((MediaType2) => {
1122
- MediaType2["ANIME"] = "ANIME";
1123
- MediaType2["MANGA"] = "MANGA";
1124
- return MediaType2;
1125
- })(MediaType || {});
1126
- var MediaFormat = /* @__PURE__ */ ((MediaFormat2) => {
1127
- MediaFormat2["TV"] = "TV";
1128
- MediaFormat2["TV_SHORT"] = "TV_SHORT";
1129
- MediaFormat2["MOVIE"] = "MOVIE";
1130
- MediaFormat2["SPECIAL"] = "SPECIAL";
1131
- MediaFormat2["OVA"] = "OVA";
1132
- MediaFormat2["ONA"] = "ONA";
1133
- MediaFormat2["MUSIC"] = "MUSIC";
1134
- MediaFormat2["MANGA"] = "MANGA";
1135
- MediaFormat2["NOVEL"] = "NOVEL";
1136
- MediaFormat2["ONE_SHOT"] = "ONE_SHOT";
1137
- return MediaFormat2;
1138
- })(MediaFormat || {});
1139
- var MediaStatus = /* @__PURE__ */ ((MediaStatus2) => {
1140
- MediaStatus2["FINISHED"] = "FINISHED";
1141
- MediaStatus2["RELEASING"] = "RELEASING";
1142
- MediaStatus2["NOT_YET_RELEASED"] = "NOT_YET_RELEASED";
1143
- MediaStatus2["CANCELLED"] = "CANCELLED";
1144
- MediaStatus2["HIATUS"] = "HIATUS";
1145
- return MediaStatus2;
1146
- })(MediaStatus || {});
1147
- var MediaSeason = /* @__PURE__ */ ((MediaSeason2) => {
1148
- MediaSeason2["WINTER"] = "WINTER";
1149
- MediaSeason2["SPRING"] = "SPRING";
1150
- MediaSeason2["SUMMER"] = "SUMMER";
1151
- MediaSeason2["FALL"] = "FALL";
1152
- return MediaSeason2;
1153
- })(MediaSeason || {});
1154
- var MediaSort = /* @__PURE__ */ ((MediaSort2) => {
1155
- MediaSort2["ID"] = "ID";
1156
- MediaSort2["TITLE_ROMAJI"] = "TITLE_ROMAJI";
1157
- MediaSort2["TITLE_ENGLISH"] = "TITLE_ENGLISH";
1158
- MediaSort2["TITLE_NATIVE"] = "TITLE_NATIVE";
1159
- MediaSort2["TYPE"] = "TYPE";
1160
- MediaSort2["FORMAT"] = "FORMAT";
1161
- MediaSort2["START_DATE"] = "START_DATE";
1162
- MediaSort2["END_DATE"] = "END_DATE";
1163
- MediaSort2["SCORE"] = "SCORE";
1164
- MediaSort2["POPULARITY"] = "POPULARITY";
1165
- MediaSort2["TRENDING"] = "TRENDING";
1166
- MediaSort2["EPISODES"] = "EPISODES";
1167
- MediaSort2["DURATION"] = "DURATION";
1168
- MediaSort2["STATUS"] = "STATUS";
1169
- MediaSort2["FAVOURITES"] = "FAVOURITES";
1170
- MediaSort2["UPDATED_AT"] = "UPDATED_AT";
1171
- MediaSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
1172
- return MediaSort2;
1173
- })(MediaSort || {});
1174
- var AiringSort = /* @__PURE__ */ ((AiringSort2) => {
1175
- AiringSort2["ID"] = "ID";
1176
- AiringSort2["ID_DESC"] = "ID_DESC";
1177
- AiringSort2["MEDIA_ID"] = "MEDIA_ID";
1178
- AiringSort2["MEDIA_ID_DESC"] = "MEDIA_ID_DESC";
1179
- AiringSort2["TIME"] = "TIME";
1180
- AiringSort2["TIME_DESC"] = "TIME_DESC";
1181
- AiringSort2["EPISODE"] = "EPISODE";
1182
- AiringSort2["EPISODE_DESC"] = "EPISODE_DESC";
1183
- return AiringSort2;
1184
- })(AiringSort || {});
1185
- var CharacterSort = /* @__PURE__ */ ((CharacterSort2) => {
1186
- CharacterSort2["ID"] = "ID";
1187
- CharacterSort2["ROLE"] = "ROLE";
1188
- CharacterSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
1189
- CharacterSort2["FAVOURITES"] = "FAVOURITES";
1190
- return CharacterSort2;
1191
- })(CharacterSort || {});
1192
- var MediaRelationType = /* @__PURE__ */ ((MediaRelationType2) => {
1193
- MediaRelationType2["ADAPTATION"] = "ADAPTATION";
1194
- MediaRelationType2["PREQUEL"] = "PREQUEL";
1195
- MediaRelationType2["SEQUEL"] = "SEQUEL";
1196
- MediaRelationType2["PARENT"] = "PARENT";
1197
- MediaRelationType2["SIDE_STORY"] = "SIDE_STORY";
1198
- MediaRelationType2["CHARACTER"] = "CHARACTER";
1199
- MediaRelationType2["SUMMARY"] = "SUMMARY";
1200
- MediaRelationType2["ALTERNATIVE"] = "ALTERNATIVE";
1201
- MediaRelationType2["SPIN_OFF"] = "SPIN_OFF";
1202
- MediaRelationType2["OTHER"] = "OTHER";
1203
- MediaRelationType2["SOURCE"] = "SOURCE";
1204
- MediaRelationType2["COMPILATION"] = "COMPILATION";
1205
- MediaRelationType2["CONTAINS"] = "CONTAINS";
1206
- return MediaRelationType2;
1207
- })(MediaRelationType || {});
1208
- var RecommendationSort = /* @__PURE__ */ ((RecommendationSort2) => {
1209
- RecommendationSort2["ID"] = "ID";
1210
- RecommendationSort2["ID_DESC"] = "ID_DESC";
1211
- RecommendationSort2["RATING"] = "RATING";
1212
- RecommendationSort2["RATING_DESC"] = "RATING_DESC";
1213
- return RecommendationSort2;
1214
- })(RecommendationSort || {});
1215
- var MediaListStatus = /* @__PURE__ */ ((MediaListStatus2) => {
1216
- MediaListStatus2["CURRENT"] = "CURRENT";
1217
- MediaListStatus2["PLANNING"] = "PLANNING";
1218
- MediaListStatus2["COMPLETED"] = "COMPLETED";
1219
- MediaListStatus2["DROPPED"] = "DROPPED";
1220
- MediaListStatus2["PAUSED"] = "PAUSED";
1221
- MediaListStatus2["REPEATING"] = "REPEATING";
1222
- return MediaListStatus2;
1223
- })(MediaListStatus || {});
1224
- var MediaListSort = /* @__PURE__ */ ((MediaListSort2) => {
1225
- MediaListSort2["MEDIA_ID"] = "MEDIA_ID";
1226
- MediaListSort2["MEDIA_ID_DESC"] = "MEDIA_ID_DESC";
1227
- MediaListSort2["SCORE"] = "SCORE";
1228
- MediaListSort2["SCORE_DESC"] = "SCORE_DESC";
1229
- MediaListSort2["STATUS"] = "STATUS";
1230
- MediaListSort2["STATUS_DESC"] = "STATUS_DESC";
1231
- MediaListSort2["PROGRESS"] = "PROGRESS";
1232
- MediaListSort2["PROGRESS_DESC"] = "PROGRESS_DESC";
1233
- MediaListSort2["PROGRESS_VOLUMES"] = "PROGRESS_VOLUMES";
1234
- MediaListSort2["PROGRESS_VOLUMES_DESC"] = "PROGRESS_VOLUMES_DESC";
1235
- MediaListSort2["REPEAT"] = "REPEAT";
1236
- MediaListSort2["REPEAT_DESC"] = "REPEAT_DESC";
1237
- MediaListSort2["PRIORITY"] = "PRIORITY";
1238
- MediaListSort2["PRIORITY_DESC"] = "PRIORITY_DESC";
1239
- MediaListSort2["STARTED_ON"] = "STARTED_ON";
1240
- MediaListSort2["STARTED_ON_DESC"] = "STARTED_ON_DESC";
1241
- MediaListSort2["FINISHED_ON"] = "FINISHED_ON";
1242
- MediaListSort2["FINISHED_ON_DESC"] = "FINISHED_ON_DESC";
1243
- MediaListSort2["ADDED_TIME"] = "ADDED_TIME";
1244
- MediaListSort2["ADDED_TIME_DESC"] = "ADDED_TIME_DESC";
1245
- MediaListSort2["UPDATED_TIME"] = "UPDATED_TIME";
1246
- MediaListSort2["UPDATED_TIME_DESC"] = "UPDATED_TIME_DESC";
1247
- MediaListSort2["MEDIA_TITLE_ROMAJI"] = "MEDIA_TITLE_ROMAJI";
1248
- MediaListSort2["MEDIA_TITLE_ROMAJI_DESC"] = "MEDIA_TITLE_ROMAJI_DESC";
1249
- MediaListSort2["MEDIA_TITLE_ENGLISH"] = "MEDIA_TITLE_ENGLISH";
1250
- MediaListSort2["MEDIA_TITLE_ENGLISH_DESC"] = "MEDIA_TITLE_ENGLISH_DESC";
1251
- MediaListSort2["MEDIA_TITLE_NATIVE"] = "MEDIA_TITLE_NATIVE";
1252
- MediaListSort2["MEDIA_TITLE_NATIVE_DESC"] = "MEDIA_TITLE_NATIVE_DESC";
1253
- MediaListSort2["MEDIA_POPULARITY"] = "MEDIA_POPULARITY";
1254
- MediaListSort2["MEDIA_POPULARITY_DESC"] = "MEDIA_POPULARITY_DESC";
1255
- return MediaListSort2;
1256
- })(MediaListSort || {});
1257
-
1258
1496
  exports.AiringSort = AiringSort;
1259
1497
  exports.AniListClient = AniListClient;
1260
1498
  exports.AniListError = AniListError;
1499
+ exports.CharacterRole = CharacterRole;
1261
1500
  exports.CharacterSort = CharacterSort;
1262
1501
  exports.MediaFormat = MediaFormat;
1263
1502
  exports.MediaListSort = MediaListSort;