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.mjs
CHANGED
|
@@ -125,28 +125,52 @@ function sortObjectKeys(obj) {
|
|
|
125
125
|
// src/cache/index.ts
|
|
126
126
|
var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
|
|
127
127
|
var MemoryCache = class {
|
|
128
|
+
ttl;
|
|
129
|
+
maxSize;
|
|
130
|
+
enabled;
|
|
131
|
+
swrMs;
|
|
132
|
+
store = /* @__PURE__ */ new Map();
|
|
133
|
+
_hits = 0;
|
|
134
|
+
_misses = 0;
|
|
135
|
+
_stales = 0;
|
|
128
136
|
constructor(options = {}) {
|
|
129
|
-
this.store = /* @__PURE__ */ new Map();
|
|
130
137
|
this.ttl = options.ttl ?? ONE_DAY_MS;
|
|
131
138
|
this.maxSize = options.maxSize ?? 500;
|
|
132
139
|
this.enabled = options.enabled ?? true;
|
|
140
|
+
this.swrMs = options.staleWhileRevalidateMs ?? 0;
|
|
133
141
|
}
|
|
134
142
|
/** Build a deterministic cache key from a query + variables pair. */
|
|
135
143
|
static key(query, variables) {
|
|
136
144
|
const normalized = normalizeQuery(query);
|
|
137
145
|
return `${normalized}|${JSON.stringify(sortObjectKeys(variables))}`;
|
|
138
146
|
}
|
|
139
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* Retrieve a cached value, or `undefined` if missing / expired.
|
|
149
|
+
* With stale-while-revalidate enabled, returns stale data within the grace window
|
|
150
|
+
* and flags it so the caller can refresh in the background.
|
|
151
|
+
*/
|
|
140
152
|
get(key) {
|
|
141
153
|
if (!this.enabled) return void 0;
|
|
142
154
|
const entry = this.store.get(key);
|
|
143
|
-
if (!entry)
|
|
144
|
-
|
|
155
|
+
if (!entry) {
|
|
156
|
+
this._misses++;
|
|
157
|
+
return void 0;
|
|
158
|
+
}
|
|
159
|
+
const now = Date.now();
|
|
160
|
+
if (now > entry.expiresAt) {
|
|
161
|
+
if (this.swrMs > 0 && now <= entry.expiresAt + this.swrMs) {
|
|
162
|
+
this.store.delete(key);
|
|
163
|
+
this.store.set(key, entry);
|
|
164
|
+
this._stales++;
|
|
165
|
+
return entry.data;
|
|
166
|
+
}
|
|
145
167
|
this.store.delete(key);
|
|
168
|
+
this._misses++;
|
|
146
169
|
return void 0;
|
|
147
170
|
}
|
|
148
171
|
this.store.delete(key);
|
|
149
172
|
this.store.set(key, entry);
|
|
173
|
+
this._hits++;
|
|
150
174
|
return entry.data;
|
|
151
175
|
}
|
|
152
176
|
/** Store a value in the cache. */
|
|
@@ -163,9 +187,12 @@ var MemoryCache = class {
|
|
|
163
187
|
delete(key) {
|
|
164
188
|
return this.store.delete(key);
|
|
165
189
|
}
|
|
166
|
-
/** Clear the entire cache. */
|
|
190
|
+
/** Clear the entire cache and reset statistics. */
|
|
167
191
|
clear() {
|
|
168
192
|
this.store.clear();
|
|
193
|
+
this._hits = 0;
|
|
194
|
+
this._misses = 0;
|
|
195
|
+
this._stales = 0;
|
|
169
196
|
}
|
|
170
197
|
/** Number of entries currently stored. */
|
|
171
198
|
get size() {
|
|
@@ -175,6 +202,32 @@ var MemoryCache = class {
|
|
|
175
202
|
keys() {
|
|
176
203
|
return [...this.store.keys()];
|
|
177
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Get cache performance statistics.
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```ts
|
|
210
|
+
* const cache = new MemoryCache();
|
|
211
|
+
* // ... after some usage ...
|
|
212
|
+
* console.log(cache.stats);
|
|
213
|
+
* // { hits: 42, misses: 8, stales: 0, hitRate: 0.84 }
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
get stats() {
|
|
217
|
+
const total = this._hits + this._misses + this._stales;
|
|
218
|
+
return {
|
|
219
|
+
hits: this._hits,
|
|
220
|
+
misses: this._misses,
|
|
221
|
+
stales: this._stales,
|
|
222
|
+
hitRate: total === 0 ? Number.NaN : this._hits / total
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
/** Reset cache statistics without clearing stored data. */
|
|
226
|
+
resetStats() {
|
|
227
|
+
this._hits = 0;
|
|
228
|
+
this._misses = 0;
|
|
229
|
+
this._stales = 0;
|
|
230
|
+
}
|
|
178
231
|
/**
|
|
179
232
|
* Remove all entries whose key matches the given pattern.
|
|
180
233
|
*
|
|
@@ -195,8 +248,101 @@ var MemoryCache = class {
|
|
|
195
248
|
}
|
|
196
249
|
};
|
|
197
250
|
|
|
251
|
+
// src/cache/redis.ts
|
|
252
|
+
var RedisCache = class {
|
|
253
|
+
client;
|
|
254
|
+
prefix;
|
|
255
|
+
ttl;
|
|
256
|
+
constructor(options) {
|
|
257
|
+
this.client = options.client;
|
|
258
|
+
this.prefix = options.prefix ?? "ani:";
|
|
259
|
+
this.ttl = options.ttl ?? 86400;
|
|
260
|
+
}
|
|
261
|
+
prefixedKey(key) {
|
|
262
|
+
return `${this.prefix}${key}`;
|
|
263
|
+
}
|
|
264
|
+
async get(key) {
|
|
265
|
+
const raw = await this.client.get(this.prefixedKey(key));
|
|
266
|
+
if (raw === null) return void 0;
|
|
267
|
+
try {
|
|
268
|
+
return JSON.parse(raw);
|
|
269
|
+
} catch {
|
|
270
|
+
return void 0;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async set(key, data) {
|
|
274
|
+
await this.client.set(this.prefixedKey(key), JSON.stringify(data), "EX", this.ttl);
|
|
275
|
+
}
|
|
276
|
+
async delete(key) {
|
|
277
|
+
const count = await this.client.del(this.prefixedKey(key));
|
|
278
|
+
return count > 0;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Collect keys matching a pattern. Uses SCAN when available, falls back to KEYS.
|
|
282
|
+
*
|
|
283
|
+
* **Warning:** The `KEYS` fallback is O(N) and blocks the Redis server.
|
|
284
|
+
* Provide a client with `scanIterator` support for production use.
|
|
285
|
+
* @internal
|
|
286
|
+
*/
|
|
287
|
+
async collectKeys(pattern) {
|
|
288
|
+
if (this.client.scanIterator) {
|
|
289
|
+
const keys = [];
|
|
290
|
+
for await (const key of this.client.scanIterator({ MATCH: pattern, COUNT: 100 })) {
|
|
291
|
+
keys.push(key);
|
|
292
|
+
}
|
|
293
|
+
return keys;
|
|
294
|
+
}
|
|
295
|
+
return this.client.keys(pattern);
|
|
296
|
+
}
|
|
297
|
+
async clear() {
|
|
298
|
+
const keys = await this.collectKeys(`${this.prefix}*`);
|
|
299
|
+
if (keys.length > 0) {
|
|
300
|
+
await this.client.del(...keys);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Get the actual number of keys with this prefix in Redis.
|
|
305
|
+
*/
|
|
306
|
+
get size() {
|
|
307
|
+
return this.getSize();
|
|
308
|
+
}
|
|
309
|
+
/** @internal */
|
|
310
|
+
async getSize() {
|
|
311
|
+
const keys = await this.collectKeys(`${this.prefix}*`);
|
|
312
|
+
return keys.length;
|
|
313
|
+
}
|
|
314
|
+
async keys() {
|
|
315
|
+
const raw = await this.collectKeys(`${this.prefix}*`);
|
|
316
|
+
return raw.map((k) => k.slice(this.prefix.length));
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Remove all entries whose key matches the given pattern.
|
|
320
|
+
*
|
|
321
|
+
* - **String**: treated as a substring match (e.g. `"Media"` removes all keys containing `"Media"`).
|
|
322
|
+
* - **RegExp**: tested against each key directly.
|
|
323
|
+
*
|
|
324
|
+
* @param pattern — A string (substring match) or RegExp.
|
|
325
|
+
* @returns Number of entries removed.
|
|
326
|
+
*/
|
|
327
|
+
async invalidate(pattern) {
|
|
328
|
+
if (typeof pattern === "string") {
|
|
329
|
+
const keys = await this.collectKeys(`${this.prefix}*${pattern}*`);
|
|
330
|
+
if (keys.length === 0) return 0;
|
|
331
|
+
return this.client.del(...keys);
|
|
332
|
+
}
|
|
333
|
+
const allKeys = await this.collectKeys(`${this.prefix}*`);
|
|
334
|
+
const matching = allKeys.filter((k) => pattern.test(k.slice(this.prefix.length)));
|
|
335
|
+
if (matching.length === 0) return 0;
|
|
336
|
+
return this.client.del(...matching);
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
198
340
|
// src/errors/index.ts
|
|
199
341
|
var AniListError = class _AniListError extends Error {
|
|
342
|
+
/** HTTP status code returned by the API */
|
|
343
|
+
status;
|
|
344
|
+
/** Raw error body from the API response */
|
|
345
|
+
errors;
|
|
200
346
|
constructor(message, status, errors = []) {
|
|
201
347
|
super(message);
|
|
202
348
|
this.name = "AniListError";
|
|
@@ -210,6 +356,32 @@ var AniListError = class _AniListError extends Error {
|
|
|
210
356
|
};
|
|
211
357
|
|
|
212
358
|
// src/queries/fragments.ts
|
|
359
|
+
var MEDIA_FIELDS_LIGHT = `
|
|
360
|
+
id
|
|
361
|
+
idMal
|
|
362
|
+
title { romaji english native userPreferred }
|
|
363
|
+
type
|
|
364
|
+
format
|
|
365
|
+
status
|
|
366
|
+
coverImage { large medium color }
|
|
367
|
+
bannerImage
|
|
368
|
+
genres
|
|
369
|
+
averageScore
|
|
370
|
+
popularity
|
|
371
|
+
favourites
|
|
372
|
+
isAdult
|
|
373
|
+
siteUrl
|
|
374
|
+
season
|
|
375
|
+
seasonYear
|
|
376
|
+
episodes
|
|
377
|
+
chapters
|
|
378
|
+
nextAiringEpisode {
|
|
379
|
+
id
|
|
380
|
+
airingAt
|
|
381
|
+
episode
|
|
382
|
+
timeUntilAiring
|
|
383
|
+
}
|
|
384
|
+
`;
|
|
213
385
|
var MEDIA_FIELDS_BASE = `
|
|
214
386
|
id
|
|
215
387
|
idMal
|
|
@@ -629,6 +801,12 @@ query ($season: MediaSeason!, $seasonYear: Int!, $type: MediaType, $sort: [Media
|
|
|
629
801
|
}
|
|
630
802
|
}
|
|
631
803
|
}`;
|
|
804
|
+
var QUERY_MEDIA_BY_MAL_ID = `
|
|
805
|
+
query ($idMal: Int!, $type: MediaType) {
|
|
806
|
+
Media(idMal: $idMal, type: $type) {
|
|
807
|
+
${MEDIA_FIELDS}
|
|
808
|
+
}
|
|
809
|
+
}`;
|
|
632
810
|
var QUERY_RECOMMENDATIONS = `
|
|
633
811
|
query ($mediaId: Int!, $page: Int, $perPage: Int, $sort: [RecommendationSort]) {
|
|
634
812
|
Media(id: $mediaId) {
|
|
@@ -664,143 +842,6 @@ query ($mediaId: Int!, $page: Int, $perPage: Int, $sort: [RecommendationSort]) {
|
|
|
664
842
|
}
|
|
665
843
|
}`;
|
|
666
844
|
|
|
667
|
-
// src/queries/character.ts
|
|
668
|
-
var QUERY_CHARACTER_BY_ID = `
|
|
669
|
-
query ($id: Int!) {
|
|
670
|
-
Character(id: $id) {
|
|
671
|
-
${CHARACTER_FIELDS}
|
|
672
|
-
}
|
|
673
|
-
}`;
|
|
674
|
-
var QUERY_CHARACTER_BY_ID_WITH_VA = `
|
|
675
|
-
query ($id: Int!) {
|
|
676
|
-
Character(id: $id) {
|
|
677
|
-
${CHARACTER_FIELDS_WITH_VA}
|
|
678
|
-
}
|
|
679
|
-
}`;
|
|
680
|
-
var QUERY_CHARACTER_SEARCH = `
|
|
681
|
-
query ($search: String, $sort: [CharacterSort], $page: Int, $perPage: Int) {
|
|
682
|
-
Page(page: $page, perPage: $perPage) {
|
|
683
|
-
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
684
|
-
characters(search: $search, sort: $sort) {
|
|
685
|
-
${CHARACTER_FIELDS}
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
}`;
|
|
689
|
-
var QUERY_CHARACTER_SEARCH_WITH_VA = `
|
|
690
|
-
query ($search: String, $sort: [CharacterSort], $page: Int, $perPage: Int) {
|
|
691
|
-
Page(page: $page, perPage: $perPage) {
|
|
692
|
-
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
693
|
-
characters(search: $search, sort: $sort) {
|
|
694
|
-
${CHARACTER_FIELDS_WITH_VA}
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
}`;
|
|
698
|
-
|
|
699
|
-
// src/queries/staff.ts
|
|
700
|
-
var QUERY_STAFF_BY_ID = `
|
|
701
|
-
query ($id: Int!) {
|
|
702
|
-
Staff(id: $id) {
|
|
703
|
-
${STAFF_FIELDS}
|
|
704
|
-
}
|
|
705
|
-
}`;
|
|
706
|
-
var QUERY_STAFF_BY_ID_WITH_MEDIA = `
|
|
707
|
-
query ($id: Int!, $perPage: Int) {
|
|
708
|
-
Staff(id: $id) {
|
|
709
|
-
${STAFF_FIELDS}
|
|
710
|
-
${STAFF_MEDIA_FIELDS}
|
|
711
|
-
}
|
|
712
|
-
}`;
|
|
713
|
-
var QUERY_STAFF_SEARCH = `
|
|
714
|
-
query ($search: String, $sort: [StaffSort], $page: Int, $perPage: Int) {
|
|
715
|
-
Page(page: $page, perPage: $perPage) {
|
|
716
|
-
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
717
|
-
staff(search: $search, sort: $sort) {
|
|
718
|
-
${STAFF_FIELDS}
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
}`;
|
|
722
|
-
|
|
723
|
-
// src/queries/user.ts
|
|
724
|
-
var QUERY_USER_BY_ID = `
|
|
725
|
-
query ($id: Int!) {
|
|
726
|
-
User(id: $id) {
|
|
727
|
-
${USER_FIELDS}
|
|
728
|
-
}
|
|
729
|
-
}`;
|
|
730
|
-
var QUERY_USER_BY_NAME = `
|
|
731
|
-
query ($name: String!) {
|
|
732
|
-
User(name: $name) {
|
|
733
|
-
${USER_FIELDS}
|
|
734
|
-
}
|
|
735
|
-
}`;
|
|
736
|
-
var QUERY_USER_SEARCH = `
|
|
737
|
-
query ($search: String, $sort: [UserSort], $page: Int, $perPage: Int) {
|
|
738
|
-
Page(page: $page, perPage: $perPage) {
|
|
739
|
-
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
740
|
-
users(search: $search, sort: $sort) {
|
|
741
|
-
${USER_FIELDS}
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
}`;
|
|
745
|
-
var QUERY_USER_MEDIA_LIST = `
|
|
746
|
-
query ($userId: Int, $userName: String, $type: MediaType!, $status: MediaListStatus, $sort: [MediaListSort], $page: Int, $perPage: Int) {
|
|
747
|
-
Page(page: $page, perPage: $perPage) {
|
|
748
|
-
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
749
|
-
mediaList(userId: $userId, userName: $userName, type: $type, status: $status, sort: $sort) {
|
|
750
|
-
${MEDIA_LIST_FIELDS}
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
}`;
|
|
754
|
-
var QUERY_USER_FAVORITES_BY_ID = `
|
|
755
|
-
query ($id: Int!) {
|
|
756
|
-
User(id: $id) {
|
|
757
|
-
id
|
|
758
|
-
name
|
|
759
|
-
${USER_FAVORITES_FIELDS}
|
|
760
|
-
}
|
|
761
|
-
}`;
|
|
762
|
-
var QUERY_USER_FAVORITES_BY_NAME = `
|
|
763
|
-
query ($name: String!) {
|
|
764
|
-
User(name: $name) {
|
|
765
|
-
id
|
|
766
|
-
name
|
|
767
|
-
${USER_FAVORITES_FIELDS}
|
|
768
|
-
}
|
|
769
|
-
}`;
|
|
770
|
-
|
|
771
|
-
// src/queries/studio.ts
|
|
772
|
-
var QUERY_STUDIO_BY_ID = `
|
|
773
|
-
query ($id: Int!) {
|
|
774
|
-
Studio(id: $id) {
|
|
775
|
-
${STUDIO_FIELDS}
|
|
776
|
-
}
|
|
777
|
-
}`;
|
|
778
|
-
var QUERY_STUDIO_SEARCH = `
|
|
779
|
-
query ($search: String, $sort: [StudioSort], $page: Int, $perPage: Int) {
|
|
780
|
-
Page(page: $page, perPage: $perPage) {
|
|
781
|
-
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
782
|
-
studios(search: $search, sort: $sort) {
|
|
783
|
-
${STUDIO_FIELDS}
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
}`;
|
|
787
|
-
|
|
788
|
-
// src/queries/metadata.ts
|
|
789
|
-
var QUERY_GENRES = `
|
|
790
|
-
query {
|
|
791
|
-
GenreCollection
|
|
792
|
-
}`;
|
|
793
|
-
var QUERY_TAGS = `
|
|
794
|
-
query {
|
|
795
|
-
MediaTagCollection {
|
|
796
|
-
id
|
|
797
|
-
name
|
|
798
|
-
description
|
|
799
|
-
category
|
|
800
|
-
isAdult
|
|
801
|
-
}
|
|
802
|
-
}`;
|
|
803
|
-
|
|
804
845
|
// src/queries/builders.ts
|
|
805
846
|
function buildMediaByIdQuery(include) {
|
|
806
847
|
if (!include) return QUERY_MEDIA_BY_ID;
|
|
@@ -906,6 +947,115 @@ var buildBatchMediaQuery = (ids) => buildBatchQuery(ids, "Media", MEDIA_FIELDS,
|
|
|
906
947
|
var buildBatchCharacterQuery = (ids) => buildBatchQuery(ids, "Character", CHARACTER_FIELDS, "c");
|
|
907
948
|
var buildBatchStaffQuery = (ids) => buildBatchQuery(ids, "Staff", STAFF_FIELDS, "s");
|
|
908
949
|
|
|
950
|
+
// src/queries/character.ts
|
|
951
|
+
var QUERY_CHARACTER_BY_ID = `
|
|
952
|
+
query ($id: Int!) {
|
|
953
|
+
Character(id: $id) {
|
|
954
|
+
${CHARACTER_FIELDS}
|
|
955
|
+
}
|
|
956
|
+
}`;
|
|
957
|
+
var QUERY_CHARACTER_BY_ID_WITH_VA = `
|
|
958
|
+
query ($id: Int!) {
|
|
959
|
+
Character(id: $id) {
|
|
960
|
+
${CHARACTER_FIELDS_WITH_VA}
|
|
961
|
+
}
|
|
962
|
+
}`;
|
|
963
|
+
var QUERY_CHARACTER_SEARCH = `
|
|
964
|
+
query ($search: String, $sort: [CharacterSort], $page: Int, $perPage: Int) {
|
|
965
|
+
Page(page: $page, perPage: $perPage) {
|
|
966
|
+
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
967
|
+
characters(search: $search, sort: $sort) {
|
|
968
|
+
${CHARACTER_FIELDS}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}`;
|
|
972
|
+
var QUERY_CHARACTER_SEARCH_WITH_VA = `
|
|
973
|
+
query ($search: String, $sort: [CharacterSort], $page: Int, $perPage: Int) {
|
|
974
|
+
Page(page: $page, perPage: $perPage) {
|
|
975
|
+
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
976
|
+
characters(search: $search, sort: $sort) {
|
|
977
|
+
${CHARACTER_FIELDS_WITH_VA}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}`;
|
|
981
|
+
|
|
982
|
+
// src/queries/metadata.ts
|
|
983
|
+
var QUERY_GENRES = `
|
|
984
|
+
query {
|
|
985
|
+
GenreCollection
|
|
986
|
+
}`;
|
|
987
|
+
var QUERY_TAGS = `
|
|
988
|
+
query {
|
|
989
|
+
MediaTagCollection {
|
|
990
|
+
id
|
|
991
|
+
name
|
|
992
|
+
description
|
|
993
|
+
category
|
|
994
|
+
isAdult
|
|
995
|
+
}
|
|
996
|
+
}`;
|
|
997
|
+
|
|
998
|
+
// src/queries/staff.ts
|
|
999
|
+
var QUERY_STAFF_BY_ID = `
|
|
1000
|
+
query ($id: Int!) {
|
|
1001
|
+
Staff(id: $id) {
|
|
1002
|
+
${STAFF_FIELDS}
|
|
1003
|
+
}
|
|
1004
|
+
}`;
|
|
1005
|
+
var QUERY_STAFF_BY_ID_WITH_MEDIA = `
|
|
1006
|
+
query ($id: Int!, $perPage: Int) {
|
|
1007
|
+
Staff(id: $id) {
|
|
1008
|
+
${STAFF_FIELDS}
|
|
1009
|
+
${STAFF_MEDIA_FIELDS}
|
|
1010
|
+
}
|
|
1011
|
+
}`;
|
|
1012
|
+
var QUERY_STAFF_SEARCH = `
|
|
1013
|
+
query ($search: String, $sort: [StaffSort], $page: Int, $perPage: Int) {
|
|
1014
|
+
Page(page: $page, perPage: $perPage) {
|
|
1015
|
+
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
1016
|
+
staff(search: $search, sort: $sort) {
|
|
1017
|
+
${STAFF_FIELDS}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}`;
|
|
1021
|
+
|
|
1022
|
+
// src/queries/studio.ts
|
|
1023
|
+
var QUERY_STUDIO_BY_ID = `
|
|
1024
|
+
query ($id: Int!) {
|
|
1025
|
+
Studio(id: $id) {
|
|
1026
|
+
${STUDIO_FIELDS}
|
|
1027
|
+
}
|
|
1028
|
+
}`;
|
|
1029
|
+
function buildStudioByIdQuery(mediaPerPage) {
|
|
1030
|
+
if (mediaPerPage === void 0) return QUERY_STUDIO_BY_ID;
|
|
1031
|
+
const pp = clampPerPage(mediaPerPage);
|
|
1032
|
+
return `
|
|
1033
|
+
query ($id: Int!) {
|
|
1034
|
+
Studio(id: $id) {
|
|
1035
|
+
id
|
|
1036
|
+
name
|
|
1037
|
+
isAnimationStudio
|
|
1038
|
+
siteUrl
|
|
1039
|
+
favourites
|
|
1040
|
+
media(page: 1, perPage: ${pp}, sort: POPULARITY_DESC) {
|
|
1041
|
+
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
1042
|
+
nodes {
|
|
1043
|
+
${MEDIA_FIELDS_LIGHT}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}`;
|
|
1048
|
+
}
|
|
1049
|
+
var QUERY_STUDIO_SEARCH = `
|
|
1050
|
+
query ($search: String, $sort: [StudioSort], $page: Int, $perPage: Int) {
|
|
1051
|
+
Page(page: $page, perPage: $perPage) {
|
|
1052
|
+
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
1053
|
+
studios(search: $search, sort: $sort) {
|
|
1054
|
+
${STUDIO_FIELDS}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}`;
|
|
1058
|
+
|
|
909
1059
|
// src/queries/thread.ts
|
|
910
1060
|
var QUERY_THREAD_BY_ID = `
|
|
911
1061
|
query ($id: Int!) {
|
|
@@ -923,13 +1073,128 @@ query ($search: String, $mediaCategoryId: Int, $categoryId: Int, $sort: [ThreadS
|
|
|
923
1073
|
}
|
|
924
1074
|
}`;
|
|
925
1075
|
|
|
1076
|
+
// src/queries/user.ts
|
|
1077
|
+
var QUERY_USER_BY_ID = `
|
|
1078
|
+
query ($id: Int!) {
|
|
1079
|
+
User(id: $id) {
|
|
1080
|
+
${USER_FIELDS}
|
|
1081
|
+
}
|
|
1082
|
+
}`;
|
|
1083
|
+
var QUERY_USER_BY_NAME = `
|
|
1084
|
+
query ($name: String!) {
|
|
1085
|
+
User(name: $name) {
|
|
1086
|
+
${USER_FIELDS}
|
|
1087
|
+
}
|
|
1088
|
+
}`;
|
|
1089
|
+
var QUERY_USER_SEARCH = `
|
|
1090
|
+
query ($search: String, $sort: [UserSort], $page: Int, $perPage: Int) {
|
|
1091
|
+
Page(page: $page, perPage: $perPage) {
|
|
1092
|
+
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
1093
|
+
users(search: $search, sort: $sort) {
|
|
1094
|
+
${USER_FIELDS}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}`;
|
|
1098
|
+
var QUERY_USER_MEDIA_LIST = `
|
|
1099
|
+
query ($userId: Int, $userName: String, $type: MediaType!, $status: MediaListStatus, $sort: [MediaListSort], $page: Int, $perPage: Int) {
|
|
1100
|
+
Page(page: $page, perPage: $perPage) {
|
|
1101
|
+
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
1102
|
+
mediaList(userId: $userId, userName: $userName, type: $type, status: $status, sort: $sort) {
|
|
1103
|
+
${MEDIA_LIST_FIELDS}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}`;
|
|
1107
|
+
var QUERY_USER_FAVORITES_BY_ID = `
|
|
1108
|
+
query ($id: Int!) {
|
|
1109
|
+
User(id: $id) {
|
|
1110
|
+
id
|
|
1111
|
+
name
|
|
1112
|
+
${USER_FAVORITES_FIELDS}
|
|
1113
|
+
}
|
|
1114
|
+
}`;
|
|
1115
|
+
var QUERY_USER_FAVORITES_BY_NAME = `
|
|
1116
|
+
query ($name: String!) {
|
|
1117
|
+
User(name: $name) {
|
|
1118
|
+
id
|
|
1119
|
+
name
|
|
1120
|
+
${USER_FAVORITES_FIELDS}
|
|
1121
|
+
}
|
|
1122
|
+
}`;
|
|
1123
|
+
function buildUserFavoritesQuery(idOrName, perPage = 25) {
|
|
1124
|
+
const pp = clampPerPage(perPage);
|
|
1125
|
+
const varDecl = idOrName === "id" ? "$id: Int!" : "$name: String!";
|
|
1126
|
+
const selector = idOrName === "id" ? "id: $id" : "name: $name";
|
|
1127
|
+
return `
|
|
1128
|
+
query (${varDecl}) {
|
|
1129
|
+
User(${selector}) {
|
|
1130
|
+
id
|
|
1131
|
+
name
|
|
1132
|
+
favourites {
|
|
1133
|
+
anime(perPage: ${pp}) {
|
|
1134
|
+
nodes {
|
|
1135
|
+
id
|
|
1136
|
+
title { romaji english native userPreferred }
|
|
1137
|
+
coverImage { large medium }
|
|
1138
|
+
type
|
|
1139
|
+
format
|
|
1140
|
+
siteUrl
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
manga(perPage: ${pp}) {
|
|
1144
|
+
nodes {
|
|
1145
|
+
id
|
|
1146
|
+
title { romaji english native userPreferred }
|
|
1147
|
+
coverImage { large medium }
|
|
1148
|
+
type
|
|
1149
|
+
format
|
|
1150
|
+
siteUrl
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
characters(perPage: ${pp}) {
|
|
1154
|
+
nodes {
|
|
1155
|
+
id
|
|
1156
|
+
name { full native }
|
|
1157
|
+
image { large medium }
|
|
1158
|
+
siteUrl
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
staff(perPage: ${pp}) {
|
|
1162
|
+
nodes {
|
|
1163
|
+
id
|
|
1164
|
+
name { full native }
|
|
1165
|
+
image { large medium }
|
|
1166
|
+
siteUrl
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
studios(perPage: ${pp}) {
|
|
1170
|
+
nodes {
|
|
1171
|
+
id
|
|
1172
|
+
name
|
|
1173
|
+
siteUrl
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}`;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
926
1181
|
// src/rate-limiter/index.ts
|
|
927
1182
|
var RateLimiter = class {
|
|
1183
|
+
maxRequests;
|
|
1184
|
+
windowMs;
|
|
1185
|
+
maxRetries;
|
|
1186
|
+
retryDelayMs;
|
|
1187
|
+
enabled;
|
|
1188
|
+
timeoutMs;
|
|
1189
|
+
retryOnNetworkError;
|
|
1190
|
+
retryStrategy;
|
|
1191
|
+
/** @internal — sliding window: circular buffer of timestamps */
|
|
1192
|
+
timestamps;
|
|
1193
|
+
head = 0;
|
|
1194
|
+
count = 0;
|
|
1195
|
+
/** @internal — active sleep timers for cleanup */
|
|
1196
|
+
activeTimers = /* @__PURE__ */ new Set();
|
|
928
1197
|
constructor(options = {}) {
|
|
929
|
-
this.head = 0;
|
|
930
|
-
this.count = 0;
|
|
931
|
-
/** @internal — active sleep timers for cleanup */
|
|
932
|
-
this.activeTimers = /* @__PURE__ */ new Set();
|
|
933
1198
|
this.maxRequests = options.maxRequests ?? 85;
|
|
934
1199
|
this.windowMs = options.windowMs ?? 6e4;
|
|
935
1200
|
this.maxRetries = options.maxRetries ?? 3;
|
|
@@ -995,7 +1260,7 @@ var RateLimiter = class {
|
|
|
995
1260
|
}
|
|
996
1261
|
}
|
|
997
1262
|
if (lastResponse) return lastResponse;
|
|
998
|
-
throw lastError;
|
|
1263
|
+
throw lastError ?? new Error(`Request failed after ${this.maxRetries} retries`);
|
|
999
1264
|
}
|
|
1000
1265
|
/** @internal — Exponential backoff with jitter, capped at 30s (or custom strategy) */
|
|
1001
1266
|
exponentialDelay(attempt) {
|
|
@@ -1070,6 +1335,68 @@ async function searchCharacters(client, options = {}) {
|
|
|
1070
1335
|
return client.pagedRequest(gqlQuery, { search, sort, page, perPage: clampPerPage(perPage) }, "characters");
|
|
1071
1336
|
}
|
|
1072
1337
|
|
|
1338
|
+
// src/types/character.ts
|
|
1339
|
+
var CharacterSort = /* @__PURE__ */ ((CharacterSort2) => {
|
|
1340
|
+
CharacterSort2["ID"] = "ID";
|
|
1341
|
+
CharacterSort2["ID_DESC"] = "ID_DESC";
|
|
1342
|
+
CharacterSort2["ROLE"] = "ROLE";
|
|
1343
|
+
CharacterSort2["ROLE_DESC"] = "ROLE_DESC";
|
|
1344
|
+
CharacterSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
|
|
1345
|
+
CharacterSort2["FAVOURITES"] = "FAVOURITES";
|
|
1346
|
+
CharacterSort2["FAVOURITES_DESC"] = "FAVOURITES_DESC";
|
|
1347
|
+
return CharacterSort2;
|
|
1348
|
+
})(CharacterSort || {});
|
|
1349
|
+
var CharacterRole = /* @__PURE__ */ ((CharacterRole2) => {
|
|
1350
|
+
CharacterRole2["MAIN"] = "MAIN";
|
|
1351
|
+
CharacterRole2["SUPPORTING"] = "SUPPORTING";
|
|
1352
|
+
CharacterRole2["BACKGROUND"] = "BACKGROUND";
|
|
1353
|
+
return CharacterRole2;
|
|
1354
|
+
})(CharacterRole || {});
|
|
1355
|
+
|
|
1356
|
+
// src/types/lists.ts
|
|
1357
|
+
var MediaListStatus = /* @__PURE__ */ ((MediaListStatus2) => {
|
|
1358
|
+
MediaListStatus2["CURRENT"] = "CURRENT";
|
|
1359
|
+
MediaListStatus2["PLANNING"] = "PLANNING";
|
|
1360
|
+
MediaListStatus2["COMPLETED"] = "COMPLETED";
|
|
1361
|
+
MediaListStatus2["DROPPED"] = "DROPPED";
|
|
1362
|
+
MediaListStatus2["PAUSED"] = "PAUSED";
|
|
1363
|
+
MediaListStatus2["REPEATING"] = "REPEATING";
|
|
1364
|
+
return MediaListStatus2;
|
|
1365
|
+
})(MediaListStatus || {});
|
|
1366
|
+
var MediaListSort = /* @__PURE__ */ ((MediaListSort2) => {
|
|
1367
|
+
MediaListSort2["MEDIA_ID"] = "MEDIA_ID";
|
|
1368
|
+
MediaListSort2["MEDIA_ID_DESC"] = "MEDIA_ID_DESC";
|
|
1369
|
+
MediaListSort2["SCORE"] = "SCORE";
|
|
1370
|
+
MediaListSort2["SCORE_DESC"] = "SCORE_DESC";
|
|
1371
|
+
MediaListSort2["STATUS"] = "STATUS";
|
|
1372
|
+
MediaListSort2["STATUS_DESC"] = "STATUS_DESC";
|
|
1373
|
+
MediaListSort2["PROGRESS"] = "PROGRESS";
|
|
1374
|
+
MediaListSort2["PROGRESS_DESC"] = "PROGRESS_DESC";
|
|
1375
|
+
MediaListSort2["PROGRESS_VOLUMES"] = "PROGRESS_VOLUMES";
|
|
1376
|
+
MediaListSort2["PROGRESS_VOLUMES_DESC"] = "PROGRESS_VOLUMES_DESC";
|
|
1377
|
+
MediaListSort2["REPEAT"] = "REPEAT";
|
|
1378
|
+
MediaListSort2["REPEAT_DESC"] = "REPEAT_DESC";
|
|
1379
|
+
MediaListSort2["PRIORITY"] = "PRIORITY";
|
|
1380
|
+
MediaListSort2["PRIORITY_DESC"] = "PRIORITY_DESC";
|
|
1381
|
+
MediaListSort2["STARTED_ON"] = "STARTED_ON";
|
|
1382
|
+
MediaListSort2["STARTED_ON_DESC"] = "STARTED_ON_DESC";
|
|
1383
|
+
MediaListSort2["FINISHED_ON"] = "FINISHED_ON";
|
|
1384
|
+
MediaListSort2["FINISHED_ON_DESC"] = "FINISHED_ON_DESC";
|
|
1385
|
+
MediaListSort2["ADDED_TIME"] = "ADDED_TIME";
|
|
1386
|
+
MediaListSort2["ADDED_TIME_DESC"] = "ADDED_TIME_DESC";
|
|
1387
|
+
MediaListSort2["UPDATED_TIME"] = "UPDATED_TIME";
|
|
1388
|
+
MediaListSort2["UPDATED_TIME_DESC"] = "UPDATED_TIME_DESC";
|
|
1389
|
+
MediaListSort2["MEDIA_TITLE_ROMAJI"] = "MEDIA_TITLE_ROMAJI";
|
|
1390
|
+
MediaListSort2["MEDIA_TITLE_ROMAJI_DESC"] = "MEDIA_TITLE_ROMAJI_DESC";
|
|
1391
|
+
MediaListSort2["MEDIA_TITLE_ENGLISH"] = "MEDIA_TITLE_ENGLISH";
|
|
1392
|
+
MediaListSort2["MEDIA_TITLE_ENGLISH_DESC"] = "MEDIA_TITLE_ENGLISH_DESC";
|
|
1393
|
+
MediaListSort2["MEDIA_TITLE_NATIVE"] = "MEDIA_TITLE_NATIVE";
|
|
1394
|
+
MediaListSort2["MEDIA_TITLE_NATIVE_DESC"] = "MEDIA_TITLE_NATIVE_DESC";
|
|
1395
|
+
MediaListSort2["MEDIA_POPULARITY"] = "MEDIA_POPULARITY";
|
|
1396
|
+
MediaListSort2["MEDIA_POPULARITY_DESC"] = "MEDIA_POPULARITY_DESC";
|
|
1397
|
+
return MediaListSort2;
|
|
1398
|
+
})(MediaListSort || {});
|
|
1399
|
+
|
|
1073
1400
|
// src/types/media.ts
|
|
1074
1401
|
var MediaType = /* @__PURE__ */ ((MediaType2) => {
|
|
1075
1402
|
MediaType2["ANIME"] = "ANIME";
|
|
@@ -1193,24 +1520,6 @@ var RecommendationSort = /* @__PURE__ */ ((RecommendationSort2) => {
|
|
|
1193
1520
|
return RecommendationSort2;
|
|
1194
1521
|
})(RecommendationSort || {});
|
|
1195
1522
|
|
|
1196
|
-
// src/types/character.ts
|
|
1197
|
-
var CharacterSort = /* @__PURE__ */ ((CharacterSort2) => {
|
|
1198
|
-
CharacterSort2["ID"] = "ID";
|
|
1199
|
-
CharacterSort2["ID_DESC"] = "ID_DESC";
|
|
1200
|
-
CharacterSort2["ROLE"] = "ROLE";
|
|
1201
|
-
CharacterSort2["ROLE_DESC"] = "ROLE_DESC";
|
|
1202
|
-
CharacterSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
|
|
1203
|
-
CharacterSort2["FAVOURITES"] = "FAVOURITES";
|
|
1204
|
-
CharacterSort2["FAVOURITES_DESC"] = "FAVOURITES_DESC";
|
|
1205
|
-
return CharacterSort2;
|
|
1206
|
-
})(CharacterSort || {});
|
|
1207
|
-
var CharacterRole = /* @__PURE__ */ ((CharacterRole2) => {
|
|
1208
|
-
CharacterRole2["MAIN"] = "MAIN";
|
|
1209
|
-
CharacterRole2["SUPPORTING"] = "SUPPORTING";
|
|
1210
|
-
CharacterRole2["BACKGROUND"] = "BACKGROUND";
|
|
1211
|
-
return CharacterRole2;
|
|
1212
|
-
})(CharacterRole || {});
|
|
1213
|
-
|
|
1214
1523
|
// src/types/staff.ts
|
|
1215
1524
|
var StaffSort = /* @__PURE__ */ ((StaffSort2) => {
|
|
1216
1525
|
StaffSort2["ID"] = "ID";
|
|
@@ -1226,20 +1535,6 @@ var StaffSort = /* @__PURE__ */ ((StaffSort2) => {
|
|
|
1226
1535
|
return StaffSort2;
|
|
1227
1536
|
})(StaffSort || {});
|
|
1228
1537
|
|
|
1229
|
-
// src/types/user.ts
|
|
1230
|
-
var UserSort = /* @__PURE__ */ ((UserSort2) => {
|
|
1231
|
-
UserSort2["ID"] = "ID";
|
|
1232
|
-
UserSort2["ID_DESC"] = "ID_DESC";
|
|
1233
|
-
UserSort2["USERNAME"] = "USERNAME";
|
|
1234
|
-
UserSort2["USERNAME_DESC"] = "USERNAME_DESC";
|
|
1235
|
-
UserSort2["WATCHED_TIME"] = "WATCHED_TIME";
|
|
1236
|
-
UserSort2["WATCHED_TIME_DESC"] = "WATCHED_TIME_DESC";
|
|
1237
|
-
UserSort2["CHAPTERS_READ"] = "CHAPTERS_READ";
|
|
1238
|
-
UserSort2["CHAPTERS_READ_DESC"] = "CHAPTERS_READ_DESC";
|
|
1239
|
-
UserSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
|
|
1240
|
-
return UserSort2;
|
|
1241
|
-
})(UserSort || {});
|
|
1242
|
-
|
|
1243
1538
|
// src/types/studio.ts
|
|
1244
1539
|
var StudioSort = /* @__PURE__ */ ((StudioSort2) => {
|
|
1245
1540
|
StudioSort2["ID"] = "ID";
|
|
@@ -1252,50 +1547,6 @@ var StudioSort = /* @__PURE__ */ ((StudioSort2) => {
|
|
|
1252
1547
|
return StudioSort2;
|
|
1253
1548
|
})(StudioSort || {});
|
|
1254
1549
|
|
|
1255
|
-
// src/types/lists.ts
|
|
1256
|
-
var MediaListStatus = /* @__PURE__ */ ((MediaListStatus2) => {
|
|
1257
|
-
MediaListStatus2["CURRENT"] = "CURRENT";
|
|
1258
|
-
MediaListStatus2["PLANNING"] = "PLANNING";
|
|
1259
|
-
MediaListStatus2["COMPLETED"] = "COMPLETED";
|
|
1260
|
-
MediaListStatus2["DROPPED"] = "DROPPED";
|
|
1261
|
-
MediaListStatus2["PAUSED"] = "PAUSED";
|
|
1262
|
-
MediaListStatus2["REPEATING"] = "REPEATING";
|
|
1263
|
-
return MediaListStatus2;
|
|
1264
|
-
})(MediaListStatus || {});
|
|
1265
|
-
var MediaListSort = /* @__PURE__ */ ((MediaListSort2) => {
|
|
1266
|
-
MediaListSort2["MEDIA_ID"] = "MEDIA_ID";
|
|
1267
|
-
MediaListSort2["MEDIA_ID_DESC"] = "MEDIA_ID_DESC";
|
|
1268
|
-
MediaListSort2["SCORE"] = "SCORE";
|
|
1269
|
-
MediaListSort2["SCORE_DESC"] = "SCORE_DESC";
|
|
1270
|
-
MediaListSort2["STATUS"] = "STATUS";
|
|
1271
|
-
MediaListSort2["STATUS_DESC"] = "STATUS_DESC";
|
|
1272
|
-
MediaListSort2["PROGRESS"] = "PROGRESS";
|
|
1273
|
-
MediaListSort2["PROGRESS_DESC"] = "PROGRESS_DESC";
|
|
1274
|
-
MediaListSort2["PROGRESS_VOLUMES"] = "PROGRESS_VOLUMES";
|
|
1275
|
-
MediaListSort2["PROGRESS_VOLUMES_DESC"] = "PROGRESS_VOLUMES_DESC";
|
|
1276
|
-
MediaListSort2["REPEAT"] = "REPEAT";
|
|
1277
|
-
MediaListSort2["REPEAT_DESC"] = "REPEAT_DESC";
|
|
1278
|
-
MediaListSort2["PRIORITY"] = "PRIORITY";
|
|
1279
|
-
MediaListSort2["PRIORITY_DESC"] = "PRIORITY_DESC";
|
|
1280
|
-
MediaListSort2["STARTED_ON"] = "STARTED_ON";
|
|
1281
|
-
MediaListSort2["STARTED_ON_DESC"] = "STARTED_ON_DESC";
|
|
1282
|
-
MediaListSort2["FINISHED_ON"] = "FINISHED_ON";
|
|
1283
|
-
MediaListSort2["FINISHED_ON_DESC"] = "FINISHED_ON_DESC";
|
|
1284
|
-
MediaListSort2["ADDED_TIME"] = "ADDED_TIME";
|
|
1285
|
-
MediaListSort2["ADDED_TIME_DESC"] = "ADDED_TIME_DESC";
|
|
1286
|
-
MediaListSort2["UPDATED_TIME"] = "UPDATED_TIME";
|
|
1287
|
-
MediaListSort2["UPDATED_TIME_DESC"] = "UPDATED_TIME_DESC";
|
|
1288
|
-
MediaListSort2["MEDIA_TITLE_ROMAJI"] = "MEDIA_TITLE_ROMAJI";
|
|
1289
|
-
MediaListSort2["MEDIA_TITLE_ROMAJI_DESC"] = "MEDIA_TITLE_ROMAJI_DESC";
|
|
1290
|
-
MediaListSort2["MEDIA_TITLE_ENGLISH"] = "MEDIA_TITLE_ENGLISH";
|
|
1291
|
-
MediaListSort2["MEDIA_TITLE_ENGLISH_DESC"] = "MEDIA_TITLE_ENGLISH_DESC";
|
|
1292
|
-
MediaListSort2["MEDIA_TITLE_NATIVE"] = "MEDIA_TITLE_NATIVE";
|
|
1293
|
-
MediaListSort2["MEDIA_TITLE_NATIVE_DESC"] = "MEDIA_TITLE_NATIVE_DESC";
|
|
1294
|
-
MediaListSort2["MEDIA_POPULARITY"] = "MEDIA_POPULARITY";
|
|
1295
|
-
MediaListSort2["MEDIA_POPULARITY_DESC"] = "MEDIA_POPULARITY_DESC";
|
|
1296
|
-
return MediaListSort2;
|
|
1297
|
-
})(MediaListSort || {});
|
|
1298
|
-
|
|
1299
1550
|
// src/types/thread.ts
|
|
1300
1551
|
var ThreadSort = /* @__PURE__ */ ((ThreadSort2) => {
|
|
1301
1552
|
ThreadSort2["ID"] = "ID";
|
|
@@ -1317,6 +1568,20 @@ var ThreadSort = /* @__PURE__ */ ((ThreadSort2) => {
|
|
|
1317
1568
|
return ThreadSort2;
|
|
1318
1569
|
})(ThreadSort || {});
|
|
1319
1570
|
|
|
1571
|
+
// src/types/user.ts
|
|
1572
|
+
var UserSort = /* @__PURE__ */ ((UserSort2) => {
|
|
1573
|
+
UserSort2["ID"] = "ID";
|
|
1574
|
+
UserSort2["ID_DESC"] = "ID_DESC";
|
|
1575
|
+
UserSort2["USERNAME"] = "USERNAME";
|
|
1576
|
+
UserSort2["USERNAME_DESC"] = "USERNAME_DESC";
|
|
1577
|
+
UserSort2["WATCHED_TIME"] = "WATCHED_TIME";
|
|
1578
|
+
UserSort2["WATCHED_TIME_DESC"] = "WATCHED_TIME_DESC";
|
|
1579
|
+
UserSort2["CHAPTERS_READ"] = "CHAPTERS_READ";
|
|
1580
|
+
UserSort2["CHAPTERS_READ_DESC"] = "CHAPTERS_READ_DESC";
|
|
1581
|
+
UserSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
|
|
1582
|
+
return UserSort2;
|
|
1583
|
+
})(UserSort || {});
|
|
1584
|
+
|
|
1320
1585
|
// src/client/media.ts
|
|
1321
1586
|
async function getMedia(client, id, include) {
|
|
1322
1587
|
validateId(id, "mediaId");
|
|
@@ -1324,6 +1589,14 @@ async function getMedia(client, id, include) {
|
|
|
1324
1589
|
const data = await client.request(query, { id });
|
|
1325
1590
|
return data.Media;
|
|
1326
1591
|
}
|
|
1592
|
+
async function getMediaByMalId(client, malId, type) {
|
|
1593
|
+
validateId(malId, "malId");
|
|
1594
|
+
const data = await client.request(QUERY_MEDIA_BY_MAL_ID, {
|
|
1595
|
+
idMal: malId,
|
|
1596
|
+
type
|
|
1597
|
+
});
|
|
1598
|
+
return data.Media;
|
|
1599
|
+
}
|
|
1327
1600
|
async function searchMedia(client, options = {}) {
|
|
1328
1601
|
const { query: search, page = 1, perPage = 20, genres, tags, genresExclude, tagsExclude, ...filters } = options;
|
|
1329
1602
|
return client.pagedRequest(
|
|
@@ -1444,7 +1717,7 @@ async function getWeeklySchedule(client, date = /* @__PURE__ */ new Date()) {
|
|
|
1444
1717
|
for await (const episode of iterator) {
|
|
1445
1718
|
const epDate = new Date(episode.airingAt * 1e3);
|
|
1446
1719
|
const dayName = names[epDate.getUTCDay()];
|
|
1447
|
-
schedule[dayName].push(episode);
|
|
1720
|
+
if (dayName) schedule[dayName].push(episode);
|
|
1448
1721
|
}
|
|
1449
1722
|
return schedule;
|
|
1450
1723
|
}
|
|
@@ -1470,8 +1743,14 @@ async function searchStaff(client, options = {}) {
|
|
|
1470
1743
|
}
|
|
1471
1744
|
|
|
1472
1745
|
// src/client/studio.ts
|
|
1473
|
-
async function getStudio(client, id) {
|
|
1746
|
+
async function getStudio(client, id, include) {
|
|
1474
1747
|
validateId(id, "studioId");
|
|
1748
|
+
if (include?.media) {
|
|
1749
|
+
const perPage = typeof include.media === "object" ? include.media.perPage : void 0;
|
|
1750
|
+
const query = buildStudioByIdQuery(perPage);
|
|
1751
|
+
const data2 = await client.request(query, { id });
|
|
1752
|
+
return data2.Studio;
|
|
1753
|
+
}
|
|
1475
1754
|
const data = await client.request(QUERY_STUDIO_BY_ID, { id });
|
|
1476
1755
|
return data.Studio;
|
|
1477
1756
|
}
|
|
@@ -1527,7 +1806,7 @@ async function searchUsers(client, options = {}) {
|
|
|
1527
1806
|
}
|
|
1528
1807
|
async function getUserMediaList(client, options) {
|
|
1529
1808
|
if (!options.userId && !options.userName) {
|
|
1530
|
-
throw new
|
|
1809
|
+
throw new TypeError("getUserMediaList requires either userId or userName");
|
|
1531
1810
|
}
|
|
1532
1811
|
if (options.userId) {
|
|
1533
1812
|
validateId(options.userId, "userId");
|
|
@@ -1546,15 +1825,18 @@ async function getUserMediaList(client, options) {
|
|
|
1546
1825
|
"mediaList"
|
|
1547
1826
|
);
|
|
1548
1827
|
}
|
|
1549
|
-
async function getUserFavorites(client, idOrName) {
|
|
1828
|
+
async function getUserFavorites(client, idOrName, options) {
|
|
1829
|
+
const useBuilder = options?.perPage !== void 0;
|
|
1550
1830
|
if (typeof idOrName === "number") {
|
|
1551
1831
|
validateId(idOrName, "userId");
|
|
1552
|
-
const
|
|
1832
|
+
const query2 = useBuilder ? buildUserFavoritesQuery("id", options.perPage) : QUERY_USER_FAVORITES_BY_ID;
|
|
1833
|
+
const data2 = await client.request(query2, {
|
|
1553
1834
|
id: idOrName
|
|
1554
1835
|
});
|
|
1555
1836
|
return mapFavorites(data2.User.favourites);
|
|
1556
1837
|
}
|
|
1557
|
-
const
|
|
1838
|
+
const query = useBuilder ? buildUserFavoritesQuery("name", options.perPage) : QUERY_USER_FAVORITES_BY_NAME;
|
|
1839
|
+
const data = await client.request(query, {
|
|
1558
1840
|
name: idOrName
|
|
1559
1841
|
});
|
|
1560
1842
|
return mapFavorites(data.User.favourites);
|
|
@@ -1571,10 +1853,19 @@ function mapFavorites(fav) {
|
|
|
1571
1853
|
|
|
1572
1854
|
// src/client/index.ts
|
|
1573
1855
|
var DEFAULT_API_URL = "https://graphql.anilist.co";
|
|
1574
|
-
var LIB_VERSION = "1.
|
|
1856
|
+
var LIB_VERSION = "1.8.1" ;
|
|
1575
1857
|
var AniListClient = class {
|
|
1858
|
+
apiUrl;
|
|
1859
|
+
headers;
|
|
1860
|
+
cacheAdapter;
|
|
1861
|
+
rateLimiter;
|
|
1862
|
+
hooks;
|
|
1863
|
+
logger;
|
|
1864
|
+
signal;
|
|
1865
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
1866
|
+
_rateLimitInfo;
|
|
1867
|
+
_lastRequestMeta;
|
|
1576
1868
|
constructor(options = {}) {
|
|
1577
|
-
this.inFlight = /* @__PURE__ */ new Map();
|
|
1578
1869
|
this.apiUrl = options.apiUrl ?? DEFAULT_API_URL;
|
|
1579
1870
|
this.headers = {
|
|
1580
1871
|
"Content-Type": "application/json",
|
|
@@ -1587,6 +1878,7 @@ var AniListClient = class {
|
|
|
1587
1878
|
this.cacheAdapter = options.cacheAdapter ?? new MemoryCache(options.cache);
|
|
1588
1879
|
this.rateLimiter = new RateLimiter(options.rateLimit);
|
|
1589
1880
|
this.hooks = options.hooks ?? {};
|
|
1881
|
+
this.logger = options.logger;
|
|
1590
1882
|
this.signal = options.signal;
|
|
1591
1883
|
}
|
|
1592
1884
|
/**
|
|
@@ -1609,6 +1901,7 @@ var AniListClient = class {
|
|
|
1609
1901
|
const cached = await this.cacheAdapter.get(cacheKey);
|
|
1610
1902
|
if (cached !== void 0) {
|
|
1611
1903
|
this.hooks.onCacheHit?.(cacheKey);
|
|
1904
|
+
this.logger?.debug("Cache hit", { cacheKey });
|
|
1612
1905
|
const meta = { durationMs: 0, fromCache: true };
|
|
1613
1906
|
this._lastRequestMeta = meta;
|
|
1614
1907
|
this.hooks.onResponse?.(query, 0, true);
|
|
@@ -1628,6 +1921,7 @@ var AniListClient = class {
|
|
|
1628
1921
|
async executeRequest(query, variables, cacheKey) {
|
|
1629
1922
|
const start = Date.now();
|
|
1630
1923
|
this.hooks.onRequest?.(query, variables);
|
|
1924
|
+
this.logger?.debug("API request", { variables });
|
|
1631
1925
|
const minifiedQuery = normalizeQuery(query);
|
|
1632
1926
|
let res;
|
|
1633
1927
|
try {
|
|
@@ -1643,13 +1937,23 @@ var AniListClient = class {
|
|
|
1643
1937
|
);
|
|
1644
1938
|
} catch (err) {
|
|
1645
1939
|
const error = err instanceof AniListError ? err : new AniListError(err.message ?? "Network request failed", 0, [err]);
|
|
1940
|
+
this.logger?.error("Request failed", { error: error.message, status: error.status });
|
|
1941
|
+
this.hooks.onError?.(error, query, variables);
|
|
1942
|
+
throw error;
|
|
1943
|
+
}
|
|
1944
|
+
let json;
|
|
1945
|
+
try {
|
|
1946
|
+
json = await res.json();
|
|
1947
|
+
} catch {
|
|
1948
|
+
const error = new AniListError(`Non-JSON response from AniList (HTTP ${res.status})`, res.status, []);
|
|
1949
|
+
this.logger?.error("Request failed", { error: error.message, status: error.status });
|
|
1646
1950
|
this.hooks.onError?.(error, query, variables);
|
|
1647
1951
|
throw error;
|
|
1648
1952
|
}
|
|
1649
|
-
const json = await res.json();
|
|
1650
1953
|
if (!res.ok || json.errors) {
|
|
1651
1954
|
const message = json.errors?.[0]?.message ?? `AniList API error (HTTP ${res.status})`;
|
|
1652
1955
|
const error = new AniListError(message, res.status, json.errors ?? []);
|
|
1956
|
+
this.logger?.error("Request failed", { error: error.message, status: error.status });
|
|
1653
1957
|
this.hooks.onError?.(error, query, variables);
|
|
1654
1958
|
throw error;
|
|
1655
1959
|
}
|
|
@@ -1668,6 +1972,7 @@ var AniListClient = class {
|
|
|
1668
1972
|
await this.cacheAdapter.set(cacheKey, data);
|
|
1669
1973
|
const meta = { durationMs, fromCache: false, rateLimitInfo: this._rateLimitInfo };
|
|
1670
1974
|
this._lastRequestMeta = meta;
|
|
1975
|
+
this.logger?.debug("Request complete", { durationMs, rateLimitInfo: this._rateLimitInfo });
|
|
1671
1976
|
this.hooks.onResponse?.(query, durationMs, false, this._rateLimitInfo);
|
|
1672
1977
|
return data;
|
|
1673
1978
|
}
|
|
@@ -1730,6 +2035,15 @@ var AniListClient = class {
|
|
|
1730
2035
|
async getAiredChapters(options = {}) {
|
|
1731
2036
|
return this.getRecentlyUpdatedManga(options);
|
|
1732
2037
|
}
|
|
2038
|
+
/**
|
|
2039
|
+
* Fetch a media entry by its MyAnimeList (MAL) ID.
|
|
2040
|
+
*
|
|
2041
|
+
* @param malId - The MyAnimeList ID
|
|
2042
|
+
* @param type - Optional media type to disambiguate (some MAL IDs map to both ANIME and MANGA)
|
|
2043
|
+
*/
|
|
2044
|
+
async getMediaByMalId(malId, type) {
|
|
2045
|
+
return getMediaByMalId(this, malId, type);
|
|
2046
|
+
}
|
|
1733
2047
|
/** Get the detailed schedule for the current week, sorted by day. */
|
|
1734
2048
|
async getWeeklySchedule(date) {
|
|
1735
2049
|
return getWeeklySchedule(this, date);
|
|
@@ -1782,20 +2096,32 @@ var AniListClient = class {
|
|
|
1782
2096
|
* Fetch a user's favorite anime, manga, characters, staff, and studios.
|
|
1783
2097
|
*
|
|
1784
2098
|
* @param idOrName - AniList user ID (number) or username (string)
|
|
2099
|
+
* @param options - Optional pagination options (perPage per category)
|
|
1785
2100
|
* @returns The user's favorites grouped by category
|
|
1786
2101
|
*
|
|
1787
2102
|
* @example
|
|
1788
2103
|
* ```typescript
|
|
1789
2104
|
* const favs = await client.getUserFavorites("AniList");
|
|
1790
2105
|
* favs.anime.forEach(a => console.log(a.title.romaji));
|
|
2106
|
+
*
|
|
2107
|
+
* // Fetch more results per category
|
|
2108
|
+
* const moreResults = await client.getUserFavorites(1, { perPage: 50 });
|
|
1791
2109
|
* ```
|
|
1792
2110
|
*/
|
|
1793
|
-
async getUserFavorites(idOrName) {
|
|
1794
|
-
return getUserFavorites(this, idOrName);
|
|
2111
|
+
async getUserFavorites(idOrName, options) {
|
|
2112
|
+
return getUserFavorites(this, idOrName, options);
|
|
1795
2113
|
}
|
|
1796
|
-
/**
|
|
1797
|
-
|
|
1798
|
-
|
|
2114
|
+
/**
|
|
2115
|
+
* Fetch a studio by its AniList ID.
|
|
2116
|
+
* Pass `include` to customise the number of media returned.
|
|
2117
|
+
*
|
|
2118
|
+
* @example
|
|
2119
|
+
* ```typescript
|
|
2120
|
+
* const studio = await client.getStudio(21, { media: { perPage: 50 } });
|
|
2121
|
+
* ```
|
|
2122
|
+
*/
|
|
2123
|
+
async getStudio(id, include) {
|
|
2124
|
+
return getStudio(this, id, include);
|
|
1799
2125
|
}
|
|
1800
2126
|
/** Search for studios by name. */
|
|
1801
2127
|
async searchStudios(options = {}) {
|
|
@@ -1845,21 +2171,24 @@ var AniListClient = class {
|
|
|
1845
2171
|
async getMediaBatch(ids) {
|
|
1846
2172
|
if (ids.length === 0) return [];
|
|
1847
2173
|
validateIds(ids, "mediaId");
|
|
1848
|
-
|
|
2174
|
+
const [singleMediaId] = ids;
|
|
2175
|
+
if (ids.length === 1 && singleMediaId !== void 0) return [await this.getMedia(singleMediaId)];
|
|
1849
2176
|
return this.executeBatch(ids, buildBatchMediaQuery, "m");
|
|
1850
2177
|
}
|
|
1851
2178
|
/** Fetch multiple characters in a single API request. */
|
|
1852
2179
|
async getCharacterBatch(ids) {
|
|
1853
2180
|
if (ids.length === 0) return [];
|
|
1854
2181
|
validateIds(ids, "characterId");
|
|
1855
|
-
|
|
2182
|
+
const [singleCharId] = ids;
|
|
2183
|
+
if (ids.length === 1 && singleCharId !== void 0) return [await this.getCharacter(singleCharId)];
|
|
1856
2184
|
return this.executeBatch(ids, buildBatchCharacterQuery, "c");
|
|
1857
2185
|
}
|
|
1858
2186
|
/** Fetch multiple staff members in a single API request. */
|
|
1859
2187
|
async getStaffBatch(ids) {
|
|
1860
2188
|
if (ids.length === 0) return [];
|
|
1861
2189
|
validateIds(ids, "staffId");
|
|
1862
|
-
|
|
2190
|
+
const [singleStaffId] = ids;
|
|
2191
|
+
if (ids.length === 1 && singleStaffId !== void 0) return [await this.getStaff(singleStaffId)];
|
|
1863
2192
|
return this.executeBatch(ids, buildBatchStaffQuery, "s");
|
|
1864
2193
|
}
|
|
1865
2194
|
/** @internal */
|
|
@@ -1904,88 +2233,23 @@ var AniListClient = class {
|
|
|
1904
2233
|
this.inFlight.clear();
|
|
1905
2234
|
this.rateLimiter.dispose();
|
|
1906
2235
|
}
|
|
1907
|
-
};
|
|
1908
|
-
|
|
1909
|
-
// src/cache/redis.ts
|
|
1910
|
-
var RedisCache = class {
|
|
1911
|
-
constructor(options) {
|
|
1912
|
-
this.client = options.client;
|
|
1913
|
-
this.prefix = options.prefix ?? "ani:";
|
|
1914
|
-
this.ttl = options.ttl ?? 86400;
|
|
1915
|
-
}
|
|
1916
|
-
prefixedKey(key) {
|
|
1917
|
-
return `${this.prefix}${key}`;
|
|
1918
|
-
}
|
|
1919
|
-
async get(key) {
|
|
1920
|
-
const raw = await this.client.get(this.prefixedKey(key));
|
|
1921
|
-
if (raw === null) return void 0;
|
|
1922
|
-
try {
|
|
1923
|
-
return JSON.parse(raw);
|
|
1924
|
-
} catch {
|
|
1925
|
-
return void 0;
|
|
1926
|
-
}
|
|
1927
|
-
}
|
|
1928
|
-
async set(key, data) {
|
|
1929
|
-
await this.client.set(this.prefixedKey(key), JSON.stringify(data), "EX", this.ttl);
|
|
1930
|
-
}
|
|
1931
|
-
async delete(key) {
|
|
1932
|
-
const count = await this.client.del(this.prefixedKey(key));
|
|
1933
|
-
return count > 0;
|
|
1934
|
-
}
|
|
1935
2236
|
/**
|
|
1936
|
-
*
|
|
2237
|
+
* Return a scoped view of this client where every request uses the given `AbortSignal`.
|
|
2238
|
+
* The returned object shares the same cache, rate limiter, and hooks.
|
|
1937
2239
|
*
|
|
1938
|
-
*
|
|
1939
|
-
*
|
|
1940
|
-
*
|
|
1941
|
-
|
|
1942
|
-
async collectKeys(pattern) {
|
|
1943
|
-
if (this.client.scanIterator) {
|
|
1944
|
-
const keys = [];
|
|
1945
|
-
for await (const key of this.client.scanIterator({ MATCH: pattern, COUNT: 100 })) {
|
|
1946
|
-
keys.push(key);
|
|
1947
|
-
}
|
|
1948
|
-
return keys;
|
|
1949
|
-
}
|
|
1950
|
-
return this.client.keys(pattern);
|
|
1951
|
-
}
|
|
1952
|
-
async clear() {
|
|
1953
|
-
const keys = await this.collectKeys(`${this.prefix}*`);
|
|
1954
|
-
if (keys.length > 0) {
|
|
1955
|
-
await this.client.del(...keys);
|
|
1956
|
-
}
|
|
1957
|
-
}
|
|
1958
|
-
/**
|
|
1959
|
-
* Get the actual number of keys with this prefix in Redis.
|
|
1960
|
-
*/
|
|
1961
|
-
get size() {
|
|
1962
|
-
return this.getSize();
|
|
1963
|
-
}
|
|
1964
|
-
/** @internal */
|
|
1965
|
-
async getSize() {
|
|
1966
|
-
const keys = await this.collectKeys(`${this.prefix}*`);
|
|
1967
|
-
return keys.length;
|
|
1968
|
-
}
|
|
1969
|
-
async keys() {
|
|
1970
|
-
const raw = await this.collectKeys(`${this.prefix}*`);
|
|
1971
|
-
return raw.map((k) => k.slice(this.prefix.length));
|
|
1972
|
-
}
|
|
1973
|
-
/**
|
|
1974
|
-
* Remove all entries whose key matches the given glob pattern.
|
|
2240
|
+
* @example
|
|
2241
|
+
* ```ts
|
|
2242
|
+
* const controller = new AbortController();
|
|
2243
|
+
* const media = await client.withSignal(controller.signal).getMedia(1);
|
|
1975
2244
|
*
|
|
1976
|
-
*
|
|
1977
|
-
*
|
|
2245
|
+
* // Cancel all in-flight requests made through the scoped view
|
|
2246
|
+
* controller.abort();
|
|
2247
|
+
* ```
|
|
1978
2248
|
*/
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
return this.client.del(...keys);
|
|
1984
|
-
}
|
|
1985
|
-
const allKeys = await this.collectKeys(`${this.prefix}*`);
|
|
1986
|
-
const matching = allKeys.filter((k) => pattern.test(k.slice(this.prefix.length)));
|
|
1987
|
-
if (matching.length === 0) return 0;
|
|
1988
|
-
return this.client.del(...matching);
|
|
2249
|
+
withSignal(signal) {
|
|
2250
|
+
const scoped = Object.create(this);
|
|
2251
|
+
Object.defineProperty(scoped, "signal", { value: signal, configurable: true });
|
|
2252
|
+
return scoped;
|
|
1989
2253
|
}
|
|
1990
2254
|
};
|
|
1991
2255
|
|