ani-client 1.7.0 → 1.8.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 +103 -45
- package/dist/index.d.mts +389 -274
- package/dist/index.d.ts +389 -274
- package/dist/index.js +582 -318
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +582 -318
- package/dist/index.mjs.map +1 -1
- package/package.json +25 -6
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
|
*
|
|
@@ -197,8 +250,101 @@ var MemoryCache = class {
|
|
|
197
250
|
}
|
|
198
251
|
};
|
|
199
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
|
+
|
|
200
342
|
// src/errors/index.ts
|
|
201
343
|
var AniListError = class _AniListError extends Error {
|
|
344
|
+
/** HTTP status code returned by the API */
|
|
345
|
+
status;
|
|
346
|
+
/** Raw error body from the API response */
|
|
347
|
+
errors;
|
|
202
348
|
constructor(message, status, errors = []) {
|
|
203
349
|
super(message);
|
|
204
350
|
this.name = "AniListError";
|
|
@@ -212,6 +358,32 @@ var AniListError = class _AniListError extends Error {
|
|
|
212
358
|
};
|
|
213
359
|
|
|
214
360
|
// src/queries/fragments.ts
|
|
361
|
+
var MEDIA_FIELDS_LIGHT = `
|
|
362
|
+
id
|
|
363
|
+
idMal
|
|
364
|
+
title { romaji english native userPreferred }
|
|
365
|
+
type
|
|
366
|
+
format
|
|
367
|
+
status
|
|
368
|
+
coverImage { large medium color }
|
|
369
|
+
bannerImage
|
|
370
|
+
genres
|
|
371
|
+
averageScore
|
|
372
|
+
popularity
|
|
373
|
+
favourites
|
|
374
|
+
isAdult
|
|
375
|
+
siteUrl
|
|
376
|
+
season
|
|
377
|
+
seasonYear
|
|
378
|
+
episodes
|
|
379
|
+
chapters
|
|
380
|
+
nextAiringEpisode {
|
|
381
|
+
id
|
|
382
|
+
airingAt
|
|
383
|
+
episode
|
|
384
|
+
timeUntilAiring
|
|
385
|
+
}
|
|
386
|
+
`;
|
|
215
387
|
var MEDIA_FIELDS_BASE = `
|
|
216
388
|
id
|
|
217
389
|
idMal
|
|
@@ -631,6 +803,12 @@ query ($season: MediaSeason!, $seasonYear: Int!, $type: MediaType, $sort: [Media
|
|
|
631
803
|
}
|
|
632
804
|
}
|
|
633
805
|
}`;
|
|
806
|
+
var QUERY_MEDIA_BY_MAL_ID = `
|
|
807
|
+
query ($idMal: Int!, $type: MediaType) {
|
|
808
|
+
Media(idMal: $idMal, type: $type) {
|
|
809
|
+
${MEDIA_FIELDS}
|
|
810
|
+
}
|
|
811
|
+
}`;
|
|
634
812
|
var QUERY_RECOMMENDATIONS = `
|
|
635
813
|
query ($mediaId: Int!, $page: Int, $perPage: Int, $sort: [RecommendationSort]) {
|
|
636
814
|
Media(id: $mediaId) {
|
|
@@ -666,143 +844,6 @@ query ($mediaId: Int!, $page: Int, $perPage: Int, $sort: [RecommendationSort]) {
|
|
|
666
844
|
}
|
|
667
845
|
}`;
|
|
668
846
|
|
|
669
|
-
// src/queries/character.ts
|
|
670
|
-
var QUERY_CHARACTER_BY_ID = `
|
|
671
|
-
query ($id: Int!) {
|
|
672
|
-
Character(id: $id) {
|
|
673
|
-
${CHARACTER_FIELDS}
|
|
674
|
-
}
|
|
675
|
-
}`;
|
|
676
|
-
var QUERY_CHARACTER_BY_ID_WITH_VA = `
|
|
677
|
-
query ($id: Int!) {
|
|
678
|
-
Character(id: $id) {
|
|
679
|
-
${CHARACTER_FIELDS_WITH_VA}
|
|
680
|
-
}
|
|
681
|
-
}`;
|
|
682
|
-
var QUERY_CHARACTER_SEARCH = `
|
|
683
|
-
query ($search: String, $sort: [CharacterSort], $page: Int, $perPage: Int) {
|
|
684
|
-
Page(page: $page, perPage: $perPage) {
|
|
685
|
-
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
686
|
-
characters(search: $search, sort: $sort) {
|
|
687
|
-
${CHARACTER_FIELDS}
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
}`;
|
|
691
|
-
var QUERY_CHARACTER_SEARCH_WITH_VA = `
|
|
692
|
-
query ($search: String, $sort: [CharacterSort], $page: Int, $perPage: Int) {
|
|
693
|
-
Page(page: $page, perPage: $perPage) {
|
|
694
|
-
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
695
|
-
characters(search: $search, sort: $sort) {
|
|
696
|
-
${CHARACTER_FIELDS_WITH_VA}
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
}`;
|
|
700
|
-
|
|
701
|
-
// src/queries/staff.ts
|
|
702
|
-
var QUERY_STAFF_BY_ID = `
|
|
703
|
-
query ($id: Int!) {
|
|
704
|
-
Staff(id: $id) {
|
|
705
|
-
${STAFF_FIELDS}
|
|
706
|
-
}
|
|
707
|
-
}`;
|
|
708
|
-
var QUERY_STAFF_BY_ID_WITH_MEDIA = `
|
|
709
|
-
query ($id: Int!, $perPage: Int) {
|
|
710
|
-
Staff(id: $id) {
|
|
711
|
-
${STAFF_FIELDS}
|
|
712
|
-
${STAFF_MEDIA_FIELDS}
|
|
713
|
-
}
|
|
714
|
-
}`;
|
|
715
|
-
var QUERY_STAFF_SEARCH = `
|
|
716
|
-
query ($search: String, $sort: [StaffSort], $page: Int, $perPage: Int) {
|
|
717
|
-
Page(page: $page, perPage: $perPage) {
|
|
718
|
-
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
719
|
-
staff(search: $search, sort: $sort) {
|
|
720
|
-
${STAFF_FIELDS}
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
}`;
|
|
724
|
-
|
|
725
|
-
// src/queries/user.ts
|
|
726
|
-
var QUERY_USER_BY_ID = `
|
|
727
|
-
query ($id: Int!) {
|
|
728
|
-
User(id: $id) {
|
|
729
|
-
${USER_FIELDS}
|
|
730
|
-
}
|
|
731
|
-
}`;
|
|
732
|
-
var QUERY_USER_BY_NAME = `
|
|
733
|
-
query ($name: String!) {
|
|
734
|
-
User(name: $name) {
|
|
735
|
-
${USER_FIELDS}
|
|
736
|
-
}
|
|
737
|
-
}`;
|
|
738
|
-
var QUERY_USER_SEARCH = `
|
|
739
|
-
query ($search: String, $sort: [UserSort], $page: Int, $perPage: Int) {
|
|
740
|
-
Page(page: $page, perPage: $perPage) {
|
|
741
|
-
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
742
|
-
users(search: $search, sort: $sort) {
|
|
743
|
-
${USER_FIELDS}
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
}`;
|
|
747
|
-
var QUERY_USER_MEDIA_LIST = `
|
|
748
|
-
query ($userId: Int, $userName: String, $type: MediaType!, $status: MediaListStatus, $sort: [MediaListSort], $page: Int, $perPage: Int) {
|
|
749
|
-
Page(page: $page, perPage: $perPage) {
|
|
750
|
-
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
751
|
-
mediaList(userId: $userId, userName: $userName, type: $type, status: $status, sort: $sort) {
|
|
752
|
-
${MEDIA_LIST_FIELDS}
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
}`;
|
|
756
|
-
var QUERY_USER_FAVORITES_BY_ID = `
|
|
757
|
-
query ($id: Int!) {
|
|
758
|
-
User(id: $id) {
|
|
759
|
-
id
|
|
760
|
-
name
|
|
761
|
-
${USER_FAVORITES_FIELDS}
|
|
762
|
-
}
|
|
763
|
-
}`;
|
|
764
|
-
var QUERY_USER_FAVORITES_BY_NAME = `
|
|
765
|
-
query ($name: String!) {
|
|
766
|
-
User(name: $name) {
|
|
767
|
-
id
|
|
768
|
-
name
|
|
769
|
-
${USER_FAVORITES_FIELDS}
|
|
770
|
-
}
|
|
771
|
-
}`;
|
|
772
|
-
|
|
773
|
-
// src/queries/studio.ts
|
|
774
|
-
var QUERY_STUDIO_BY_ID = `
|
|
775
|
-
query ($id: Int!) {
|
|
776
|
-
Studio(id: $id) {
|
|
777
|
-
${STUDIO_FIELDS}
|
|
778
|
-
}
|
|
779
|
-
}`;
|
|
780
|
-
var QUERY_STUDIO_SEARCH = `
|
|
781
|
-
query ($search: String, $sort: [StudioSort], $page: Int, $perPage: Int) {
|
|
782
|
-
Page(page: $page, perPage: $perPage) {
|
|
783
|
-
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
784
|
-
studios(search: $search, sort: $sort) {
|
|
785
|
-
${STUDIO_FIELDS}
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
}`;
|
|
789
|
-
|
|
790
|
-
// src/queries/metadata.ts
|
|
791
|
-
var QUERY_GENRES = `
|
|
792
|
-
query {
|
|
793
|
-
GenreCollection
|
|
794
|
-
}`;
|
|
795
|
-
var QUERY_TAGS = `
|
|
796
|
-
query {
|
|
797
|
-
MediaTagCollection {
|
|
798
|
-
id
|
|
799
|
-
name
|
|
800
|
-
description
|
|
801
|
-
category
|
|
802
|
-
isAdult
|
|
803
|
-
}
|
|
804
|
-
}`;
|
|
805
|
-
|
|
806
847
|
// src/queries/builders.ts
|
|
807
848
|
function buildMediaByIdQuery(include) {
|
|
808
849
|
if (!include) return QUERY_MEDIA_BY_ID;
|
|
@@ -908,6 +949,115 @@ var buildBatchMediaQuery = (ids) => buildBatchQuery(ids, "Media", MEDIA_FIELDS,
|
|
|
908
949
|
var buildBatchCharacterQuery = (ids) => buildBatchQuery(ids, "Character", CHARACTER_FIELDS, "c");
|
|
909
950
|
var buildBatchStaffQuery = (ids) => buildBatchQuery(ids, "Staff", STAFF_FIELDS, "s");
|
|
910
951
|
|
|
952
|
+
// src/queries/character.ts
|
|
953
|
+
var QUERY_CHARACTER_BY_ID = `
|
|
954
|
+
query ($id: Int!) {
|
|
955
|
+
Character(id: $id) {
|
|
956
|
+
${CHARACTER_FIELDS}
|
|
957
|
+
}
|
|
958
|
+
}`;
|
|
959
|
+
var QUERY_CHARACTER_BY_ID_WITH_VA = `
|
|
960
|
+
query ($id: Int!) {
|
|
961
|
+
Character(id: $id) {
|
|
962
|
+
${CHARACTER_FIELDS_WITH_VA}
|
|
963
|
+
}
|
|
964
|
+
}`;
|
|
965
|
+
var QUERY_CHARACTER_SEARCH = `
|
|
966
|
+
query ($search: String, $sort: [CharacterSort], $page: Int, $perPage: Int) {
|
|
967
|
+
Page(page: $page, perPage: $perPage) {
|
|
968
|
+
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
969
|
+
characters(search: $search, sort: $sort) {
|
|
970
|
+
${CHARACTER_FIELDS}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}`;
|
|
974
|
+
var QUERY_CHARACTER_SEARCH_WITH_VA = `
|
|
975
|
+
query ($search: String, $sort: [CharacterSort], $page: Int, $perPage: Int) {
|
|
976
|
+
Page(page: $page, perPage: $perPage) {
|
|
977
|
+
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
978
|
+
characters(search: $search, sort: $sort) {
|
|
979
|
+
${CHARACTER_FIELDS_WITH_VA}
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
}`;
|
|
983
|
+
|
|
984
|
+
// src/queries/metadata.ts
|
|
985
|
+
var QUERY_GENRES = `
|
|
986
|
+
query {
|
|
987
|
+
GenreCollection
|
|
988
|
+
}`;
|
|
989
|
+
var QUERY_TAGS = `
|
|
990
|
+
query {
|
|
991
|
+
MediaTagCollection {
|
|
992
|
+
id
|
|
993
|
+
name
|
|
994
|
+
description
|
|
995
|
+
category
|
|
996
|
+
isAdult
|
|
997
|
+
}
|
|
998
|
+
}`;
|
|
999
|
+
|
|
1000
|
+
// src/queries/staff.ts
|
|
1001
|
+
var QUERY_STAFF_BY_ID = `
|
|
1002
|
+
query ($id: Int!) {
|
|
1003
|
+
Staff(id: $id) {
|
|
1004
|
+
${STAFF_FIELDS}
|
|
1005
|
+
}
|
|
1006
|
+
}`;
|
|
1007
|
+
var QUERY_STAFF_BY_ID_WITH_MEDIA = `
|
|
1008
|
+
query ($id: Int!, $perPage: Int) {
|
|
1009
|
+
Staff(id: $id) {
|
|
1010
|
+
${STAFF_FIELDS}
|
|
1011
|
+
${STAFF_MEDIA_FIELDS}
|
|
1012
|
+
}
|
|
1013
|
+
}`;
|
|
1014
|
+
var QUERY_STAFF_SEARCH = `
|
|
1015
|
+
query ($search: String, $sort: [StaffSort], $page: Int, $perPage: Int) {
|
|
1016
|
+
Page(page: $page, perPage: $perPage) {
|
|
1017
|
+
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
1018
|
+
staff(search: $search, sort: $sort) {
|
|
1019
|
+
${STAFF_FIELDS}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}`;
|
|
1023
|
+
|
|
1024
|
+
// src/queries/studio.ts
|
|
1025
|
+
var QUERY_STUDIO_BY_ID = `
|
|
1026
|
+
query ($id: Int!) {
|
|
1027
|
+
Studio(id: $id) {
|
|
1028
|
+
${STUDIO_FIELDS}
|
|
1029
|
+
}
|
|
1030
|
+
}`;
|
|
1031
|
+
function buildStudioByIdQuery(mediaPerPage) {
|
|
1032
|
+
if (mediaPerPage === void 0) return QUERY_STUDIO_BY_ID;
|
|
1033
|
+
const pp = clampPerPage(mediaPerPage);
|
|
1034
|
+
return `
|
|
1035
|
+
query ($id: Int!) {
|
|
1036
|
+
Studio(id: $id) {
|
|
1037
|
+
id
|
|
1038
|
+
name
|
|
1039
|
+
isAnimationStudio
|
|
1040
|
+
siteUrl
|
|
1041
|
+
favourites
|
|
1042
|
+
media(page: 1, perPage: ${pp}, sort: POPULARITY_DESC) {
|
|
1043
|
+
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
1044
|
+
nodes {
|
|
1045
|
+
${MEDIA_FIELDS_LIGHT}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
}`;
|
|
1050
|
+
}
|
|
1051
|
+
var QUERY_STUDIO_SEARCH = `
|
|
1052
|
+
query ($search: String, $sort: [StudioSort], $page: Int, $perPage: Int) {
|
|
1053
|
+
Page(page: $page, perPage: $perPage) {
|
|
1054
|
+
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
1055
|
+
studios(search: $search, sort: $sort) {
|
|
1056
|
+
${STUDIO_FIELDS}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}`;
|
|
1060
|
+
|
|
911
1061
|
// src/queries/thread.ts
|
|
912
1062
|
var QUERY_THREAD_BY_ID = `
|
|
913
1063
|
query ($id: Int!) {
|
|
@@ -925,13 +1075,128 @@ query ($search: String, $mediaCategoryId: Int, $categoryId: Int, $sort: [ThreadS
|
|
|
925
1075
|
}
|
|
926
1076
|
}`;
|
|
927
1077
|
|
|
1078
|
+
// src/queries/user.ts
|
|
1079
|
+
var QUERY_USER_BY_ID = `
|
|
1080
|
+
query ($id: Int!) {
|
|
1081
|
+
User(id: $id) {
|
|
1082
|
+
${USER_FIELDS}
|
|
1083
|
+
}
|
|
1084
|
+
}`;
|
|
1085
|
+
var QUERY_USER_BY_NAME = `
|
|
1086
|
+
query ($name: String!) {
|
|
1087
|
+
User(name: $name) {
|
|
1088
|
+
${USER_FIELDS}
|
|
1089
|
+
}
|
|
1090
|
+
}`;
|
|
1091
|
+
var QUERY_USER_SEARCH = `
|
|
1092
|
+
query ($search: String, $sort: [UserSort], $page: Int, $perPage: Int) {
|
|
1093
|
+
Page(page: $page, perPage: $perPage) {
|
|
1094
|
+
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
1095
|
+
users(search: $search, sort: $sort) {
|
|
1096
|
+
${USER_FIELDS}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}`;
|
|
1100
|
+
var QUERY_USER_MEDIA_LIST = `
|
|
1101
|
+
query ($userId: Int, $userName: String, $type: MediaType!, $status: MediaListStatus, $sort: [MediaListSort], $page: Int, $perPage: Int) {
|
|
1102
|
+
Page(page: $page, perPage: $perPage) {
|
|
1103
|
+
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
1104
|
+
mediaList(userId: $userId, userName: $userName, type: $type, status: $status, sort: $sort) {
|
|
1105
|
+
${MEDIA_LIST_FIELDS}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}`;
|
|
1109
|
+
var QUERY_USER_FAVORITES_BY_ID = `
|
|
1110
|
+
query ($id: Int!) {
|
|
1111
|
+
User(id: $id) {
|
|
1112
|
+
id
|
|
1113
|
+
name
|
|
1114
|
+
${USER_FAVORITES_FIELDS}
|
|
1115
|
+
}
|
|
1116
|
+
}`;
|
|
1117
|
+
var QUERY_USER_FAVORITES_BY_NAME = `
|
|
1118
|
+
query ($name: String!) {
|
|
1119
|
+
User(name: $name) {
|
|
1120
|
+
id
|
|
1121
|
+
name
|
|
1122
|
+
${USER_FAVORITES_FIELDS}
|
|
1123
|
+
}
|
|
1124
|
+
}`;
|
|
1125
|
+
function buildUserFavoritesQuery(idOrName, perPage = 25) {
|
|
1126
|
+
const pp = clampPerPage(perPage);
|
|
1127
|
+
const varDecl = idOrName === "id" ? "$id: Int!" : "$name: String!";
|
|
1128
|
+
const selector = idOrName === "id" ? "id: $id" : "name: $name";
|
|
1129
|
+
return `
|
|
1130
|
+
query (${varDecl}) {
|
|
1131
|
+
User(${selector}) {
|
|
1132
|
+
id
|
|
1133
|
+
name
|
|
1134
|
+
favourites {
|
|
1135
|
+
anime(perPage: ${pp}) {
|
|
1136
|
+
nodes {
|
|
1137
|
+
id
|
|
1138
|
+
title { romaji english native userPreferred }
|
|
1139
|
+
coverImage { large medium }
|
|
1140
|
+
type
|
|
1141
|
+
format
|
|
1142
|
+
siteUrl
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
manga(perPage: ${pp}) {
|
|
1146
|
+
nodes {
|
|
1147
|
+
id
|
|
1148
|
+
title { romaji english native userPreferred }
|
|
1149
|
+
coverImage { large medium }
|
|
1150
|
+
type
|
|
1151
|
+
format
|
|
1152
|
+
siteUrl
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
characters(perPage: ${pp}) {
|
|
1156
|
+
nodes {
|
|
1157
|
+
id
|
|
1158
|
+
name { full native }
|
|
1159
|
+
image { large medium }
|
|
1160
|
+
siteUrl
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
staff(perPage: ${pp}) {
|
|
1164
|
+
nodes {
|
|
1165
|
+
id
|
|
1166
|
+
name { full native }
|
|
1167
|
+
image { large medium }
|
|
1168
|
+
siteUrl
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
studios(perPage: ${pp}) {
|
|
1172
|
+
nodes {
|
|
1173
|
+
id
|
|
1174
|
+
name
|
|
1175
|
+
siteUrl
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}`;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
928
1183
|
// src/rate-limiter/index.ts
|
|
929
1184
|
var RateLimiter = class {
|
|
1185
|
+
maxRequests;
|
|
1186
|
+
windowMs;
|
|
1187
|
+
maxRetries;
|
|
1188
|
+
retryDelayMs;
|
|
1189
|
+
enabled;
|
|
1190
|
+
timeoutMs;
|
|
1191
|
+
retryOnNetworkError;
|
|
1192
|
+
retryStrategy;
|
|
1193
|
+
/** @internal — sliding window: circular buffer of timestamps */
|
|
1194
|
+
timestamps;
|
|
1195
|
+
head = 0;
|
|
1196
|
+
count = 0;
|
|
1197
|
+
/** @internal — active sleep timers for cleanup */
|
|
1198
|
+
activeTimers = /* @__PURE__ */ new Set();
|
|
930
1199
|
constructor(options = {}) {
|
|
931
|
-
this.head = 0;
|
|
932
|
-
this.count = 0;
|
|
933
|
-
/** @internal — active sleep timers for cleanup */
|
|
934
|
-
this.activeTimers = /* @__PURE__ */ new Set();
|
|
935
1200
|
this.maxRequests = options.maxRequests ?? 85;
|
|
936
1201
|
this.windowMs = options.windowMs ?? 6e4;
|
|
937
1202
|
this.maxRetries = options.maxRetries ?? 3;
|
|
@@ -997,7 +1262,7 @@ var RateLimiter = class {
|
|
|
997
1262
|
}
|
|
998
1263
|
}
|
|
999
1264
|
if (lastResponse) return lastResponse;
|
|
1000
|
-
throw lastError;
|
|
1265
|
+
throw lastError ?? new Error(`Request failed after ${this.maxRetries} retries`);
|
|
1001
1266
|
}
|
|
1002
1267
|
/** @internal — Exponential backoff with jitter, capped at 30s (or custom strategy) */
|
|
1003
1268
|
exponentialDelay(attempt) {
|
|
@@ -1072,6 +1337,68 @@ async function searchCharacters(client, options = {}) {
|
|
|
1072
1337
|
return client.pagedRequest(gqlQuery, { search, sort, page, perPage: clampPerPage(perPage) }, "characters");
|
|
1073
1338
|
}
|
|
1074
1339
|
|
|
1340
|
+
// src/types/character.ts
|
|
1341
|
+
var CharacterSort = /* @__PURE__ */ ((CharacterSort2) => {
|
|
1342
|
+
CharacterSort2["ID"] = "ID";
|
|
1343
|
+
CharacterSort2["ID_DESC"] = "ID_DESC";
|
|
1344
|
+
CharacterSort2["ROLE"] = "ROLE";
|
|
1345
|
+
CharacterSort2["ROLE_DESC"] = "ROLE_DESC";
|
|
1346
|
+
CharacterSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
|
|
1347
|
+
CharacterSort2["FAVOURITES"] = "FAVOURITES";
|
|
1348
|
+
CharacterSort2["FAVOURITES_DESC"] = "FAVOURITES_DESC";
|
|
1349
|
+
return CharacterSort2;
|
|
1350
|
+
})(CharacterSort || {});
|
|
1351
|
+
var CharacterRole = /* @__PURE__ */ ((CharacterRole2) => {
|
|
1352
|
+
CharacterRole2["MAIN"] = "MAIN";
|
|
1353
|
+
CharacterRole2["SUPPORTING"] = "SUPPORTING";
|
|
1354
|
+
CharacterRole2["BACKGROUND"] = "BACKGROUND";
|
|
1355
|
+
return CharacterRole2;
|
|
1356
|
+
})(CharacterRole || {});
|
|
1357
|
+
|
|
1358
|
+
// src/types/lists.ts
|
|
1359
|
+
var MediaListStatus = /* @__PURE__ */ ((MediaListStatus2) => {
|
|
1360
|
+
MediaListStatus2["CURRENT"] = "CURRENT";
|
|
1361
|
+
MediaListStatus2["PLANNING"] = "PLANNING";
|
|
1362
|
+
MediaListStatus2["COMPLETED"] = "COMPLETED";
|
|
1363
|
+
MediaListStatus2["DROPPED"] = "DROPPED";
|
|
1364
|
+
MediaListStatus2["PAUSED"] = "PAUSED";
|
|
1365
|
+
MediaListStatus2["REPEATING"] = "REPEATING";
|
|
1366
|
+
return MediaListStatus2;
|
|
1367
|
+
})(MediaListStatus || {});
|
|
1368
|
+
var MediaListSort = /* @__PURE__ */ ((MediaListSort2) => {
|
|
1369
|
+
MediaListSort2["MEDIA_ID"] = "MEDIA_ID";
|
|
1370
|
+
MediaListSort2["MEDIA_ID_DESC"] = "MEDIA_ID_DESC";
|
|
1371
|
+
MediaListSort2["SCORE"] = "SCORE";
|
|
1372
|
+
MediaListSort2["SCORE_DESC"] = "SCORE_DESC";
|
|
1373
|
+
MediaListSort2["STATUS"] = "STATUS";
|
|
1374
|
+
MediaListSort2["STATUS_DESC"] = "STATUS_DESC";
|
|
1375
|
+
MediaListSort2["PROGRESS"] = "PROGRESS";
|
|
1376
|
+
MediaListSort2["PROGRESS_DESC"] = "PROGRESS_DESC";
|
|
1377
|
+
MediaListSort2["PROGRESS_VOLUMES"] = "PROGRESS_VOLUMES";
|
|
1378
|
+
MediaListSort2["PROGRESS_VOLUMES_DESC"] = "PROGRESS_VOLUMES_DESC";
|
|
1379
|
+
MediaListSort2["REPEAT"] = "REPEAT";
|
|
1380
|
+
MediaListSort2["REPEAT_DESC"] = "REPEAT_DESC";
|
|
1381
|
+
MediaListSort2["PRIORITY"] = "PRIORITY";
|
|
1382
|
+
MediaListSort2["PRIORITY_DESC"] = "PRIORITY_DESC";
|
|
1383
|
+
MediaListSort2["STARTED_ON"] = "STARTED_ON";
|
|
1384
|
+
MediaListSort2["STARTED_ON_DESC"] = "STARTED_ON_DESC";
|
|
1385
|
+
MediaListSort2["FINISHED_ON"] = "FINISHED_ON";
|
|
1386
|
+
MediaListSort2["FINISHED_ON_DESC"] = "FINISHED_ON_DESC";
|
|
1387
|
+
MediaListSort2["ADDED_TIME"] = "ADDED_TIME";
|
|
1388
|
+
MediaListSort2["ADDED_TIME_DESC"] = "ADDED_TIME_DESC";
|
|
1389
|
+
MediaListSort2["UPDATED_TIME"] = "UPDATED_TIME";
|
|
1390
|
+
MediaListSort2["UPDATED_TIME_DESC"] = "UPDATED_TIME_DESC";
|
|
1391
|
+
MediaListSort2["MEDIA_TITLE_ROMAJI"] = "MEDIA_TITLE_ROMAJI";
|
|
1392
|
+
MediaListSort2["MEDIA_TITLE_ROMAJI_DESC"] = "MEDIA_TITLE_ROMAJI_DESC";
|
|
1393
|
+
MediaListSort2["MEDIA_TITLE_ENGLISH"] = "MEDIA_TITLE_ENGLISH";
|
|
1394
|
+
MediaListSort2["MEDIA_TITLE_ENGLISH_DESC"] = "MEDIA_TITLE_ENGLISH_DESC";
|
|
1395
|
+
MediaListSort2["MEDIA_TITLE_NATIVE"] = "MEDIA_TITLE_NATIVE";
|
|
1396
|
+
MediaListSort2["MEDIA_TITLE_NATIVE_DESC"] = "MEDIA_TITLE_NATIVE_DESC";
|
|
1397
|
+
MediaListSort2["MEDIA_POPULARITY"] = "MEDIA_POPULARITY";
|
|
1398
|
+
MediaListSort2["MEDIA_POPULARITY_DESC"] = "MEDIA_POPULARITY_DESC";
|
|
1399
|
+
return MediaListSort2;
|
|
1400
|
+
})(MediaListSort || {});
|
|
1401
|
+
|
|
1075
1402
|
// src/types/media.ts
|
|
1076
1403
|
var MediaType = /* @__PURE__ */ ((MediaType2) => {
|
|
1077
1404
|
MediaType2["ANIME"] = "ANIME";
|
|
@@ -1195,24 +1522,6 @@ var RecommendationSort = /* @__PURE__ */ ((RecommendationSort2) => {
|
|
|
1195
1522
|
return RecommendationSort2;
|
|
1196
1523
|
})(RecommendationSort || {});
|
|
1197
1524
|
|
|
1198
|
-
// src/types/character.ts
|
|
1199
|
-
var CharacterSort = /* @__PURE__ */ ((CharacterSort2) => {
|
|
1200
|
-
CharacterSort2["ID"] = "ID";
|
|
1201
|
-
CharacterSort2["ID_DESC"] = "ID_DESC";
|
|
1202
|
-
CharacterSort2["ROLE"] = "ROLE";
|
|
1203
|
-
CharacterSort2["ROLE_DESC"] = "ROLE_DESC";
|
|
1204
|
-
CharacterSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
|
|
1205
|
-
CharacterSort2["FAVOURITES"] = "FAVOURITES";
|
|
1206
|
-
CharacterSort2["FAVOURITES_DESC"] = "FAVOURITES_DESC";
|
|
1207
|
-
return CharacterSort2;
|
|
1208
|
-
})(CharacterSort || {});
|
|
1209
|
-
var CharacterRole = /* @__PURE__ */ ((CharacterRole2) => {
|
|
1210
|
-
CharacterRole2["MAIN"] = "MAIN";
|
|
1211
|
-
CharacterRole2["SUPPORTING"] = "SUPPORTING";
|
|
1212
|
-
CharacterRole2["BACKGROUND"] = "BACKGROUND";
|
|
1213
|
-
return CharacterRole2;
|
|
1214
|
-
})(CharacterRole || {});
|
|
1215
|
-
|
|
1216
1525
|
// src/types/staff.ts
|
|
1217
1526
|
var StaffSort = /* @__PURE__ */ ((StaffSort2) => {
|
|
1218
1527
|
StaffSort2["ID"] = "ID";
|
|
@@ -1228,20 +1537,6 @@ var StaffSort = /* @__PURE__ */ ((StaffSort2) => {
|
|
|
1228
1537
|
return StaffSort2;
|
|
1229
1538
|
})(StaffSort || {});
|
|
1230
1539
|
|
|
1231
|
-
// src/types/user.ts
|
|
1232
|
-
var UserSort = /* @__PURE__ */ ((UserSort2) => {
|
|
1233
|
-
UserSort2["ID"] = "ID";
|
|
1234
|
-
UserSort2["ID_DESC"] = "ID_DESC";
|
|
1235
|
-
UserSort2["USERNAME"] = "USERNAME";
|
|
1236
|
-
UserSort2["USERNAME_DESC"] = "USERNAME_DESC";
|
|
1237
|
-
UserSort2["WATCHED_TIME"] = "WATCHED_TIME";
|
|
1238
|
-
UserSort2["WATCHED_TIME_DESC"] = "WATCHED_TIME_DESC";
|
|
1239
|
-
UserSort2["CHAPTERS_READ"] = "CHAPTERS_READ";
|
|
1240
|
-
UserSort2["CHAPTERS_READ_DESC"] = "CHAPTERS_READ_DESC";
|
|
1241
|
-
UserSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
|
|
1242
|
-
return UserSort2;
|
|
1243
|
-
})(UserSort || {});
|
|
1244
|
-
|
|
1245
1540
|
// src/types/studio.ts
|
|
1246
1541
|
var StudioSort = /* @__PURE__ */ ((StudioSort2) => {
|
|
1247
1542
|
StudioSort2["ID"] = "ID";
|
|
@@ -1254,50 +1549,6 @@ var StudioSort = /* @__PURE__ */ ((StudioSort2) => {
|
|
|
1254
1549
|
return StudioSort2;
|
|
1255
1550
|
})(StudioSort || {});
|
|
1256
1551
|
|
|
1257
|
-
// src/types/lists.ts
|
|
1258
|
-
var MediaListStatus = /* @__PURE__ */ ((MediaListStatus2) => {
|
|
1259
|
-
MediaListStatus2["CURRENT"] = "CURRENT";
|
|
1260
|
-
MediaListStatus2["PLANNING"] = "PLANNING";
|
|
1261
|
-
MediaListStatus2["COMPLETED"] = "COMPLETED";
|
|
1262
|
-
MediaListStatus2["DROPPED"] = "DROPPED";
|
|
1263
|
-
MediaListStatus2["PAUSED"] = "PAUSED";
|
|
1264
|
-
MediaListStatus2["REPEATING"] = "REPEATING";
|
|
1265
|
-
return MediaListStatus2;
|
|
1266
|
-
})(MediaListStatus || {});
|
|
1267
|
-
var MediaListSort = /* @__PURE__ */ ((MediaListSort2) => {
|
|
1268
|
-
MediaListSort2["MEDIA_ID"] = "MEDIA_ID";
|
|
1269
|
-
MediaListSort2["MEDIA_ID_DESC"] = "MEDIA_ID_DESC";
|
|
1270
|
-
MediaListSort2["SCORE"] = "SCORE";
|
|
1271
|
-
MediaListSort2["SCORE_DESC"] = "SCORE_DESC";
|
|
1272
|
-
MediaListSort2["STATUS"] = "STATUS";
|
|
1273
|
-
MediaListSort2["STATUS_DESC"] = "STATUS_DESC";
|
|
1274
|
-
MediaListSort2["PROGRESS"] = "PROGRESS";
|
|
1275
|
-
MediaListSort2["PROGRESS_DESC"] = "PROGRESS_DESC";
|
|
1276
|
-
MediaListSort2["PROGRESS_VOLUMES"] = "PROGRESS_VOLUMES";
|
|
1277
|
-
MediaListSort2["PROGRESS_VOLUMES_DESC"] = "PROGRESS_VOLUMES_DESC";
|
|
1278
|
-
MediaListSort2["REPEAT"] = "REPEAT";
|
|
1279
|
-
MediaListSort2["REPEAT_DESC"] = "REPEAT_DESC";
|
|
1280
|
-
MediaListSort2["PRIORITY"] = "PRIORITY";
|
|
1281
|
-
MediaListSort2["PRIORITY_DESC"] = "PRIORITY_DESC";
|
|
1282
|
-
MediaListSort2["STARTED_ON"] = "STARTED_ON";
|
|
1283
|
-
MediaListSort2["STARTED_ON_DESC"] = "STARTED_ON_DESC";
|
|
1284
|
-
MediaListSort2["FINISHED_ON"] = "FINISHED_ON";
|
|
1285
|
-
MediaListSort2["FINISHED_ON_DESC"] = "FINISHED_ON_DESC";
|
|
1286
|
-
MediaListSort2["ADDED_TIME"] = "ADDED_TIME";
|
|
1287
|
-
MediaListSort2["ADDED_TIME_DESC"] = "ADDED_TIME_DESC";
|
|
1288
|
-
MediaListSort2["UPDATED_TIME"] = "UPDATED_TIME";
|
|
1289
|
-
MediaListSort2["UPDATED_TIME_DESC"] = "UPDATED_TIME_DESC";
|
|
1290
|
-
MediaListSort2["MEDIA_TITLE_ROMAJI"] = "MEDIA_TITLE_ROMAJI";
|
|
1291
|
-
MediaListSort2["MEDIA_TITLE_ROMAJI_DESC"] = "MEDIA_TITLE_ROMAJI_DESC";
|
|
1292
|
-
MediaListSort2["MEDIA_TITLE_ENGLISH"] = "MEDIA_TITLE_ENGLISH";
|
|
1293
|
-
MediaListSort2["MEDIA_TITLE_ENGLISH_DESC"] = "MEDIA_TITLE_ENGLISH_DESC";
|
|
1294
|
-
MediaListSort2["MEDIA_TITLE_NATIVE"] = "MEDIA_TITLE_NATIVE";
|
|
1295
|
-
MediaListSort2["MEDIA_TITLE_NATIVE_DESC"] = "MEDIA_TITLE_NATIVE_DESC";
|
|
1296
|
-
MediaListSort2["MEDIA_POPULARITY"] = "MEDIA_POPULARITY";
|
|
1297
|
-
MediaListSort2["MEDIA_POPULARITY_DESC"] = "MEDIA_POPULARITY_DESC";
|
|
1298
|
-
return MediaListSort2;
|
|
1299
|
-
})(MediaListSort || {});
|
|
1300
|
-
|
|
1301
1552
|
// src/types/thread.ts
|
|
1302
1553
|
var ThreadSort = /* @__PURE__ */ ((ThreadSort2) => {
|
|
1303
1554
|
ThreadSort2["ID"] = "ID";
|
|
@@ -1319,6 +1570,20 @@ var ThreadSort = /* @__PURE__ */ ((ThreadSort2) => {
|
|
|
1319
1570
|
return ThreadSort2;
|
|
1320
1571
|
})(ThreadSort || {});
|
|
1321
1572
|
|
|
1573
|
+
// src/types/user.ts
|
|
1574
|
+
var UserSort = /* @__PURE__ */ ((UserSort2) => {
|
|
1575
|
+
UserSort2["ID"] = "ID";
|
|
1576
|
+
UserSort2["ID_DESC"] = "ID_DESC";
|
|
1577
|
+
UserSort2["USERNAME"] = "USERNAME";
|
|
1578
|
+
UserSort2["USERNAME_DESC"] = "USERNAME_DESC";
|
|
1579
|
+
UserSort2["WATCHED_TIME"] = "WATCHED_TIME";
|
|
1580
|
+
UserSort2["WATCHED_TIME_DESC"] = "WATCHED_TIME_DESC";
|
|
1581
|
+
UserSort2["CHAPTERS_READ"] = "CHAPTERS_READ";
|
|
1582
|
+
UserSort2["CHAPTERS_READ_DESC"] = "CHAPTERS_READ_DESC";
|
|
1583
|
+
UserSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
|
|
1584
|
+
return UserSort2;
|
|
1585
|
+
})(UserSort || {});
|
|
1586
|
+
|
|
1322
1587
|
// src/client/media.ts
|
|
1323
1588
|
async function getMedia(client, id, include) {
|
|
1324
1589
|
validateId(id, "mediaId");
|
|
@@ -1326,6 +1591,14 @@ async function getMedia(client, id, include) {
|
|
|
1326
1591
|
const data = await client.request(query, { id });
|
|
1327
1592
|
return data.Media;
|
|
1328
1593
|
}
|
|
1594
|
+
async function getMediaByMalId(client, malId, type) {
|
|
1595
|
+
validateId(malId, "malId");
|
|
1596
|
+
const data = await client.request(QUERY_MEDIA_BY_MAL_ID, {
|
|
1597
|
+
idMal: malId,
|
|
1598
|
+
type
|
|
1599
|
+
});
|
|
1600
|
+
return data.Media;
|
|
1601
|
+
}
|
|
1329
1602
|
async function searchMedia(client, options = {}) {
|
|
1330
1603
|
const { query: search, page = 1, perPage = 20, genres, tags, genresExclude, tagsExclude, ...filters } = options;
|
|
1331
1604
|
return client.pagedRequest(
|
|
@@ -1446,7 +1719,7 @@ async function getWeeklySchedule(client, date = /* @__PURE__ */ new Date()) {
|
|
|
1446
1719
|
for await (const episode of iterator) {
|
|
1447
1720
|
const epDate = new Date(episode.airingAt * 1e3);
|
|
1448
1721
|
const dayName = names[epDate.getUTCDay()];
|
|
1449
|
-
schedule[dayName].push(episode);
|
|
1722
|
+
if (dayName) schedule[dayName].push(episode);
|
|
1450
1723
|
}
|
|
1451
1724
|
return schedule;
|
|
1452
1725
|
}
|
|
@@ -1472,8 +1745,14 @@ async function searchStaff(client, options = {}) {
|
|
|
1472
1745
|
}
|
|
1473
1746
|
|
|
1474
1747
|
// src/client/studio.ts
|
|
1475
|
-
async function getStudio(client, id) {
|
|
1748
|
+
async function getStudio(client, id, include) {
|
|
1476
1749
|
validateId(id, "studioId");
|
|
1750
|
+
if (include?.media) {
|
|
1751
|
+
const perPage = typeof include.media === "object" ? include.media.perPage : void 0;
|
|
1752
|
+
const query = buildStudioByIdQuery(perPage);
|
|
1753
|
+
const data2 = await client.request(query, { id });
|
|
1754
|
+
return data2.Studio;
|
|
1755
|
+
}
|
|
1477
1756
|
const data = await client.request(QUERY_STUDIO_BY_ID, { id });
|
|
1478
1757
|
return data.Studio;
|
|
1479
1758
|
}
|
|
@@ -1529,7 +1808,7 @@ async function searchUsers(client, options = {}) {
|
|
|
1529
1808
|
}
|
|
1530
1809
|
async function getUserMediaList(client, options) {
|
|
1531
1810
|
if (!options.userId && !options.userName) {
|
|
1532
|
-
throw new
|
|
1811
|
+
throw new TypeError("getUserMediaList requires either userId or userName");
|
|
1533
1812
|
}
|
|
1534
1813
|
if (options.userId) {
|
|
1535
1814
|
validateId(options.userId, "userId");
|
|
@@ -1548,15 +1827,18 @@ async function getUserMediaList(client, options) {
|
|
|
1548
1827
|
"mediaList"
|
|
1549
1828
|
);
|
|
1550
1829
|
}
|
|
1551
|
-
async function getUserFavorites(client, idOrName) {
|
|
1830
|
+
async function getUserFavorites(client, idOrName, options) {
|
|
1831
|
+
const useBuilder = options?.perPage !== void 0;
|
|
1552
1832
|
if (typeof idOrName === "number") {
|
|
1553
1833
|
validateId(idOrName, "userId");
|
|
1554
|
-
const
|
|
1834
|
+
const query2 = useBuilder ? buildUserFavoritesQuery("id", options.perPage) : QUERY_USER_FAVORITES_BY_ID;
|
|
1835
|
+
const data2 = await client.request(query2, {
|
|
1555
1836
|
id: idOrName
|
|
1556
1837
|
});
|
|
1557
1838
|
return mapFavorites(data2.User.favourites);
|
|
1558
1839
|
}
|
|
1559
|
-
const
|
|
1840
|
+
const query = useBuilder ? buildUserFavoritesQuery("name", options.perPage) : QUERY_USER_FAVORITES_BY_NAME;
|
|
1841
|
+
const data = await client.request(query, {
|
|
1560
1842
|
name: idOrName
|
|
1561
1843
|
});
|
|
1562
1844
|
return mapFavorites(data.User.favourites);
|
|
@@ -1573,10 +1855,19 @@ function mapFavorites(fav) {
|
|
|
1573
1855
|
|
|
1574
1856
|
// src/client/index.ts
|
|
1575
1857
|
var DEFAULT_API_URL = "https://graphql.anilist.co";
|
|
1576
|
-
var LIB_VERSION = "1.
|
|
1858
|
+
var LIB_VERSION = "1.8.1" ;
|
|
1577
1859
|
var AniListClient = class {
|
|
1860
|
+
apiUrl;
|
|
1861
|
+
headers;
|
|
1862
|
+
cacheAdapter;
|
|
1863
|
+
rateLimiter;
|
|
1864
|
+
hooks;
|
|
1865
|
+
logger;
|
|
1866
|
+
signal;
|
|
1867
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
1868
|
+
_rateLimitInfo;
|
|
1869
|
+
_lastRequestMeta;
|
|
1578
1870
|
constructor(options = {}) {
|
|
1579
|
-
this.inFlight = /* @__PURE__ */ new Map();
|
|
1580
1871
|
this.apiUrl = options.apiUrl ?? DEFAULT_API_URL;
|
|
1581
1872
|
this.headers = {
|
|
1582
1873
|
"Content-Type": "application/json",
|
|
@@ -1589,6 +1880,7 @@ var AniListClient = class {
|
|
|
1589
1880
|
this.cacheAdapter = options.cacheAdapter ?? new MemoryCache(options.cache);
|
|
1590
1881
|
this.rateLimiter = new RateLimiter(options.rateLimit);
|
|
1591
1882
|
this.hooks = options.hooks ?? {};
|
|
1883
|
+
this.logger = options.logger;
|
|
1592
1884
|
this.signal = options.signal;
|
|
1593
1885
|
}
|
|
1594
1886
|
/**
|
|
@@ -1611,6 +1903,7 @@ var AniListClient = class {
|
|
|
1611
1903
|
const cached = await this.cacheAdapter.get(cacheKey);
|
|
1612
1904
|
if (cached !== void 0) {
|
|
1613
1905
|
this.hooks.onCacheHit?.(cacheKey);
|
|
1906
|
+
this.logger?.debug("Cache hit", { cacheKey });
|
|
1614
1907
|
const meta = { durationMs: 0, fromCache: true };
|
|
1615
1908
|
this._lastRequestMeta = meta;
|
|
1616
1909
|
this.hooks.onResponse?.(query, 0, true);
|
|
@@ -1630,6 +1923,7 @@ var AniListClient = class {
|
|
|
1630
1923
|
async executeRequest(query, variables, cacheKey) {
|
|
1631
1924
|
const start = Date.now();
|
|
1632
1925
|
this.hooks.onRequest?.(query, variables);
|
|
1926
|
+
this.logger?.debug("API request", { variables });
|
|
1633
1927
|
const minifiedQuery = normalizeQuery(query);
|
|
1634
1928
|
let res;
|
|
1635
1929
|
try {
|
|
@@ -1645,13 +1939,23 @@ var AniListClient = class {
|
|
|
1645
1939
|
);
|
|
1646
1940
|
} catch (err) {
|
|
1647
1941
|
const error = err instanceof AniListError ? err : new AniListError(err.message ?? "Network request failed", 0, [err]);
|
|
1942
|
+
this.logger?.error("Request failed", { error: error.message, status: error.status });
|
|
1943
|
+
this.hooks.onError?.(error, query, variables);
|
|
1944
|
+
throw error;
|
|
1945
|
+
}
|
|
1946
|
+
let json;
|
|
1947
|
+
try {
|
|
1948
|
+
json = await res.json();
|
|
1949
|
+
} catch {
|
|
1950
|
+
const error = new AniListError(`Non-JSON response from AniList (HTTP ${res.status})`, res.status, []);
|
|
1951
|
+
this.logger?.error("Request failed", { error: error.message, status: error.status });
|
|
1648
1952
|
this.hooks.onError?.(error, query, variables);
|
|
1649
1953
|
throw error;
|
|
1650
1954
|
}
|
|
1651
|
-
const json = await res.json();
|
|
1652
1955
|
if (!res.ok || json.errors) {
|
|
1653
1956
|
const message = json.errors?.[0]?.message ?? `AniList API error (HTTP ${res.status})`;
|
|
1654
1957
|
const error = new AniListError(message, res.status, json.errors ?? []);
|
|
1958
|
+
this.logger?.error("Request failed", { error: error.message, status: error.status });
|
|
1655
1959
|
this.hooks.onError?.(error, query, variables);
|
|
1656
1960
|
throw error;
|
|
1657
1961
|
}
|
|
@@ -1670,6 +1974,7 @@ var AniListClient = class {
|
|
|
1670
1974
|
await this.cacheAdapter.set(cacheKey, data);
|
|
1671
1975
|
const meta = { durationMs, fromCache: false, rateLimitInfo: this._rateLimitInfo };
|
|
1672
1976
|
this._lastRequestMeta = meta;
|
|
1977
|
+
this.logger?.debug("Request complete", { durationMs, rateLimitInfo: this._rateLimitInfo });
|
|
1673
1978
|
this.hooks.onResponse?.(query, durationMs, false, this._rateLimitInfo);
|
|
1674
1979
|
return data;
|
|
1675
1980
|
}
|
|
@@ -1732,6 +2037,15 @@ var AniListClient = class {
|
|
|
1732
2037
|
async getAiredChapters(options = {}) {
|
|
1733
2038
|
return this.getRecentlyUpdatedManga(options);
|
|
1734
2039
|
}
|
|
2040
|
+
/**
|
|
2041
|
+
* Fetch a media entry by its MyAnimeList (MAL) ID.
|
|
2042
|
+
*
|
|
2043
|
+
* @param malId - The MyAnimeList ID
|
|
2044
|
+
* @param type - Optional media type to disambiguate (some MAL IDs map to both ANIME and MANGA)
|
|
2045
|
+
*/
|
|
2046
|
+
async getMediaByMalId(malId, type) {
|
|
2047
|
+
return getMediaByMalId(this, malId, type);
|
|
2048
|
+
}
|
|
1735
2049
|
/** Get the detailed schedule for the current week, sorted by day. */
|
|
1736
2050
|
async getWeeklySchedule(date) {
|
|
1737
2051
|
return getWeeklySchedule(this, date);
|
|
@@ -1784,20 +2098,32 @@ var AniListClient = class {
|
|
|
1784
2098
|
* Fetch a user's favorite anime, manga, characters, staff, and studios.
|
|
1785
2099
|
*
|
|
1786
2100
|
* @param idOrName - AniList user ID (number) or username (string)
|
|
2101
|
+
* @param options - Optional pagination options (perPage per category)
|
|
1787
2102
|
* @returns The user's favorites grouped by category
|
|
1788
2103
|
*
|
|
1789
2104
|
* @example
|
|
1790
2105
|
* ```typescript
|
|
1791
2106
|
* const favs = await client.getUserFavorites("AniList");
|
|
1792
2107
|
* favs.anime.forEach(a => console.log(a.title.romaji));
|
|
2108
|
+
*
|
|
2109
|
+
* // Fetch more results per category
|
|
2110
|
+
* const moreResults = await client.getUserFavorites(1, { perPage: 50 });
|
|
1793
2111
|
* ```
|
|
1794
2112
|
*/
|
|
1795
|
-
async getUserFavorites(idOrName) {
|
|
1796
|
-
return getUserFavorites(this, idOrName);
|
|
2113
|
+
async getUserFavorites(idOrName, options) {
|
|
2114
|
+
return getUserFavorites(this, idOrName, options);
|
|
1797
2115
|
}
|
|
1798
|
-
/**
|
|
1799
|
-
|
|
1800
|
-
|
|
2116
|
+
/**
|
|
2117
|
+
* Fetch a studio by its AniList ID.
|
|
2118
|
+
* Pass `include` to customise the number of media returned.
|
|
2119
|
+
*
|
|
2120
|
+
* @example
|
|
2121
|
+
* ```typescript
|
|
2122
|
+
* const studio = await client.getStudio(21, { media: { perPage: 50 } });
|
|
2123
|
+
* ```
|
|
2124
|
+
*/
|
|
2125
|
+
async getStudio(id, include) {
|
|
2126
|
+
return getStudio(this, id, include);
|
|
1801
2127
|
}
|
|
1802
2128
|
/** Search for studios by name. */
|
|
1803
2129
|
async searchStudios(options = {}) {
|
|
@@ -1847,21 +2173,24 @@ var AniListClient = class {
|
|
|
1847
2173
|
async getMediaBatch(ids) {
|
|
1848
2174
|
if (ids.length === 0) return [];
|
|
1849
2175
|
validateIds(ids, "mediaId");
|
|
1850
|
-
|
|
2176
|
+
const [singleMediaId] = ids;
|
|
2177
|
+
if (ids.length === 1 && singleMediaId !== void 0) return [await this.getMedia(singleMediaId)];
|
|
1851
2178
|
return this.executeBatch(ids, buildBatchMediaQuery, "m");
|
|
1852
2179
|
}
|
|
1853
2180
|
/** Fetch multiple characters in a single API request. */
|
|
1854
2181
|
async getCharacterBatch(ids) {
|
|
1855
2182
|
if (ids.length === 0) return [];
|
|
1856
2183
|
validateIds(ids, "characterId");
|
|
1857
|
-
|
|
2184
|
+
const [singleCharId] = ids;
|
|
2185
|
+
if (ids.length === 1 && singleCharId !== void 0) return [await this.getCharacter(singleCharId)];
|
|
1858
2186
|
return this.executeBatch(ids, buildBatchCharacterQuery, "c");
|
|
1859
2187
|
}
|
|
1860
2188
|
/** Fetch multiple staff members in a single API request. */
|
|
1861
2189
|
async getStaffBatch(ids) {
|
|
1862
2190
|
if (ids.length === 0) return [];
|
|
1863
2191
|
validateIds(ids, "staffId");
|
|
1864
|
-
|
|
2192
|
+
const [singleStaffId] = ids;
|
|
2193
|
+
if (ids.length === 1 && singleStaffId !== void 0) return [await this.getStaff(singleStaffId)];
|
|
1865
2194
|
return this.executeBatch(ids, buildBatchStaffQuery, "s");
|
|
1866
2195
|
}
|
|
1867
2196
|
/** @internal */
|
|
@@ -1906,88 +2235,23 @@ var AniListClient = class {
|
|
|
1906
2235
|
this.inFlight.clear();
|
|
1907
2236
|
this.rateLimiter.dispose();
|
|
1908
2237
|
}
|
|
1909
|
-
};
|
|
1910
|
-
|
|
1911
|
-
// src/cache/redis.ts
|
|
1912
|
-
var RedisCache = class {
|
|
1913
|
-
constructor(options) {
|
|
1914
|
-
this.client = options.client;
|
|
1915
|
-
this.prefix = options.prefix ?? "ani:";
|
|
1916
|
-
this.ttl = options.ttl ?? 86400;
|
|
1917
|
-
}
|
|
1918
|
-
prefixedKey(key) {
|
|
1919
|
-
return `${this.prefix}${key}`;
|
|
1920
|
-
}
|
|
1921
|
-
async get(key) {
|
|
1922
|
-
const raw = await this.client.get(this.prefixedKey(key));
|
|
1923
|
-
if (raw === null) return void 0;
|
|
1924
|
-
try {
|
|
1925
|
-
return JSON.parse(raw);
|
|
1926
|
-
} catch {
|
|
1927
|
-
return void 0;
|
|
1928
|
-
}
|
|
1929
|
-
}
|
|
1930
|
-
async set(key, data) {
|
|
1931
|
-
await this.client.set(this.prefixedKey(key), JSON.stringify(data), "EX", this.ttl);
|
|
1932
|
-
}
|
|
1933
|
-
async delete(key) {
|
|
1934
|
-
const count = await this.client.del(this.prefixedKey(key));
|
|
1935
|
-
return count > 0;
|
|
1936
|
-
}
|
|
1937
2238
|
/**
|
|
1938
|
-
*
|
|
2239
|
+
* Return a scoped view of this client where every request uses the given `AbortSignal`.
|
|
2240
|
+
* The returned object shares the same cache, rate limiter, and hooks.
|
|
1939
2241
|
*
|
|
1940
|
-
*
|
|
1941
|
-
*
|
|
1942
|
-
*
|
|
1943
|
-
|
|
1944
|
-
async collectKeys(pattern) {
|
|
1945
|
-
if (this.client.scanIterator) {
|
|
1946
|
-
const keys = [];
|
|
1947
|
-
for await (const key of this.client.scanIterator({ MATCH: pattern, COUNT: 100 })) {
|
|
1948
|
-
keys.push(key);
|
|
1949
|
-
}
|
|
1950
|
-
return keys;
|
|
1951
|
-
}
|
|
1952
|
-
return this.client.keys(pattern);
|
|
1953
|
-
}
|
|
1954
|
-
async clear() {
|
|
1955
|
-
const keys = await this.collectKeys(`${this.prefix}*`);
|
|
1956
|
-
if (keys.length > 0) {
|
|
1957
|
-
await this.client.del(...keys);
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
/**
|
|
1961
|
-
* Get the actual number of keys with this prefix in Redis.
|
|
1962
|
-
*/
|
|
1963
|
-
get size() {
|
|
1964
|
-
return this.getSize();
|
|
1965
|
-
}
|
|
1966
|
-
/** @internal */
|
|
1967
|
-
async getSize() {
|
|
1968
|
-
const keys = await this.collectKeys(`${this.prefix}*`);
|
|
1969
|
-
return keys.length;
|
|
1970
|
-
}
|
|
1971
|
-
async keys() {
|
|
1972
|
-
const raw = await this.collectKeys(`${this.prefix}*`);
|
|
1973
|
-
return raw.map((k) => k.slice(this.prefix.length));
|
|
1974
|
-
}
|
|
1975
|
-
/**
|
|
1976
|
-
* Remove all entries whose key matches the given glob pattern.
|
|
2242
|
+
* @example
|
|
2243
|
+
* ```ts
|
|
2244
|
+
* const controller = new AbortController();
|
|
2245
|
+
* const media = await client.withSignal(controller.signal).getMedia(1);
|
|
1977
2246
|
*
|
|
1978
|
-
*
|
|
1979
|
-
*
|
|
2247
|
+
* // Cancel all in-flight requests made through the scoped view
|
|
2248
|
+
* controller.abort();
|
|
2249
|
+
* ```
|
|
1980
2250
|
*/
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
return this.client.del(...keys);
|
|
1986
|
-
}
|
|
1987
|
-
const allKeys = await this.collectKeys(`${this.prefix}*`);
|
|
1988
|
-
const matching = allKeys.filter((k) => pattern.test(k.slice(this.prefix.length)));
|
|
1989
|
-
if (matching.length === 0) return 0;
|
|
1990
|
-
return this.client.del(...matching);
|
|
2251
|
+
withSignal(signal) {
|
|
2252
|
+
const scoped = Object.create(this);
|
|
2253
|
+
Object.defineProperty(scoped, "signal", { value: signal, configurable: true });
|
|
2254
|
+
return scoped;
|
|
1991
2255
|
}
|
|
1992
2256
|
};
|
|
1993
2257
|
|