ani-client 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +77 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +77 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
- **LRU cache** with TTL, stale-while-revalidate, and hit/miss stats
|
|
19
19
|
- **Rate-limit protection** with exponential backoff, retries, and custom strategies
|
|
20
20
|
- **Request deduplication** — concurrent identical queries share a single in-flight request
|
|
21
|
-
- **
|
|
21
|
+
- **Automatic batching (DataLoader)** — groups rapid, individual `getMedia(id)` calls within a 50ms window into a single batch query, saving API limits.
|
|
22
|
+
- **Batch queries** — manually fetch up to 50 media, characters, or staff in a single API call
|
|
22
23
|
- **Auto-pagination** — async iterator that yields items across all pages
|
|
23
24
|
- **AbortSignal support** — cancel globally or per-request via `withSignal()`
|
|
24
25
|
- **Injectable logger** — plug in `console`, pino, winston, or any compatible logger
|
package/dist/index.d.mts
CHANGED
|
@@ -1069,6 +1069,9 @@ declare class AniListClient implements ClientBase {
|
|
|
1069
1069
|
private readonly inFlight;
|
|
1070
1070
|
private _rateLimitInfo?;
|
|
1071
1071
|
private _lastRequestMeta?;
|
|
1072
|
+
private readonly mediaLoader;
|
|
1073
|
+
private readonly characterLoader;
|
|
1074
|
+
private readonly staffLoader;
|
|
1072
1075
|
constructor(options?: AniListClientOptions);
|
|
1073
1076
|
/**
|
|
1074
1077
|
* The current rate limit information from the last API response.
|
package/dist/index.d.ts
CHANGED
|
@@ -1069,6 +1069,9 @@ declare class AniListClient implements ClientBase {
|
|
|
1069
1069
|
private readonly inFlight;
|
|
1070
1070
|
private _rateLimitInfo?;
|
|
1071
1071
|
private _lastRequestMeta?;
|
|
1072
|
+
private readonly mediaLoader;
|
|
1073
|
+
private readonly characterLoader;
|
|
1074
|
+
private readonly staffLoader;
|
|
1072
1075
|
constructor(options?: AniListClientOptions);
|
|
1073
1076
|
/**
|
|
1074
1077
|
* The current rate limit information from the last API response.
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,62 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// src/utils/dataloader.ts
|
|
4
|
+
var BatchLoader = class {
|
|
5
|
+
constructor(batchFetch, maxWaitMs = 50) {
|
|
6
|
+
this.batchFetch = batchFetch;
|
|
7
|
+
this.maxWaitMs = maxWaitMs;
|
|
8
|
+
}
|
|
9
|
+
queue = /* @__PURE__ */ new Map();
|
|
10
|
+
timeout = null;
|
|
11
|
+
/**
|
|
12
|
+
* Queue an ID to be fetched in the next batch.
|
|
13
|
+
* Returns a Promise that resolves when the batch request completes.
|
|
14
|
+
*/
|
|
15
|
+
async load(id) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
let callbacks = this.queue.get(id);
|
|
18
|
+
if (!callbacks) {
|
|
19
|
+
callbacks = [];
|
|
20
|
+
this.queue.set(id, callbacks);
|
|
21
|
+
}
|
|
22
|
+
callbacks.push({ resolve, reject });
|
|
23
|
+
if (this.timeout === null) {
|
|
24
|
+
this.timeout = setTimeout(() => this.dispatch(), this.maxWaitMs);
|
|
25
|
+
if (typeof this.timeout.unref === "function") {
|
|
26
|
+
this.timeout.unref();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
async dispatch() {
|
|
32
|
+
this.timeout = null;
|
|
33
|
+
if (this.queue.size === 0) return;
|
|
34
|
+
const currentQueue = this.queue;
|
|
35
|
+
this.queue = /* @__PURE__ */ new Map();
|
|
36
|
+
const ids = Array.from(currentQueue.keys());
|
|
37
|
+
try {
|
|
38
|
+
const results = await this.batchFetch(ids);
|
|
39
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
40
|
+
for (const item of results) {
|
|
41
|
+
resultMap.set(item.id, item);
|
|
42
|
+
}
|
|
43
|
+
for (const [id, callbacks] of currentQueue.entries()) {
|
|
44
|
+
const result = resultMap.get(id);
|
|
45
|
+
if (result) {
|
|
46
|
+
for (const cb of callbacks) cb.resolve(result);
|
|
47
|
+
} else {
|
|
48
|
+
const err = new Error(`Item with ID ${id} not found in batch response`);
|
|
49
|
+
for (const cb of callbacks) cb.reject(err);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch (err) {
|
|
53
|
+
for (const callbacks of currentQueue.values()) {
|
|
54
|
+
for (const cb of callbacks) cb.reject(err);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
3
60
|
// src/utils/markdown.ts
|
|
4
61
|
function isSafeUrl(url) {
|
|
5
62
|
return /^https?:\/\//i.test(url);
|
|
@@ -2531,7 +2588,7 @@ function mapFavorites(fav) {
|
|
|
2531
2588
|
|
|
2532
2589
|
// src/client/index.ts
|
|
2533
2590
|
var DEFAULT_API_URL = "https://graphql.anilist.co";
|
|
2534
|
-
var LIB_VERSION = "2.
|
|
2591
|
+
var LIB_VERSION = "2.4.0" ;
|
|
2535
2592
|
var AniListClient = class {
|
|
2536
2593
|
apiUrl;
|
|
2537
2594
|
headers;
|
|
@@ -2543,6 +2600,9 @@ var AniListClient = class {
|
|
|
2543
2600
|
inFlight = /* @__PURE__ */ new Map();
|
|
2544
2601
|
_rateLimitInfo;
|
|
2545
2602
|
_lastRequestMeta;
|
|
2603
|
+
mediaLoader;
|
|
2604
|
+
characterLoader;
|
|
2605
|
+
staffLoader;
|
|
2546
2606
|
constructor(options = {}) {
|
|
2547
2607
|
this.apiUrl = options.apiUrl ?? DEFAULT_API_URL;
|
|
2548
2608
|
this.headers = {
|
|
@@ -2558,6 +2618,9 @@ var AniListClient = class {
|
|
|
2558
2618
|
this.hooks = options.hooks ?? {};
|
|
2559
2619
|
this.logger = options.logger;
|
|
2560
2620
|
this.signal = options.signal;
|
|
2621
|
+
this.mediaLoader = new BatchLoader((ids) => this.getMediaBatch(ids), 50);
|
|
2622
|
+
this.characterLoader = new BatchLoader((ids) => this.getCharacterBatch(ids), 50);
|
|
2623
|
+
this.staffLoader = new BatchLoader((ids) => this.getStaffBatch(ids), 50);
|
|
2561
2624
|
}
|
|
2562
2625
|
/**
|
|
2563
2626
|
* The current rate limit information from the last API response.
|
|
@@ -2688,6 +2751,9 @@ var AniListClient = class {
|
|
|
2688
2751
|
* @param include - Optional related data to include
|
|
2689
2752
|
*/
|
|
2690
2753
|
async getMedia(id, include) {
|
|
2754
|
+
if (!include) {
|
|
2755
|
+
return this.mediaLoader.load(id);
|
|
2756
|
+
}
|
|
2691
2757
|
return getMedia(this, id, include);
|
|
2692
2758
|
}
|
|
2693
2759
|
async getMediaCharacters(mediaId, options = {}) {
|
|
@@ -2756,6 +2822,9 @@ var AniListClient = class {
|
|
|
2756
2822
|
}
|
|
2757
2823
|
/** Fetch a character by AniList ID. Pass `{ voiceActors: true }` to include VA data. */
|
|
2758
2824
|
async getCharacter(id, include) {
|
|
2825
|
+
if (!include) {
|
|
2826
|
+
return this.characterLoader.load(id);
|
|
2827
|
+
}
|
|
2759
2828
|
return getCharacter(this, id, include);
|
|
2760
2829
|
}
|
|
2761
2830
|
/** Search for characters by name. */
|
|
@@ -2764,6 +2833,9 @@ var AniListClient = class {
|
|
|
2764
2833
|
}
|
|
2765
2834
|
/** Fetch a staff member by AniList ID. Pass `{ media: true }` or `{ media: { perPage } }` for media credits. */
|
|
2766
2835
|
async getStaff(id, include) {
|
|
2836
|
+
if (!include) {
|
|
2837
|
+
return this.staffLoader.load(id);
|
|
2838
|
+
}
|
|
2767
2839
|
return getStaff(this, id, include);
|
|
2768
2840
|
}
|
|
2769
2841
|
/** Search for staff (voice actors, directors, etc.). */
|
|
@@ -2883,7 +2955,7 @@ var AniListClient = class {
|
|
|
2883
2955
|
if (ids.length === 0) return [];
|
|
2884
2956
|
validateIds(ids, "mediaId");
|
|
2885
2957
|
const [singleMediaId] = ids;
|
|
2886
|
-
if (ids.length === 1 && singleMediaId !== void 0) return [await
|
|
2958
|
+
if (ids.length === 1 && singleMediaId !== void 0) return [await getMedia(this, singleMediaId)];
|
|
2887
2959
|
return this.executeBatch(ids, buildBatchMediaQuery, "m");
|
|
2888
2960
|
}
|
|
2889
2961
|
/** Fetch multiple characters in a single API request. */
|
|
@@ -2891,7 +2963,8 @@ var AniListClient = class {
|
|
|
2891
2963
|
if (ids.length === 0) return [];
|
|
2892
2964
|
validateIds(ids, "characterId");
|
|
2893
2965
|
const [singleCharId] = ids;
|
|
2894
|
-
if (ids.length === 1 && singleCharId !== void 0)
|
|
2966
|
+
if (ids.length === 1 && singleCharId !== void 0)
|
|
2967
|
+
return [await getCharacter(this, singleCharId)];
|
|
2895
2968
|
return this.executeBatch(ids, buildBatchCharacterQuery, "c");
|
|
2896
2969
|
}
|
|
2897
2970
|
/** Fetch multiple staff members in a single API request. */
|
|
@@ -2899,7 +2972,7 @@ var AniListClient = class {
|
|
|
2899
2972
|
if (ids.length === 0) return [];
|
|
2900
2973
|
validateIds(ids, "staffId");
|
|
2901
2974
|
const [singleStaffId] = ids;
|
|
2902
|
-
if (ids.length === 1 && singleStaffId !== void 0) return [await
|
|
2975
|
+
if (ids.length === 1 && singleStaffId !== void 0) return [await getStaff(this, singleStaffId)];
|
|
2903
2976
|
return this.executeBatch(ids, buildBatchStaffQuery, "s");
|
|
2904
2977
|
}
|
|
2905
2978
|
/** @internal */
|