ani-client 1.4.1 → 1.4.3

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
@@ -129,6 +129,46 @@ var STAFF_FIELDS = `
129
129
  favourites
130
130
  siteUrl
131
131
  `;
132
+ var STAFF_MEDIA_FIELDS = `
133
+ staffMedia(perPage: $perPage, sort: [POPULARITY_DESC]) {
134
+ nodes {
135
+ id
136
+ title { romaji english native userPreferred }
137
+ type
138
+ format
139
+ status
140
+ coverImage { extraLarge large medium color }
141
+ bannerImage
142
+ genres
143
+ averageScore
144
+ meanScore
145
+ popularity
146
+ favourites
147
+ episodes
148
+ trending
149
+ hashtag
150
+ season
151
+ seasonYear
152
+ startDate { year month day }
153
+ endDate { year month day }
154
+ nextAiringEpisode {
155
+ id
156
+ airingAt
157
+ episode
158
+ mediaId
159
+ timeUntilAiring
160
+ }
161
+ studios {
162
+ edges {
163
+ node {
164
+ name
165
+ }
166
+ }
167
+ }
168
+ siteUrl
169
+ }
170
+ }
171
+ `;
132
172
  var USER_FIELDS = `
133
173
  id
134
174
  name
@@ -230,6 +270,13 @@ query ($id: Int!) {
230
270
  ${STAFF_FIELDS}
231
271
  }
232
272
  }`;
273
+ var QUERY_STAFF_BY_ID_WITH_MEDIA = `
274
+ query ($id: Int!, $perPage: Int) {
275
+ Staff(id: $id) {
276
+ ${STAFF_FIELDS}
277
+ ${STAFF_MEDIA_FIELDS}
278
+ }
279
+ }`;
233
280
  var QUERY_STAFF_SEARCH = `
234
281
  query ($search: String, $sort: [StaffSort], $page: Int, $perPage: Int) {
235
282
  Page(page: $page, perPage: $perPage) {
@@ -582,6 +629,7 @@ var AniListError = class _AniListError extends Error {
582
629
  this.name = "AniListError";
583
630
  this.status = status;
584
631
  this.errors = errors;
632
+ Object.setPrototypeOf(this, _AniListError.prototype);
585
633
  if (Error.captureStackTrace) {
586
634
  Error.captureStackTrace(this, _AniListError);
587
635
  }
@@ -683,7 +731,7 @@ function isNetworkError(err) {
683
731
  return false;
684
732
  }
685
733
 
686
- // src/types/index.ts
734
+ // src/types/media.ts
687
735
  var MediaType = /* @__PURE__ */ ((MediaType2) => {
688
736
  MediaType2["ANIME"] = "ANIME";
689
737
  MediaType2["MANGA"] = "MANGA";
@@ -764,22 +812,6 @@ var AiringSort = /* @__PURE__ */ ((AiringSort2) => {
764
812
  AiringSort2["EPISODE_DESC"] = "EPISODE_DESC";
765
813
  return AiringSort2;
766
814
  })(AiringSort || {});
767
- var CharacterSort = /* @__PURE__ */ ((CharacterSort2) => {
768
- CharacterSort2["ID"] = "ID";
769
- CharacterSort2["ID_DESC"] = "ID_DESC";
770
- CharacterSort2["ROLE"] = "ROLE";
771
- CharacterSort2["ROLE_DESC"] = "ROLE_DESC";
772
- CharacterSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
773
- CharacterSort2["FAVOURITES"] = "FAVOURITES";
774
- CharacterSort2["FAVOURITES_DESC"] = "FAVOURITES_DESC";
775
- return CharacterSort2;
776
- })(CharacterSort || {});
777
- var CharacterRole = /* @__PURE__ */ ((CharacterRole2) => {
778
- CharacterRole2["MAIN"] = "MAIN";
779
- CharacterRole2["SUPPORTING"] = "SUPPORTING";
780
- CharacterRole2["BACKGROUND"] = "BACKGROUND";
781
- return CharacterRole2;
782
- })(CharacterRole || {});
783
815
  var MediaRelationType = /* @__PURE__ */ ((MediaRelationType2) => {
784
816
  MediaRelationType2["ADAPTATION"] = "ADAPTATION";
785
817
  MediaRelationType2["PREQUEL"] = "PREQUEL";
@@ -803,6 +835,26 @@ var RecommendationSort = /* @__PURE__ */ ((RecommendationSort2) => {
803
835
  RecommendationSort2["RATING_DESC"] = "RATING_DESC";
804
836
  return RecommendationSort2;
805
837
  })(RecommendationSort || {});
838
+
839
+ // src/types/character.ts
840
+ var CharacterSort = /* @__PURE__ */ ((CharacterSort2) => {
841
+ CharacterSort2["ID"] = "ID";
842
+ CharacterSort2["ID_DESC"] = "ID_DESC";
843
+ CharacterSort2["ROLE"] = "ROLE";
844
+ CharacterSort2["ROLE_DESC"] = "ROLE_DESC";
845
+ CharacterSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
846
+ CharacterSort2["FAVOURITES"] = "FAVOURITES";
847
+ CharacterSort2["FAVOURITES_DESC"] = "FAVOURITES_DESC";
848
+ return CharacterSort2;
849
+ })(CharacterSort || {});
850
+ var CharacterRole = /* @__PURE__ */ ((CharacterRole2) => {
851
+ CharacterRole2["MAIN"] = "MAIN";
852
+ CharacterRole2["SUPPORTING"] = "SUPPORTING";
853
+ CharacterRole2["BACKGROUND"] = "BACKGROUND";
854
+ return CharacterRole2;
855
+ })(CharacterRole || {});
856
+
857
+ // src/types/lists.ts
806
858
  var MediaListStatus = /* @__PURE__ */ ((MediaListStatus2) => {
807
859
  MediaListStatus2["CURRENT"] = "CURRENT";
808
860
  MediaListStatus2["PLANNING"] = "PLANNING";
@@ -846,6 +898,18 @@ var MediaListSort = /* @__PURE__ */ ((MediaListSort2) => {
846
898
  return MediaListSort2;
847
899
  })(MediaListSort || {});
848
900
 
901
+ // src/utils/index.ts
902
+ function clampPerPage(value) {
903
+ return Math.min(Math.max(value, 1), 50);
904
+ }
905
+ function chunk(arr, size) {
906
+ const chunks = [];
907
+ for (let i = 0; i < arr.length; i += size) {
908
+ chunks.push(arr.slice(i, i + size));
909
+ }
910
+ return chunks;
911
+ }
912
+
849
913
  // src/client/index.ts
850
914
  var DEFAULT_API_URL = "https://graphql.anilist.co";
851
915
  var AniListClient = class {
@@ -888,12 +952,13 @@ var AniListClient = class {
888
952
  async executeRequest(query, variables, cacheKey) {
889
953
  const start = Date.now();
890
954
  this.hooks.onRequest?.(query, variables);
955
+ const minifiedQuery = query.replace(/\s+/g, " ").trim();
891
956
  const res = await this.rateLimiter.fetchWithRetry(
892
957
  this.apiUrl,
893
958
  {
894
959
  method: "POST",
895
960
  headers: this.headers,
896
- body: JSON.stringify({ query, variables })
961
+ body: JSON.stringify({ query: minifiedQuery, variables })
897
962
  },
898
963
  { onRetry: this.hooks.onRetry, onRateLimit: this.hooks.onRateLimit }
899
964
  );
@@ -919,13 +984,6 @@ var AniListClient = class {
919
984
  }
920
985
  return { pageInfo: data.Page.pageInfo, results };
921
986
  }
922
- /**
923
- * @internal
924
- * Clamp perPage to AniList's maximum of 50.
925
- */
926
- clampPerPage(value) {
927
- return Math.min(Math.max(value, 1), 50);
928
- }
929
987
  /**
930
988
  * Fetch a single media entry by its AniList ID.
931
989
  *
@@ -985,7 +1043,7 @@ var AniListClient = class {
985
1043
  const { query: search, page = 1, perPage = 20, ...filters } = options;
986
1044
  return this.pagedRequest(
987
1045
  QUERY_MEDIA_SEARCH,
988
- { search, ...filters, page, perPage: this.clampPerPage(perPage) },
1046
+ { search, ...filters, page, perPage: clampPerPage(perPage) },
989
1047
  "media"
990
1048
  );
991
1049
  }
@@ -997,7 +1055,7 @@ var AniListClient = class {
997
1055
  * @param perPage - Results per page (default 20, max 50)
998
1056
  */
999
1057
  async getTrending(type = "ANIME" /* ANIME */, page = 1, perPage = 20) {
1000
- return this.pagedRequest(QUERY_TRENDING, { type, page, perPage: this.clampPerPage(perPage) }, "media");
1058
+ return this.pagedRequest(QUERY_TRENDING, { type, page, perPage: clampPerPage(perPage) }, "media");
1001
1059
  }
1002
1060
  /**
1003
1061
  * Fetch a character by AniList ID.
@@ -1043,7 +1101,7 @@ var AniListClient = class {
1043
1101
  const gqlQuery = voiceActors ? QUERY_CHARACTER_SEARCH_WITH_VA : QUERY_CHARACTER_SEARCH;
1044
1102
  return this.pagedRequest(
1045
1103
  gqlQuery,
1046
- { search, ...rest, page, perPage: this.clampPerPage(perPage) },
1104
+ { search, ...rest, page, perPage: clampPerPage(perPage) },
1047
1105
  "characters"
1048
1106
  );
1049
1107
  }
@@ -1051,15 +1109,26 @@ var AniListClient = class {
1051
1109
  * Fetch a staff member by AniList ID.
1052
1110
  *
1053
1111
  * @param id - The AniList staff ID
1112
+ * @param include - Optional include options to fetch related data (e.g. media)
1054
1113
  * @returns The staff object
1055
1114
  *
1056
1115
  * @example
1057
1116
  * ```ts
1058
1117
  * const staff = await client.getStaff(95001);
1059
1118
  * console.log(staff.name.full);
1119
+ *
1120
+ * // With media the staff worked on
1121
+ * const staff = await client.getStaff(95001, { media: true });
1122
+ * staff.staffMedia?.nodes.forEach((m) => console.log(m.title.romaji));
1060
1123
  * ```
1061
1124
  */
1062
- async getStaff(id) {
1125
+ async getStaff(id, include) {
1126
+ if (include?.media) {
1127
+ const opts = typeof include.media === "object" ? include.media : {};
1128
+ const perPage = opts.perPage ?? 25;
1129
+ const data2 = await this.request(QUERY_STAFF_BY_ID_WITH_MEDIA, { id, perPage });
1130
+ return data2.Staff;
1131
+ }
1063
1132
  const data = await this.request(QUERY_STAFF_BY_ID, { id });
1064
1133
  return data.Staff;
1065
1134
  }
@@ -1078,7 +1147,7 @@ var AniListClient = class {
1078
1147
  const { query: search, page = 1, perPage = 20, sort } = options;
1079
1148
  return this.pagedRequest(
1080
1149
  QUERY_STAFF_SEARCH,
1081
- { search, sort, page, perPage: this.clampPerPage(perPage) },
1150
+ { search, sort, page, perPage: clampPerPage(perPage) },
1082
1151
  "staff"
1083
1152
  );
1084
1153
  }
@@ -1147,7 +1216,7 @@ var AniListClient = class {
1147
1216
  airingAt_lesser: options.airingAtLesser ?? now,
1148
1217
  sort: options.sort ?? ["TIME_DESC"],
1149
1218
  page: options.page ?? 1,
1150
- perPage: this.clampPerPage(options.perPage ?? 20)
1219
+ perPage: clampPerPage(options.perPage ?? 20)
1151
1220
  };
1152
1221
  return this.pagedRequest(QUERY_AIRING_SCHEDULE, variables, "airingSchedules");
1153
1222
  }
@@ -1170,7 +1239,7 @@ var AniListClient = class {
1170
1239
  QUERY_RECENT_CHAPTERS,
1171
1240
  {
1172
1241
  page: options.page ?? 1,
1173
- perPage: this.clampPerPage(options.perPage ?? 20)
1242
+ perPage: clampPerPage(options.perPage ?? 20)
1174
1243
  },
1175
1244
  "media"
1176
1245
  );
@@ -1196,7 +1265,7 @@ var AniListClient = class {
1196
1265
  type: options.type,
1197
1266
  sort: options.sort ?? ["POPULARITY_DESC"],
1198
1267
  page: options.page ?? 1,
1199
- perPage: this.clampPerPage(options.perPage ?? 20)
1268
+ perPage: clampPerPage(options.perPage ?? 20)
1200
1269
  },
1201
1270
  "media"
1202
1271
  );
@@ -1258,7 +1327,7 @@ var AniListClient = class {
1258
1327
  type: options.type ?? "ANIME",
1259
1328
  sort: options.sort ?? ["POPULARITY_DESC"],
1260
1329
  page: options.page ?? 1,
1261
- perPage: this.clampPerPage(options.perPage ?? 20)
1330
+ perPage: clampPerPage(options.perPage ?? 20)
1262
1331
  },
1263
1332
  "media"
1264
1333
  );
@@ -1300,7 +1369,7 @@ var AniListClient = class {
1300
1369
  status: options.status,
1301
1370
  sort: options.sort,
1302
1371
  page: options.page ?? 1,
1303
- perPage: this.clampPerPage(options.perPage ?? 20)
1372
+ perPage: clampPerPage(options.perPage ?? 20)
1304
1373
  },
1305
1374
  "mediaList"
1306
1375
  );
@@ -1333,7 +1402,7 @@ var AniListClient = class {
1333
1402
  {
1334
1403
  search: options.query,
1335
1404
  page: options.page ?? 1,
1336
- perPage: this.clampPerPage(options.perPage ?? 20)
1405
+ perPage: clampPerPage(options.perPage ?? 20)
1337
1406
  },
1338
1407
  "studios"
1339
1408
  );
@@ -1433,23 +1502,14 @@ var AniListClient = class {
1433
1502
  }
1434
1503
  /** @internal */
1435
1504
  async executeBatch(ids, buildQuery, prefix) {
1436
- const chunks = this.chunk(ids, 50);
1437
- const chunkResults = await Promise.all(
1438
- chunks.map(async (chunk) => {
1439
- const query = buildQuery(chunk);
1440
- const data = await this.request(query);
1441
- return chunk.map((_, i) => data[`${prefix}${i}`]);
1442
- })
1443
- );
1444
- return chunkResults.flat();
1445
- }
1446
- /** @internal */
1447
- chunk(arr, size) {
1448
- const chunks = [];
1449
- for (let i = 0; i < arr.length; i += size) {
1450
- chunks.push(arr.slice(i, i + size));
1505
+ const chunks = chunk(ids, 50);
1506
+ const chunkResults = [];
1507
+ for (const idChunk of chunks) {
1508
+ const query = buildQuery(idChunk);
1509
+ const data = await this.request(query);
1510
+ chunkResults.push(idChunk.map((_, i) => data[`${prefix}${i}`]));
1451
1511
  }
1452
- return chunks;
1512
+ return chunkResults.flat();
1453
1513
  }
1454
1514
  // ── Cache management ──
1455
1515
  /**
@@ -1459,8 +1519,8 @@ var AniListClient = class {
1459
1519
  await this.cacheAdapter.clear();
1460
1520
  }
1461
1521
  /**
1462
- * Number of entries currently in the cache (sync).
1463
- * For async adapters like Redis, this may be approximate.
1522
+ * Number of entries currently in the cache.
1523
+ * For async adapters like Redis, this may return a Promise.
1464
1524
  */
1465
1525
  get cacheSize() {
1466
1526
  return this.cacheAdapter.size;
@@ -1538,15 +1598,14 @@ var RedisCache = class {
1538
1598
  }
1539
1599
  }
1540
1600
  /**
1541
- * Returns -1 because Redis keys can expire silently via TTL.
1542
- * Use `getSize()` for an accurate count.
1601
+ * Get the actual number of keys with this prefix in Redis.
1543
1602
  */
1544
1603
  get size() {
1545
- return -1;
1604
+ return this.getSize();
1546
1605
  }
1547
- /** Get the actual number of keys with this prefix in Redis. */
1606
+ /** @internal */
1548
1607
  async getSize() {
1549
- const keys = await this.client.keys(`${this.prefix}*`);
1608
+ const keys = await this.collectKeys(`${this.prefix}*`);
1550
1609
  return keys.length;
1551
1610
  }
1552
1611
  async keys() {