ani-client 1.8.0 → 1.9.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
@@ -250,6 +250,95 @@ var MemoryCache = class {
250
250
  }
251
251
  };
252
252
 
253
+ // src/cache/redis.ts
254
+ var RedisCache = class {
255
+ client;
256
+ prefix;
257
+ ttl;
258
+ constructor(options) {
259
+ this.client = options.client;
260
+ this.prefix = options.prefix ?? "ani:";
261
+ this.ttl = options.ttl ?? 86400;
262
+ }
263
+ prefixedKey(key) {
264
+ return `${this.prefix}${key}`;
265
+ }
266
+ async get(key) {
267
+ const raw = await this.client.get(this.prefixedKey(key));
268
+ if (raw === null) return void 0;
269
+ try {
270
+ return JSON.parse(raw);
271
+ } catch {
272
+ return void 0;
273
+ }
274
+ }
275
+ async set(key, data) {
276
+ await this.client.set(this.prefixedKey(key), JSON.stringify(data), "EX", this.ttl);
277
+ }
278
+ async delete(key) {
279
+ const count = await this.client.del(this.prefixedKey(key));
280
+ return count > 0;
281
+ }
282
+ /**
283
+ * Collect keys matching a pattern. Uses SCAN when available, falls back to KEYS.
284
+ *
285
+ * **Warning:** The `KEYS` fallback is O(N) and blocks the Redis server.
286
+ * Provide a client with `scanIterator` support for production use.
287
+ * @internal
288
+ */
289
+ async collectKeys(pattern) {
290
+ if (this.client.scanIterator) {
291
+ const keys = [];
292
+ for await (const key of this.client.scanIterator({ MATCH: pattern, COUNT: 100 })) {
293
+ keys.push(key);
294
+ }
295
+ return keys;
296
+ }
297
+ return this.client.keys(pattern);
298
+ }
299
+ async clear() {
300
+ const keys = await this.collectKeys(`${this.prefix}*`);
301
+ if (keys.length > 0) {
302
+ await this.client.del(...keys);
303
+ }
304
+ }
305
+ /**
306
+ * Get the actual number of keys with this prefix in Redis.
307
+ */
308
+ get size() {
309
+ return this.getSize();
310
+ }
311
+ /** @internal */
312
+ async getSize() {
313
+ const keys = await this.collectKeys(`${this.prefix}*`);
314
+ return keys.length;
315
+ }
316
+ async keys() {
317
+ const raw = await this.collectKeys(`${this.prefix}*`);
318
+ return raw.map((k) => k.slice(this.prefix.length));
319
+ }
320
+ /**
321
+ * Remove all entries whose key matches the given pattern.
322
+ *
323
+ * - **String**: treated as a substring match (e.g. `"Media"` removes all keys containing `"Media"`).
324
+ * - **RegExp**: tested against each key directly.
325
+ *
326
+ * @param pattern — A string (substring match) or RegExp.
327
+ * @returns Number of entries removed.
328
+ */
329
+ async invalidate(pattern) {
330
+ if (typeof pattern === "string") {
331
+ const keys = await this.collectKeys(`${this.prefix}*${pattern}*`);
332
+ if (keys.length === 0) return 0;
333
+ return this.client.del(...keys);
334
+ }
335
+ const allKeys = await this.collectKeys(`${this.prefix}*`);
336
+ const matching = allKeys.filter((k) => pattern.test(k.slice(this.prefix.length)));
337
+ if (matching.length === 0) return 0;
338
+ return this.client.del(...matching);
339
+ }
340
+ };
341
+
253
342
  // src/errors/index.ts
254
343
  var AniListError = class _AniListError extends Error {
255
344
  /** HTTP status code returned by the API */
@@ -624,8 +713,10 @@ query ($id: Int!) {
624
713
  var QUERY_MEDIA_SEARCH = `
625
714
  query (
626
715
  $search: String,
716
+ $countryOfOrigin: CountryCode,
627
717
  $type: MediaType,
628
718
  $format: MediaFormat,
719
+ $format_in: [MediaFormat],
629
720
  $status: MediaStatus,
630
721
  $season: MediaSeason,
631
722
  $seasonYear: Int,
@@ -644,8 +735,10 @@ query (
644
735
  pageInfo { total perPage currentPage lastPage hasNextPage }
645
736
  media(
646
737
  search: $search,
738
+ countryOfOrigin: $countryOfOrigin,
647
739
  type: $type,
648
740
  format: $format,
741
+ format_in: $format_in,
649
742
  status: $status,
650
743
  season: $season,
651
744
  seasonYear: $seasonYear,
@@ -755,6 +848,111 @@ query ($mediaId: Int!, $page: Int, $perPage: Int, $sort: [RecommendationSort]) {
755
848
  }
756
849
  }`;
757
850
 
851
+ // src/queries/builders.ts
852
+ function buildMediaByIdQuery(include) {
853
+ if (!include) return QUERY_MEDIA_BY_ID;
854
+ const extra = [];
855
+ if (include.relations !== false) {
856
+ extra.push(RELATIONS_FIELDS);
857
+ }
858
+ if (include.characters) {
859
+ const opts = typeof include.characters === "object" ? include.characters : {};
860
+ const perPage = clampPerPage(opts.perPage ?? 25);
861
+ const sortClause = opts.sort !== false ? ", sort: [ROLE, RELEVANCE, ID]" : "";
862
+ const voiceActorBlock = opts.voiceActors ? `
863
+ voiceActors {
864
+ ${VOICE_ACTOR_FIELDS_COMPACT}
865
+ }` : "";
866
+ extra.push(`
867
+ characters(perPage: ${perPage}${sortClause}) {
868
+ edges {
869
+ role
870
+ node {
871
+ ${CHARACTER_FIELDS_COMPACT}
872
+ }${voiceActorBlock}
873
+ }
874
+ }`);
875
+ }
876
+ if (include.staff) {
877
+ const opts = typeof include.staff === "object" ? include.staff : {};
878
+ const perPage = clampPerPage(opts.perPage ?? 25);
879
+ const sortClause = opts.sort !== false ? ", sort: [RELEVANCE, ID]" : "";
880
+ extra.push(`
881
+ staff(perPage: ${perPage}${sortClause}) {
882
+ edges {
883
+ role
884
+ node {
885
+ ${STAFF_FIELDS}
886
+ }
887
+ }
888
+ }`);
889
+ }
890
+ if (include.recommendations) {
891
+ const perPage = clampPerPage(
892
+ typeof include.recommendations === "object" ? include.recommendations.perPage ?? 10 : 10
893
+ );
894
+ extra.push(`
895
+ recommendations(perPage: ${perPage}, sort: [RATING_DESC]) {
896
+ nodes {
897
+ id
898
+ rating
899
+ mediaRecommendation {
900
+ id
901
+ title { romaji english native userPreferred }
902
+ type
903
+ format
904
+ coverImage { large medium }
905
+ averageScore
906
+ siteUrl
907
+ }
908
+ }
909
+ }`);
910
+ }
911
+ if (include.streamingEpisodes) {
912
+ extra.push(`
913
+ streamingEpisodes {
914
+ title
915
+ thumbnail
916
+ url
917
+ site
918
+ }`);
919
+ }
920
+ if (include.externalLinks) {
921
+ extra.push(`
922
+ externalLinks {
923
+ id
924
+ url
925
+ site
926
+ type
927
+ icon
928
+ color
929
+ }`);
930
+ }
931
+ if (include.stats) {
932
+ extra.push(`
933
+ stats {
934
+ scoreDistribution { score amount }
935
+ statusDistribution { status amount }
936
+ }`);
937
+ }
938
+ return `
939
+ query ($id: Int!) {
940
+ Media(id: $id) {
941
+ ${MEDIA_FIELDS_BASE}
942
+ ${extra.join("\n")}
943
+ }
944
+ }`;
945
+ }
946
+ function buildBatchQuery(ids, typeName, fields, prefix) {
947
+ const aliases = ids.map((id, i) => `${prefix}${i}: ${typeName}(id: ${id}) { ${fields} }`).join("\n ");
948
+ return `query {
949
+ ${aliases}
950
+ }`;
951
+ }
952
+ var buildBatchMediaQuery = (ids) => buildBatchQuery(ids, "Media", MEDIA_FIELDS, "m");
953
+ var buildBatchCharacterQuery = (ids) => buildBatchQuery(ids, "Character", CHARACTER_FIELDS, "c");
954
+ var buildBatchStaffQuery = (ids) => buildBatchQuery(ids, "Staff", STAFF_FIELDS, "s");
955
+
758
956
  // src/queries/character.ts
759
957
  var QUERY_CHARACTER_BY_ID = `
760
958
  query ($id: Int!) {
@@ -787,6 +985,22 @@ query ($search: String, $sort: [CharacterSort], $page: Int, $perPage: Int) {
787
985
  }
788
986
  }`;
789
987
 
988
+ // src/queries/metadata.ts
989
+ var QUERY_GENRES = `
990
+ query {
991
+ GenreCollection
992
+ }`;
993
+ var QUERY_TAGS = `
994
+ query {
995
+ MediaTagCollection {
996
+ id
997
+ name
998
+ description
999
+ category
1000
+ isAdult
1001
+ }
1002
+ }`;
1003
+
790
1004
  // src/queries/staff.ts
791
1005
  var QUERY_STAFF_BY_ID = `
792
1006
  query ($id: Int!) {
@@ -811,6 +1025,60 @@ query ($search: String, $sort: [StaffSort], $page: Int, $perPage: Int) {
811
1025
  }
812
1026
  }`;
813
1027
 
1028
+ // src/queries/studio.ts
1029
+ var QUERY_STUDIO_BY_ID = `
1030
+ query ($id: Int!) {
1031
+ Studio(id: $id) {
1032
+ ${STUDIO_FIELDS}
1033
+ }
1034
+ }`;
1035
+ function buildStudioByIdQuery(mediaPerPage) {
1036
+ if (mediaPerPage === void 0) return QUERY_STUDIO_BY_ID;
1037
+ const pp = clampPerPage(mediaPerPage);
1038
+ return `
1039
+ query ($id: Int!) {
1040
+ Studio(id: $id) {
1041
+ id
1042
+ name
1043
+ isAnimationStudio
1044
+ siteUrl
1045
+ favourites
1046
+ media(page: 1, perPage: ${pp}, sort: POPULARITY_DESC) {
1047
+ pageInfo { total perPage currentPage lastPage hasNextPage }
1048
+ nodes {
1049
+ ${MEDIA_FIELDS_LIGHT}
1050
+ }
1051
+ }
1052
+ }
1053
+ }`;
1054
+ }
1055
+ var QUERY_STUDIO_SEARCH = `
1056
+ query ($search: String, $sort: [StudioSort], $page: Int, $perPage: Int) {
1057
+ Page(page: $page, perPage: $perPage) {
1058
+ pageInfo { total perPage currentPage lastPage hasNextPage }
1059
+ studios(search: $search, sort: $sort) {
1060
+ ${STUDIO_FIELDS}
1061
+ }
1062
+ }
1063
+ }`;
1064
+
1065
+ // src/queries/thread.ts
1066
+ var QUERY_THREAD_BY_ID = `
1067
+ query ($id: Int!) {
1068
+ Thread(id: $id) {
1069
+ ${THREAD_FIELDS}
1070
+ }
1071
+ }`;
1072
+ var QUERY_THREAD_SEARCH = `
1073
+ query ($search: String, $mediaCategoryId: Int, $categoryId: Int, $sort: [ThreadSort], $page: Int, $perPage: Int) {
1074
+ Page(page: $page, perPage: $perPage) {
1075
+ pageInfo { total perPage currentPage lastPage hasNextPage }
1076
+ threads(search: $search, mediaCategoryId: $mediaCategoryId, categoryId: $categoryId, sort: $sort) {
1077
+ ${THREAD_FIELDS}
1078
+ }
1079
+ }
1080
+ }`;
1081
+
814
1082
  // src/queries/user.ts
815
1083
  var QUERY_USER_BY_ID = `
816
1084
  query ($id: Int!) {
@@ -897,199 +1165,24 @@ query (${varDecl}) {
897
1165
  }
898
1166
  }
899
1167
  staff(perPage: ${pp}) {
900
- nodes {
901
- id
902
- name { full native }
903
- image { large medium }
904
- siteUrl
905
- }
906
- }
907
- studios(perPage: ${pp}) {
908
- nodes {
909
- id
910
- name
911
- siteUrl
912
- }
913
- }
914
- }
915
- }
916
- }`;
917
- }
918
-
919
- // src/queries/studio.ts
920
- var QUERY_STUDIO_BY_ID = `
921
- query ($id: Int!) {
922
- Studio(id: $id) {
923
- ${STUDIO_FIELDS}
924
- }
925
- }`;
926
- function buildStudioByIdQuery(mediaPerPage) {
927
- if (mediaPerPage === void 0) return QUERY_STUDIO_BY_ID;
928
- const pp = clampPerPage(mediaPerPage);
929
- return `
930
- query ($id: Int!) {
931
- Studio(id: $id) {
932
- id
933
- name
934
- isAnimationStudio
935
- siteUrl
936
- favourites
937
- media(page: 1, perPage: ${pp}, sort: POPULARITY_DESC) {
938
- pageInfo { total perPage currentPage lastPage hasNextPage }
939
- nodes {
940
- ${MEDIA_FIELDS_LIGHT}
941
- }
942
- }
943
- }
944
- }`;
945
- }
946
- var QUERY_STUDIO_SEARCH = `
947
- query ($search: String, $sort: [StudioSort], $page: Int, $perPage: Int) {
948
- Page(page: $page, perPage: $perPage) {
949
- pageInfo { total perPage currentPage lastPage hasNextPage }
950
- studios(search: $search, sort: $sort) {
951
- ${STUDIO_FIELDS}
952
- }
953
- }
954
- }`;
955
-
956
- // src/queries/metadata.ts
957
- var QUERY_GENRES = `
958
- query {
959
- GenreCollection
960
- }`;
961
- var QUERY_TAGS = `
962
- query {
963
- MediaTagCollection {
964
- id
965
- name
966
- description
967
- category
968
- isAdult
969
- }
970
- }`;
971
-
972
- // src/queries/builders.ts
973
- function buildMediaByIdQuery(include) {
974
- if (!include) return QUERY_MEDIA_BY_ID;
975
- const extra = [];
976
- if (include.relations !== false) {
977
- extra.push(RELATIONS_FIELDS);
978
- }
979
- if (include.characters) {
980
- const opts = typeof include.characters === "object" ? include.characters : {};
981
- const perPage = clampPerPage(opts.perPage ?? 25);
982
- const sortClause = opts.sort !== false ? ", sort: [ROLE, RELEVANCE, ID]" : "";
983
- const voiceActorBlock = opts.voiceActors ? `
984
- voiceActors {
985
- ${VOICE_ACTOR_FIELDS_COMPACT}
986
- }` : "";
987
- extra.push(`
988
- characters(perPage: ${perPage}${sortClause}) {
989
- edges {
990
- role
991
- node {
992
- ${CHARACTER_FIELDS_COMPACT}
993
- }${voiceActorBlock}
994
- }
995
- }`);
996
- }
997
- if (include.staff) {
998
- const opts = typeof include.staff === "object" ? include.staff : {};
999
- const perPage = clampPerPage(opts.perPage ?? 25);
1000
- const sortClause = opts.sort !== false ? ", sort: [RELEVANCE, ID]" : "";
1001
- extra.push(`
1002
- staff(perPage: ${perPage}${sortClause}) {
1003
- edges {
1004
- role
1005
- node {
1006
- ${STAFF_FIELDS}
1007
- }
1008
- }
1009
- }`);
1010
- }
1011
- if (include.recommendations) {
1012
- const perPage = clampPerPage(
1013
- typeof include.recommendations === "object" ? include.recommendations.perPage ?? 10 : 10
1014
- );
1015
- extra.push(`
1016
- recommendations(perPage: ${perPage}, sort: [RATING_DESC]) {
1017
- nodes {
1018
- id
1019
- rating
1020
- mediaRecommendation {
1021
- id
1022
- title { romaji english native userPreferred }
1023
- type
1024
- format
1025
- coverImage { large medium }
1026
- averageScore
1027
- siteUrl
1028
- }
1029
- }
1030
- }`);
1031
- }
1032
- if (include.streamingEpisodes) {
1033
- extra.push(`
1034
- streamingEpisodes {
1035
- title
1036
- thumbnail
1037
- url
1038
- site
1039
- }`);
1040
- }
1041
- if (include.externalLinks) {
1042
- extra.push(`
1043
- externalLinks {
1044
- id
1045
- url
1046
- site
1047
- type
1048
- icon
1049
- color
1050
- }`);
1051
- }
1052
- if (include.stats) {
1053
- extra.push(`
1054
- stats {
1055
- scoreDistribution { score amount }
1056
- statusDistribution { status amount }
1057
- }`);
1058
- }
1059
- return `
1060
- query ($id: Int!) {
1061
- Media(id: $id) {
1062
- ${MEDIA_FIELDS_BASE}
1063
- ${extra.join("\n")}
1064
- }
1065
- }`;
1066
- }
1067
- function buildBatchQuery(ids, typeName, fields, prefix) {
1068
- const aliases = ids.map((id, i) => `${prefix}${i}: ${typeName}(id: ${id}) { ${fields} }`).join("\n ");
1069
- return `query {
1070
- ${aliases}
1071
- }`;
1072
- }
1073
- var buildBatchMediaQuery = (ids) => buildBatchQuery(ids, "Media", MEDIA_FIELDS, "m");
1074
- var buildBatchCharacterQuery = (ids) => buildBatchQuery(ids, "Character", CHARACTER_FIELDS, "c");
1075
- var buildBatchStaffQuery = (ids) => buildBatchQuery(ids, "Staff", STAFF_FIELDS, "s");
1076
-
1077
- // src/queries/thread.ts
1078
- var QUERY_THREAD_BY_ID = `
1079
- query ($id: Int!) {
1080
- Thread(id: $id) {
1081
- ${THREAD_FIELDS}
1082
- }
1083
- }`;
1084
- var QUERY_THREAD_SEARCH = `
1085
- query ($search: String, $mediaCategoryId: Int, $categoryId: Int, $sort: [ThreadSort], $page: Int, $perPage: Int) {
1086
- Page(page: $page, perPage: $perPage) {
1087
- pageInfo { total perPage currentPage lastPage hasNextPage }
1088
- threads(search: $search, mediaCategoryId: $mediaCategoryId, categoryId: $categoryId, sort: $sort) {
1089
- ${THREAD_FIELDS}
1168
+ nodes {
1169
+ id
1170
+ name { full native }
1171
+ image { large medium }
1172
+ siteUrl
1173
+ }
1174
+ }
1175
+ studios(perPage: ${pp}) {
1176
+ nodes {
1177
+ id
1178
+ name
1179
+ siteUrl
1180
+ }
1181
+ }
1090
1182
  }
1091
1183
  }
1092
1184
  }`;
1185
+ }
1093
1186
 
1094
1187
  // src/rate-limiter/index.ts
1095
1188
  var RateLimiter = class {
@@ -1173,7 +1266,7 @@ var RateLimiter = class {
1173
1266
  }
1174
1267
  }
1175
1268
  if (lastResponse) return lastResponse;
1176
- throw lastError;
1269
+ throw lastError ?? new Error(`Request failed after ${this.maxRetries} retries`);
1177
1270
  }
1178
1271
  /** @internal — Exponential backoff with jitter, capped at 30s (or custom strategy) */
1179
1272
  exponentialDelay(attempt) {
@@ -1248,6 +1341,68 @@ async function searchCharacters(client, options = {}) {
1248
1341
  return client.pagedRequest(gqlQuery, { search, sort, page, perPage: clampPerPage(perPage) }, "characters");
1249
1342
  }
1250
1343
 
1344
+ // src/types/character.ts
1345
+ var CharacterSort = /* @__PURE__ */ ((CharacterSort2) => {
1346
+ CharacterSort2["ID"] = "ID";
1347
+ CharacterSort2["ID_DESC"] = "ID_DESC";
1348
+ CharacterSort2["ROLE"] = "ROLE";
1349
+ CharacterSort2["ROLE_DESC"] = "ROLE_DESC";
1350
+ CharacterSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
1351
+ CharacterSort2["FAVOURITES"] = "FAVOURITES";
1352
+ CharacterSort2["FAVOURITES_DESC"] = "FAVOURITES_DESC";
1353
+ return CharacterSort2;
1354
+ })(CharacterSort || {});
1355
+ var CharacterRole = /* @__PURE__ */ ((CharacterRole2) => {
1356
+ CharacterRole2["MAIN"] = "MAIN";
1357
+ CharacterRole2["SUPPORTING"] = "SUPPORTING";
1358
+ CharacterRole2["BACKGROUND"] = "BACKGROUND";
1359
+ return CharacterRole2;
1360
+ })(CharacterRole || {});
1361
+
1362
+ // src/types/lists.ts
1363
+ var MediaListStatus = /* @__PURE__ */ ((MediaListStatus2) => {
1364
+ MediaListStatus2["CURRENT"] = "CURRENT";
1365
+ MediaListStatus2["PLANNING"] = "PLANNING";
1366
+ MediaListStatus2["COMPLETED"] = "COMPLETED";
1367
+ MediaListStatus2["DROPPED"] = "DROPPED";
1368
+ MediaListStatus2["PAUSED"] = "PAUSED";
1369
+ MediaListStatus2["REPEATING"] = "REPEATING";
1370
+ return MediaListStatus2;
1371
+ })(MediaListStatus || {});
1372
+ var MediaListSort = /* @__PURE__ */ ((MediaListSort2) => {
1373
+ MediaListSort2["MEDIA_ID"] = "MEDIA_ID";
1374
+ MediaListSort2["MEDIA_ID_DESC"] = "MEDIA_ID_DESC";
1375
+ MediaListSort2["SCORE"] = "SCORE";
1376
+ MediaListSort2["SCORE_DESC"] = "SCORE_DESC";
1377
+ MediaListSort2["STATUS"] = "STATUS";
1378
+ MediaListSort2["STATUS_DESC"] = "STATUS_DESC";
1379
+ MediaListSort2["PROGRESS"] = "PROGRESS";
1380
+ MediaListSort2["PROGRESS_DESC"] = "PROGRESS_DESC";
1381
+ MediaListSort2["PROGRESS_VOLUMES"] = "PROGRESS_VOLUMES";
1382
+ MediaListSort2["PROGRESS_VOLUMES_DESC"] = "PROGRESS_VOLUMES_DESC";
1383
+ MediaListSort2["REPEAT"] = "REPEAT";
1384
+ MediaListSort2["REPEAT_DESC"] = "REPEAT_DESC";
1385
+ MediaListSort2["PRIORITY"] = "PRIORITY";
1386
+ MediaListSort2["PRIORITY_DESC"] = "PRIORITY_DESC";
1387
+ MediaListSort2["STARTED_ON"] = "STARTED_ON";
1388
+ MediaListSort2["STARTED_ON_DESC"] = "STARTED_ON_DESC";
1389
+ MediaListSort2["FINISHED_ON"] = "FINISHED_ON";
1390
+ MediaListSort2["FINISHED_ON_DESC"] = "FINISHED_ON_DESC";
1391
+ MediaListSort2["ADDED_TIME"] = "ADDED_TIME";
1392
+ MediaListSort2["ADDED_TIME_DESC"] = "ADDED_TIME_DESC";
1393
+ MediaListSort2["UPDATED_TIME"] = "UPDATED_TIME";
1394
+ MediaListSort2["UPDATED_TIME_DESC"] = "UPDATED_TIME_DESC";
1395
+ MediaListSort2["MEDIA_TITLE_ROMAJI"] = "MEDIA_TITLE_ROMAJI";
1396
+ MediaListSort2["MEDIA_TITLE_ROMAJI_DESC"] = "MEDIA_TITLE_ROMAJI_DESC";
1397
+ MediaListSort2["MEDIA_TITLE_ENGLISH"] = "MEDIA_TITLE_ENGLISH";
1398
+ MediaListSort2["MEDIA_TITLE_ENGLISH_DESC"] = "MEDIA_TITLE_ENGLISH_DESC";
1399
+ MediaListSort2["MEDIA_TITLE_NATIVE"] = "MEDIA_TITLE_NATIVE";
1400
+ MediaListSort2["MEDIA_TITLE_NATIVE_DESC"] = "MEDIA_TITLE_NATIVE_DESC";
1401
+ MediaListSort2["MEDIA_POPULARITY"] = "MEDIA_POPULARITY";
1402
+ MediaListSort2["MEDIA_POPULARITY_DESC"] = "MEDIA_POPULARITY_DESC";
1403
+ return MediaListSort2;
1404
+ })(MediaListSort || {});
1405
+
1251
1406
  // src/types/media.ts
1252
1407
  var MediaType = /* @__PURE__ */ ((MediaType2) => {
1253
1408
  MediaType2["ANIME"] = "ANIME";
@@ -1371,24 +1526,6 @@ var RecommendationSort = /* @__PURE__ */ ((RecommendationSort2) => {
1371
1526
  return RecommendationSort2;
1372
1527
  })(RecommendationSort || {});
1373
1528
 
1374
- // src/types/character.ts
1375
- var CharacterSort = /* @__PURE__ */ ((CharacterSort2) => {
1376
- CharacterSort2["ID"] = "ID";
1377
- CharacterSort2["ID_DESC"] = "ID_DESC";
1378
- CharacterSort2["ROLE"] = "ROLE";
1379
- CharacterSort2["ROLE_DESC"] = "ROLE_DESC";
1380
- CharacterSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
1381
- CharacterSort2["FAVOURITES"] = "FAVOURITES";
1382
- CharacterSort2["FAVOURITES_DESC"] = "FAVOURITES_DESC";
1383
- return CharacterSort2;
1384
- })(CharacterSort || {});
1385
- var CharacterRole = /* @__PURE__ */ ((CharacterRole2) => {
1386
- CharacterRole2["MAIN"] = "MAIN";
1387
- CharacterRole2["SUPPORTING"] = "SUPPORTING";
1388
- CharacterRole2["BACKGROUND"] = "BACKGROUND";
1389
- return CharacterRole2;
1390
- })(CharacterRole || {});
1391
-
1392
1529
  // src/types/staff.ts
1393
1530
  var StaffSort = /* @__PURE__ */ ((StaffSort2) => {
1394
1531
  StaffSort2["ID"] = "ID";
@@ -1404,20 +1541,6 @@ var StaffSort = /* @__PURE__ */ ((StaffSort2) => {
1404
1541
  return StaffSort2;
1405
1542
  })(StaffSort || {});
1406
1543
 
1407
- // src/types/user.ts
1408
- var UserSort = /* @__PURE__ */ ((UserSort2) => {
1409
- UserSort2["ID"] = "ID";
1410
- UserSort2["ID_DESC"] = "ID_DESC";
1411
- UserSort2["USERNAME"] = "USERNAME";
1412
- UserSort2["USERNAME_DESC"] = "USERNAME_DESC";
1413
- UserSort2["WATCHED_TIME"] = "WATCHED_TIME";
1414
- UserSort2["WATCHED_TIME_DESC"] = "WATCHED_TIME_DESC";
1415
- UserSort2["CHAPTERS_READ"] = "CHAPTERS_READ";
1416
- UserSort2["CHAPTERS_READ_DESC"] = "CHAPTERS_READ_DESC";
1417
- UserSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
1418
- return UserSort2;
1419
- })(UserSort || {});
1420
-
1421
1544
  // src/types/studio.ts
1422
1545
  var StudioSort = /* @__PURE__ */ ((StudioSort2) => {
1423
1546
  StudioSort2["ID"] = "ID";
@@ -1430,50 +1553,6 @@ var StudioSort = /* @__PURE__ */ ((StudioSort2) => {
1430
1553
  return StudioSort2;
1431
1554
  })(StudioSort || {});
1432
1555
 
1433
- // src/types/lists.ts
1434
- var MediaListStatus = /* @__PURE__ */ ((MediaListStatus2) => {
1435
- MediaListStatus2["CURRENT"] = "CURRENT";
1436
- MediaListStatus2["PLANNING"] = "PLANNING";
1437
- MediaListStatus2["COMPLETED"] = "COMPLETED";
1438
- MediaListStatus2["DROPPED"] = "DROPPED";
1439
- MediaListStatus2["PAUSED"] = "PAUSED";
1440
- MediaListStatus2["REPEATING"] = "REPEATING";
1441
- return MediaListStatus2;
1442
- })(MediaListStatus || {});
1443
- var MediaListSort = /* @__PURE__ */ ((MediaListSort2) => {
1444
- MediaListSort2["MEDIA_ID"] = "MEDIA_ID";
1445
- MediaListSort2["MEDIA_ID_DESC"] = "MEDIA_ID_DESC";
1446
- MediaListSort2["SCORE"] = "SCORE";
1447
- MediaListSort2["SCORE_DESC"] = "SCORE_DESC";
1448
- MediaListSort2["STATUS"] = "STATUS";
1449
- MediaListSort2["STATUS_DESC"] = "STATUS_DESC";
1450
- MediaListSort2["PROGRESS"] = "PROGRESS";
1451
- MediaListSort2["PROGRESS_DESC"] = "PROGRESS_DESC";
1452
- MediaListSort2["PROGRESS_VOLUMES"] = "PROGRESS_VOLUMES";
1453
- MediaListSort2["PROGRESS_VOLUMES_DESC"] = "PROGRESS_VOLUMES_DESC";
1454
- MediaListSort2["REPEAT"] = "REPEAT";
1455
- MediaListSort2["REPEAT_DESC"] = "REPEAT_DESC";
1456
- MediaListSort2["PRIORITY"] = "PRIORITY";
1457
- MediaListSort2["PRIORITY_DESC"] = "PRIORITY_DESC";
1458
- MediaListSort2["STARTED_ON"] = "STARTED_ON";
1459
- MediaListSort2["STARTED_ON_DESC"] = "STARTED_ON_DESC";
1460
- MediaListSort2["FINISHED_ON"] = "FINISHED_ON";
1461
- MediaListSort2["FINISHED_ON_DESC"] = "FINISHED_ON_DESC";
1462
- MediaListSort2["ADDED_TIME"] = "ADDED_TIME";
1463
- MediaListSort2["ADDED_TIME_DESC"] = "ADDED_TIME_DESC";
1464
- MediaListSort2["UPDATED_TIME"] = "UPDATED_TIME";
1465
- MediaListSort2["UPDATED_TIME_DESC"] = "UPDATED_TIME_DESC";
1466
- MediaListSort2["MEDIA_TITLE_ROMAJI"] = "MEDIA_TITLE_ROMAJI";
1467
- MediaListSort2["MEDIA_TITLE_ROMAJI_DESC"] = "MEDIA_TITLE_ROMAJI_DESC";
1468
- MediaListSort2["MEDIA_TITLE_ENGLISH"] = "MEDIA_TITLE_ENGLISH";
1469
- MediaListSort2["MEDIA_TITLE_ENGLISH_DESC"] = "MEDIA_TITLE_ENGLISH_DESC";
1470
- MediaListSort2["MEDIA_TITLE_NATIVE"] = "MEDIA_TITLE_NATIVE";
1471
- MediaListSort2["MEDIA_TITLE_NATIVE_DESC"] = "MEDIA_TITLE_NATIVE_DESC";
1472
- MediaListSort2["MEDIA_POPULARITY"] = "MEDIA_POPULARITY";
1473
- MediaListSort2["MEDIA_POPULARITY_DESC"] = "MEDIA_POPULARITY_DESC";
1474
- return MediaListSort2;
1475
- })(MediaListSort || {});
1476
-
1477
1556
  // src/types/thread.ts
1478
1557
  var ThreadSort = /* @__PURE__ */ ((ThreadSort2) => {
1479
1558
  ThreadSort2["ID"] = "ID";
@@ -1495,6 +1574,20 @@ var ThreadSort = /* @__PURE__ */ ((ThreadSort2) => {
1495
1574
  return ThreadSort2;
1496
1575
  })(ThreadSort || {});
1497
1576
 
1577
+ // src/types/user.ts
1578
+ var UserSort = /* @__PURE__ */ ((UserSort2) => {
1579
+ UserSort2["ID"] = "ID";
1580
+ UserSort2["ID_DESC"] = "ID_DESC";
1581
+ UserSort2["USERNAME"] = "USERNAME";
1582
+ UserSort2["USERNAME_DESC"] = "USERNAME_DESC";
1583
+ UserSort2["WATCHED_TIME"] = "WATCHED_TIME";
1584
+ UserSort2["WATCHED_TIME_DESC"] = "WATCHED_TIME_DESC";
1585
+ UserSort2["CHAPTERS_READ"] = "CHAPTERS_READ";
1586
+ UserSort2["CHAPTERS_READ_DESC"] = "CHAPTERS_READ_DESC";
1587
+ UserSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
1588
+ return UserSort2;
1589
+ })(UserSort || {});
1590
+
1498
1591
  // src/client/media.ts
1499
1592
  async function getMedia(client, id, include) {
1500
1593
  validateId(id, "mediaId");
@@ -1511,12 +1604,24 @@ async function getMediaByMalId(client, malId, type) {
1511
1604
  return data.Media;
1512
1605
  }
1513
1606
  async function searchMedia(client, options = {}) {
1514
- const { query: search, page = 1, perPage = 20, genres, tags, genresExclude, tagsExclude, ...filters } = options;
1607
+ const {
1608
+ query: search,
1609
+ page = 1,
1610
+ perPage = 20,
1611
+ genres,
1612
+ tags,
1613
+ genresExclude,
1614
+ tagsExclude,
1615
+ format,
1616
+ ...filters
1617
+ } = options;
1515
1618
  return client.pagedRequest(
1516
1619
  QUERY_MEDIA_SEARCH,
1517
1620
  {
1518
1621
  search,
1519
1622
  ...filters,
1623
+ format: Array.isArray(format) ? void 0 : format,
1624
+ format_in: Array.isArray(format) ? format : void 0,
1520
1625
  genre_in: genres,
1521
1626
  tag_in: tags,
1522
1627
  genre_not_in: genresExclude,
@@ -1719,7 +1824,7 @@ async function searchUsers(client, options = {}) {
1719
1824
  }
1720
1825
  async function getUserMediaList(client, options) {
1721
1826
  if (!options.userId && !options.userName) {
1722
- throw new AniListError("getUserMediaList requires either userId or userName", 0, []);
1827
+ throw new TypeError("getUserMediaList requires either userId or userName");
1723
1828
  }
1724
1829
  if (options.userId) {
1725
1830
  validateId(options.userId, "userId");
@@ -1766,7 +1871,7 @@ function mapFavorites(fav) {
1766
1871
 
1767
1872
  // src/client/index.ts
1768
1873
  var DEFAULT_API_URL = "https://graphql.anilist.co";
1769
- var LIB_VERSION = "1.8.0" ;
1874
+ var LIB_VERSION = "1.9.0" ;
1770
1875
  var AniListClient = class {
1771
1876
  apiUrl;
1772
1877
  headers;
@@ -1854,7 +1959,15 @@ var AniListClient = class {
1854
1959
  this.hooks.onError?.(error, query, variables);
1855
1960
  throw error;
1856
1961
  }
1857
- const json = await res.json();
1962
+ let json;
1963
+ try {
1964
+ json = await res.json();
1965
+ } catch {
1966
+ const error = new AniListError(`Non-JSON response from AniList (HTTP ${res.status})`, res.status, []);
1967
+ this.logger?.error("Request failed", { error: error.message, status: error.status });
1968
+ this.hooks.onError?.(error, query, variables);
1969
+ throw error;
1970
+ }
1858
1971
  if (!res.ok || json.errors) {
1859
1972
  const message = json.errors?.[0]?.message ?? `AniList API error (HTTP ${res.status})`;
1860
1973
  const error = new AniListError(message, res.status, json.errors ?? []);
@@ -2158,92 +2271,6 @@ var AniListClient = class {
2158
2271
  }
2159
2272
  };
2160
2273
 
2161
- // src/cache/redis.ts
2162
- var RedisCache = class {
2163
- client;
2164
- prefix;
2165
- ttl;
2166
- constructor(options) {
2167
- this.client = options.client;
2168
- this.prefix = options.prefix ?? "ani:";
2169
- this.ttl = options.ttl ?? 86400;
2170
- }
2171
- prefixedKey(key) {
2172
- return `${this.prefix}${key}`;
2173
- }
2174
- async get(key) {
2175
- const raw = await this.client.get(this.prefixedKey(key));
2176
- if (raw === null) return void 0;
2177
- try {
2178
- return JSON.parse(raw);
2179
- } catch {
2180
- return void 0;
2181
- }
2182
- }
2183
- async set(key, data) {
2184
- await this.client.set(this.prefixedKey(key), JSON.stringify(data), "EX", this.ttl);
2185
- }
2186
- async delete(key) {
2187
- const count = await this.client.del(this.prefixedKey(key));
2188
- return count > 0;
2189
- }
2190
- /**
2191
- * Collect keys matching a pattern. Uses SCAN when available, falls back to KEYS.
2192
- *
2193
- * **Warning:** The `KEYS` fallback is O(N) and blocks the Redis server.
2194
- * Provide a client with `scanIterator` support for production use.
2195
- * @internal
2196
- */
2197
- async collectKeys(pattern) {
2198
- if (this.client.scanIterator) {
2199
- const keys = [];
2200
- for await (const key of this.client.scanIterator({ MATCH: pattern, COUNT: 100 })) {
2201
- keys.push(key);
2202
- }
2203
- return keys;
2204
- }
2205
- return this.client.keys(pattern);
2206
- }
2207
- async clear() {
2208
- const keys = await this.collectKeys(`${this.prefix}*`);
2209
- if (keys.length > 0) {
2210
- await this.client.del(...keys);
2211
- }
2212
- }
2213
- /**
2214
- * Get the actual number of keys with this prefix in Redis.
2215
- */
2216
- get size() {
2217
- return this.getSize();
2218
- }
2219
- /** @internal */
2220
- async getSize() {
2221
- const keys = await this.collectKeys(`${this.prefix}*`);
2222
- return keys.length;
2223
- }
2224
- async keys() {
2225
- const raw = await this.collectKeys(`${this.prefix}*`);
2226
- return raw.map((k) => k.slice(this.prefix.length));
2227
- }
2228
- /**
2229
- * Remove all entries whose key matches the given glob pattern.
2230
- *
2231
- * @param pattern — A glob pattern (e.g. `"*Media*"`)
2232
- * @returns Number of entries removed.
2233
- */
2234
- async invalidate(pattern) {
2235
- if (typeof pattern === "string") {
2236
- const keys = await this.collectKeys(`${this.prefix}${pattern}`);
2237
- if (keys.length === 0) return 0;
2238
- return this.client.del(...keys);
2239
- }
2240
- const allKeys = await this.collectKeys(`${this.prefix}*`);
2241
- const matching = allKeys.filter((k) => pattern.test(k.slice(this.prefix.length)));
2242
- if (matching.length === 0) return 0;
2243
- return this.client.del(...matching);
2244
- }
2245
- };
2246
-
2247
2274
  exports.AiringSort = AiringSort;
2248
2275
  exports.AniListClient = AniListClient;
2249
2276
  exports.AniListError = AniListError;