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