ani-client 1.6.0 → 1.6.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/README.md +2 -2
- package/dist/index.d.mts +14 -2
- package/dist/index.d.ts +14 -2
- package/dist/index.js +101 -41
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +101 -41
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
> A simple, typed client to fetch anime, manga, character, staff and user data from [AniList](https://anilist.co).
|
|
9
9
|
|
|
10
|
-
✨ **Showcase**: [Check here](https://
|
|
10
|
+
✨ **Showcase**: [Check here](https://ani-client.js.org/showcase) to see which projects use this package!
|
|
11
11
|
|
|
12
12
|
- **Zero dependencies** — uses the native `fetch` API
|
|
13
13
|
- **Universal** — Node.js ≥ 20, Bun, Deno and modern browsers
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
The full API reference, usage guide, and configuration examples are available on our official documentation website!
|
|
20
20
|
|
|
21
|
-
**[👉 View the full documentation here](https://
|
|
21
|
+
**[👉 View the full documentation here](https://ani-client.js.org)**
|
|
22
22
|
|
|
23
23
|
## Install
|
|
24
24
|
|
package/dist/index.d.mts
CHANGED
|
@@ -90,6 +90,8 @@ interface AniListHooks {
|
|
|
90
90
|
onRetry?: (attempt: number, reason: string, delayMs: number) => void;
|
|
91
91
|
/** Called when a request completes. */
|
|
92
92
|
onResponse?: (query: string, durationMs: number, fromCache: boolean, rateLimitInfo?: RateLimitInfo) => void;
|
|
93
|
+
/** Called when a request fails with an error. */
|
|
94
|
+
onError?: (error: Error, query: string, variables: Record<string, unknown>) => void;
|
|
93
95
|
}
|
|
94
96
|
/** Rate limit information parsed from AniList API response headers. */
|
|
95
97
|
interface RateLimitInfo {
|
|
@@ -1077,7 +1079,7 @@ declare class AniListClient {
|
|
|
1077
1079
|
/** Clear the entire response cache. */
|
|
1078
1080
|
clearCache(): Promise<void>;
|
|
1079
1081
|
/** Number of entries currently in the cache. */
|
|
1080
|
-
|
|
1082
|
+
cacheSize(): Promise<number>;
|
|
1081
1083
|
/** Remove cache entries whose key matches the given pattern. */
|
|
1082
1084
|
invalidateCache(pattern: string | RegExp): Promise<number>;
|
|
1083
1085
|
/** Clean up resources held by the client. */
|
|
@@ -1219,6 +1221,8 @@ declare class RateLimiter {
|
|
|
1219
1221
|
private readonly timestamps;
|
|
1220
1222
|
private head;
|
|
1221
1223
|
private count;
|
|
1224
|
+
/** @internal — active sleep timers for cleanup */
|
|
1225
|
+
private readonly activeTimers;
|
|
1222
1226
|
constructor(options?: RateLimitOptions);
|
|
1223
1227
|
/**
|
|
1224
1228
|
* Wait until it's safe to make a request (respects rate limit window).
|
|
@@ -1237,11 +1241,19 @@ declare class RateLimiter {
|
|
|
1237
1241
|
/** @internal */
|
|
1238
1242
|
private fetchWithTimeout;
|
|
1239
1243
|
private sleep;
|
|
1244
|
+
/** Cancel all pending sleep timers and reset internal state. */
|
|
1245
|
+
dispose(): void;
|
|
1240
1246
|
}
|
|
1241
1247
|
|
|
1242
1248
|
/**
|
|
1243
1249
|
* Parses AniList specific markdown into standard HTML.
|
|
1244
|
-
* Includes formatting for spoilers, images, videos (youtube/webm),
|
|
1250
|
+
* Includes formatting for spoilers, images, videos (youtube/webm), headings,
|
|
1251
|
+
* lists, code blocks, and standard markdown elements.
|
|
1252
|
+
*
|
|
1253
|
+
* @security This function escapes HTML entities to prevent XSS attacks.
|
|
1254
|
+
* However, the output is still raw HTML — consumers should always use a
|
|
1255
|
+
* Content Security Policy and consider additional sanitization when rendering
|
|
1256
|
+
* user-generated content in a browser.
|
|
1245
1257
|
*
|
|
1246
1258
|
* @param text The AniList markdown text to parse
|
|
1247
1259
|
* @returns The parsed HTML string
|
package/dist/index.d.ts
CHANGED
|
@@ -90,6 +90,8 @@ interface AniListHooks {
|
|
|
90
90
|
onRetry?: (attempt: number, reason: string, delayMs: number) => void;
|
|
91
91
|
/** Called when a request completes. */
|
|
92
92
|
onResponse?: (query: string, durationMs: number, fromCache: boolean, rateLimitInfo?: RateLimitInfo) => void;
|
|
93
|
+
/** Called when a request fails with an error. */
|
|
94
|
+
onError?: (error: Error, query: string, variables: Record<string, unknown>) => void;
|
|
93
95
|
}
|
|
94
96
|
/** Rate limit information parsed from AniList API response headers. */
|
|
95
97
|
interface RateLimitInfo {
|
|
@@ -1077,7 +1079,7 @@ declare class AniListClient {
|
|
|
1077
1079
|
/** Clear the entire response cache. */
|
|
1078
1080
|
clearCache(): Promise<void>;
|
|
1079
1081
|
/** Number of entries currently in the cache. */
|
|
1080
|
-
|
|
1082
|
+
cacheSize(): Promise<number>;
|
|
1081
1083
|
/** Remove cache entries whose key matches the given pattern. */
|
|
1082
1084
|
invalidateCache(pattern: string | RegExp): Promise<number>;
|
|
1083
1085
|
/** Clean up resources held by the client. */
|
|
@@ -1219,6 +1221,8 @@ declare class RateLimiter {
|
|
|
1219
1221
|
private readonly timestamps;
|
|
1220
1222
|
private head;
|
|
1221
1223
|
private count;
|
|
1224
|
+
/** @internal — active sleep timers for cleanup */
|
|
1225
|
+
private readonly activeTimers;
|
|
1222
1226
|
constructor(options?: RateLimitOptions);
|
|
1223
1227
|
/**
|
|
1224
1228
|
* Wait until it's safe to make a request (respects rate limit window).
|
|
@@ -1237,11 +1241,19 @@ declare class RateLimiter {
|
|
|
1237
1241
|
/** @internal */
|
|
1238
1242
|
private fetchWithTimeout;
|
|
1239
1243
|
private sleep;
|
|
1244
|
+
/** Cancel all pending sleep timers and reset internal state. */
|
|
1245
|
+
dispose(): void;
|
|
1240
1246
|
}
|
|
1241
1247
|
|
|
1242
1248
|
/**
|
|
1243
1249
|
* Parses AniList specific markdown into standard HTML.
|
|
1244
|
-
* Includes formatting for spoilers, images, videos (youtube/webm),
|
|
1250
|
+
* Includes formatting for spoilers, images, videos (youtube/webm), headings,
|
|
1251
|
+
* lists, code blocks, and standard markdown elements.
|
|
1252
|
+
*
|
|
1253
|
+
* @security This function escapes HTML entities to prevent XSS attacks.
|
|
1254
|
+
* However, the output is still raw HTML — consumers should always use a
|
|
1255
|
+
* Content Security Policy and consider additional sanitization when rendering
|
|
1256
|
+
* user-generated content in a browser.
|
|
1245
1257
|
*
|
|
1246
1258
|
* @param text The AniList markdown text to parse
|
|
1247
1259
|
* @returns The parsed HTML string
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
function parseAniListMarkdown(text) {
|
|
5
5
|
if (!text) return "";
|
|
6
6
|
let html = text;
|
|
7
|
+
html = html.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
8
|
+
html = html.replace(/```([\s\S]*?)```/g, (_match, code) => {
|
|
9
|
+
return `<pre><code>${code.trim()}</code></pre>`;
|
|
10
|
+
});
|
|
11
|
+
html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
7
12
|
html = html.replace(/~!(.*?)!~/gs, '<span class="anilist-spoiler">$1</span>');
|
|
8
13
|
html = html.replace(/~~~(.*?)~~~/gs, '<div class="anilist-center">$1</div>');
|
|
9
14
|
html = html.replace(/img(\d+)\((.*?)\)/gi, '<img src="$2" width="$1" alt="" class="anilist-image" />');
|
|
@@ -13,6 +18,12 @@ function parseAniListMarkdown(text) {
|
|
|
13
18
|
'<iframe src="https://www.youtube.com/embed/$1" frameborder="0" allowfullscreen class="anilist-youtube"></iframe>'
|
|
14
19
|
);
|
|
15
20
|
html = html.replace(/webm\((.*?)\)/gi, '<video src="$1" controls class="anilist-webm"></video>');
|
|
21
|
+
html = html.replace(/^######\s+(.+)$/gm, "<h6>$1</h6>");
|
|
22
|
+
html = html.replace(/^#####\s+(.+)$/gm, "<h5>$1</h5>");
|
|
23
|
+
html = html.replace(/^####\s+(.+)$/gm, "<h4>$1</h4>");
|
|
24
|
+
html = html.replace(/^###\s+(.+)$/gm, "<h3>$1</h3>");
|
|
25
|
+
html = html.replace(/^##\s+(.+)$/gm, "<h2>$1</h2>");
|
|
26
|
+
html = html.replace(/^#\s+(.+)$/gm, "<h1>$1</h1>");
|
|
16
27
|
html = html.replace(/__(.*?)__/g, "<strong>$1</strong>");
|
|
17
28
|
html = html.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>");
|
|
18
29
|
html = html.replace(/_(.*?)_/g, "<em>$1</em>");
|
|
@@ -20,14 +31,46 @@ function parseAniListMarkdown(text) {
|
|
|
20
31
|
html = html.replace(/~~(.*?)~~/g, "<del>$1</del>");
|
|
21
32
|
html = html.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
22
33
|
html = html.replace(/\r\n/g, "\n");
|
|
34
|
+
const lines = html.split("\n");
|
|
35
|
+
const processed = [];
|
|
36
|
+
let listType = null;
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
const ulMatch = line.match(/^[\s]*[-*]\s+(.*)/);
|
|
39
|
+
const olMatch = line.match(/^[\s]*\d+\.\s+(.*)/);
|
|
40
|
+
if (ulMatch) {
|
|
41
|
+
if (listType !== "ul") {
|
|
42
|
+
if (listType) processed.push(`</${listType}>`);
|
|
43
|
+
processed.push("<ul>");
|
|
44
|
+
listType = "ul";
|
|
45
|
+
}
|
|
46
|
+
processed.push(`<li>${ulMatch[1]}</li>`);
|
|
47
|
+
} else if (olMatch) {
|
|
48
|
+
if (listType !== "ol") {
|
|
49
|
+
if (listType) processed.push(`</${listType}>`);
|
|
50
|
+
processed.push("<ol>");
|
|
51
|
+
listType = "ol";
|
|
52
|
+
}
|
|
53
|
+
processed.push(`<li>${olMatch[1]}</li>`);
|
|
54
|
+
} else {
|
|
55
|
+
if (listType) {
|
|
56
|
+
processed.push(`</${listType}>`);
|
|
57
|
+
listType = null;
|
|
58
|
+
}
|
|
59
|
+
processed.push(line);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (listType) processed.push(`</${listType}>`);
|
|
63
|
+
html = processed.join("\n");
|
|
23
64
|
const paragraphs = html.split(/\n{2,}/);
|
|
24
65
|
html = paragraphs.map((p) => {
|
|
25
|
-
const
|
|
26
|
-
if (
|
|
66
|
+
const trimmed = p.trim();
|
|
67
|
+
if (!trimmed) return "";
|
|
68
|
+
const withBr = trimmed.replace(/\n/g, "<br />");
|
|
69
|
+
if (withBr.match(/^(<div|<iframe|<video|<img|<h[1-6]|<ul|<ol|<pre)/)) {
|
|
27
70
|
return withBr;
|
|
28
71
|
}
|
|
29
72
|
return `<p>${withBr}</p>`;
|
|
30
|
-
}).join("\n");
|
|
73
|
+
}).filter(Boolean).join("\n");
|
|
31
74
|
return html;
|
|
32
75
|
}
|
|
33
76
|
|
|
@@ -802,7 +845,7 @@ function buildBatchQuery(ids, typeName, fields, prefix) {
|
|
|
802
845
|
${aliases}
|
|
803
846
|
}`;
|
|
804
847
|
}
|
|
805
|
-
var buildBatchMediaQuery = (ids) => buildBatchQuery(ids, "Media",
|
|
848
|
+
var buildBatchMediaQuery = (ids) => buildBatchQuery(ids, "Media", MEDIA_FIELDS, "m");
|
|
806
849
|
var buildBatchCharacterQuery = (ids) => buildBatchQuery(ids, "Character", CHARACTER_FIELDS, "c");
|
|
807
850
|
var buildBatchStaffQuery = (ids) => buildBatchQuery(ids, "Staff", STAFF_FIELDS, "s");
|
|
808
851
|
|
|
@@ -870,6 +913,8 @@ var RateLimiter = class {
|
|
|
870
913
|
constructor(options = {}) {
|
|
871
914
|
this.head = 0;
|
|
872
915
|
this.count = 0;
|
|
916
|
+
/** @internal — active sleep timers for cleanup */
|
|
917
|
+
this.activeTimers = /* @__PURE__ */ new Set();
|
|
873
918
|
this.maxRequests = options.maxRequests ?? 85;
|
|
874
919
|
this.windowMs = options.windowMs ?? 6e4;
|
|
875
920
|
this.maxRetries = options.maxRetries ?? 3;
|
|
@@ -887,8 +932,7 @@ var RateLimiter = class {
|
|
|
887
932
|
if (!this.enabled) return;
|
|
888
933
|
if (this.count >= this.maxRequests) {
|
|
889
934
|
const oldest = this.timestamps[this.head];
|
|
890
|
-
const
|
|
891
|
-
const elapsed = now2 - oldest;
|
|
935
|
+
const elapsed = Date.now() - oldest;
|
|
892
936
|
if (elapsed < this.windowMs) {
|
|
893
937
|
const waitMs = this.windowMs - elapsed + 50;
|
|
894
938
|
await this.sleep(waitMs);
|
|
@@ -952,14 +996,32 @@ var RateLimiter = class {
|
|
|
952
996
|
if (this.timeoutMs <= 0) return fetch(url, init);
|
|
953
997
|
const controller = new AbortController();
|
|
954
998
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
999
|
+
const signals = [controller.signal, init.signal].filter(Boolean);
|
|
1000
|
+
const combinedSignal = signals.length > 1 ? AbortSignal.any(signals) : signals[0];
|
|
955
1001
|
try {
|
|
956
|
-
return await fetch(url, { ...init, signal:
|
|
1002
|
+
return await fetch(url, { ...init, signal: combinedSignal });
|
|
957
1003
|
} finally {
|
|
958
1004
|
clearTimeout(timer);
|
|
959
1005
|
}
|
|
960
1006
|
}
|
|
961
1007
|
sleep(ms) {
|
|
962
|
-
return new Promise((resolve) =>
|
|
1008
|
+
return new Promise((resolve) => {
|
|
1009
|
+
const timer = setTimeout(() => {
|
|
1010
|
+
this.activeTimers.delete(timer);
|
|
1011
|
+
resolve();
|
|
1012
|
+
}, ms);
|
|
1013
|
+
this.activeTimers.add(timer);
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
/** Cancel all pending sleep timers and reset internal state. */
|
|
1017
|
+
dispose() {
|
|
1018
|
+
for (const timer of this.activeTimers) {
|
|
1019
|
+
clearTimeout(timer);
|
|
1020
|
+
}
|
|
1021
|
+
this.activeTimers.clear();
|
|
1022
|
+
this.head = 0;
|
|
1023
|
+
this.count = 0;
|
|
1024
|
+
this.timestamps.fill(0);
|
|
963
1025
|
}
|
|
964
1026
|
};
|
|
965
1027
|
var RETRYABLE_NETWORK_CODES = /* @__PURE__ */ new Set([
|
|
@@ -1333,14 +1395,12 @@ async function getWeeklySchedule(client, date = /* @__PURE__ */ new Date()) {
|
|
|
1333
1395
|
Saturday: [],
|
|
1334
1396
|
Sunday: []
|
|
1335
1397
|
};
|
|
1336
|
-
const
|
|
1337
|
-
const
|
|
1338
|
-
const
|
|
1339
|
-
startOfWeek.setDate(diff);
|
|
1340
|
-
startOfWeek.setHours(0, 0, 0, 0);
|
|
1398
|
+
const utcDay = date.getUTCDay();
|
|
1399
|
+
const diff = utcDay === 0 ? -6 : 1 - utcDay;
|
|
1400
|
+
const startOfWeek = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + diff, 0, 0, 0));
|
|
1341
1401
|
const endOfWeek = new Date(startOfWeek);
|
|
1342
|
-
endOfWeek.
|
|
1343
|
-
endOfWeek.
|
|
1402
|
+
endOfWeek.setUTCDate(startOfWeek.getUTCDate() + 6);
|
|
1403
|
+
endOfWeek.setUTCHours(23, 59, 59, 999);
|
|
1344
1404
|
const startTimestamp = Math.floor(startOfWeek.getTime() / 1e3);
|
|
1345
1405
|
const endTimestamp = Math.floor(endOfWeek.getTime() / 1e3);
|
|
1346
1406
|
const iterator = client.paginate(
|
|
@@ -1354,7 +1414,7 @@ async function getWeeklySchedule(client, date = /* @__PURE__ */ new Date()) {
|
|
|
1354
1414
|
const names = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
1355
1415
|
for await (const episode of iterator) {
|
|
1356
1416
|
const epDate = new Date(episode.airingAt * 1e3);
|
|
1357
|
-
const dayName = names[epDate.
|
|
1417
|
+
const dayName = names[epDate.getUTCDay()];
|
|
1358
1418
|
schedule[dayName].push(episode);
|
|
1359
1419
|
}
|
|
1360
1420
|
return schedule;
|
|
@@ -1477,13 +1537,15 @@ function mapFavorites(fav) {
|
|
|
1477
1537
|
|
|
1478
1538
|
// src/client/index.ts
|
|
1479
1539
|
var DEFAULT_API_URL = "https://graphql.anilist.co";
|
|
1540
|
+
var LIB_VERSION = "1.6.1" ;
|
|
1480
1541
|
var AniListClient = class {
|
|
1481
1542
|
constructor(options = {}) {
|
|
1482
1543
|
this.inFlight = /* @__PURE__ */ new Map();
|
|
1483
1544
|
this.apiUrl = options.apiUrl ?? DEFAULT_API_URL;
|
|
1484
1545
|
this.headers = {
|
|
1485
1546
|
"Content-Type": "application/json",
|
|
1486
|
-
Accept: "application/json"
|
|
1547
|
+
Accept: "application/json",
|
|
1548
|
+
"User-Agent": `ani-client/${LIB_VERSION}`
|
|
1487
1549
|
};
|
|
1488
1550
|
if (options.token) {
|
|
1489
1551
|
this.headers.Authorization = `Bearer ${options.token}`;
|
|
@@ -1507,7 +1569,6 @@ var AniListClient = class {
|
|
|
1507
1569
|
get lastRequestMeta() {
|
|
1508
1570
|
return this._lastRequestMeta;
|
|
1509
1571
|
}
|
|
1510
|
-
// ── Core infrastructure (internal) ──
|
|
1511
1572
|
/** @internal */
|
|
1512
1573
|
async request(query, variables = {}) {
|
|
1513
1574
|
const cacheKey = MemoryCache.key(query, variables);
|
|
@@ -1534,20 +1595,29 @@ var AniListClient = class {
|
|
|
1534
1595
|
const start = Date.now();
|
|
1535
1596
|
this.hooks.onRequest?.(query, variables);
|
|
1536
1597
|
const minifiedQuery = normalizeQuery(query);
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1598
|
+
let res;
|
|
1599
|
+
try {
|
|
1600
|
+
res = await this.rateLimiter.fetchWithRetry(
|
|
1601
|
+
this.apiUrl,
|
|
1602
|
+
{
|
|
1603
|
+
method: "POST",
|
|
1604
|
+
headers: this.headers,
|
|
1605
|
+
body: JSON.stringify({ query: minifiedQuery, variables }),
|
|
1606
|
+
signal: this.signal
|
|
1607
|
+
},
|
|
1608
|
+
{ onRetry: this.hooks.onRetry, onRateLimit: this.hooks.onRateLimit }
|
|
1609
|
+
);
|
|
1610
|
+
} catch (err) {
|
|
1611
|
+
const error = err instanceof AniListError ? err : new AniListError(err.message ?? "Network request failed", 0, [err]);
|
|
1612
|
+
this.hooks.onError?.(error, query, variables);
|
|
1613
|
+
throw error;
|
|
1614
|
+
}
|
|
1547
1615
|
const json = await res.json();
|
|
1548
1616
|
if (!res.ok || json.errors) {
|
|
1549
1617
|
const message = json.errors?.[0]?.message ?? `AniList API error (HTTP ${res.status})`;
|
|
1550
|
-
|
|
1618
|
+
const error = new AniListError(message, res.status, json.errors ?? []);
|
|
1619
|
+
this.hooks.onError?.(error, query, variables);
|
|
1620
|
+
throw error;
|
|
1551
1621
|
}
|
|
1552
1622
|
const rlLimit = res.headers.get("X-RateLimit-Limit");
|
|
1553
1623
|
const rlRemaining = res.headers.get("X-RateLimit-Remaining");
|
|
@@ -1576,7 +1646,6 @@ var AniListClient = class {
|
|
|
1576
1646
|
}
|
|
1577
1647
|
return { pageInfo: data.Page.pageInfo, results };
|
|
1578
1648
|
}
|
|
1579
|
-
// ── Media ──
|
|
1580
1649
|
/**
|
|
1581
1650
|
* Fetch a single media entry by its AniList ID.
|
|
1582
1651
|
*
|
|
@@ -1633,7 +1702,6 @@ var AniListClient = class {
|
|
|
1633
1702
|
async getMediaBySeason(options) {
|
|
1634
1703
|
return getMediaBySeason(this, options);
|
|
1635
1704
|
}
|
|
1636
|
-
// ── Characters ──
|
|
1637
1705
|
/** Fetch a character by AniList ID. Pass `{ voiceActors: true }` to include VA data. */
|
|
1638
1706
|
async getCharacter(id, include) {
|
|
1639
1707
|
return getCharacter(this, id, include);
|
|
@@ -1642,7 +1710,6 @@ var AniListClient = class {
|
|
|
1642
1710
|
async searchCharacters(options = {}) {
|
|
1643
1711
|
return searchCharacters(this, options);
|
|
1644
1712
|
}
|
|
1645
|
-
// ── Staff ──
|
|
1646
1713
|
/** Fetch a staff member by AniList ID. Pass `{ media: true }` or `{ media: { perPage } }` for media credits. */
|
|
1647
1714
|
async getStaff(id, include) {
|
|
1648
1715
|
return getStaff(this, id, include);
|
|
@@ -1651,7 +1718,6 @@ var AniListClient = class {
|
|
|
1651
1718
|
async searchStaff(options = {}) {
|
|
1652
1719
|
return searchStaff(this, options);
|
|
1653
1720
|
}
|
|
1654
|
-
// ── Users ──
|
|
1655
1721
|
/**
|
|
1656
1722
|
* Fetch a user by AniList ID or username.
|
|
1657
1723
|
*
|
|
@@ -1683,7 +1749,6 @@ var AniListClient = class {
|
|
|
1683
1749
|
async getUserFavorites(idOrName) {
|
|
1684
1750
|
return getUserFavorites(this, idOrName);
|
|
1685
1751
|
}
|
|
1686
|
-
// ── Studios ──
|
|
1687
1752
|
/** Fetch a studio by its AniList ID. */
|
|
1688
1753
|
async getStudio(id) {
|
|
1689
1754
|
return getStudio(this, id);
|
|
@@ -1692,8 +1757,6 @@ var AniListClient = class {
|
|
|
1692
1757
|
async searchStudios(options = {}) {
|
|
1693
1758
|
return searchStudios(this, options);
|
|
1694
1759
|
}
|
|
1695
|
-
// ── Metadata ──
|
|
1696
|
-
// ── Threads ──
|
|
1697
1760
|
/** Fetch a forum thread by its AniList ID. */
|
|
1698
1761
|
async getThread(id) {
|
|
1699
1762
|
return getThread(this, id);
|
|
@@ -1712,12 +1775,10 @@ var AniListClient = class {
|
|
|
1712
1775
|
const data = await this.request(QUERY_TAGS);
|
|
1713
1776
|
return data.MediaTagCollection;
|
|
1714
1777
|
}
|
|
1715
|
-
// ── Raw query ──
|
|
1716
1778
|
/** Execute an arbitrary GraphQL query against the AniList API. */
|
|
1717
1779
|
async raw(query, variables) {
|
|
1718
1780
|
return this.request(query, variables ?? {});
|
|
1719
1781
|
}
|
|
1720
|
-
// ── Pagination ──
|
|
1721
1782
|
/**
|
|
1722
1783
|
* Auto-paginating async iterator. Yields individual items across all pages.
|
|
1723
1784
|
*
|
|
@@ -1736,7 +1797,6 @@ var AniListClient = class {
|
|
|
1736
1797
|
page++;
|
|
1737
1798
|
}
|
|
1738
1799
|
}
|
|
1739
|
-
// ── Batch queries ──
|
|
1740
1800
|
/** Fetch multiple media entries in a single API request. */
|
|
1741
1801
|
async getMediaBatch(ids) {
|
|
1742
1802
|
if (ids.length === 0) return [];
|
|
@@ -1770,13 +1830,12 @@ var AniListClient = class {
|
|
|
1770
1830
|
);
|
|
1771
1831
|
return chunkResults.flat();
|
|
1772
1832
|
}
|
|
1773
|
-
// ── Cache management ──
|
|
1774
1833
|
/** Clear the entire response cache. */
|
|
1775
1834
|
async clearCache() {
|
|
1776
1835
|
await this.cacheAdapter.clear();
|
|
1777
1836
|
}
|
|
1778
1837
|
/** Number of entries currently in the cache. */
|
|
1779
|
-
|
|
1838
|
+
async cacheSize() {
|
|
1780
1839
|
return this.cacheAdapter.size;
|
|
1781
1840
|
}
|
|
1782
1841
|
/** Remove cache entries whose key matches the given pattern. */
|
|
@@ -1799,6 +1858,7 @@ var AniListClient = class {
|
|
|
1799
1858
|
async destroy() {
|
|
1800
1859
|
await this.cacheAdapter.clear();
|
|
1801
1860
|
this.inFlight.clear();
|
|
1861
|
+
this.rateLimiter.dispose();
|
|
1802
1862
|
}
|
|
1803
1863
|
};
|
|
1804
1864
|
|