ani-client 2.3.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.js CHANGED
@@ -1,5 +1,80 @@
1
1
  'use strict';
2
2
 
3
+ var __defProp = Object.defineProperty;
4
+ var __export = (target, all) => {
5
+ for (var name in all)
6
+ __defProp(target, name, { get: all[name], enumerable: true });
7
+ };
8
+
9
+ // src/utils/dataloader.ts
10
+ var BatchLoader = class {
11
+ constructor(batchFetch, maxWaitMs = 50, maxBatchSize = 50) {
12
+ this.batchFetch = batchFetch;
13
+ this.maxWaitMs = maxWaitMs;
14
+ this.maxBatchSize = maxBatchSize;
15
+ }
16
+ queue = /* @__PURE__ */ new Map();
17
+ timeout = null;
18
+ /**
19
+ * Queue an ID to be fetched in the next batch.
20
+ * Returns a Promise that resolves when the batch request completes.
21
+ */
22
+ async load(id) {
23
+ return new Promise((resolve, reject) => {
24
+ let callbacks = this.queue.get(id);
25
+ if (!callbacks) {
26
+ callbacks = [];
27
+ this.queue.set(id, callbacks);
28
+ }
29
+ callbacks.push({ resolve, reject });
30
+ if (this.queue.size >= this.maxBatchSize) {
31
+ if (this.timeout) {
32
+ clearTimeout(this.timeout);
33
+ this.timeout = null;
34
+ }
35
+ void this.dispatch();
36
+ return;
37
+ }
38
+ if (this.timeout === null) {
39
+ this.timeout = setTimeout(() => {
40
+ void this.dispatch();
41
+ }, this.maxWaitMs);
42
+ const timer = this.timeout;
43
+ if (typeof timer.unref === "function") {
44
+ timer.unref();
45
+ }
46
+ }
47
+ });
48
+ }
49
+ async dispatch() {
50
+ this.timeout = null;
51
+ if (this.queue.size === 0) return;
52
+ const currentQueue = this.queue;
53
+ this.queue = /* @__PURE__ */ new Map();
54
+ const ids = Array.from(currentQueue.keys());
55
+ try {
56
+ const results = await this.batchFetch(ids);
57
+ const resultMap = /* @__PURE__ */ new Map();
58
+ for (const item of results) {
59
+ resultMap.set(item.id, item);
60
+ }
61
+ for (const [id, callbacks] of currentQueue.entries()) {
62
+ const result = resultMap.get(id);
63
+ if (result) {
64
+ for (const cb of callbacks) cb.resolve(result);
65
+ } else {
66
+ const err = new Error(`Item with ID ${id} not found in batch response`);
67
+ for (const cb of callbacks) cb.reject(err);
68
+ }
69
+ }
70
+ } catch (err) {
71
+ for (const callbacks of currentQueue.values()) {
72
+ for (const cb of callbacks) cb.reject(err);
73
+ }
74
+ }
75
+ }
76
+ };
77
+
3
78
  // src/utils/markdown.ts
4
79
  function isSafeUrl(url) {
5
80
  return /^https?:\/\//i.test(url);
@@ -575,6 +650,25 @@ var AniListError = class _AniListError extends Error {
575
650
  };
576
651
 
577
652
  // src/queries/fragments.ts
653
+ var fragments_exports = {};
654
+ __export(fragments_exports, {
655
+ CHARACTER_FIELDS: () => CHARACTER_FIELDS,
656
+ CHARACTER_FIELDS_COMPACT: () => CHARACTER_FIELDS_COMPACT,
657
+ CHARACTER_FIELDS_WITH_VA: () => CHARACTER_FIELDS_WITH_VA,
658
+ MEDIA_FIELDS: () => MEDIA_FIELDS,
659
+ MEDIA_FIELDS_BASE: () => MEDIA_FIELDS_BASE,
660
+ MEDIA_FIELDS_LIGHT: () => MEDIA_FIELDS_LIGHT,
661
+ MEDIA_LIST_FIELDS: () => MEDIA_LIST_FIELDS,
662
+ MEDIA_RECOMMENDATION_FIELDS: () => MEDIA_RECOMMENDATION_FIELDS,
663
+ RELATIONS_FIELDS: () => RELATIONS_FIELDS,
664
+ STAFF_FIELDS: () => STAFF_FIELDS,
665
+ STAFF_MEDIA_FIELDS: () => STAFF_MEDIA_FIELDS,
666
+ STUDIO_FIELDS: () => STUDIO_FIELDS,
667
+ THREAD_FIELDS: () => THREAD_FIELDS,
668
+ USER_FAVORITES_FIELDS: () => USER_FAVORITES_FIELDS,
669
+ USER_FIELDS: () => USER_FIELDS,
670
+ VOICE_ACTOR_FIELDS_COMPACT: () => VOICE_ACTOR_FIELDS_COMPACT
671
+ });
578
672
  var MEDIA_FIELDS_LIGHT = `
579
673
  __typename
580
674
  id
@@ -1265,6 +1359,9 @@ function buildMediaIncludeQuery(include) {
1265
1359
  statusDistribution { status amount }
1266
1360
  }`);
1267
1361
  }
1362
+ if (include.customFields) {
1363
+ extra.push(include.customFields);
1364
+ }
1268
1365
  return extra;
1269
1366
  }
1270
1367
  function buildMediaByIdQuery(include) {
@@ -1492,6 +1589,14 @@ query ($id: Int!) {
1492
1589
  ${CHARACTER_FIELDS_WITH_VA}
1493
1590
  }
1494
1591
  }`;
1592
+ function buildCharacterByIdQuery(include) {
1593
+ if (!include?.customFields) {
1594
+ return include?.voiceActors ? QUERY_CHARACTER_BY_ID_WITH_VA : QUERY_CHARACTER_BY_ID;
1595
+ }
1596
+ const fields = include.voiceActors ? CHARACTER_FIELDS_WITH_VA : CHARACTER_FIELDS;
1597
+ return `query ($id: Int!) { Character(id: $id) { ${fields}
1598
+ ${include.customFields} } }`;
1599
+ }
1495
1600
  var QUERY_CHARACTER_SEARCH = `
1496
1601
  query ($search: String, $sort: [CharacterSort], $page: Int, $perPage: Int) {
1497
1602
  Page(page: $page, perPage: $perPage) {
@@ -1610,6 +1715,15 @@ query ($id: Int!, $perPage: Int) {
1610
1715
  ${STAFF_MEDIA_FIELDS}
1611
1716
  }
1612
1717
  }`;
1718
+ function buildStaffByIdQuery(include) {
1719
+ if (!include?.customFields) {
1720
+ return include?.characterRoles || include?.media ? QUERY_STAFF_BY_ID_WITH_MEDIA : QUERY_STAFF_BY_ID;
1721
+ }
1722
+ const extra = include?.characterRoles || include?.media ? `
1723
+ ${STAFF_MEDIA_FIELDS}` : "";
1724
+ return `query ($id: Int!, $perPage: Int) { Staff(id: $id) { ${STAFF_FIELDS}${extra}
1725
+ ${include.customFields} } }`;
1726
+ }
1613
1727
  var QUERY_STAFF_SEARCH = `
1614
1728
  query ($search: String, $sort: [StaffSort], $page: Int, $perPage: Int) {
1615
1729
  Page(page: $page, perPage: $perPage) {
@@ -1627,9 +1741,15 @@ query ($id: Int!) {
1627
1741
  ${STUDIO_FIELDS}
1628
1742
  }
1629
1743
  }`;
1630
- function buildStudioByIdQuery(mediaPerPage) {
1631
- if (mediaPerPage === void 0) return QUERY_STUDIO_BY_ID;
1632
- const pp = clampPerPage(mediaPerPage);
1744
+ function buildStudioByIdQuery(include) {
1745
+ const custom = include?.customFields ? `
1746
+ ${include.customFields}` : "";
1747
+ if (!include?.media) {
1748
+ if (!custom) return QUERY_STUDIO_BY_ID;
1749
+ return `query ($id: Int!) { Studio(id: $id) { ${STUDIO_FIELDS}${custom} } }`;
1750
+ }
1751
+ const mediaOpts = typeof include.media === "object" ? include.media : {};
1752
+ const pp = clampPerPage(mediaOpts.perPage ?? 25);
1633
1753
  return `
1634
1754
  query ($id: Int!) {
1635
1755
  Studio(id: $id) {
@@ -1643,7 +1763,7 @@ query ($id: Int!) {
1643
1763
  nodes {
1644
1764
  ${MEDIA_FIELDS_LIGHT}
1645
1765
  }
1646
- }
1766
+ }${custom}
1647
1767
  }
1648
1768
  }`;
1649
1769
  }
@@ -1926,7 +2046,7 @@ function isNetworkError(err) {
1926
2046
  // src/client/character.ts
1927
2047
  async function getCharacter(client, id, include) {
1928
2048
  validateId(id, "characterId");
1929
- const query = include?.voiceActors ? QUERY_CHARACTER_BY_ID_WITH_VA : QUERY_CHARACTER_BY_ID;
2049
+ const query = buildCharacterByIdQuery(include);
1930
2050
  const data = await client.request(query, { id });
1931
2051
  return data.Character;
1932
2052
  }
@@ -2403,12 +2523,13 @@ async function searchReviews(client, options = {}) {
2403
2523
  // src/client/staff.ts
2404
2524
  async function getStaff(client, id, include) {
2405
2525
  validateId(id, "staffId");
2406
- if (include?.media) {
2526
+ const query = buildStaffByIdQuery(include);
2527
+ if (include?.characterRoles || include?.media) {
2407
2528
  const perPage = typeof include.media === "object" ? include.media.perPage ?? 25 : 25;
2408
- const data2 = await client.request(QUERY_STAFF_BY_ID_WITH_MEDIA, { id, perPage });
2529
+ const data2 = await client.request(query, { id, perPage });
2409
2530
  return data2.Staff;
2410
2531
  }
2411
- const data = await client.request(QUERY_STAFF_BY_ID, { id });
2532
+ const data = await client.request(query, { id });
2412
2533
  return data.Staff;
2413
2534
  }
2414
2535
  async function searchStaff(client, options = {}) {
@@ -2423,13 +2544,8 @@ async function searchStaff(client, options = {}) {
2423
2544
  // src/client/studio.ts
2424
2545
  async function getStudio(client, id, include) {
2425
2546
  validateId(id, "studioId");
2426
- if (include?.media) {
2427
- const perPage = typeof include.media === "object" ? include.media.perPage : void 0;
2428
- const query = buildStudioByIdQuery(perPage);
2429
- const data2 = await client.request(query, { id });
2430
- return data2.Studio;
2431
- }
2432
- const data = await client.request(QUERY_STUDIO_BY_ID, { id });
2547
+ const query = buildStudioByIdQuery(include);
2548
+ const data = await client.request(query, { id });
2433
2549
  return data.Studio;
2434
2550
  }
2435
2551
  async function searchStudios(client, options = {}) {
@@ -2531,7 +2647,7 @@ function mapFavorites(fav) {
2531
2647
 
2532
2648
  // src/client/index.ts
2533
2649
  var DEFAULT_API_URL = "https://graphql.anilist.co";
2534
- var LIB_VERSION = "2.3.0" ;
2650
+ var LIB_VERSION = "2.5.0" ;
2535
2651
  var AniListClient = class {
2536
2652
  apiUrl;
2537
2653
  headers;
@@ -2543,6 +2659,10 @@ var AniListClient = class {
2543
2659
  inFlight = /* @__PURE__ */ new Map();
2544
2660
  _rateLimitInfo;
2545
2661
  _lastRequestMeta;
2662
+ mediaLoader;
2663
+ characterLoader;
2664
+ staffLoader;
2665
+ batchingEnabled;
2546
2666
  constructor(options = {}) {
2547
2667
  this.apiUrl = options.apiUrl ?? DEFAULT_API_URL;
2548
2668
  this.headers = {
@@ -2558,6 +2678,12 @@ var AniListClient = class {
2558
2678
  this.hooks = options.hooks ?? {};
2559
2679
  this.logger = options.logger;
2560
2680
  this.signal = options.signal;
2681
+ this.batchingEnabled = options.batching?.enabled ?? true;
2682
+ const windowMs = options.batching?.windowMs ?? 50;
2683
+ const maxBatchSize = options.batching?.maxBatchSize ?? 50;
2684
+ this.mediaLoader = new BatchLoader((ids) => this.getMediaBatch(ids), windowMs, maxBatchSize);
2685
+ this.characterLoader = new BatchLoader((ids) => this.getCharacterBatch(ids), windowMs, maxBatchSize);
2686
+ this.staffLoader = new BatchLoader((ids) => this.getStaffBatch(ids), windowMs, maxBatchSize);
2561
2687
  }
2562
2688
  /**
2563
2689
  * The current rate limit information from the last API response.
@@ -2660,6 +2786,17 @@ var AniListClient = class {
2660
2786
  remaining: Number.parseInt(rlRemaining, 10),
2661
2787
  reset: Number.parseInt(rlReset, 10)
2662
2788
  };
2789
+ const remainingPercent = this._rateLimitInfo.remaining / this._rateLimitInfo.limit;
2790
+ if (remainingPercent <= 0.15) {
2791
+ const resetIn = this._rateLimitInfo.reset - Math.floor(Date.now() / 1e3);
2792
+ const resetInSeconds = resetIn > 0 ? resetIn : 0;
2793
+ this.logger?.warn("Smart Alert: AniList API rate limit is critically low", {
2794
+ remaining: this._rateLimitInfo.remaining,
2795
+ limit: this._rateLimitInfo.limit,
2796
+ resetInSeconds
2797
+ });
2798
+ this.hooks.onRateLimitAlert?.(this._rateLimitInfo.remaining, resetInSeconds);
2799
+ }
2663
2800
  }
2664
2801
  const durationMs = Date.now() - start;
2665
2802
  const data = json.data;
@@ -2688,7 +2825,10 @@ var AniListClient = class {
2688
2825
  * @param include - Optional related data to include
2689
2826
  */
2690
2827
  async getMedia(id, include) {
2691
- return getMedia(this, id, include);
2828
+ if (!this.batchingEnabled || include) {
2829
+ return getMedia(this, id, include);
2830
+ }
2831
+ return this.mediaLoader.load(id);
2692
2832
  }
2693
2833
  async getMediaCharacters(mediaId, options = {}) {
2694
2834
  return getMediaCharacters(this, mediaId, options);
@@ -2756,7 +2896,10 @@ var AniListClient = class {
2756
2896
  }
2757
2897
  /** Fetch a character by AniList ID. Pass `{ voiceActors: true }` to include VA data. */
2758
2898
  async getCharacter(id, include) {
2759
- return getCharacter(this, id, include);
2899
+ if (!this.batchingEnabled || include) {
2900
+ return getCharacter(this, id, include);
2901
+ }
2902
+ return this.characterLoader.load(id);
2760
2903
  }
2761
2904
  /** Search for characters by name. */
2762
2905
  async searchCharacters(options = {}) {
@@ -2764,7 +2907,10 @@ var AniListClient = class {
2764
2907
  }
2765
2908
  /** Fetch a staff member by AniList ID. Pass `{ media: true }` or `{ media: { perPage } }` for media credits. */
2766
2909
  async getStaff(id, include) {
2767
- return getStaff(this, id, include);
2910
+ if (!this.batchingEnabled || include) {
2911
+ return getStaff(this, id, include);
2912
+ }
2913
+ return this.staffLoader.load(id);
2768
2914
  }
2769
2915
  /** Search for staff (voice actors, directors, etc.). */
2770
2916
  async searchStaff(options = {}) {
@@ -2878,12 +3024,25 @@ var AniListClient = class {
2878
3024
  page++;
2879
3025
  }
2880
3026
  }
3027
+ /**
3028
+ * Utility to fetch all pages of a paginated request and return them as a single array.
3029
+ *
3030
+ * @param fetchPage - A function that takes a page number and returns a `PagedResult<T>`
3031
+ * @param maxPages - Maximum number of pages to fetch (default: Infinity)
3032
+ */
3033
+ async fetchAll(fetchPage, maxPages = Number.POSITIVE_INFINITY) {
3034
+ const results = [];
3035
+ for await (const item of this.paginate(fetchPage, maxPages)) {
3036
+ results.push(item);
3037
+ }
3038
+ return results;
3039
+ }
2881
3040
  /** Fetch multiple media entries in a single API request. */
2882
3041
  async getMediaBatch(ids) {
2883
3042
  if (ids.length === 0) return [];
2884
3043
  validateIds(ids, "mediaId");
2885
3044
  const [singleMediaId] = ids;
2886
- if (ids.length === 1 && singleMediaId !== void 0) return [await this.getMedia(singleMediaId)];
3045
+ if (ids.length === 1 && singleMediaId !== void 0) return [await getMedia(this, singleMediaId)];
2887
3046
  return this.executeBatch(ids, buildBatchMediaQuery, "m");
2888
3047
  }
2889
3048
  /** Fetch multiple characters in a single API request. */
@@ -2891,7 +3050,8 @@ var AniListClient = class {
2891
3050
  if (ids.length === 0) return [];
2892
3051
  validateIds(ids, "characterId");
2893
3052
  const [singleCharId] = ids;
2894
- if (ids.length === 1 && singleCharId !== void 0) return [await this.getCharacter(singleCharId)];
3053
+ if (ids.length === 1 && singleCharId !== void 0)
3054
+ return [await getCharacter(this, singleCharId)];
2895
3055
  return this.executeBatch(ids, buildBatchCharacterQuery, "c");
2896
3056
  }
2897
3057
  /** Fetch multiple staff members in a single API request. */
@@ -2899,7 +3059,7 @@ var AniListClient = class {
2899
3059
  if (ids.length === 0) return [];
2900
3060
  validateIds(ids, "staffId");
2901
3061
  const [singleStaffId] = ids;
2902
- if (ids.length === 1 && singleStaffId !== void 0) return [await this.getStaff(singleStaffId)];
3062
+ if (ids.length === 1 && singleStaffId !== void 0) return [await getStaff(this, singleStaffId)];
2903
3063
  return this.executeBatch(ids, buildBatchStaffQuery, "s");
2904
3064
  }
2905
3065
  /** @internal */
@@ -2969,6 +3129,7 @@ exports.AniListClient = AniListClient;
2969
3129
  exports.AniListError = AniListError;
2970
3130
  exports.CharacterRole = CharacterRole;
2971
3131
  exports.CharacterSort = CharacterSort;
3132
+ exports.Fragments = fragments_exports;
2972
3133
  exports.MediaFormat = MediaFormat;
2973
3134
  exports.MediaListSort = MediaListSort;
2974
3135
  exports.MediaListStatus = MediaListStatus;