ani-client 2.1.4 → 2.2.1

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
@@ -135,6 +135,7 @@ var NormalizedCache = class {
135
135
  _hits = 0;
136
136
  _misses = 0;
137
137
  _stales = 0;
138
+ gcTimeout;
138
139
  constructor(options = {}) {
139
140
  this.ttl = options.ttl ?? 24 * 60 * 60 * 1e3;
140
141
  this.maxSize = options.maxSize ?? 500;
@@ -246,8 +247,10 @@ var NormalizedCache = class {
246
247
  this.queryStore.delete(key);
247
248
  if (this.maxSize > 0 && this.queryStore.size >= this.maxSize) {
248
249
  const firstKey = this.queryStore.keys().next().value;
249
- if (firstKey !== void 0) this.queryStore.delete(firstKey);
250
- this.gc();
250
+ if (firstKey !== void 0) {
251
+ this.queryStore.delete(firstKey);
252
+ }
253
+ this.scheduleGc();
251
254
  }
252
255
  this.queryStore.set(key, { data: normalizedData, expiresAt: Date.now() + this.ttl });
253
256
  }
@@ -255,6 +258,10 @@ var NormalizedCache = class {
255
258
  return this.queryStore.delete(key);
256
259
  }
257
260
  clear() {
261
+ if (this.gcTimeout) {
262
+ clearTimeout(this.gcTimeout);
263
+ this.gcTimeout = void 0;
264
+ }
258
265
  this.queryStore.clear();
259
266
  this.entityStore.clear();
260
267
  this._hits = 0;
@@ -291,6 +298,16 @@ var NormalizedCache = class {
291
298
  this._misses = 0;
292
299
  this._stales = 0;
293
300
  }
301
+ scheduleGc() {
302
+ if (this.gcTimeout) return;
303
+ this.gcTimeout = setTimeout(() => {
304
+ this.gc();
305
+ this.gcTimeout = void 0;
306
+ }, 500);
307
+ if (typeof this.gcTimeout.unref === "function") {
308
+ this.gcTimeout.unref();
309
+ }
310
+ }
294
311
  /**
295
312
  * Garbage-collect orphaned entities that are no longer referenced by any query.
296
313
  * Called automatically on LRU eviction to prevent unbounded entity store growth.
@@ -468,7 +485,7 @@ var RedisCache = class {
468
485
  constructor(options) {
469
486
  this.client = options.client;
470
487
  this.prefix = options.prefix ?? "ani:";
471
- this.ttl = options.ttl ?? 86400;
488
+ this.ttl = options.ttl !== void 0 ? Math.floor(options.ttl / 1e3) : 86400;
472
489
  }
473
490
  prefixedKey(key) {
474
491
  return `${this.prefix}${key}`;
@@ -1183,8 +1200,7 @@ query ($mediaId: Int!, $page: Int, $perPage: Int, $sort: [RecommendationSort]) {
1183
1200
  }`;
1184
1201
 
1185
1202
  // src/queries/builders.ts
1186
- function buildMediaByIdQuery(include) {
1187
- if (!include) return QUERY_MEDIA_BY_ID;
1203
+ function buildMediaIncludeQuery(include) {
1188
1204
  const extra = [];
1189
1205
  if (include.relations !== false) {
1190
1206
  extra.push(RELATIONS_FIELDS);
@@ -1259,14 +1275,172 @@ function buildMediaByIdQuery(include) {
1259
1275
  statusDistribution { status amount }
1260
1276
  }`);
1261
1277
  }
1278
+ return extra;
1279
+ }
1280
+ function buildMediaByIdQuery(include) {
1281
+ if (!include) return QUERY_MEDIA_BY_ID;
1262
1282
  return `
1263
1283
  query ($id: Int!) {
1264
1284
  Media(id: $id) {
1265
1285
  ${MEDIA_FIELDS_BASE}
1266
- ${extra.join("\n")}
1286
+ ${buildMediaIncludeQuery(include).join("\n")}
1267
1287
  }
1268
1288
  }`;
1269
1289
  }
1290
+ function buildSearchMediaQuery(include) {
1291
+ if (!include) return QUERY_MEDIA_SEARCH;
1292
+ return `
1293
+ query (
1294
+ $search: String,
1295
+ $countryOfOrigin: CountryCode,
1296
+ $type: MediaType,
1297
+ $format: MediaFormat,
1298
+ $format_in: [MediaFormat],
1299
+ $status: MediaStatus,
1300
+ $season: MediaSeason,
1301
+ $seasonYear: Int,
1302
+ $genre: String,
1303
+ $tag: String,
1304
+ $genre_in: [String],
1305
+ $tag_in: [String],
1306
+ $genre_not_in: [String],
1307
+ $tag_not_in: [String],
1308
+ $isAdult: Boolean,
1309
+ $idNotIn: [Int],
1310
+ $sort: [MediaSort],
1311
+ $page: Int,
1312
+ $perPage: Int
1313
+ ) {
1314
+ Page(page: $page, perPage: $perPage) {
1315
+ pageInfo { total perPage currentPage lastPage hasNextPage }
1316
+ media(
1317
+ search: $search,
1318
+ countryOfOrigin: $countryOfOrigin,
1319
+ type: $type,
1320
+ format: $format,
1321
+ format_in: $format_in,
1322
+ status: $status,
1323
+ season: $season,
1324
+ seasonYear: $seasonYear,
1325
+ genre: $genre,
1326
+ tag: $tag,
1327
+ genre_in: $genre_in,
1328
+ tag_in: $tag_in,
1329
+ genre_not_in: $genre_not_in,
1330
+ tag_not_in: $tag_not_in,
1331
+ isAdult: $isAdult,
1332
+ id_not_in: $idNotIn,
1333
+ sort: $sort
1334
+ ) {
1335
+ ${MEDIA_FIELDS_BASE}
1336
+ ${buildMediaIncludeQuery(include).join("\n")}
1337
+ }
1338
+ }
1339
+ }`;
1340
+ }
1341
+ function buildGetTrendingQuery(include) {
1342
+ if (!include) return QUERY_TRENDING;
1343
+ return `
1344
+ query (
1345
+ $type: MediaType,
1346
+ $isAdult: Boolean,
1347
+ $idNotIn: [Int],
1348
+ $page: Int,
1349
+ $perPage: Int
1350
+ ) {
1351
+ Page(page: $page, perPage: $perPage) {
1352
+ pageInfo { total perPage currentPage lastPage hasNextPage }
1353
+ media(
1354
+ type: $type,
1355
+ isAdult: $isAdult,
1356
+ id_not_in: $idNotIn,
1357
+ sort: TRENDING_DESC
1358
+ ) {
1359
+ ${MEDIA_FIELDS_BASE}
1360
+ ${buildMediaIncludeQuery(include).join("\n")}
1361
+ }
1362
+ }
1363
+ }`;
1364
+ }
1365
+ function buildGetMediaBySeasonQuery(include) {
1366
+ if (!include) return QUERY_MEDIA_BY_SEASON;
1367
+ return `
1368
+ query (
1369
+ $season: MediaSeason!,
1370
+ $seasonYear: Int!,
1371
+ $type: MediaType,
1372
+ $isAdult: Boolean,
1373
+ $idNotIn: [Int],
1374
+ $sort: [MediaSort],
1375
+ $page: Int,
1376
+ $perPage: Int
1377
+ ) {
1378
+ Page(page: $page, perPage: $perPage) {
1379
+ pageInfo { total perPage currentPage lastPage hasNextPage }
1380
+ media(
1381
+ season: $season,
1382
+ seasonYear: $seasonYear,
1383
+ type: $type,
1384
+ isAdult: $isAdult,
1385
+ id_not_in: $idNotIn,
1386
+ sort: $sort
1387
+ ) {
1388
+ ${MEDIA_FIELDS_BASE}
1389
+ ${buildMediaIncludeQuery(include).join("\n")}
1390
+ }
1391
+ }
1392
+ }`;
1393
+ }
1394
+ function buildGetRecentlyUpdatedMangaQuery(include) {
1395
+ if (!include) return QUERY_RECENT_CHAPTERS;
1396
+ return `
1397
+ query (
1398
+ $isAdult: Boolean,
1399
+ $idNotIn: [Int],
1400
+ $page: Int,
1401
+ $perPage: Int
1402
+ ) {
1403
+ Page(page: $page, perPage: $perPage) {
1404
+ pageInfo { total perPage currentPage lastPage hasNextPage }
1405
+ media(
1406
+ type: MANGA,
1407
+ isAdult: $isAdult,
1408
+ id_not_in: $idNotIn,
1409
+ status: RELEASING,
1410
+ sort: UPDATED_AT_DESC
1411
+ ) {
1412
+ ${MEDIA_FIELDS_BASE}
1413
+ ${buildMediaIncludeQuery(include).join("\n")}
1414
+ }
1415
+ }
1416
+ }`;
1417
+ }
1418
+ function buildGetPlanningQuery(include) {
1419
+ if (!include) return QUERY_PLANNING;
1420
+ return `
1421
+ query (
1422
+ $type: MediaType,
1423
+ $isAdult: Boolean,
1424
+ $idNotIn: [Int],
1425
+ $sort: [MediaSort],
1426
+ $page: Int,
1427
+ $perPage: Int
1428
+ ) {
1429
+ Page(page: $page, perPage: $perPage) {
1430
+ pageInfo { total perPage currentPage lastPage hasNextPage }
1431
+ media(
1432
+ type: $type,
1433
+ isAdult: $isAdult,
1434
+ id_not_in: $idNotIn,
1435
+ status: NOT_YET_RELEASED,
1436
+ sort: $sort
1437
+ ) {
1438
+ ${MEDIA_FIELDS_BASE}
1439
+ ${buildMediaIncludeQuery(include).join("\n")}
1440
+ }
1441
+ }
1442
+ }`;
1443
+ }
1270
1444
  function buildMediaCharactersQuery(options = {}) {
1271
1445
  const sortClause = options.sort === false ? "" : ", sort: [ROLE, RELEVANCE, ID]";
1272
1446
  const voiceActorBlock = options.voiceActors ? `
@@ -2068,7 +2242,7 @@ async function getMediaByMalId(client, malId, type) {
2068
2242
  });
2069
2243
  return data.Media;
2070
2244
  }
2071
- async function searchMedia(client, options = {}) {
2245
+ async function searchMedia(client, options = {}, include) {
2072
2246
  const {
2073
2247
  query: search,
2074
2248
  page = 1,
@@ -2080,8 +2254,9 @@ async function searchMedia(client, options = {}) {
2080
2254
  format,
2081
2255
  ...filters
2082
2256
  } = options;
2257
+ const query = buildSearchMediaQuery(include);
2083
2258
  return client.pagedRequest(
2084
- QUERY_MEDIA_SEARCH,
2259
+ query,
2085
2260
  {
2086
2261
  search,
2087
2262
  ...filters,
@@ -2097,21 +2272,18 @@ async function searchMedia(client, options = {}) {
2097
2272
  "media"
2098
2273
  );
2099
2274
  }
2100
- async function getTrending(client, options) {
2275
+ async function getTrending(client, options, include) {
2101
2276
  const { type = "ANIME" /* ANIME */, isAdult = false, idNotIn = [], page = 1, perPage = 20 } = options;
2102
- return client.pagedRequest(
2103
- QUERY_TRENDING,
2104
- { type, isAdult, idNotIn, page, perPage: clampPerPage(perPage) },
2105
- "media"
2106
- );
2277
+ const query = buildGetTrendingQuery(include);
2278
+ return client.pagedRequest(query, { type, isAdult, idNotIn, page, perPage: clampPerPage(perPage) }, "media");
2107
2279
  }
2108
- async function getPopular(client, options) {
2280
+ async function getPopular(client, options, include) {
2109
2281
  const { type = "ANIME" /* ANIME */, isAdult = false, idNotIn = [], page = 1, perPage = 20 } = options;
2110
- return searchMedia(client, { type, isAdult, idNotIn, sort: ["POPULARITY_DESC" /* POPULARITY_DESC */], page, perPage });
2282
+ return searchMedia(client, { type, isAdult, idNotIn, sort: ["POPULARITY_DESC" /* POPULARITY_DESC */], page, perPage }, include);
2111
2283
  }
2112
- async function getTopRated(client, options) {
2284
+ async function getTopRated(client, options, include) {
2113
2285
  const { type = "ANIME" /* ANIME */, isAdult = false, idNotIn = [], page = 1, perPage = 20 } = options;
2114
- return searchMedia(client, { type, isAdult, idNotIn, sort: ["SCORE_DESC" /* SCORE_DESC */], page, perPage });
2286
+ return searchMedia(client, { type, isAdult, idNotIn, sort: ["SCORE_DESC" /* SCORE_DESC */], page, perPage }, include);
2115
2287
  }
2116
2288
  async function getAiredEpisodes(client, options = {}) {
2117
2289
  const now = Math.floor(Date.now() / 1e3);
@@ -2128,9 +2300,10 @@ async function getAiredEpisodes(client, options = {}) {
2128
2300
  "airingSchedules"
2129
2301
  );
2130
2302
  }
2131
- async function getRecentlyUpdatedManga(client, options = {}) {
2303
+ async function getRecentlyUpdatedManga(client, options = {}, include) {
2304
+ const query = buildGetRecentlyUpdatedMangaQuery(include);
2132
2305
  return client.pagedRequest(
2133
- QUERY_RECENT_CHAPTERS,
2306
+ query,
2134
2307
  {
2135
2308
  isAdult: options.isAdult ?? false,
2136
2309
  idNotIn: options.idNotIn ?? [],
@@ -2140,9 +2313,10 @@ async function getRecentlyUpdatedManga(client, options = {}) {
2140
2313
  "media"
2141
2314
  );
2142
2315
  }
2143
- async function getPlanning(client, options = {}) {
2316
+ async function getPlanning(client, options = {}, include) {
2317
+ const query = buildGetPlanningQuery(include);
2144
2318
  return client.pagedRequest(
2145
- QUERY_PLANNING,
2319
+ query,
2146
2320
  {
2147
2321
  type: options.type,
2148
2322
  isAdult: options.isAdult ?? false,
@@ -2167,9 +2341,10 @@ async function getRecommendations(client, mediaId, options = {}) {
2167
2341
  results: data.Media.recommendations.nodes
2168
2342
  };
2169
2343
  }
2170
- async function getMediaBySeason(client, options) {
2344
+ async function getMediaBySeason(client, options, include) {
2345
+ const query = buildGetMediaBySeasonQuery(include);
2171
2346
  return client.pagedRequest(
2172
- QUERY_MEDIA_BY_SEASON,
2347
+ query,
2173
2348
  {
2174
2349
  season: options.season,
2175
2350
  seasonYear: options.seasonYear,
@@ -2366,7 +2541,7 @@ function mapFavorites(fav) {
2366
2541
 
2367
2542
  // src/client/index.ts
2368
2543
  var DEFAULT_API_URL = "https://graphql.anilist.co";
2369
- var LIB_VERSION = "2.1.4" ;
2544
+ var LIB_VERSION = "2.2.1" ;
2370
2545
  var AniListClient = class {
2371
2546
  apiUrl;
2372
2547
  headers;
@@ -2479,7 +2654,8 @@ var AniListClient = class {
2479
2654
  throw error;
2480
2655
  }
2481
2656
  if (!res.ok || json.errors) {
2482
- const message = json.errors?.[0]?.message ?? `AniList API error (HTTP ${res.status})`;
2657
+ const msgs = (json.errors || []).map((e) => e.message).filter(Boolean);
2658
+ const message = msgs.length > 0 ? msgs.join(" | ") : `AniList API error (HTTP ${res.status})`;
2483
2659
  const error = new AniListError(message, res.status, json.errors ?? []);
2484
2660
  this.logger?.error("Request failed", { error: error.message, status: error.status });
2485
2661
  this.hooks.onError?.(error, query, variables);
@@ -2536,20 +2712,20 @@ var AniListClient = class {
2536
2712
  * @param options - Search / filter parameters
2537
2713
  * @returns Paginated results with matching media
2538
2714
  */
2539
- async searchMedia(options = {}) {
2540
- return searchMedia(this, options);
2715
+ async searchMedia(options = {}, include) {
2716
+ return searchMedia(this, options, include);
2541
2717
  }
2542
2718
  /** Get currently trending anime or manga. */
2543
- async getTrending(options = {}) {
2544
- return getTrending(this, options);
2719
+ async getTrending(options = {}, include) {
2720
+ return getTrending(this, options, include);
2545
2721
  }
2546
2722
  /** Get the most popular anime or manga. */
2547
- async getPopular(options = {}) {
2548
- return getPopular(this, options);
2723
+ async getPopular(options = {}, include) {
2724
+ return getPopular(this, options, include);
2549
2725
  }
2550
2726
  /** Get the highest-rated anime or manga. */
2551
- async getTopRated(options = {}) {
2552
- return getTopRated(this, options);
2727
+ async getTopRated(options = {}, include) {
2728
+ return getTopRated(this, options, include);
2553
2729
  }
2554
2730
  /** Get recently aired anime episodes. */
2555
2731
  async getAiredEpisodes(options = {}) {
@@ -2560,14 +2736,8 @@ var AniListClient = class {
2560
2736
  *
2561
2737
  * @param options - Pagination parameters
2562
2738
  */
2563
- async getRecentlyUpdatedManga(options = {}) {
2564
- return getRecentlyUpdatedManga(this, options);
2565
- }
2566
- /**
2567
- * @deprecated Use `getRecentlyUpdatedManga()` instead. This alias will be removed in v3.
2568
- */
2569
- async getAiredChapters(options = {}) {
2570
- return this.getRecentlyUpdatedManga(options);
2739
+ async getRecentlyUpdatedManga(options = {}, include) {
2740
+ return getRecentlyUpdatedManga(this, options, include);
2571
2741
  }
2572
2742
  /**
2573
2743
  * Fetch a media entry by its MyAnimeList (MAL) ID.
@@ -2583,16 +2753,16 @@ var AniListClient = class {
2583
2753
  return getWeeklySchedule(this, date, idNotIn);
2584
2754
  }
2585
2755
  /** Get upcoming (not yet released) media. */
2586
- async getPlanning(options = {}) {
2587
- return getPlanning(this, options);
2756
+ async getPlanning(options = {}, include) {
2757
+ return getPlanning(this, options, include);
2588
2758
  }
2589
2759
  /** Get recommendations for a specific media. */
2590
2760
  async getRecommendations(mediaId, options = {}) {
2591
2761
  return getRecommendations(this, mediaId, options);
2592
2762
  }
2593
2763
  /** Get anime (or manga) for a specific season and year. */
2594
- async getMediaBySeason(options) {
2595
- return getMediaBySeason(this, options);
2764
+ async getMediaBySeason(options, include) {
2765
+ return getMediaBySeason(this, options, include);
2596
2766
  }
2597
2767
  /** Fetch a character by AniList ID. Pass `{ voiceActors: true }` to include VA data. */
2598
2768
  async getCharacter(id, include) {