ani-client 1.7.0 → 1.8.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/README.md +104 -44
- package/dist/index.d.mts +117 -6
- package/dist/index.d.ts +117 -6
- package/dist/index.js +277 -24
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +277 -24
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -127,28 +127,52 @@ function sortObjectKeys(obj) {
|
|
|
127
127
|
// src/cache/index.ts
|
|
128
128
|
var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
|
|
129
129
|
var MemoryCache = class {
|
|
130
|
+
ttl;
|
|
131
|
+
maxSize;
|
|
132
|
+
enabled;
|
|
133
|
+
swrMs;
|
|
134
|
+
store = /* @__PURE__ */ new Map();
|
|
135
|
+
_hits = 0;
|
|
136
|
+
_misses = 0;
|
|
137
|
+
_stales = 0;
|
|
130
138
|
constructor(options = {}) {
|
|
131
|
-
this.store = /* @__PURE__ */ new Map();
|
|
132
139
|
this.ttl = options.ttl ?? ONE_DAY_MS;
|
|
133
140
|
this.maxSize = options.maxSize ?? 500;
|
|
134
141
|
this.enabled = options.enabled ?? true;
|
|
142
|
+
this.swrMs = options.staleWhileRevalidateMs ?? 0;
|
|
135
143
|
}
|
|
136
144
|
/** Build a deterministic cache key from a query + variables pair. */
|
|
137
145
|
static key(query, variables) {
|
|
138
146
|
const normalized = normalizeQuery(query);
|
|
139
147
|
return `${normalized}|${JSON.stringify(sortObjectKeys(variables))}`;
|
|
140
148
|
}
|
|
141
|
-
/**
|
|
149
|
+
/**
|
|
150
|
+
* Retrieve a cached value, or `undefined` if missing / expired.
|
|
151
|
+
* With stale-while-revalidate enabled, returns stale data within the grace window
|
|
152
|
+
* and flags it so the caller can refresh in the background.
|
|
153
|
+
*/
|
|
142
154
|
get(key) {
|
|
143
155
|
if (!this.enabled) return void 0;
|
|
144
156
|
const entry = this.store.get(key);
|
|
145
|
-
if (!entry)
|
|
146
|
-
|
|
157
|
+
if (!entry) {
|
|
158
|
+
this._misses++;
|
|
159
|
+
return void 0;
|
|
160
|
+
}
|
|
161
|
+
const now = Date.now();
|
|
162
|
+
if (now > entry.expiresAt) {
|
|
163
|
+
if (this.swrMs > 0 && now <= entry.expiresAt + this.swrMs) {
|
|
164
|
+
this.store.delete(key);
|
|
165
|
+
this.store.set(key, entry);
|
|
166
|
+
this._stales++;
|
|
167
|
+
return entry.data;
|
|
168
|
+
}
|
|
147
169
|
this.store.delete(key);
|
|
170
|
+
this._misses++;
|
|
148
171
|
return void 0;
|
|
149
172
|
}
|
|
150
173
|
this.store.delete(key);
|
|
151
174
|
this.store.set(key, entry);
|
|
175
|
+
this._hits++;
|
|
152
176
|
return entry.data;
|
|
153
177
|
}
|
|
154
178
|
/** Store a value in the cache. */
|
|
@@ -165,9 +189,12 @@ var MemoryCache = class {
|
|
|
165
189
|
delete(key) {
|
|
166
190
|
return this.store.delete(key);
|
|
167
191
|
}
|
|
168
|
-
/** Clear the entire cache. */
|
|
192
|
+
/** Clear the entire cache and reset statistics. */
|
|
169
193
|
clear() {
|
|
170
194
|
this.store.clear();
|
|
195
|
+
this._hits = 0;
|
|
196
|
+
this._misses = 0;
|
|
197
|
+
this._stales = 0;
|
|
171
198
|
}
|
|
172
199
|
/** Number of entries currently stored. */
|
|
173
200
|
get size() {
|
|
@@ -177,6 +204,32 @@ var MemoryCache = class {
|
|
|
177
204
|
keys() {
|
|
178
205
|
return [...this.store.keys()];
|
|
179
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* Get cache performance statistics.
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```ts
|
|
212
|
+
* const cache = new MemoryCache();
|
|
213
|
+
* // ... after some usage ...
|
|
214
|
+
* console.log(cache.stats);
|
|
215
|
+
* // { hits: 42, misses: 8, stales: 0, hitRate: 0.84 }
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
get stats() {
|
|
219
|
+
const total = this._hits + this._misses + this._stales;
|
|
220
|
+
return {
|
|
221
|
+
hits: this._hits,
|
|
222
|
+
misses: this._misses,
|
|
223
|
+
stales: this._stales,
|
|
224
|
+
hitRate: total === 0 ? Number.NaN : this._hits / total
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/** Reset cache statistics without clearing stored data. */
|
|
228
|
+
resetStats() {
|
|
229
|
+
this._hits = 0;
|
|
230
|
+
this._misses = 0;
|
|
231
|
+
this._stales = 0;
|
|
232
|
+
}
|
|
180
233
|
/**
|
|
181
234
|
* Remove all entries whose key matches the given pattern.
|
|
182
235
|
*
|
|
@@ -199,6 +252,10 @@ var MemoryCache = class {
|
|
|
199
252
|
|
|
200
253
|
// src/errors/index.ts
|
|
201
254
|
var AniListError = class _AniListError extends Error {
|
|
255
|
+
/** HTTP status code returned by the API */
|
|
256
|
+
status;
|
|
257
|
+
/** Raw error body from the API response */
|
|
258
|
+
errors;
|
|
202
259
|
constructor(message, status, errors = []) {
|
|
203
260
|
super(message);
|
|
204
261
|
this.name = "AniListError";
|
|
@@ -212,6 +269,32 @@ var AniListError = class _AniListError extends Error {
|
|
|
212
269
|
};
|
|
213
270
|
|
|
214
271
|
// src/queries/fragments.ts
|
|
272
|
+
var MEDIA_FIELDS_LIGHT = `
|
|
273
|
+
id
|
|
274
|
+
idMal
|
|
275
|
+
title { romaji english native userPreferred }
|
|
276
|
+
type
|
|
277
|
+
format
|
|
278
|
+
status
|
|
279
|
+
coverImage { large medium color }
|
|
280
|
+
bannerImage
|
|
281
|
+
genres
|
|
282
|
+
averageScore
|
|
283
|
+
popularity
|
|
284
|
+
favourites
|
|
285
|
+
isAdult
|
|
286
|
+
siteUrl
|
|
287
|
+
season
|
|
288
|
+
seasonYear
|
|
289
|
+
episodes
|
|
290
|
+
chapters
|
|
291
|
+
nextAiringEpisode {
|
|
292
|
+
id
|
|
293
|
+
airingAt
|
|
294
|
+
episode
|
|
295
|
+
timeUntilAiring
|
|
296
|
+
}
|
|
297
|
+
`;
|
|
215
298
|
var MEDIA_FIELDS_BASE = `
|
|
216
299
|
id
|
|
217
300
|
idMal
|
|
@@ -631,6 +714,12 @@ query ($season: MediaSeason!, $seasonYear: Int!, $type: MediaType, $sort: [Media
|
|
|
631
714
|
}
|
|
632
715
|
}
|
|
633
716
|
}`;
|
|
717
|
+
var QUERY_MEDIA_BY_MAL_ID = `
|
|
718
|
+
query ($idMal: Int!, $type: MediaType) {
|
|
719
|
+
Media(idMal: $idMal, type: $type) {
|
|
720
|
+
${MEDIA_FIELDS}
|
|
721
|
+
}
|
|
722
|
+
}`;
|
|
634
723
|
var QUERY_RECOMMENDATIONS = `
|
|
635
724
|
query ($mediaId: Int!, $page: Int, $perPage: Int, $sort: [RecommendationSort]) {
|
|
636
725
|
Media(id: $mediaId) {
|
|
@@ -769,6 +858,63 @@ query ($name: String!) {
|
|
|
769
858
|
${USER_FAVORITES_FIELDS}
|
|
770
859
|
}
|
|
771
860
|
}`;
|
|
861
|
+
function buildUserFavoritesQuery(idOrName, perPage = 25) {
|
|
862
|
+
const pp = clampPerPage(perPage);
|
|
863
|
+
const varDecl = idOrName === "id" ? "$id: Int!" : "$name: String!";
|
|
864
|
+
const selector = idOrName === "id" ? "id: $id" : "name: $name";
|
|
865
|
+
return `
|
|
866
|
+
query (${varDecl}) {
|
|
867
|
+
User(${selector}) {
|
|
868
|
+
id
|
|
869
|
+
name
|
|
870
|
+
favourites {
|
|
871
|
+
anime(perPage: ${pp}) {
|
|
872
|
+
nodes {
|
|
873
|
+
id
|
|
874
|
+
title { romaji english native userPreferred }
|
|
875
|
+
coverImage { large medium }
|
|
876
|
+
type
|
|
877
|
+
format
|
|
878
|
+
siteUrl
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
manga(perPage: ${pp}) {
|
|
882
|
+
nodes {
|
|
883
|
+
id
|
|
884
|
+
title { romaji english native userPreferred }
|
|
885
|
+
coverImage { large medium }
|
|
886
|
+
type
|
|
887
|
+
format
|
|
888
|
+
siteUrl
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
characters(perPage: ${pp}) {
|
|
892
|
+
nodes {
|
|
893
|
+
id
|
|
894
|
+
name { full native }
|
|
895
|
+
image { large medium }
|
|
896
|
+
siteUrl
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
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
|
+
}
|
|
772
918
|
|
|
773
919
|
// src/queries/studio.ts
|
|
774
920
|
var QUERY_STUDIO_BY_ID = `
|
|
@@ -777,6 +923,26 @@ query ($id: Int!) {
|
|
|
777
923
|
${STUDIO_FIELDS}
|
|
778
924
|
}
|
|
779
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
|
+
}
|
|
780
946
|
var QUERY_STUDIO_SEARCH = `
|
|
781
947
|
query ($search: String, $sort: [StudioSort], $page: Int, $perPage: Int) {
|
|
782
948
|
Page(page: $page, perPage: $perPage) {
|
|
@@ -927,11 +1093,21 @@ query ($search: String, $mediaCategoryId: Int, $categoryId: Int, $sort: [ThreadS
|
|
|
927
1093
|
|
|
928
1094
|
// src/rate-limiter/index.ts
|
|
929
1095
|
var RateLimiter = class {
|
|
1096
|
+
maxRequests;
|
|
1097
|
+
windowMs;
|
|
1098
|
+
maxRetries;
|
|
1099
|
+
retryDelayMs;
|
|
1100
|
+
enabled;
|
|
1101
|
+
timeoutMs;
|
|
1102
|
+
retryOnNetworkError;
|
|
1103
|
+
retryStrategy;
|
|
1104
|
+
/** @internal — sliding window: circular buffer of timestamps */
|
|
1105
|
+
timestamps;
|
|
1106
|
+
head = 0;
|
|
1107
|
+
count = 0;
|
|
1108
|
+
/** @internal — active sleep timers for cleanup */
|
|
1109
|
+
activeTimers = /* @__PURE__ */ new Set();
|
|
930
1110
|
constructor(options = {}) {
|
|
931
|
-
this.head = 0;
|
|
932
|
-
this.count = 0;
|
|
933
|
-
/** @internal — active sleep timers for cleanup */
|
|
934
|
-
this.activeTimers = /* @__PURE__ */ new Set();
|
|
935
1111
|
this.maxRequests = options.maxRequests ?? 85;
|
|
936
1112
|
this.windowMs = options.windowMs ?? 6e4;
|
|
937
1113
|
this.maxRetries = options.maxRetries ?? 3;
|
|
@@ -1326,6 +1502,14 @@ async function getMedia(client, id, include) {
|
|
|
1326
1502
|
const data = await client.request(query, { id });
|
|
1327
1503
|
return data.Media;
|
|
1328
1504
|
}
|
|
1505
|
+
async function getMediaByMalId(client, malId, type) {
|
|
1506
|
+
validateId(malId, "malId");
|
|
1507
|
+
const data = await client.request(QUERY_MEDIA_BY_MAL_ID, {
|
|
1508
|
+
idMal: malId,
|
|
1509
|
+
type
|
|
1510
|
+
});
|
|
1511
|
+
return data.Media;
|
|
1512
|
+
}
|
|
1329
1513
|
async function searchMedia(client, options = {}) {
|
|
1330
1514
|
const { query: search, page = 1, perPage = 20, genres, tags, genresExclude, tagsExclude, ...filters } = options;
|
|
1331
1515
|
return client.pagedRequest(
|
|
@@ -1446,7 +1630,7 @@ async function getWeeklySchedule(client, date = /* @__PURE__ */ new Date()) {
|
|
|
1446
1630
|
for await (const episode of iterator) {
|
|
1447
1631
|
const epDate = new Date(episode.airingAt * 1e3);
|
|
1448
1632
|
const dayName = names[epDate.getUTCDay()];
|
|
1449
|
-
schedule[dayName].push(episode);
|
|
1633
|
+
if (dayName) schedule[dayName].push(episode);
|
|
1450
1634
|
}
|
|
1451
1635
|
return schedule;
|
|
1452
1636
|
}
|
|
@@ -1472,8 +1656,14 @@ async function searchStaff(client, options = {}) {
|
|
|
1472
1656
|
}
|
|
1473
1657
|
|
|
1474
1658
|
// src/client/studio.ts
|
|
1475
|
-
async function getStudio(client, id) {
|
|
1659
|
+
async function getStudio(client, id, include) {
|
|
1476
1660
|
validateId(id, "studioId");
|
|
1661
|
+
if (include?.media) {
|
|
1662
|
+
const perPage = typeof include.media === "object" ? include.media.perPage : void 0;
|
|
1663
|
+
const query = buildStudioByIdQuery(perPage);
|
|
1664
|
+
const data2 = await client.request(query, { id });
|
|
1665
|
+
return data2.Studio;
|
|
1666
|
+
}
|
|
1477
1667
|
const data = await client.request(QUERY_STUDIO_BY_ID, { id });
|
|
1478
1668
|
return data.Studio;
|
|
1479
1669
|
}
|
|
@@ -1548,15 +1738,18 @@ async function getUserMediaList(client, options) {
|
|
|
1548
1738
|
"mediaList"
|
|
1549
1739
|
);
|
|
1550
1740
|
}
|
|
1551
|
-
async function getUserFavorites(client, idOrName) {
|
|
1741
|
+
async function getUserFavorites(client, idOrName, options) {
|
|
1742
|
+
const useBuilder = options?.perPage !== void 0;
|
|
1552
1743
|
if (typeof idOrName === "number") {
|
|
1553
1744
|
validateId(idOrName, "userId");
|
|
1554
|
-
const
|
|
1745
|
+
const query2 = useBuilder ? buildUserFavoritesQuery("id", options.perPage) : QUERY_USER_FAVORITES_BY_ID;
|
|
1746
|
+
const data2 = await client.request(query2, {
|
|
1555
1747
|
id: idOrName
|
|
1556
1748
|
});
|
|
1557
1749
|
return mapFavorites(data2.User.favourites);
|
|
1558
1750
|
}
|
|
1559
|
-
const
|
|
1751
|
+
const query = useBuilder ? buildUserFavoritesQuery("name", options.perPage) : QUERY_USER_FAVORITES_BY_NAME;
|
|
1752
|
+
const data = await client.request(query, {
|
|
1560
1753
|
name: idOrName
|
|
1561
1754
|
});
|
|
1562
1755
|
return mapFavorites(data.User.favourites);
|
|
@@ -1573,10 +1766,19 @@ function mapFavorites(fav) {
|
|
|
1573
1766
|
|
|
1574
1767
|
// src/client/index.ts
|
|
1575
1768
|
var DEFAULT_API_URL = "https://graphql.anilist.co";
|
|
1576
|
-
var LIB_VERSION = "1.
|
|
1769
|
+
var LIB_VERSION = "1.8.0" ;
|
|
1577
1770
|
var AniListClient = class {
|
|
1771
|
+
apiUrl;
|
|
1772
|
+
headers;
|
|
1773
|
+
cacheAdapter;
|
|
1774
|
+
rateLimiter;
|
|
1775
|
+
hooks;
|
|
1776
|
+
logger;
|
|
1777
|
+
signal;
|
|
1778
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
1779
|
+
_rateLimitInfo;
|
|
1780
|
+
_lastRequestMeta;
|
|
1578
1781
|
constructor(options = {}) {
|
|
1579
|
-
this.inFlight = /* @__PURE__ */ new Map();
|
|
1580
1782
|
this.apiUrl = options.apiUrl ?? DEFAULT_API_URL;
|
|
1581
1783
|
this.headers = {
|
|
1582
1784
|
"Content-Type": "application/json",
|
|
@@ -1589,6 +1791,7 @@ var AniListClient = class {
|
|
|
1589
1791
|
this.cacheAdapter = options.cacheAdapter ?? new MemoryCache(options.cache);
|
|
1590
1792
|
this.rateLimiter = new RateLimiter(options.rateLimit);
|
|
1591
1793
|
this.hooks = options.hooks ?? {};
|
|
1794
|
+
this.logger = options.logger;
|
|
1592
1795
|
this.signal = options.signal;
|
|
1593
1796
|
}
|
|
1594
1797
|
/**
|
|
@@ -1611,6 +1814,7 @@ var AniListClient = class {
|
|
|
1611
1814
|
const cached = await this.cacheAdapter.get(cacheKey);
|
|
1612
1815
|
if (cached !== void 0) {
|
|
1613
1816
|
this.hooks.onCacheHit?.(cacheKey);
|
|
1817
|
+
this.logger?.debug("Cache hit", { cacheKey });
|
|
1614
1818
|
const meta = { durationMs: 0, fromCache: true };
|
|
1615
1819
|
this._lastRequestMeta = meta;
|
|
1616
1820
|
this.hooks.onResponse?.(query, 0, true);
|
|
@@ -1630,6 +1834,7 @@ var AniListClient = class {
|
|
|
1630
1834
|
async executeRequest(query, variables, cacheKey) {
|
|
1631
1835
|
const start = Date.now();
|
|
1632
1836
|
this.hooks.onRequest?.(query, variables);
|
|
1837
|
+
this.logger?.debug("API request", { variables });
|
|
1633
1838
|
const minifiedQuery = normalizeQuery(query);
|
|
1634
1839
|
let res;
|
|
1635
1840
|
try {
|
|
@@ -1645,6 +1850,7 @@ var AniListClient = class {
|
|
|
1645
1850
|
);
|
|
1646
1851
|
} catch (err) {
|
|
1647
1852
|
const error = err instanceof AniListError ? err : new AniListError(err.message ?? "Network request failed", 0, [err]);
|
|
1853
|
+
this.logger?.error("Request failed", { error: error.message, status: error.status });
|
|
1648
1854
|
this.hooks.onError?.(error, query, variables);
|
|
1649
1855
|
throw error;
|
|
1650
1856
|
}
|
|
@@ -1652,6 +1858,7 @@ var AniListClient = class {
|
|
|
1652
1858
|
if (!res.ok || json.errors) {
|
|
1653
1859
|
const message = json.errors?.[0]?.message ?? `AniList API error (HTTP ${res.status})`;
|
|
1654
1860
|
const error = new AniListError(message, res.status, json.errors ?? []);
|
|
1861
|
+
this.logger?.error("Request failed", { error: error.message, status: error.status });
|
|
1655
1862
|
this.hooks.onError?.(error, query, variables);
|
|
1656
1863
|
throw error;
|
|
1657
1864
|
}
|
|
@@ -1670,6 +1877,7 @@ var AniListClient = class {
|
|
|
1670
1877
|
await this.cacheAdapter.set(cacheKey, data);
|
|
1671
1878
|
const meta = { durationMs, fromCache: false, rateLimitInfo: this._rateLimitInfo };
|
|
1672
1879
|
this._lastRequestMeta = meta;
|
|
1880
|
+
this.logger?.debug("Request complete", { durationMs, rateLimitInfo: this._rateLimitInfo });
|
|
1673
1881
|
this.hooks.onResponse?.(query, durationMs, false, this._rateLimitInfo);
|
|
1674
1882
|
return data;
|
|
1675
1883
|
}
|
|
@@ -1732,6 +1940,15 @@ var AniListClient = class {
|
|
|
1732
1940
|
async getAiredChapters(options = {}) {
|
|
1733
1941
|
return this.getRecentlyUpdatedManga(options);
|
|
1734
1942
|
}
|
|
1943
|
+
/**
|
|
1944
|
+
* Fetch a media entry by its MyAnimeList (MAL) ID.
|
|
1945
|
+
*
|
|
1946
|
+
* @param malId - The MyAnimeList ID
|
|
1947
|
+
* @param type - Optional media type to disambiguate (some MAL IDs map to both ANIME and MANGA)
|
|
1948
|
+
*/
|
|
1949
|
+
async getMediaByMalId(malId, type) {
|
|
1950
|
+
return getMediaByMalId(this, malId, type);
|
|
1951
|
+
}
|
|
1735
1952
|
/** Get the detailed schedule for the current week, sorted by day. */
|
|
1736
1953
|
async getWeeklySchedule(date) {
|
|
1737
1954
|
return getWeeklySchedule(this, date);
|
|
@@ -1784,20 +2001,32 @@ var AniListClient = class {
|
|
|
1784
2001
|
* Fetch a user's favorite anime, manga, characters, staff, and studios.
|
|
1785
2002
|
*
|
|
1786
2003
|
* @param idOrName - AniList user ID (number) or username (string)
|
|
2004
|
+
* @param options - Optional pagination options (perPage per category)
|
|
1787
2005
|
* @returns The user's favorites grouped by category
|
|
1788
2006
|
*
|
|
1789
2007
|
* @example
|
|
1790
2008
|
* ```typescript
|
|
1791
2009
|
* const favs = await client.getUserFavorites("AniList");
|
|
1792
2010
|
* favs.anime.forEach(a => console.log(a.title.romaji));
|
|
2011
|
+
*
|
|
2012
|
+
* // Fetch more results per category
|
|
2013
|
+
* const moreResults = await client.getUserFavorites(1, { perPage: 50 });
|
|
1793
2014
|
* ```
|
|
1794
2015
|
*/
|
|
1795
|
-
async getUserFavorites(idOrName) {
|
|
1796
|
-
return getUserFavorites(this, idOrName);
|
|
2016
|
+
async getUserFavorites(idOrName, options) {
|
|
2017
|
+
return getUserFavorites(this, idOrName, options);
|
|
1797
2018
|
}
|
|
1798
|
-
/**
|
|
1799
|
-
|
|
1800
|
-
|
|
2019
|
+
/**
|
|
2020
|
+
* Fetch a studio by its AniList ID.
|
|
2021
|
+
* Pass `include` to customise the number of media returned.
|
|
2022
|
+
*
|
|
2023
|
+
* @example
|
|
2024
|
+
* ```typescript
|
|
2025
|
+
* const studio = await client.getStudio(21, { media: { perPage: 50 } });
|
|
2026
|
+
* ```
|
|
2027
|
+
*/
|
|
2028
|
+
async getStudio(id, include) {
|
|
2029
|
+
return getStudio(this, id, include);
|
|
1801
2030
|
}
|
|
1802
2031
|
/** Search for studios by name. */
|
|
1803
2032
|
async searchStudios(options = {}) {
|
|
@@ -1847,21 +2076,24 @@ var AniListClient = class {
|
|
|
1847
2076
|
async getMediaBatch(ids) {
|
|
1848
2077
|
if (ids.length === 0) return [];
|
|
1849
2078
|
validateIds(ids, "mediaId");
|
|
1850
|
-
|
|
2079
|
+
const [singleMediaId] = ids;
|
|
2080
|
+
if (ids.length === 1 && singleMediaId !== void 0) return [await this.getMedia(singleMediaId)];
|
|
1851
2081
|
return this.executeBatch(ids, buildBatchMediaQuery, "m");
|
|
1852
2082
|
}
|
|
1853
2083
|
/** Fetch multiple characters in a single API request. */
|
|
1854
2084
|
async getCharacterBatch(ids) {
|
|
1855
2085
|
if (ids.length === 0) return [];
|
|
1856
2086
|
validateIds(ids, "characterId");
|
|
1857
|
-
|
|
2087
|
+
const [singleCharId] = ids;
|
|
2088
|
+
if (ids.length === 1 && singleCharId !== void 0) return [await this.getCharacter(singleCharId)];
|
|
1858
2089
|
return this.executeBatch(ids, buildBatchCharacterQuery, "c");
|
|
1859
2090
|
}
|
|
1860
2091
|
/** Fetch multiple staff members in a single API request. */
|
|
1861
2092
|
async getStaffBatch(ids) {
|
|
1862
2093
|
if (ids.length === 0) return [];
|
|
1863
2094
|
validateIds(ids, "staffId");
|
|
1864
|
-
|
|
2095
|
+
const [singleStaffId] = ids;
|
|
2096
|
+
if (ids.length === 1 && singleStaffId !== void 0) return [await this.getStaff(singleStaffId)];
|
|
1865
2097
|
return this.executeBatch(ids, buildBatchStaffQuery, "s");
|
|
1866
2098
|
}
|
|
1867
2099
|
/** @internal */
|
|
@@ -1906,10 +2138,31 @@ var AniListClient = class {
|
|
|
1906
2138
|
this.inFlight.clear();
|
|
1907
2139
|
this.rateLimiter.dispose();
|
|
1908
2140
|
}
|
|
2141
|
+
/**
|
|
2142
|
+
* Return a scoped view of this client where every request uses the given `AbortSignal`.
|
|
2143
|
+
* The returned object shares the same cache, rate limiter, and hooks.
|
|
2144
|
+
*
|
|
2145
|
+
* @example
|
|
2146
|
+
* ```ts
|
|
2147
|
+
* const controller = new AbortController();
|
|
2148
|
+
* const media = await client.withSignal(controller.signal).getMedia(1);
|
|
2149
|
+
*
|
|
2150
|
+
* // Cancel all in-flight requests made through the scoped view
|
|
2151
|
+
* controller.abort();
|
|
2152
|
+
* ```
|
|
2153
|
+
*/
|
|
2154
|
+
withSignal(signal) {
|
|
2155
|
+
const scoped = Object.create(this);
|
|
2156
|
+
Object.defineProperty(scoped, "signal", { value: signal, configurable: true });
|
|
2157
|
+
return scoped;
|
|
2158
|
+
}
|
|
1909
2159
|
};
|
|
1910
2160
|
|
|
1911
2161
|
// src/cache/redis.ts
|
|
1912
2162
|
var RedisCache = class {
|
|
2163
|
+
client;
|
|
2164
|
+
prefix;
|
|
2165
|
+
ttl;
|
|
1913
2166
|
constructor(options) {
|
|
1914
2167
|
this.client = options.client;
|
|
1915
2168
|
this.prefix = options.prefix ?? "ani:";
|