ani-client 2.4.0 → 2.5.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,8 +1,15 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
1
7
  // src/utils/dataloader.ts
2
8
  var BatchLoader = class {
3
- constructor(batchFetch, maxWaitMs = 50) {
9
+ constructor(batchFetch, maxWaitMs = 50, maxBatchSize = 50) {
4
10
  this.batchFetch = batchFetch;
5
11
  this.maxWaitMs = maxWaitMs;
12
+ this.maxBatchSize = maxBatchSize;
6
13
  }
7
14
  queue = /* @__PURE__ */ new Map();
8
15
  timeout = null;
@@ -18,10 +25,21 @@ var BatchLoader = class {
18
25
  this.queue.set(id, callbacks);
19
26
  }
20
27
  callbacks.push({ resolve, reject });
28
+ if (this.queue.size >= this.maxBatchSize) {
29
+ if (this.timeout) {
30
+ clearTimeout(this.timeout);
31
+ this.timeout = null;
32
+ }
33
+ void this.dispatch();
34
+ return;
35
+ }
21
36
  if (this.timeout === null) {
22
- this.timeout = setTimeout(() => this.dispatch(), this.maxWaitMs);
23
- if (typeof this.timeout.unref === "function") {
24
- this.timeout.unref();
37
+ this.timeout = setTimeout(() => {
38
+ void this.dispatch();
39
+ }, this.maxWaitMs);
40
+ const timer = this.timeout;
41
+ if (typeof timer.unref === "function") {
42
+ timer.unref();
25
43
  }
26
44
  }
27
45
  });
@@ -630,6 +648,25 @@ var AniListError = class _AniListError extends Error {
630
648
  };
631
649
 
632
650
  // src/queries/fragments.ts
651
+ var fragments_exports = {};
652
+ __export(fragments_exports, {
653
+ CHARACTER_FIELDS: () => CHARACTER_FIELDS,
654
+ CHARACTER_FIELDS_COMPACT: () => CHARACTER_FIELDS_COMPACT,
655
+ CHARACTER_FIELDS_WITH_VA: () => CHARACTER_FIELDS_WITH_VA,
656
+ MEDIA_FIELDS: () => MEDIA_FIELDS,
657
+ MEDIA_FIELDS_BASE: () => MEDIA_FIELDS_BASE,
658
+ MEDIA_FIELDS_LIGHT: () => MEDIA_FIELDS_LIGHT,
659
+ MEDIA_LIST_FIELDS: () => MEDIA_LIST_FIELDS,
660
+ MEDIA_RECOMMENDATION_FIELDS: () => MEDIA_RECOMMENDATION_FIELDS,
661
+ RELATIONS_FIELDS: () => RELATIONS_FIELDS,
662
+ STAFF_FIELDS: () => STAFF_FIELDS,
663
+ STAFF_MEDIA_FIELDS: () => STAFF_MEDIA_FIELDS,
664
+ STUDIO_FIELDS: () => STUDIO_FIELDS,
665
+ THREAD_FIELDS: () => THREAD_FIELDS,
666
+ USER_FAVORITES_FIELDS: () => USER_FAVORITES_FIELDS,
667
+ USER_FIELDS: () => USER_FIELDS,
668
+ VOICE_ACTOR_FIELDS_COMPACT: () => VOICE_ACTOR_FIELDS_COMPACT
669
+ });
633
670
  var MEDIA_FIELDS_LIGHT = `
634
671
  __typename
635
672
  id
@@ -1320,6 +1357,9 @@ function buildMediaIncludeQuery(include) {
1320
1357
  statusDistribution { status amount }
1321
1358
  }`);
1322
1359
  }
1360
+ if (include.customFields) {
1361
+ extra.push(include.customFields);
1362
+ }
1323
1363
  return extra;
1324
1364
  }
1325
1365
  function buildMediaByIdQuery(include) {
@@ -1547,6 +1587,14 @@ query ($id: Int!) {
1547
1587
  ${CHARACTER_FIELDS_WITH_VA}
1548
1588
  }
1549
1589
  }`;
1590
+ function buildCharacterByIdQuery(include) {
1591
+ if (!include?.customFields) {
1592
+ return include?.voiceActors ? QUERY_CHARACTER_BY_ID_WITH_VA : QUERY_CHARACTER_BY_ID;
1593
+ }
1594
+ const fields = include.voiceActors ? CHARACTER_FIELDS_WITH_VA : CHARACTER_FIELDS;
1595
+ return `query ($id: Int!) { Character(id: $id) { ${fields}
1596
+ ${include.customFields} } }`;
1597
+ }
1550
1598
  var QUERY_CHARACTER_SEARCH = `
1551
1599
  query ($search: String, $sort: [CharacterSort], $page: Int, $perPage: Int) {
1552
1600
  Page(page: $page, perPage: $perPage) {
@@ -1665,6 +1713,15 @@ query ($id: Int!, $perPage: Int) {
1665
1713
  ${STAFF_MEDIA_FIELDS}
1666
1714
  }
1667
1715
  }`;
1716
+ function buildStaffByIdQuery(include) {
1717
+ if (!include?.customFields) {
1718
+ return include?.characterRoles || include?.media ? QUERY_STAFF_BY_ID_WITH_MEDIA : QUERY_STAFF_BY_ID;
1719
+ }
1720
+ const extra = include?.characterRoles || include?.media ? `
1721
+ ${STAFF_MEDIA_FIELDS}` : "";
1722
+ return `query ($id: Int!, $perPage: Int) { Staff(id: $id) { ${STAFF_FIELDS}${extra}
1723
+ ${include.customFields} } }`;
1724
+ }
1668
1725
  var QUERY_STAFF_SEARCH = `
1669
1726
  query ($search: String, $sort: [StaffSort], $page: Int, $perPage: Int) {
1670
1727
  Page(page: $page, perPage: $perPage) {
@@ -1682,9 +1739,15 @@ query ($id: Int!) {
1682
1739
  ${STUDIO_FIELDS}
1683
1740
  }
1684
1741
  }`;
1685
- function buildStudioByIdQuery(mediaPerPage) {
1686
- if (mediaPerPage === void 0) return QUERY_STUDIO_BY_ID;
1687
- const pp = clampPerPage(mediaPerPage);
1742
+ function buildStudioByIdQuery(include) {
1743
+ const custom = include?.customFields ? `
1744
+ ${include.customFields}` : "";
1745
+ if (!include?.media) {
1746
+ if (!custom) return QUERY_STUDIO_BY_ID;
1747
+ return `query ($id: Int!) { Studio(id: $id) { ${STUDIO_FIELDS}${custom} } }`;
1748
+ }
1749
+ const mediaOpts = typeof include.media === "object" ? include.media : {};
1750
+ const pp = clampPerPage(mediaOpts.perPage ?? 25);
1688
1751
  return `
1689
1752
  query ($id: Int!) {
1690
1753
  Studio(id: $id) {
@@ -1698,7 +1761,7 @@ query ($id: Int!) {
1698
1761
  nodes {
1699
1762
  ${MEDIA_FIELDS_LIGHT}
1700
1763
  }
1701
- }
1764
+ }${custom}
1702
1765
  }
1703
1766
  }`;
1704
1767
  }
@@ -1981,7 +2044,7 @@ function isNetworkError(err) {
1981
2044
  // src/client/character.ts
1982
2045
  async function getCharacter(client, id, include) {
1983
2046
  validateId(id, "characterId");
1984
- const query = include?.voiceActors ? QUERY_CHARACTER_BY_ID_WITH_VA : QUERY_CHARACTER_BY_ID;
2047
+ const query = buildCharacterByIdQuery(include);
1985
2048
  const data = await client.request(query, { id });
1986
2049
  return data.Character;
1987
2050
  }
@@ -2458,12 +2521,13 @@ async function searchReviews(client, options = {}) {
2458
2521
  // src/client/staff.ts
2459
2522
  async function getStaff(client, id, include) {
2460
2523
  validateId(id, "staffId");
2461
- if (include?.media) {
2524
+ const query = buildStaffByIdQuery(include);
2525
+ if (include?.characterRoles || include?.media) {
2462
2526
  const perPage = typeof include.media === "object" ? include.media.perPage ?? 25 : 25;
2463
- const data2 = await client.request(QUERY_STAFF_BY_ID_WITH_MEDIA, { id, perPage });
2527
+ const data2 = await client.request(query, { id, perPage });
2464
2528
  return data2.Staff;
2465
2529
  }
2466
- const data = await client.request(QUERY_STAFF_BY_ID, { id });
2530
+ const data = await client.request(query, { id });
2467
2531
  return data.Staff;
2468
2532
  }
2469
2533
  async function searchStaff(client, options = {}) {
@@ -2478,13 +2542,8 @@ async function searchStaff(client, options = {}) {
2478
2542
  // src/client/studio.ts
2479
2543
  async function getStudio(client, id, include) {
2480
2544
  validateId(id, "studioId");
2481
- if (include?.media) {
2482
- const perPage = typeof include.media === "object" ? include.media.perPage : void 0;
2483
- const query = buildStudioByIdQuery(perPage);
2484
- const data2 = await client.request(query, { id });
2485
- return data2.Studio;
2486
- }
2487
- const data = await client.request(QUERY_STUDIO_BY_ID, { id });
2545
+ const query = buildStudioByIdQuery(include);
2546
+ const data = await client.request(query, { id });
2488
2547
  return data.Studio;
2489
2548
  }
2490
2549
  async function searchStudios(client, options = {}) {
@@ -2586,7 +2645,7 @@ function mapFavorites(fav) {
2586
2645
 
2587
2646
  // src/client/index.ts
2588
2647
  var DEFAULT_API_URL = "https://graphql.anilist.co";
2589
- var LIB_VERSION = "2.4.0" ;
2648
+ var LIB_VERSION = "2.5.0" ;
2590
2649
  var AniListClient = class {
2591
2650
  apiUrl;
2592
2651
  headers;
@@ -2601,6 +2660,7 @@ var AniListClient = class {
2601
2660
  mediaLoader;
2602
2661
  characterLoader;
2603
2662
  staffLoader;
2663
+ batchingEnabled;
2604
2664
  constructor(options = {}) {
2605
2665
  this.apiUrl = options.apiUrl ?? DEFAULT_API_URL;
2606
2666
  this.headers = {
@@ -2616,9 +2676,12 @@ var AniListClient = class {
2616
2676
  this.hooks = options.hooks ?? {};
2617
2677
  this.logger = options.logger;
2618
2678
  this.signal = options.signal;
2619
- this.mediaLoader = new BatchLoader((ids) => this.getMediaBatch(ids), 50);
2620
- this.characterLoader = new BatchLoader((ids) => this.getCharacterBatch(ids), 50);
2621
- this.staffLoader = new BatchLoader((ids) => this.getStaffBatch(ids), 50);
2679
+ this.batchingEnabled = options.batching?.enabled ?? true;
2680
+ const windowMs = options.batching?.windowMs ?? 50;
2681
+ const maxBatchSize = options.batching?.maxBatchSize ?? 50;
2682
+ this.mediaLoader = new BatchLoader((ids) => this.getMediaBatch(ids), windowMs, maxBatchSize);
2683
+ this.characterLoader = new BatchLoader((ids) => this.getCharacterBatch(ids), windowMs, maxBatchSize);
2684
+ this.staffLoader = new BatchLoader((ids) => this.getStaffBatch(ids), windowMs, maxBatchSize);
2622
2685
  }
2623
2686
  /**
2624
2687
  * The current rate limit information from the last API response.
@@ -2721,6 +2784,17 @@ var AniListClient = class {
2721
2784
  remaining: Number.parseInt(rlRemaining, 10),
2722
2785
  reset: Number.parseInt(rlReset, 10)
2723
2786
  };
2787
+ const remainingPercent = this._rateLimitInfo.remaining / this._rateLimitInfo.limit;
2788
+ if (remainingPercent <= 0.15) {
2789
+ const resetIn = this._rateLimitInfo.reset - Math.floor(Date.now() / 1e3);
2790
+ const resetInSeconds = resetIn > 0 ? resetIn : 0;
2791
+ this.logger?.warn("Smart Alert: AniList API rate limit is critically low", {
2792
+ remaining: this._rateLimitInfo.remaining,
2793
+ limit: this._rateLimitInfo.limit,
2794
+ resetInSeconds
2795
+ });
2796
+ this.hooks.onRateLimitAlert?.(this._rateLimitInfo.remaining, resetInSeconds);
2797
+ }
2724
2798
  }
2725
2799
  const durationMs = Date.now() - start;
2726
2800
  const data = json.data;
@@ -2749,10 +2823,10 @@ var AniListClient = class {
2749
2823
  * @param include - Optional related data to include
2750
2824
  */
2751
2825
  async getMedia(id, include) {
2752
- if (!include) {
2753
- return this.mediaLoader.load(id);
2826
+ if (!this.batchingEnabled || include) {
2827
+ return getMedia(this, id, include);
2754
2828
  }
2755
- return getMedia(this, id, include);
2829
+ return this.mediaLoader.load(id);
2756
2830
  }
2757
2831
  async getMediaCharacters(mediaId, options = {}) {
2758
2832
  return getMediaCharacters(this, mediaId, options);
@@ -2820,10 +2894,10 @@ var AniListClient = class {
2820
2894
  }
2821
2895
  /** Fetch a character by AniList ID. Pass `{ voiceActors: true }` to include VA data. */
2822
2896
  async getCharacter(id, include) {
2823
- if (!include) {
2824
- return this.characterLoader.load(id);
2897
+ if (!this.batchingEnabled || include) {
2898
+ return getCharacter(this, id, include);
2825
2899
  }
2826
- return getCharacter(this, id, include);
2900
+ return this.characterLoader.load(id);
2827
2901
  }
2828
2902
  /** Search for characters by name. */
2829
2903
  async searchCharacters(options = {}) {
@@ -2831,10 +2905,10 @@ var AniListClient = class {
2831
2905
  }
2832
2906
  /** Fetch a staff member by AniList ID. Pass `{ media: true }` or `{ media: { perPage } }` for media credits. */
2833
2907
  async getStaff(id, include) {
2834
- if (!include) {
2835
- return this.staffLoader.load(id);
2908
+ if (!this.batchingEnabled || include) {
2909
+ return getStaff(this, id, include);
2836
2910
  }
2837
- return getStaff(this, id, include);
2911
+ return this.staffLoader.load(id);
2838
2912
  }
2839
2913
  /** Search for staff (voice actors, directors, etc.). */
2840
2914
  async searchStaff(options = {}) {
@@ -2948,6 +3022,19 @@ var AniListClient = class {
2948
3022
  page++;
2949
3023
  }
2950
3024
  }
3025
+ /**
3026
+ * Utility to fetch all pages of a paginated request and return them as a single array.
3027
+ *
3028
+ * @param fetchPage - A function that takes a page number and returns a `PagedResult<T>`
3029
+ * @param maxPages - Maximum number of pages to fetch (default: Infinity)
3030
+ */
3031
+ async fetchAll(fetchPage, maxPages = Number.POSITIVE_INFINITY) {
3032
+ const results = [];
3033
+ for await (const item of this.paginate(fetchPage, maxPages)) {
3034
+ results.push(item);
3035
+ }
3036
+ return results;
3037
+ }
2951
3038
  /** Fetch multiple media entries in a single API request. */
2952
3039
  async getMediaBatch(ids) {
2953
3040
  if (ids.length === 0) return [];
@@ -3035,6 +3122,6 @@ var AniListClient = class {
3035
3122
  }
3036
3123
  };
3037
3124
 
3038
- export { AiringSort, AniListClient, AniListError, CharacterRole, CharacterSort, MediaFormat, MediaListSort, MediaListStatus, MediaRelationType, MediaSeason, MediaSort, MediaSource, MediaStatus, MediaType, MemoryCache, NormalizedCache, RateLimiter, RecommendationSort, RedisCache, ReviewSort, StaffSort, StudioSort, ThreadSort, UserSort, parseAniListMarkdown };
3125
+ export { AiringSort, AniListClient, AniListError, CharacterRole, CharacterSort, fragments_exports as Fragments, MediaFormat, MediaListSort, MediaListStatus, MediaRelationType, MediaSeason, MediaSort, MediaSource, MediaStatus, MediaType, MemoryCache, NormalizedCache, RateLimiter, RecommendationSort, RedisCache, ReviewSort, StaffSort, StudioSort, ThreadSort, UserSort, parseAniListMarkdown };
3039
3126
  //# sourceMappingURL=index.mjs.map
3040
3127
  //# sourceMappingURL=index.mjs.map