anilink-api-wrapper 2.0.0 → 2.2.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 +32 -484
- package/dist/AniLink.d.ts +2406 -765
- package/dist/AniLink.mjs +2362 -683
- package/dist/anilist.d.ts +1 -1
- package/dist/anilist.mjs +1 -1
- package/dist/mal.d.ts +1 -0
- package/dist/mal.mjs +5 -0
- package/package.json +34 -19
package/dist/AniLink.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { randomInt, randomUUID, createHash } from 'node:crypto';
|
|
1
2
|
import http from 'node:http';
|
|
2
3
|
import https from 'node:https';
|
|
3
|
-
import { randomInt } from 'node:crypto';
|
|
4
4
|
import axios from 'axios';
|
|
5
5
|
|
|
6
6
|
const AniLinkErrorCodes = {
|
|
@@ -15,6 +15,7 @@ const AniLinkErrorCodes = {
|
|
|
15
15
|
UNKNOWN: "UNKNOWN_ERROR"
|
|
16
16
|
};
|
|
17
17
|
class AniLinkError extends Error {
|
|
18
|
+
/** Stable code used to classify the failure. */
|
|
18
19
|
code;
|
|
19
20
|
/**
|
|
20
21
|
* Creates a sanitized AniLink error.
|
|
@@ -22,19 +23,25 @@ class AniLinkError extends Error {
|
|
|
22
23
|
* @param message - A safe message intended for application logs.
|
|
23
24
|
* @param code - The stable code used to classify the failure.
|
|
24
25
|
* @param rawAxiosError - The original Axios error when raw diagnostics are enabled.
|
|
26
|
+
* @param options - Additional error metadata such as the request correlation ID.
|
|
25
27
|
*/
|
|
26
|
-
constructor(message, code, rawAxiosError) {
|
|
28
|
+
constructor(message, code, rawAxiosError, options) {
|
|
27
29
|
super(message, rawAxiosError instanceof Error ? { cause: rawAxiosError } : void 0);
|
|
28
30
|
this.name = "AniLinkError";
|
|
29
31
|
this.code = code;
|
|
30
32
|
if (rawAxiosError !== void 0) {
|
|
31
33
|
this.rawAxiosError = rawAxiosError;
|
|
32
34
|
}
|
|
35
|
+
if (options?.requestId !== void 0) {
|
|
36
|
+
this.requestId = options.requestId;
|
|
37
|
+
}
|
|
33
38
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
34
39
|
}
|
|
35
40
|
}
|
|
36
41
|
class AniLinkApiError extends AniLinkError {
|
|
42
|
+
/** HTTP status returned by the upstream API. For GraphQL failures this is the upstream GraphQL error status when available, and the HTTP envelope status (`200`) otherwise. */
|
|
37
43
|
status;
|
|
44
|
+
/** Response body returned by the upstream API, preserved verbatim. */
|
|
38
45
|
data;
|
|
39
46
|
/**
|
|
40
47
|
* Creates an API error while preserving the upstream response body.
|
|
@@ -42,18 +49,32 @@ class AniLinkApiError extends AniLinkError {
|
|
|
42
49
|
* @param status - The HTTP status returned by AniList.
|
|
43
50
|
* @param data - The response body returned by AniList.
|
|
44
51
|
* @param rawAxiosError - The original Axios error when raw diagnostics are enabled.
|
|
45
|
-
* @param options - Additional error metadata such as rate-limit headers.
|
|
52
|
+
* @param options - Additional error metadata such as rate-limit headers and the response content type.
|
|
46
53
|
*/
|
|
47
54
|
constructor(status, data, rawAxiosError, options) {
|
|
48
|
-
super(`API request failed with status ${status}.`, AniLinkErrorCodes.API, rawAxiosError
|
|
55
|
+
super(`API request failed with status ${status}.`, AniLinkErrorCodes.API, rawAxiosError, {
|
|
56
|
+
requestId: options?.requestId
|
|
57
|
+
});
|
|
49
58
|
this.name = "AniLinkApiError";
|
|
50
59
|
this.status = status;
|
|
51
60
|
this.data = data;
|
|
52
61
|
if (options?.rateLimit !== void 0) {
|
|
53
62
|
this.rateLimit = options.rateLimit;
|
|
54
63
|
}
|
|
64
|
+
if (options?.contentType !== void 0) {
|
|
65
|
+
this.contentType = options.contentType;
|
|
66
|
+
}
|
|
55
67
|
}
|
|
56
68
|
}
|
|
69
|
+
const extractUpstreamStatus = (errors) => {
|
|
70
|
+
for (const entry of errors) {
|
|
71
|
+
const status = entry.status;
|
|
72
|
+
if (typeof status === "number" && Number.isFinite(status)) {
|
|
73
|
+
return status;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return void 0;
|
|
77
|
+
};
|
|
57
78
|
class AniLinkGraphQLError extends AniLinkApiError {
|
|
58
79
|
/**
|
|
59
80
|
* The upstream GraphQL `errors` array carried by the envelope, preserved
|
|
@@ -68,9 +89,10 @@ class AniLinkGraphQLError extends AniLinkApiError {
|
|
|
68
89
|
* @param errors - The upstream GraphQL errors; each entry should carry a `message`.
|
|
69
90
|
* @param data - The partial `data` object returned alongside the errors, when any. Exposed as {@link AniLinkGraphQLError.partialData}.
|
|
70
91
|
* @param rawAxiosError - The original Axios error when raw diagnostics are enabled.
|
|
92
|
+
* @param options - Additional error metadata such as rate-limit headers and the response content type. AniList returns rate-limit headers even on HTTP 200 envelopes carrying GraphQL errors (for example a GraphQL-level `429`), so threading them here keeps {@link AniLinkGraphQLError.rateLimit} consistent with the HTTP-failure path.
|
|
71
93
|
*/
|
|
72
|
-
constructor(errors, data, rawAxiosError) {
|
|
73
|
-
super(200, data, rawAxiosError);
|
|
94
|
+
constructor(errors, data, rawAxiosError, options) {
|
|
95
|
+
super(extractUpstreamStatus(errors) ?? 200, data, rawAxiosError, options);
|
|
74
96
|
this.name = "AniLinkGraphQLError";
|
|
75
97
|
this.code = AniLinkErrorCodes.GRAPHQL;
|
|
76
98
|
this.message = `The request failed with GraphQL errors: ${errors.map((graphqlError) => graphqlError.message).join("; ")}`;
|
|
@@ -95,7 +117,7 @@ class AniLinkAuthError extends AniLinkError {
|
|
|
95
117
|
}
|
|
96
118
|
}
|
|
97
119
|
class AniLinkValidationError extends AniLinkError {
|
|
98
|
-
/**
|
|
120
|
+
/** Individual validation problems, one per entry. */
|
|
99
121
|
details;
|
|
100
122
|
/**
|
|
101
123
|
* Creates a validation error for invalid operation variables.
|
|
@@ -113,6 +135,18 @@ ${details.join("\n")}`,
|
|
|
113
135
|
}
|
|
114
136
|
}
|
|
115
137
|
class AniLinkRestError extends AniLinkApiError {
|
|
138
|
+
/**
|
|
139
|
+
* Creates a REST error carrying the upstream HTTP status and body.
|
|
140
|
+
*
|
|
141
|
+
* @param status - The HTTP status returned by the upstream REST API.
|
|
142
|
+
* @param data - The response body returned by the upstream REST API.
|
|
143
|
+
* @param rawAxiosError - The original Axios error when raw diagnostics are enabled.
|
|
144
|
+
* @param options - Additional error metadata such as rate-limit headers, the response content type, and the request correlation ID.
|
|
145
|
+
*/
|
|
146
|
+
constructor(status, data, rawAxiosError, options) {
|
|
147
|
+
super(status, data, rawAxiosError, options);
|
|
148
|
+
this.name = "AniLinkRestError";
|
|
149
|
+
}
|
|
116
150
|
}
|
|
117
151
|
class AniLinkNetworkError extends AniLinkError {
|
|
118
152
|
/**
|
|
@@ -121,21 +155,104 @@ class AniLinkNetworkError extends AniLinkError {
|
|
|
121
155
|
* @param code - The stable code for the transport failure.
|
|
122
156
|
* @param message - A safe message intended for application logs.
|
|
123
157
|
* @param rawAxiosError - The original Axios error when raw diagnostics are enabled.
|
|
124
|
-
* @param options - Additional transport metadata such as the effective timeout duration.
|
|
158
|
+
* @param options - Additional transport metadata such as the effective timeout duration and the request correlation ID.
|
|
125
159
|
*/
|
|
126
160
|
constructor(code, message, rawAxiosError, options) {
|
|
127
|
-
super(message, code, rawAxiosError);
|
|
161
|
+
super(message, code, rawAxiosError, { requestId: options?.requestId });
|
|
128
162
|
this.name = "AniLinkNetworkError";
|
|
129
163
|
if (options?.timeoutMs !== void 0) {
|
|
130
164
|
this.timeoutMs = options.timeoutMs;
|
|
131
165
|
}
|
|
166
|
+
if (options?.abortedDuringPacing !== void 0) {
|
|
167
|
+
this.abortedDuringPacing = options.abortedDuringPacing;
|
|
168
|
+
}
|
|
132
169
|
}
|
|
133
170
|
}
|
|
134
171
|
|
|
135
172
|
const DEFAULT_REQUEST_TIMEOUT = 3e4;
|
|
136
|
-
const MAX_RETRY_AFTER_MS = 6e4;
|
|
137
173
|
const MAX_FREE_SOCKETS = 5;
|
|
138
174
|
const MAX_SOCKETS = 20;
|
|
175
|
+
|
|
176
|
+
const defaultHttpAgent = new http.Agent({
|
|
177
|
+
keepAlive: true,
|
|
178
|
+
maxSockets: MAX_SOCKETS,
|
|
179
|
+
maxFreeSockets: MAX_FREE_SOCKETS,
|
|
180
|
+
scheduling: "lifo"
|
|
181
|
+
});
|
|
182
|
+
const defaultHttpsAgent = new https.Agent({
|
|
183
|
+
keepAlive: true,
|
|
184
|
+
maxSockets: MAX_SOCKETS,
|
|
185
|
+
maxFreeSockets: MAX_FREE_SOCKETS,
|
|
186
|
+
scheduling: "lifo"
|
|
187
|
+
});
|
|
188
|
+
const axiosClient = axios.create({
|
|
189
|
+
timeout: DEFAULT_REQUEST_TIMEOUT,
|
|
190
|
+
httpAgent: defaultHttpAgent,
|
|
191
|
+
httpsAgent: defaultHttpsAgent
|
|
192
|
+
});
|
|
193
|
+
const MAX_CACHED_AGENT_PAIRS = 8;
|
|
194
|
+
const cachedAgentPairs = /* @__PURE__ */ new Map();
|
|
195
|
+
const parkedEvictedPairs = [];
|
|
196
|
+
const buildAgentCacheKey = (maxSockets, maxFreeSockets) => `${maxSockets}:${maxFreeSockets}`;
|
|
197
|
+
const evictLruAgentPair = () => {
|
|
198
|
+
const oldestKey = cachedAgentPairs.keys().next().value;
|
|
199
|
+
if (oldestKey !== void 0) {
|
|
200
|
+
const evicted = cachedAgentPairs.get(oldestKey);
|
|
201
|
+
cachedAgentPairs.delete(oldestKey);
|
|
202
|
+
if (evicted !== void 0) {
|
|
203
|
+
parkedEvictedPairs.push(evicted);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const destroyCachedAgents = () => {
|
|
208
|
+
for (const pair of cachedAgentPairs.values()) {
|
|
209
|
+
pair.httpAgent.destroy();
|
|
210
|
+
pair.httpsAgent.destroy();
|
|
211
|
+
}
|
|
212
|
+
cachedAgentPairs.clear();
|
|
213
|
+
for (const pair of parkedEvictedPairs) {
|
|
214
|
+
pair.httpAgent.destroy();
|
|
215
|
+
pair.httpsAgent.destroy();
|
|
216
|
+
}
|
|
217
|
+
parkedEvictedPairs.length = 0;
|
|
218
|
+
};
|
|
219
|
+
const resolveAgents = (maxSockets, maxFreeSockets) => {
|
|
220
|
+
if (maxSockets === void 0 && maxFreeSockets === void 0) {
|
|
221
|
+
return { httpAgent: defaultHttpAgent, httpsAgent: defaultHttpsAgent };
|
|
222
|
+
}
|
|
223
|
+
const normalizeSockets = (value, fallback, min) => {
|
|
224
|
+
if (value === void 0 || !Number.isFinite(value) || !Number.isInteger(value)) {
|
|
225
|
+
return Math.max(min, fallback);
|
|
226
|
+
}
|
|
227
|
+
return Math.max(min, value);
|
|
228
|
+
};
|
|
229
|
+
const sockets = normalizeSockets(maxSockets, MAX_SOCKETS, 1);
|
|
230
|
+
const freeSockets = normalizeSockets(maxFreeSockets, MAX_FREE_SOCKETS, 0);
|
|
231
|
+
const key = buildAgentCacheKey(sockets, freeSockets);
|
|
232
|
+
const cached = cachedAgentPairs.get(key);
|
|
233
|
+
if (cached !== void 0) {
|
|
234
|
+
cachedAgentPairs.delete(key);
|
|
235
|
+
cachedAgentPairs.set(key, cached);
|
|
236
|
+
return { httpAgent: cached.httpAgent, httpsAgent: cached.httpsAgent };
|
|
237
|
+
}
|
|
238
|
+
if (cachedAgentPairs.size >= MAX_CACHED_AGENT_PAIRS) {
|
|
239
|
+
evictLruAgentPair();
|
|
240
|
+
}
|
|
241
|
+
const agentOptions = {
|
|
242
|
+
keepAlive: true,
|
|
243
|
+
maxSockets: sockets,
|
|
244
|
+
maxFreeSockets: freeSockets,
|
|
245
|
+
scheduling: "lifo"
|
|
246
|
+
};
|
|
247
|
+
const pair = {
|
|
248
|
+
httpAgent: new http.Agent(agentOptions),
|
|
249
|
+
httpsAgent: new https.Agent(agentOptions)
|
|
250
|
+
};
|
|
251
|
+
cachedAgentPairs.set(key, pair);
|
|
252
|
+
return { httpAgent: pair.httpAgent, httpsAgent: pair.httpsAgent };
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const MAX_RETRY_AFTER_MS = 6e4;
|
|
139
256
|
const DEFAULT_RETRY_POLICY = {
|
|
140
257
|
maxRetries: 3,
|
|
141
258
|
baseDelayMs: 250,
|
|
@@ -144,21 +261,6 @@ const DEFAULT_RETRY_POLICY = {
|
|
|
144
261
|
retryOnNetworkError: true,
|
|
145
262
|
jitter: true
|
|
146
263
|
};
|
|
147
|
-
const axiosClient = axios.create({
|
|
148
|
-
timeout: DEFAULT_REQUEST_TIMEOUT,
|
|
149
|
-
httpAgent: new http.Agent({
|
|
150
|
-
keepAlive: true,
|
|
151
|
-
maxSockets: MAX_SOCKETS,
|
|
152
|
-
maxFreeSockets: MAX_FREE_SOCKETS,
|
|
153
|
-
scheduling: "lifo"
|
|
154
|
-
}),
|
|
155
|
-
httpsAgent: new https.Agent({
|
|
156
|
-
keepAlive: true,
|
|
157
|
-
maxSockets: MAX_SOCKETS,
|
|
158
|
-
maxFreeSockets: MAX_FREE_SOCKETS,
|
|
159
|
-
scheduling: "lifo"
|
|
160
|
-
})
|
|
161
|
-
});
|
|
162
264
|
const resolveRetryPolicy = (retry) => {
|
|
163
265
|
if (retry === false) {
|
|
164
266
|
return null;
|
|
@@ -166,47 +268,190 @@ const resolveRetryPolicy = (retry) => {
|
|
|
166
268
|
if (retry === void 0 || retry === true) {
|
|
167
269
|
return { ...DEFAULT_RETRY_POLICY };
|
|
168
270
|
}
|
|
169
|
-
|
|
271
|
+
const filtered = {};
|
|
272
|
+
for (const [key, value] of Object.entries(retry)) {
|
|
273
|
+
if (value !== void 0) {
|
|
274
|
+
filtered[key] = value;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return { ...DEFAULT_RETRY_POLICY, ...filtered };
|
|
278
|
+
};
|
|
279
|
+
const parseRetryAfter = (header, now) => {
|
|
280
|
+
if (header === void 0 || header === null || header === "") {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
const seconds = Number(header);
|
|
284
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
285
|
+
return Math.min(seconds * 1e3, MAX_RETRY_AFTER_MS);
|
|
286
|
+
}
|
|
287
|
+
const date = Date.parse(header);
|
|
288
|
+
if (Number.isFinite(date)) {
|
|
289
|
+
return Math.max(0, Math.min(date - now, MAX_RETRY_AFTER_MS));
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
170
292
|
};
|
|
293
|
+
const getRetryAfterDelay = (error) => {
|
|
294
|
+
if (axios.isAxiosError(error)) {
|
|
295
|
+
const header = error.response?.headers?.["retry-after"];
|
|
296
|
+
if (typeof header === "string" || header === void 0) {
|
|
297
|
+
return parseRetryAfter(header, Date.now());
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
};
|
|
302
|
+
const getBackoffDelay = (attempt, policy) => Math.min(policy.baseDelayMs * 2 ** attempt, policy.maxDelayMs);
|
|
303
|
+
const applyJitter = (cap, policy) => policy.jitter === false ? cap : randomInt(0, cap + 1);
|
|
304
|
+
const getRetryDelay = (error, rawError, attempt, policy) => {
|
|
305
|
+
if (attempt >= policy.maxRetries) {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
if (error instanceof AniLinkGraphQLError) {
|
|
309
|
+
if (error.status === 429) {
|
|
310
|
+
return getRetryAfterDelay(rawError) ?? applyJitter(getBackoffDelay(attempt, policy), policy);
|
|
311
|
+
}
|
|
312
|
+
if (policy.retryOnStatus.includes(error.status)) {
|
|
313
|
+
return applyJitter(getBackoffDelay(attempt, policy), policy);
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
if (error instanceof AniLinkApiError) {
|
|
318
|
+
if (error.status === 429) {
|
|
319
|
+
return getRetryAfterDelay(rawError) ?? applyJitter(getBackoffDelay(attempt, policy), policy);
|
|
320
|
+
}
|
|
321
|
+
if (policy.retryOnStatus.includes(error.status)) {
|
|
322
|
+
return applyJitter(getBackoffDelay(attempt, policy), policy);
|
|
323
|
+
}
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
if (error instanceof AniLinkNetworkError) {
|
|
327
|
+
if (error.code === AniLinkErrorCodes.ABORTED) {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
if (policy.retryOnNetworkError) {
|
|
331
|
+
return applyJitter(getBackoffDelay(attempt, policy), policy);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return null;
|
|
335
|
+
};
|
|
336
|
+
const computeNextRetryDelay = (input) => {
|
|
337
|
+
const { normalized, rawError, attempt, policy, budgetState, budget, wasProbe } = input;
|
|
338
|
+
if (wasProbe || policy === null) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
if (budgetState !== void 0 && budget !== void 0 && budgetState.retriesUsed >= budget.maxRetriesPerWindow) {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
return getRetryDelay(normalized, rawError, attempt, policy);
|
|
345
|
+
};
|
|
346
|
+
const retryBudgetStates = /* @__PURE__ */ new WeakMap();
|
|
347
|
+
const getRetryBudgetState = (owner, budget) => {
|
|
348
|
+
if (owner === void 0 || budget === void 0) {
|
|
349
|
+
return void 0;
|
|
350
|
+
}
|
|
351
|
+
let state = retryBudgetStates.get(owner);
|
|
352
|
+
if (state === void 0) {
|
|
353
|
+
state = { retriesUsed: 0, windowEndsAt: 0 };
|
|
354
|
+
retryBudgetStates.set(owner, state);
|
|
355
|
+
}
|
|
356
|
+
if (Date.now() >= state.windowEndsAt) {
|
|
357
|
+
state.retriesUsed = 0;
|
|
358
|
+
state.windowEndsAt = Date.now() + budget.windowMs;
|
|
359
|
+
}
|
|
360
|
+
return state;
|
|
361
|
+
};
|
|
362
|
+
|
|
171
363
|
const resolveRequestOptions = (options = {}) => {
|
|
172
364
|
const timeout = options.timeout ?? DEFAULT_REQUEST_TIMEOUT;
|
|
173
365
|
if (!Number.isFinite(timeout) || timeout < 0) {
|
|
174
366
|
throw new TypeError("timeout must be a finite number greater than or equal to 0");
|
|
175
367
|
}
|
|
368
|
+
const agents = resolveAgents(options.maxSockets, options.maxFreeSockets);
|
|
176
369
|
return {
|
|
177
370
|
timeout,
|
|
178
371
|
signal: options.signal,
|
|
179
372
|
exposeRawAxiosError: options.exposeRawAxiosError ?? false,
|
|
180
373
|
retry: resolveRetryPolicy(options.retry),
|
|
181
|
-
paceWithRateLimit: options.paceWithRateLimit ??
|
|
374
|
+
paceWithRateLimit: options.paceWithRateLimit ?? true,
|
|
182
375
|
rateLimitFloor: Math.max(1, options.rateLimitFloor ?? 1),
|
|
183
376
|
circuitBreaker: options.circuitBreaker,
|
|
377
|
+
retryBudget: options.retryBudget,
|
|
378
|
+
httpAgent: agents.httpAgent,
|
|
379
|
+
httpsAgent: agents.httpsAgent,
|
|
184
380
|
onError: options.onError,
|
|
185
381
|
onRetry: options.onRetry,
|
|
186
382
|
onRequestStart: options.onRequestStart,
|
|
187
|
-
onResponse: options.onResponse
|
|
383
|
+
onResponse: options.onResponse,
|
|
384
|
+
onPace: options.onPace,
|
|
385
|
+
onHookError: options.onHookError,
|
|
386
|
+
onCircuitOpen: options.onCircuitOpen,
|
|
387
|
+
onCircuitClose: options.onCircuitClose,
|
|
388
|
+
ignorePaceDeadline: options.ignorePaceDeadline ?? false,
|
|
389
|
+
responseCache: options.responseCache
|
|
188
390
|
};
|
|
189
391
|
};
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
392
|
+
|
|
393
|
+
const SENSITIVE_HEADER_KEYS = /^(authorization|cookie|set-cookie|proxy-authorization|proxy-auth|authentication|(x-)?api[-_]?key|(x-)?auth[-_]?token|(x-)?session(-id)?|session)$/i;
|
|
394
|
+
const redactAxiosError = (error) => {
|
|
395
|
+
const config = error.config;
|
|
396
|
+
const response = error.response;
|
|
397
|
+
const isHeaderMap = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
398
|
+
const configNeedsRedaction = config !== void 0 && (isHeaderMap(config.headers) || config.data !== void 0 || config.auth !== void 0);
|
|
399
|
+
const responseNeedsRedaction = response !== void 0 && (response.config !== void 0 || isHeaderMap(response.headers) || response.request !== void 0);
|
|
400
|
+
const requestNeedsRedaction = error.request !== void 0;
|
|
401
|
+
if (!configNeedsRedaction && !responseNeedsRedaction && !requestNeedsRedaction) {
|
|
402
|
+
return error;
|
|
195
403
|
}
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
404
|
+
const redactHeaders = (headers) => {
|
|
405
|
+
const scrubbed = {};
|
|
406
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
407
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
408
|
+
scrubbed[key] = redactHeaders(value);
|
|
409
|
+
} else {
|
|
410
|
+
scrubbed[key] = SENSITIVE_HEADER_KEYS.test(key) ? "[REDACTED]" : value;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return scrubbed;
|
|
414
|
+
};
|
|
415
|
+
const redactConfig = (source) => {
|
|
416
|
+
const cloned2 = { ...source };
|
|
417
|
+
const headers = source.headers;
|
|
418
|
+
if (isHeaderMap(headers)) {
|
|
419
|
+
cloned2.headers = redactHeaders(headers);
|
|
420
|
+
}
|
|
421
|
+
if (source.data !== void 0) {
|
|
422
|
+
cloned2.data = "[REDACTED]";
|
|
423
|
+
}
|
|
424
|
+
if (source.auth !== void 0) {
|
|
425
|
+
cloned2.auth = "[REDACTED]";
|
|
426
|
+
}
|
|
427
|
+
return cloned2;
|
|
428
|
+
};
|
|
429
|
+
const cloned = { ...error };
|
|
430
|
+
const clonedRecord = cloned;
|
|
431
|
+
if (requestNeedsRedaction) {
|
|
432
|
+
clonedRecord.request = "[REDACTED]";
|
|
199
433
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
434
|
+
if (config !== void 0) {
|
|
435
|
+
clonedRecord.config = redactConfig(config);
|
|
436
|
+
}
|
|
437
|
+
if (response !== void 0) {
|
|
438
|
+
const clonedResponse = { ...response };
|
|
439
|
+
const responseConfig = response.config;
|
|
440
|
+
if (responseConfig !== void 0) {
|
|
441
|
+
clonedResponse.config = redactConfig(responseConfig);
|
|
442
|
+
}
|
|
443
|
+
const responseHeaders = response.headers;
|
|
444
|
+
if (isHeaderMap(responseHeaders)) {
|
|
445
|
+
clonedResponse.headers = redactHeaders(responseHeaders);
|
|
446
|
+
}
|
|
447
|
+
if (response.request !== void 0) {
|
|
448
|
+
clonedResponse.request = "[REDACTED]";
|
|
449
|
+
}
|
|
450
|
+
clonedRecord.response = clonedResponse;
|
|
206
451
|
}
|
|
207
|
-
return
|
|
452
|
+
return cloned;
|
|
208
453
|
};
|
|
209
|
-
const getRawAxiosError = (resolved, error) => resolved.exposeRawAxiosError ? error : void 0;
|
|
454
|
+
const getRawAxiosError = (resolved, error) => resolved.exposeRawAxiosError ? axios.isAxiosError(error) ? redactAxiosError(error) : error : void 0;
|
|
210
455
|
const getRateLimitInfo = (headers) => {
|
|
211
456
|
if (!headers) {
|
|
212
457
|
return void 0;
|
|
@@ -219,133 +464,163 @@ const getRateLimitInfo = (headers) => {
|
|
|
219
464
|
}
|
|
220
465
|
return { limit, remaining, reset };
|
|
221
466
|
};
|
|
222
|
-
const
|
|
467
|
+
const getResponseContentType = (headers) => {
|
|
468
|
+
if (!headers) return void 0;
|
|
469
|
+
const raw = headers["content-type"] ?? headers["Content-Type"];
|
|
470
|
+
return typeof raw === "string" ? raw : void 0;
|
|
471
|
+
};
|
|
472
|
+
const normalizeAxiosError = (resolved, error, isRestCall = false, requestId) => {
|
|
223
473
|
if (axios.isCancel(error)) {
|
|
224
474
|
return new AniLinkNetworkError(
|
|
225
475
|
AniLinkErrorCodes.ABORTED,
|
|
226
476
|
"The request was cancelled.",
|
|
227
|
-
getRawAxiosError(resolved, error)
|
|
477
|
+
getRawAxiosError(resolved, error),
|
|
478
|
+
{ requestId }
|
|
228
479
|
);
|
|
229
480
|
}
|
|
230
481
|
if (error.response?.status !== void 0) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
482
|
+
const status = error.response.status;
|
|
483
|
+
const data = error.response.data;
|
|
484
|
+
const rawAxiosError = getRawAxiosError(resolved, error);
|
|
485
|
+
const options = {
|
|
486
|
+
rateLimit: getRateLimitInfo(error.response.headers),
|
|
487
|
+
contentType: getResponseContentType(error.response.headers),
|
|
488
|
+
requestId
|
|
489
|
+
};
|
|
490
|
+
return isRestCall ? new AniLinkRestError(status, data, rawAxiosError, options) : new AniLinkApiError(status, data, rawAxiosError, options);
|
|
237
491
|
}
|
|
238
492
|
if (error.code === "ECONNABORTED" || error.code === "ETIMEDOUT") {
|
|
239
493
|
return new AniLinkNetworkError(
|
|
240
494
|
AniLinkErrorCodes.TIMEOUT,
|
|
241
495
|
"The request timed out.",
|
|
242
496
|
getRawAxiosError(resolved, error),
|
|
243
|
-
resolved.timeout > 0 ?
|
|
497
|
+
{ timeoutMs: resolved.timeout > 0 ? resolved.timeout : void 0, requestId }
|
|
244
498
|
);
|
|
245
499
|
}
|
|
246
500
|
return new AniLinkNetworkError(
|
|
247
501
|
AniLinkErrorCodes.NETWORK,
|
|
248
502
|
"The request failed due to a network error.",
|
|
249
|
-
getRawAxiosError(resolved, error)
|
|
503
|
+
getRawAxiosError(resolved, error),
|
|
504
|
+
{ requestId }
|
|
250
505
|
);
|
|
251
506
|
};
|
|
252
|
-
const
|
|
507
|
+
const stampRequestId = (error, requestId) => {
|
|
508
|
+
if (requestId === void 0 || error.requestId !== void 0) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
Object.defineProperty(error, "requestId", {
|
|
512
|
+
value: requestId,
|
|
513
|
+
writable: false,
|
|
514
|
+
enumerable: true,
|
|
515
|
+
configurable: false
|
|
516
|
+
});
|
|
517
|
+
};
|
|
518
|
+
const normalizeRequestError = (resolved, error, isRestCall = false, requestId) => {
|
|
253
519
|
if (error instanceof AniLinkError) {
|
|
520
|
+
stampRequestId(error, requestId);
|
|
254
521
|
return error;
|
|
255
522
|
}
|
|
256
523
|
if (axios.isAxiosError(error)) {
|
|
257
|
-
return normalizeAxiosError(resolved, error);
|
|
524
|
+
return normalizeAxiosError(resolved, error, isRestCall, requestId);
|
|
258
525
|
}
|
|
259
526
|
return new AniLinkError(
|
|
260
527
|
"The request failed.",
|
|
261
528
|
AniLinkErrorCodes.UNKNOWN,
|
|
262
|
-
getRawAxiosError(resolved, error)
|
|
529
|
+
getRawAxiosError(resolved, error),
|
|
530
|
+
{ requestId }
|
|
263
531
|
);
|
|
264
532
|
};
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
return Math.min(seconds * 1e3, MAX_RETRY_AFTER_MS);
|
|
272
|
-
}
|
|
273
|
-
const date = Date.parse(header);
|
|
274
|
-
if (Number.isFinite(date)) {
|
|
275
|
-
return Math.max(0, Math.min(date - now, MAX_RETRY_AFTER_MS));
|
|
533
|
+
|
|
534
|
+
const unwrapSingleRootField = (response) => {
|
|
535
|
+
const envelope = response;
|
|
536
|
+
const queryData = envelope?.data;
|
|
537
|
+
if (!queryData || typeof queryData !== "object") {
|
|
538
|
+
return void 0;
|
|
276
539
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
if (axios.isAxiosError(error)) {
|
|
281
|
-
const header = error.response?.headers?.["retry-after"];
|
|
282
|
-
if (typeof header === "string" || header === void 0) {
|
|
283
|
-
return parseRetryAfter(header, Date.now());
|
|
284
|
-
}
|
|
540
|
+
const fields = Object.keys(queryData);
|
|
541
|
+
if (fields.length === 1) {
|
|
542
|
+
return queryData[fields[0]];
|
|
285
543
|
}
|
|
286
|
-
return
|
|
544
|
+
return void 0;
|
|
287
545
|
};
|
|
288
|
-
const
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (error.status === 429) {
|
|
296
|
-
return getRetryAfterDelay(rawError) ?? applyJitter(getBackoffDelay(attempt, policy), policy);
|
|
297
|
-
}
|
|
298
|
-
if (policy.retryOnStatus.includes(error.status)) {
|
|
299
|
-
return applyJitter(getBackoffDelay(attempt, policy), policy);
|
|
300
|
-
}
|
|
301
|
-
return null;
|
|
302
|
-
}
|
|
303
|
-
if (error instanceof AniLinkNetworkError) {
|
|
304
|
-
if (error.code === AniLinkErrorCodes.ABORTED) {
|
|
305
|
-
return null;
|
|
306
|
-
}
|
|
307
|
-
if (policy.retryOnNetworkError) {
|
|
308
|
-
return applyJitter(getBackoffDelay(attempt, policy), policy);
|
|
309
|
-
}
|
|
546
|
+
const unwrapGraphQLResponse = (response, headers) => {
|
|
547
|
+
const envelope = response;
|
|
548
|
+
if (Array.isArray(envelope?.errors) && envelope.errors.length > 0) {
|
|
549
|
+
throw new AniLinkGraphQLError(envelope.errors, envelope?.data, void 0, {
|
|
550
|
+
rateLimit: getRateLimitInfo(headers),
|
|
551
|
+
contentType: getResponseContentType(headers ?? {})
|
|
552
|
+
});
|
|
310
553
|
}
|
|
311
|
-
|
|
554
|
+
const unwrapped = unwrapSingleRootField(response);
|
|
555
|
+
return unwrapped === void 0 ? response : unwrapped;
|
|
312
556
|
};
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
signal?.removeEventListener("abort", abort);
|
|
316
|
-
resolve();
|
|
317
|
-
}, ms);
|
|
318
|
-
const abort = () => {
|
|
319
|
-
clearTimeout(timeout);
|
|
320
|
-
reject(
|
|
321
|
-
new AniLinkNetworkError(AniLinkErrorCodes.ABORTED, "The request was cancelled.")
|
|
322
|
-
);
|
|
323
|
-
};
|
|
324
|
-
if (signal?.aborted) {
|
|
325
|
-
abort();
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
329
|
-
});
|
|
330
|
-
const safeInvoke = (hook, name, ...args) => {
|
|
557
|
+
|
|
558
|
+
const safeInvoke = (hook, name, onHookError, ...args) => {
|
|
331
559
|
if (hook === void 0) {
|
|
332
560
|
return;
|
|
333
561
|
}
|
|
334
562
|
try {
|
|
335
563
|
hook(...args);
|
|
336
564
|
} catch (hookError) {
|
|
565
|
+
if (onHookError !== void 0) {
|
|
566
|
+
try {
|
|
567
|
+
onHookError(name, hookError);
|
|
568
|
+
} catch {
|
|
569
|
+
}
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
const firstArg = args[0];
|
|
573
|
+
const requestId = firstArg !== null && typeof firstArg === "object" && "requestId" in firstArg && typeof firstArg.requestId === "string" ? firstArg.requestId : void 0;
|
|
574
|
+
const correlation = requestId === void 0 ? "" : ` (requestId: ${requestId})`;
|
|
337
575
|
console.warn(
|
|
338
|
-
`[AniLink] ${name} hook threw and was ignored:`,
|
|
576
|
+
`[AniLink] ${name} hook threw and was ignored${correlation}:`,
|
|
339
577
|
hookError instanceof Error ? hookError.message : hookError
|
|
340
578
|
);
|
|
341
579
|
}
|
|
342
580
|
};
|
|
581
|
+
const buildErrorContext = (requestId, url, method, attempt, normalized, nextDelayMs) => ({
|
|
582
|
+
requestId,
|
|
583
|
+
url,
|
|
584
|
+
method,
|
|
585
|
+
attempt,
|
|
586
|
+
code: normalized.code,
|
|
587
|
+
...normalized instanceof AniLinkApiError ? { status: normalized.status } : {},
|
|
588
|
+
...normalized instanceof AniLinkApiError && normalized.rateLimit !== void 0 ? { rateLimit: normalized.rateLimit } : {},
|
|
589
|
+
...nextDelayMs === void 0 ? {} : { nextDelayMs }
|
|
590
|
+
});
|
|
591
|
+
const reportFailure = (requestId, url, method, attempt, normalized, resolved, nextDelayMs) => {
|
|
592
|
+
const context = buildErrorContext(requestId, url, method, attempt, normalized, nextDelayMs);
|
|
593
|
+
if (nextDelayMs !== void 0) {
|
|
594
|
+
safeInvoke(
|
|
595
|
+
resolved.onRetry ?? resolved.onError,
|
|
596
|
+
resolved.onRetry === void 0 ? "onError" : "onRetry",
|
|
597
|
+
resolved.onHookError,
|
|
598
|
+
normalized,
|
|
599
|
+
context
|
|
600
|
+
);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
safeInvoke(resolved.onError, "onError", resolved.onHookError, normalized, context);
|
|
604
|
+
};
|
|
605
|
+
|
|
343
606
|
const circuitStates = /* @__PURE__ */ new WeakMap();
|
|
344
|
-
const
|
|
607
|
+
const isAvailabilityFailure = (error) => {
|
|
608
|
+
if (error instanceof AniLinkNetworkError) {
|
|
609
|
+
return error.code !== AniLinkErrorCodes.ABORTED;
|
|
610
|
+
}
|
|
611
|
+
if (error instanceof AniLinkApiError) {
|
|
612
|
+
return error.status === 429 || error.status >= 500;
|
|
613
|
+
}
|
|
614
|
+
return false;
|
|
615
|
+
};
|
|
616
|
+
const MAX_CIRCUIT_SCOPES_PER_OWNER = 64;
|
|
617
|
+
const circuitScopeOf = (url, requestId) => {
|
|
345
618
|
try {
|
|
346
619
|
return new URL(url).host;
|
|
347
620
|
} catch {
|
|
348
|
-
|
|
621
|
+
const error = new AniLinkValidationError(["Unparseable request URL"]);
|
|
622
|
+
stampRequestId(error, requestId);
|
|
623
|
+
throw error;
|
|
349
624
|
}
|
|
350
625
|
};
|
|
351
626
|
const getCircuitState = (owner, scope) => {
|
|
@@ -355,67 +630,178 @@ const getCircuitState = (owner, scope) => {
|
|
|
355
630
|
circuitStates.set(owner, scopes);
|
|
356
631
|
}
|
|
357
632
|
let state = scopes.get(scope);
|
|
358
|
-
if (state
|
|
359
|
-
|
|
633
|
+
if (state !== void 0) {
|
|
634
|
+
scopes.delete(scope);
|
|
360
635
|
scopes.set(scope, state);
|
|
636
|
+
return state;
|
|
361
637
|
}
|
|
638
|
+
if (scopes.size >= MAX_CIRCUIT_SCOPES_PER_OWNER) {
|
|
639
|
+
const oldestScope = scopes.keys().next().value;
|
|
640
|
+
if (oldestScope !== void 0) {
|
|
641
|
+
scopes.delete(oldestScope);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
state = {
|
|
645
|
+
consecutiveFailures: 0,
|
|
646
|
+
openedAt: null,
|
|
647
|
+
probeInFlight: false
|
|
648
|
+
};
|
|
649
|
+
scopes.set(scope, state);
|
|
362
650
|
return state;
|
|
363
651
|
};
|
|
364
|
-
const
|
|
365
|
-
if (circuit === void 0 || breaker === void 0
|
|
366
|
-
return;
|
|
652
|
+
const checkCircuitOpen = (circuit, breaker) => {
|
|
653
|
+
if (circuit === void 0 || breaker === void 0) {
|
|
654
|
+
return void 0;
|
|
655
|
+
}
|
|
656
|
+
if (circuit.probeInFlight) {
|
|
657
|
+
return new AniLinkNetworkError(
|
|
658
|
+
AniLinkErrorCodes.CIRCUIT,
|
|
659
|
+
`The request failed fast: the circuit breaker is probing the upstream after ${breaker.threshold} consecutive failures. Retrying is possible once the probe settles.`
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
if (circuit.openedAt === null) {
|
|
663
|
+
return void 0;
|
|
367
664
|
}
|
|
368
665
|
if (Date.now() - circuit.openedAt < breaker.cooldownMs) {
|
|
369
|
-
|
|
666
|
+
return new AniLinkNetworkError(
|
|
370
667
|
AniLinkErrorCodes.CIRCUIT,
|
|
371
668
|
`The request failed fast: the circuit breaker is open after ${breaker.threshold} consecutive failures. Retrying is possible after the cooldown elapses.`
|
|
372
669
|
);
|
|
373
670
|
}
|
|
374
671
|
circuit.openedAt = null;
|
|
672
|
+
circuit.probeInFlight = true;
|
|
673
|
+
return void 0;
|
|
375
674
|
};
|
|
376
|
-
const recordCircuitSuccess = (circuit) => {
|
|
377
|
-
if (circuit
|
|
378
|
-
|
|
675
|
+
const recordCircuitSuccess = (circuit, resolved, hookContext, host) => {
|
|
676
|
+
if (circuit === void 0) {
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
const wasOpen = circuit.openedAt !== null || circuit.probeInFlight;
|
|
680
|
+
circuit.probeInFlight = false;
|
|
681
|
+
circuit.consecutiveFailures = 0;
|
|
682
|
+
circuit.openedAt = null;
|
|
683
|
+
if (wasOpen && resolved.onCircuitClose !== void 0) {
|
|
684
|
+
safeInvoke(resolved.onCircuitClose, "onCircuitClose", resolved.onHookError, {
|
|
685
|
+
...hookContext,
|
|
686
|
+
host
|
|
687
|
+
});
|
|
379
688
|
}
|
|
380
689
|
};
|
|
381
|
-
const recordCircuitFailure = (circuit, breaker) => {
|
|
690
|
+
const recordCircuitFailure = (circuit, breaker, normalized, resolved, hookContext, host) => {
|
|
382
691
|
if (circuit === void 0 || breaker === void 0) {
|
|
383
692
|
return;
|
|
384
693
|
}
|
|
694
|
+
if (!isAvailabilityFailure(normalized)) {
|
|
695
|
+
recordCircuitSuccess(circuit, resolved, hookContext, host);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
if (circuit.probeInFlight) {
|
|
699
|
+
circuit.probeInFlight = false;
|
|
700
|
+
circuit.openedAt = Date.now();
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
385
703
|
circuit.consecutiveFailures += 1;
|
|
386
|
-
if (circuit.consecutiveFailures >= breaker.threshold) {
|
|
704
|
+
if (circuit.consecutiveFailures >= breaker.threshold && circuit.openedAt === null) {
|
|
387
705
|
circuit.openedAt = Date.now();
|
|
706
|
+
if (resolved.onCircuitOpen !== void 0) {
|
|
707
|
+
safeInvoke(resolved.onCircuitOpen, "onCircuitOpen", resolved.onHookError, {
|
|
708
|
+
...hookContext,
|
|
709
|
+
host,
|
|
710
|
+
failures: circuit.consecutiveFailures
|
|
711
|
+
});
|
|
712
|
+
}
|
|
388
713
|
}
|
|
389
714
|
};
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
715
|
+
|
|
716
|
+
const sleep = (ms, signal, requestId) => new Promise((resolve, reject) => {
|
|
717
|
+
const timeout = setTimeout(() => {
|
|
718
|
+
signal?.removeEventListener("abort", abort);
|
|
719
|
+
resolve();
|
|
720
|
+
}, ms);
|
|
721
|
+
timeout.unref();
|
|
722
|
+
const abort = () => {
|
|
723
|
+
clearTimeout(timeout);
|
|
724
|
+
const error = new AniLinkNetworkError(
|
|
725
|
+
AniLinkErrorCodes.ABORTED,
|
|
726
|
+
"The request was cancelled."
|
|
727
|
+
);
|
|
728
|
+
stampRequestId(error, requestId);
|
|
729
|
+
reject(error);
|
|
730
|
+
};
|
|
731
|
+
if (signal?.aborted) {
|
|
732
|
+
abort();
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
397
736
|
});
|
|
398
|
-
|
|
399
|
-
|
|
737
|
+
|
|
738
|
+
const paceDeadlines = /* @__PURE__ */ new WeakMap();
|
|
739
|
+
const MAX_PACE_WAIT_MS = 5 * 60 * 1e3;
|
|
740
|
+
const sleepForPacing = async (delayMs, resolved, hookContext) => {
|
|
741
|
+
try {
|
|
742
|
+
await sleep(delayMs, resolved.signal, hookContext.requestId);
|
|
743
|
+
} catch (error) {
|
|
744
|
+
if (error instanceof AniLinkNetworkError && error.code === AniLinkErrorCodes.ABORTED && !axios.isCancel(error)) {
|
|
745
|
+
const pacingError = new AniLinkNetworkError(
|
|
746
|
+
AniLinkErrorCodes.ABORTED,
|
|
747
|
+
"The request was cancelled while waiting for the rate-limit window to reset.",
|
|
748
|
+
void 0,
|
|
749
|
+
{ abortedDuringPacing: true }
|
|
750
|
+
);
|
|
751
|
+
stampRequestId(pacingError, hookContext.requestId);
|
|
752
|
+
throw pacingError;
|
|
753
|
+
}
|
|
754
|
+
throw error;
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
const recordPaceDeadline = (owner, host, deadlineMs) => {
|
|
758
|
+
let scopes = paceDeadlines.get(owner);
|
|
759
|
+
if (scopes === void 0) {
|
|
760
|
+
scopes = /* @__PURE__ */ new Map();
|
|
761
|
+
paceDeadlines.set(owner, scopes);
|
|
762
|
+
}
|
|
763
|
+
if (deadlineMs <= Date.now()) {
|
|
764
|
+
scopes.delete(host);
|
|
400
765
|
return;
|
|
401
766
|
}
|
|
402
|
-
const
|
|
403
|
-
if (
|
|
404
|
-
|
|
767
|
+
const existing = scopes.get(host);
|
|
768
|
+
if (existing !== void 0 && deadlineMs <= existing) {
|
|
769
|
+
return;
|
|
405
770
|
}
|
|
771
|
+
scopes.set(host, deadlineMs);
|
|
406
772
|
};
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
773
|
+
const awaitPaceDeadline = async (owner, host, resolved, hookContext) => {
|
|
774
|
+
if (owner === void 0 || !resolved.paceWithRateLimit || resolved.ignorePaceDeadline) {
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
const deadlineMs = paceDeadlines.get(owner)?.get(host);
|
|
778
|
+
if (deadlineMs === void 0) {
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
const delayMs = deadlineMs - Date.now();
|
|
782
|
+
if (delayMs <= 0) {
|
|
783
|
+
paceDeadlines.get(owner)?.delete(host);
|
|
416
784
|
return;
|
|
417
785
|
}
|
|
418
|
-
safeInvoke(resolved.
|
|
786
|
+
safeInvoke(resolved.onPace, "onPace", resolved.onHookError, { ...hookContext, delayMs });
|
|
787
|
+
await sleepForPacing(delayMs, resolved, hookContext);
|
|
788
|
+
};
|
|
789
|
+
const paceAfterSuccess = async (response, resolved, hookContext, rateLimit, owner, host) => {
|
|
790
|
+
if (!resolved.paceWithRateLimit) {
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
const info = rateLimit ?? getRateLimitInfo(response.headers);
|
|
794
|
+
if (info !== void 0 && info.remaining < resolved.rateLimitFloor) {
|
|
795
|
+
const rawDeadlineMs = info.reset * 1e3;
|
|
796
|
+
const now = Date.now();
|
|
797
|
+
const deadlineMs = Math.min(rawDeadlineMs, now + MAX_PACE_WAIT_MS);
|
|
798
|
+
const delayMs = Math.max(0, deadlineMs - now);
|
|
799
|
+
if (owner !== void 0 && host !== void 0) {
|
|
800
|
+
recordPaceDeadline(owner, host, deadlineMs);
|
|
801
|
+
}
|
|
802
|
+
safeInvoke(resolved.onPace, "onPace", resolved.onHookError, { ...hookContext, delayMs });
|
|
803
|
+
await sleepForPacing(delayMs, resolved, hookContext);
|
|
804
|
+
}
|
|
419
805
|
};
|
|
420
806
|
const isPacingAbort = (resolved, error) => resolved.paceWithRateLimit && error instanceof AniLinkNetworkError && error.code === AniLinkErrorCodes.ABORTED && !axios.isCancel(error);
|
|
421
807
|
const rethrowIfPacingAbort = (resolved, error) => {
|
|
@@ -423,16 +809,46 @@ const rethrowIfPacingAbort = (resolved, error) => {
|
|
|
423
809
|
throw error;
|
|
424
810
|
}
|
|
425
811
|
};
|
|
426
|
-
|
|
812
|
+
|
|
813
|
+
const executeWithRetry = async (options, resolved, stateOwner, rawPassthrough = false) => {
|
|
427
814
|
const { url, method, data, headers } = options;
|
|
428
815
|
const policy = resolved.retry;
|
|
429
|
-
const
|
|
816
|
+
const requestId = randomUUID();
|
|
817
|
+
const host = circuitScopeOf(url, requestId);
|
|
818
|
+
const circuit = resolved.circuitBreaker !== void 0 && stateOwner !== void 0 ? getCircuitState(stateOwner, host) : void 0;
|
|
819
|
+
const budgetState = getRetryBudgetState(stateOwner, resolved.retryBudget);
|
|
430
820
|
let attempt = 0;
|
|
431
821
|
for (; ; ) {
|
|
432
|
-
throwIfCircuitOpen(circuit, resolved.circuitBreaker);
|
|
433
822
|
const startedAt = Date.now();
|
|
434
|
-
const hookContext = { url, method, attempt: attempt + 1 };
|
|
435
|
-
|
|
823
|
+
const hookContext = { requestId, url, method, attempt: attempt + 1 };
|
|
824
|
+
const circuitError = checkCircuitOpen(circuit, resolved.circuitBreaker);
|
|
825
|
+
if (circuitError !== void 0) {
|
|
826
|
+
safeInvoke(
|
|
827
|
+
resolved.onRequestStart,
|
|
828
|
+
"onRequestStart",
|
|
829
|
+
resolved.onHookError,
|
|
830
|
+
hookContext
|
|
831
|
+
);
|
|
832
|
+
stampRequestId(circuitError, requestId);
|
|
833
|
+
safeInvoke(
|
|
834
|
+
resolved.onError,
|
|
835
|
+
"onError",
|
|
836
|
+
resolved.onHookError,
|
|
837
|
+
circuitError,
|
|
838
|
+
buildErrorContext(requestId, url, method, attempt + 1, circuitError)
|
|
839
|
+
);
|
|
840
|
+
throw circuitError;
|
|
841
|
+
}
|
|
842
|
+
try {
|
|
843
|
+
await awaitPaceDeadline(stateOwner, host, resolved, hookContext);
|
|
844
|
+
} catch (paceError) {
|
|
845
|
+
if (circuit !== void 0 && circuit.probeInFlight) {
|
|
846
|
+
recordCircuitSuccess(circuit, resolved, hookContext, host);
|
|
847
|
+
}
|
|
848
|
+
throw paceError;
|
|
849
|
+
}
|
|
850
|
+
safeInvoke(resolved.onRequestStart, "onRequestStart", resolved.onHookError, hookContext);
|
|
851
|
+
let responseReported = false;
|
|
436
852
|
try {
|
|
437
853
|
const response = await axiosClient({
|
|
438
854
|
url,
|
|
@@ -440,50 +856,162 @@ const executeWithRetry = async (options, resolved, stateKey, rawPassthrough = fa
|
|
|
440
856
|
data,
|
|
441
857
|
headers,
|
|
442
858
|
timeout: resolved.timeout,
|
|
443
|
-
signal: resolved.signal
|
|
859
|
+
signal: resolved.signal,
|
|
860
|
+
httpAgent: resolved.httpAgent,
|
|
861
|
+
httpsAgent: resolved.httpsAgent
|
|
444
862
|
});
|
|
445
|
-
|
|
863
|
+
const rateLimit = getRateLimitInfo(response.headers);
|
|
864
|
+
safeInvoke(resolved.onResponse, "onResponse", resolved.onHookError, {
|
|
446
865
|
...hookContext,
|
|
447
|
-
durationMs: Date.now() - startedAt
|
|
866
|
+
durationMs: Date.now() - startedAt,
|
|
867
|
+
...rateLimit !== void 0 ? { rateLimit } : {}
|
|
448
868
|
});
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
869
|
+
responseReported = true;
|
|
870
|
+
const result = rawPassthrough ? response.data : unwrapGraphQLResponse(
|
|
871
|
+
response.data,
|
|
872
|
+
response.headers
|
|
873
|
+
);
|
|
874
|
+
recordCircuitSuccess(circuit, resolved, hookContext, host);
|
|
875
|
+
await paceAfterSuccess(response, resolved, hookContext, rateLimit, stateOwner, host);
|
|
876
|
+
return result;
|
|
452
877
|
} catch (error) {
|
|
453
878
|
rethrowIfPacingAbort(resolved, error);
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
879
|
+
if (!responseReported) {
|
|
880
|
+
safeInvoke(resolved.onResponse, "onResponse", resolved.onHookError, {
|
|
881
|
+
...hookContext,
|
|
882
|
+
durationMs: Date.now() - startedAt
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
const normalized = normalizeRequestError(resolved, error, rawPassthrough, requestId);
|
|
886
|
+
const wasProbe = circuit?.probeInFlight === true;
|
|
887
|
+
recordCircuitFailure(
|
|
888
|
+
circuit,
|
|
889
|
+
resolved.circuitBreaker,
|
|
890
|
+
normalized,
|
|
891
|
+
resolved,
|
|
892
|
+
hookContext,
|
|
893
|
+
host
|
|
894
|
+
);
|
|
895
|
+
const delay = computeNextRetryDelay({
|
|
896
|
+
normalized,
|
|
897
|
+
rawError: error,
|
|
898
|
+
attempt,
|
|
899
|
+
policy,
|
|
900
|
+
budgetState,
|
|
901
|
+
budget: resolved.retryBudget,
|
|
902
|
+
wasProbe
|
|
457
903
|
});
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
reportFailure(
|
|
904
|
+
if (delay !== null && budgetState !== void 0) {
|
|
905
|
+
budgetState.retriesUsed += 1;
|
|
906
|
+
}
|
|
907
|
+
reportFailure(
|
|
908
|
+
requestId,
|
|
909
|
+
url,
|
|
910
|
+
method,
|
|
911
|
+
attempt + 1,
|
|
912
|
+
normalized,
|
|
913
|
+
resolved,
|
|
914
|
+
delay ?? void 0
|
|
915
|
+
);
|
|
462
916
|
if (delay === null) {
|
|
463
917
|
throw normalized;
|
|
464
918
|
}
|
|
465
919
|
attempt += 1;
|
|
466
|
-
await sleep(delay, resolved.signal);
|
|
920
|
+
await sleep(delay, resolved.signal, requestId);
|
|
467
921
|
}
|
|
468
922
|
}
|
|
469
923
|
};
|
|
470
|
-
const
|
|
471
|
-
|
|
924
|
+
const buildAuthCacheKey = (token) => token === void 0 ? "none" : `bearer:${createHash("sha256").update(token).digest("hex").slice(0, 16)}`;
|
|
925
|
+
const buildCacheAuthKey = (hasBearerToken, hasCredentialHeaders, token) => {
|
|
926
|
+
if (hasCredentialHeaders) {
|
|
927
|
+
return void 0;
|
|
928
|
+
}
|
|
929
|
+
if (hasBearerToken) {
|
|
930
|
+
return buildAuthCacheKey(token);
|
|
931
|
+
}
|
|
932
|
+
return buildAuthCacheKey(void 0);
|
|
933
|
+
};
|
|
934
|
+
let warnedOptionsKeyedState = false;
|
|
935
|
+
const warnOptionsKeyedState = (onHookError) => {
|
|
936
|
+
if (warnedOptionsKeyedState) {
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
warnedOptionsKeyedState = true;
|
|
940
|
+
const message = "[AniLink] circuit-breaker/retry-budget state is keyed by the per-request options object because no stateOwner was passed. Pass a stable stateOwner (or reuse one options object across calls) so failure streaks accumulate.";
|
|
941
|
+
if (onHookError !== void 0) {
|
|
942
|
+
try {
|
|
943
|
+
onHookError("stateOwner", new Error(message));
|
|
944
|
+
} catch {
|
|
945
|
+
}
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
console.warn(message);
|
|
949
|
+
};
|
|
950
|
+
const sendRequest = async (url, method, data, auth, sendOptions) => {
|
|
951
|
+
const {
|
|
952
|
+
requiresAuth = false,
|
|
953
|
+
options,
|
|
954
|
+
operation,
|
|
955
|
+
protocol,
|
|
956
|
+
contentType,
|
|
957
|
+
stateOwner
|
|
958
|
+
} = sendOptions ?? {};
|
|
959
|
+
const isRestCall = protocol === "rest" || protocol === void 0 && contentType !== void 0;
|
|
960
|
+
const resolvedAuth = typeof auth === "string" ? { token: auth } : auth;
|
|
961
|
+
const hasBearerToken = resolvedAuth?.token !== void 0 && resolvedAuth.token !== "";
|
|
962
|
+
const hasAuthorizationHeader = Object.entries(resolvedAuth?.headers ?? {}).some(
|
|
963
|
+
([key, value]) => key.toLowerCase() === "authorization" && value !== ""
|
|
964
|
+
);
|
|
965
|
+
const hasCredentialHeaders = Object.entries(resolvedAuth?.headers ?? {}).some(
|
|
966
|
+
([, value]) => value !== ""
|
|
967
|
+
);
|
|
968
|
+
const hasAuthMaterial = hasBearerToken || hasAuthorizationHeader;
|
|
969
|
+
if (requiresAuth && !hasAuthMaterial) {
|
|
472
970
|
throw new AniLinkAuthError(operation);
|
|
473
971
|
}
|
|
474
972
|
const headers = contentType === void 0 ? {
|
|
475
973
|
"Content-Type": "application/json",
|
|
476
974
|
Accept: "application/json"
|
|
477
975
|
} : { "Content-Type": contentType };
|
|
478
|
-
|
|
479
|
-
|
|
976
|
+
Object.assign(headers, resolvedAuth?.headers);
|
|
977
|
+
if (hasBearerToken && !hasAuthorizationHeader) {
|
|
978
|
+
headers.Authorization = `Bearer ${resolvedAuth.token}`;
|
|
979
|
+
}
|
|
980
|
+
const resolved = resolveRequestOptions(options);
|
|
981
|
+
if (stateOwner === void 0 && options !== void 0 && (resolved.circuitBreaker !== void 0 || resolved.retryBudget !== void 0)) {
|
|
982
|
+
warnOptionsKeyedState(resolved.onHookError);
|
|
983
|
+
}
|
|
984
|
+
const cacheEnabled = resolved.responseCache !== void 0 && method === "GET";
|
|
985
|
+
const cacheAuthKey = cacheEnabled ? buildCacheAuthKey(hasBearerToken, hasCredentialHeaders, resolvedAuth?.token) : void 0;
|
|
986
|
+
const cacheActive = cacheEnabled && cacheAuthKey !== void 0;
|
|
987
|
+
if (cacheActive) {
|
|
988
|
+
const cached = resolved.responseCache.get(method, url, data, cacheAuthKey);
|
|
989
|
+
if (cached !== void 0) {
|
|
990
|
+
const requestId = randomUUID();
|
|
991
|
+
const hookContext = { requestId, url, method, attempt: 1 };
|
|
992
|
+
safeInvoke(
|
|
993
|
+
resolved.onRequestStart,
|
|
994
|
+
"onRequestStart",
|
|
995
|
+
resolved.onHookError,
|
|
996
|
+
hookContext
|
|
997
|
+
);
|
|
998
|
+
safeInvoke(resolved.onResponse, "onResponse", resolved.onHookError, {
|
|
999
|
+
...hookContext,
|
|
1000
|
+
durationMs: 0,
|
|
1001
|
+
cacheHit: true
|
|
1002
|
+
});
|
|
1003
|
+
return cached;
|
|
1004
|
+
}
|
|
480
1005
|
}
|
|
481
1006
|
const result = await executeWithRetry(
|
|
482
1007
|
{ url, method, data, headers },
|
|
483
|
-
|
|
484
|
-
options,
|
|
485
|
-
|
|
1008
|
+
resolved,
|
|
1009
|
+
stateOwner ?? options,
|
|
1010
|
+
isRestCall
|
|
486
1011
|
);
|
|
1012
|
+
if (cacheActive) {
|
|
1013
|
+
resolved.responseCache.set(method, url, data, cacheAuthKey, result);
|
|
1014
|
+
}
|
|
487
1015
|
return result;
|
|
488
1016
|
};
|
|
489
1017
|
|
|
@@ -497,10 +1025,17 @@ const resolveOperationLabel = (operation) => {
|
|
|
497
1025
|
return typeof name === "string" && name.length > 0 ? name : void 0;
|
|
498
1026
|
};
|
|
499
1027
|
class BaseOperation {
|
|
1028
|
+
/**
|
|
1029
|
+
* Stable per-instance object keying cross-request transport state (the
|
|
1030
|
+
* circuit breaker and retry budget). One per instance so failure streaks
|
|
1031
|
+
* accumulate across every request this operation dispatches, regardless
|
|
1032
|
+
* of per-request option objects.
|
|
1033
|
+
*/
|
|
1034
|
+
stateOwner = {};
|
|
500
1035
|
/**
|
|
501
1036
|
* The authentication token shared by all operations of an instance.
|
|
502
1037
|
*/
|
|
503
|
-
|
|
1038
|
+
requestAuth;
|
|
504
1039
|
/**
|
|
505
1040
|
* The transport settings resolved at construction time.
|
|
506
1041
|
*/
|
|
@@ -508,21 +1043,33 @@ class BaseOperation {
|
|
|
508
1043
|
/**
|
|
509
1044
|
* Constructs a new `BaseOperation` instance.
|
|
510
1045
|
*
|
|
511
|
-
* @param authToken - The authentication
|
|
1046
|
+
* @param authToken - The authentication material used for API requests. A string is treated as a bearer token for backwards compatibility.
|
|
512
1047
|
* @param options - Transport settings scoped to this instance (timeout, cancellation, retry policy, lifecycle hooks).
|
|
513
1048
|
*/
|
|
514
1049
|
constructor(authToken, options) {
|
|
515
|
-
this.
|
|
1050
|
+
this.requestAuth = authToken;
|
|
516
1051
|
this.resolvedOptions = options;
|
|
517
1052
|
}
|
|
518
1053
|
/**
|
|
519
1054
|
* The instance authentication token, readable by protocol subclasses.
|
|
1055
|
+
*
|
|
1056
|
+
* @returns The bearer token from {@link RequestAuthInput}, or `undefined`.
|
|
520
1057
|
*/
|
|
521
1058
|
get token() {
|
|
522
|
-
return this.
|
|
1059
|
+
return typeof this.requestAuth === "string" ? this.requestAuth : this.requestAuth?.token;
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* The provider-specific authentication material, readable by protocol subclasses.
|
|
1063
|
+
*
|
|
1064
|
+
* @returns The configured {@link RequestAuthInput}, or `undefined`.
|
|
1065
|
+
*/
|
|
1066
|
+
get auth() {
|
|
1067
|
+
return this.requestAuth;
|
|
523
1068
|
}
|
|
524
1069
|
/**
|
|
525
1070
|
* The instance transport settings, readable by protocol subclasses.
|
|
1071
|
+
*
|
|
1072
|
+
* @returns The configured {@link RequestOptions}, or `undefined`.
|
|
526
1073
|
*/
|
|
527
1074
|
get instanceOptions() {
|
|
528
1075
|
return this.resolvedOptions;
|
|
@@ -538,25 +1085,24 @@ class BaseOperation {
|
|
|
538
1085
|
* @typeParam T - The parsed response type returned verbatim by the pipeline.
|
|
539
1086
|
* @param url - The absolute endpoint URL to call.
|
|
540
1087
|
* @param method - The HTTP method for the call.
|
|
541
|
-
* @param data - The request body payload, when the call carries one.
|
|
542
|
-
* @param
|
|
543
|
-
* @param operation - Human-readable operation name included in missing-token auth errors. Defaults to the concrete subclass name.
|
|
544
|
-
* @param transportOptions - Optional per-request transport settings merged over the instance-level ones. A field set here wins; unset fields keep the instance value.
|
|
545
|
-
* @param contentType - Optional `Content-Type` override. When provided, the response body is returned verbatim instead of being unwrapped as a GraphQL envelope.
|
|
1088
|
+
* @param data - The request body payload, when the call carries one: a JSON-serializable object, or a pre-encoded string body (for example form-urlencoded OAuth grants).
|
|
1089
|
+
* @param options - Named trailing options; see {@link DispatchOptions}.
|
|
546
1090
|
* @returns Whatever the shared pipeline resolves for the call.
|
|
547
|
-
* @throws An
|
|
1091
|
+
* @throws An {@link AniLinkAuthError} when `requiresAuth` is true and no token is set, or a normalized {@link AniLinkError} when the request fails.
|
|
548
1092
|
*/
|
|
549
|
-
async dispatch(url, method, data,
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
this
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
1093
|
+
async dispatch(url, method, data, options = {}) {
|
|
1094
|
+
const { requiresAuth, operation, transportOptions, contentType, protocol } = options;
|
|
1095
|
+
return await sendRequest(url, method, data, this.requestAuth, {
|
|
1096
|
+
requiresAuth: requiresAuth || void 0,
|
|
1097
|
+
options: mergeOptions(this.resolvedOptions, transportOptions),
|
|
1098
|
+
operation: operation ?? resolveOperationLabel(this),
|
|
1099
|
+
contentType,
|
|
1100
|
+
protocol,
|
|
1101
|
+
// The instance keys cross-request transport state (circuit
|
|
1102
|
+
// breaker, retry budget) so failure streaks accumulate across
|
|
1103
|
+
// requests even when each call carries fresh per-request options.
|
|
1104
|
+
stateOwner: this.stateOwner
|
|
1105
|
+
});
|
|
560
1106
|
}
|
|
561
1107
|
}
|
|
562
1108
|
|
|
@@ -565,12 +1111,42 @@ const isPrimitive = (mapping) => typeof mapping === "string" && PRIMITIVES.inclu
|
|
|
565
1111
|
const isArrayType = (mapping) => typeof mapping === "string" && mapping.endsWith("[]");
|
|
566
1112
|
const isAllowlist = (mapping) => Array.isArray(mapping);
|
|
567
1113
|
const isObjectMapping = (mapping) => typeof mapping === "object" && mapping !== null && !Array.isArray(mapping);
|
|
568
|
-
const
|
|
1114
|
+
const SENSITIVE_KEY_PATTERN = /token|secret|password|authorization|cookie|credential|api[-_]?key|pass|session|otp|bearer/i;
|
|
1115
|
+
const isPlainObject$1 = (value) => {
|
|
1116
|
+
const proto = Object.getPrototypeOf(value);
|
|
1117
|
+
return proto === Object.prototype || proto === null;
|
|
1118
|
+
};
|
|
1119
|
+
const redactValue = (key, value) => {
|
|
1120
|
+
if (SENSITIVE_KEY_PATTERN.test(key)) {
|
|
1121
|
+
return "[REDACTED]";
|
|
1122
|
+
}
|
|
1123
|
+
if (Array.isArray(value)) {
|
|
1124
|
+
return value.map((item) => redactValue(key, item));
|
|
1125
|
+
}
|
|
1126
|
+
if (value !== null && typeof value === "object" && !isPlainObject$1(value)) {
|
|
1127
|
+
const entries = Object.entries(value);
|
|
1128
|
+
if (entries.length === 0) {
|
|
1129
|
+
return value;
|
|
1130
|
+
}
|
|
1131
|
+
return Object.assign(
|
|
1132
|
+
Object.create(Object.getPrototypeOf(value)),
|
|
1133
|
+
Object.fromEntries(entries.map(([k, v]) => [k, redactValue(k, v)]))
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
if (value !== null && typeof value === "object") {
|
|
1137
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, redactValue(k, v)]));
|
|
1138
|
+
}
|
|
1139
|
+
return value;
|
|
1140
|
+
};
|
|
1141
|
+
const describeValue = (path, value) => {
|
|
1142
|
+
if (SENSITIVE_KEY_PATTERN.test(path)) {
|
|
1143
|
+
return "[REDACTED]";
|
|
1144
|
+
}
|
|
569
1145
|
if (value === null || typeof value !== "object" && typeof value !== "function") {
|
|
570
1146
|
return String(value);
|
|
571
1147
|
}
|
|
572
1148
|
try {
|
|
573
|
-
return JSON.stringify(value) ?? String(value);
|
|
1149
|
+
return JSON.stringify(redactValue(path, value)) ?? String(value);
|
|
574
1150
|
} catch {
|
|
575
1151
|
return `[${typeof value}]`;
|
|
576
1152
|
}
|
|
@@ -578,14 +1154,18 @@ const describeValue = (value) => {
|
|
|
578
1154
|
const validateValue = (path, value, mapping, errors, rejectUnknownKeys) => {
|
|
579
1155
|
if (isPrimitive(mapping)) {
|
|
580
1156
|
if (typeof value !== mapping) {
|
|
581
|
-
errors.push(
|
|
1157
|
+
errors.push(
|
|
1158
|
+
`Invalid ${path}: ${describeValue(path, value)}. Expected type: ${mapping}`
|
|
1159
|
+
);
|
|
582
1160
|
}
|
|
583
1161
|
return;
|
|
584
1162
|
}
|
|
585
1163
|
if (isArrayType(mapping)) {
|
|
586
1164
|
const elementType = mapping.slice(0, -2);
|
|
587
1165
|
if (!Array.isArray(value) || !value.every((element) => typeof element === elementType)) {
|
|
588
|
-
errors.push(
|
|
1166
|
+
errors.push(
|
|
1167
|
+
`Invalid ${path}: ${describeValue(path, value)}. Expected type: ${mapping}`
|
|
1168
|
+
);
|
|
589
1169
|
}
|
|
590
1170
|
return;
|
|
591
1171
|
}
|
|
@@ -594,13 +1174,13 @@ const validateValue = (path, value, mapping, errors, rejectUnknownKeys) => {
|
|
|
594
1174
|
value.forEach((item, index) => {
|
|
595
1175
|
if (!mapping.includes(item)) {
|
|
596
1176
|
errors.push(
|
|
597
|
-
`Invalid ${path}[${index}]: ${describeValue(item)}. Expected one of: ${mapping.join(", ")}`
|
|
1177
|
+
`Invalid ${path}[${index}]: ${describeValue(`${path}[${index}]`, item)}. Expected one of: ${mapping.join(", ")}`
|
|
598
1178
|
);
|
|
599
1179
|
}
|
|
600
1180
|
});
|
|
601
1181
|
} else if (!mapping.includes(value)) {
|
|
602
1182
|
errors.push(
|
|
603
|
-
`Invalid ${path}: ${describeValue(value)}. Expected one of: ${mapping.join(", ")}`
|
|
1183
|
+
`Invalid ${path}: ${describeValue(path, value)}. Expected one of: ${mapping.join(", ")}`
|
|
604
1184
|
);
|
|
605
1185
|
}
|
|
606
1186
|
return;
|
|
@@ -617,7 +1197,7 @@ const validateValue = (path, value, mapping, errors, rejectUnknownKeys) => {
|
|
|
617
1197
|
};
|
|
618
1198
|
const validateObject = (path, value, mapping, errors, rejectUnknownKeys) => {
|
|
619
1199
|
if (value === null || typeof value !== "object") {
|
|
620
|
-
errors.push(`Invalid ${path}: ${describeValue(value)}. Expected an object.`);
|
|
1200
|
+
errors.push(`Invalid ${path}: ${describeValue(path, value)}. Expected an object.`);
|
|
621
1201
|
return;
|
|
622
1202
|
}
|
|
623
1203
|
for (const [prop, propValue] of Object.entries(value)) {
|
|
@@ -686,22 +1266,18 @@ class GraphQLOperation extends BaseOperation {
|
|
|
686
1266
|
*
|
|
687
1267
|
* @param query - The GraphQL document to execute.
|
|
688
1268
|
* @param variables - The variables for the document. When omitted the request body contains only the query.
|
|
689
|
-
* @param
|
|
690
|
-
* @param operation - Optional human-readable operation name included in missing-token auth errors. Defaults to the concrete operation class name.
|
|
691
|
-
* @param transportOptions - Optional per-request transport settings merged over the instance-level ones. A field set here wins; unset fields keep the instance value.
|
|
1269
|
+
* @param options - Named trailing options; see {@link GraphQLRequestOptions}.
|
|
692
1270
|
* @returns The unwrapped response data. For documents with a single root field this is the bare field value; otherwise it is the full `{ data }` envelope.
|
|
693
|
-
* @throws An
|
|
1271
|
+
* @throws An {@link AniLinkAuthError} when `requiresAuth` is true and no token is set, or a normalized {@link AniLinkError} when the request fails.
|
|
694
1272
|
*/
|
|
695
|
-
async request(query, variables,
|
|
1273
|
+
async request(query, variables, options = {}) {
|
|
1274
|
+
const { requiresAuth, operation, transportOptions } = options;
|
|
696
1275
|
const data = variables === void 0 ? { query } : { query, variables };
|
|
697
|
-
return await this.dispatch(
|
|
698
|
-
this.graphqlUrl,
|
|
699
|
-
"POST",
|
|
700
|
-
data,
|
|
1276
|
+
return await this.dispatch(this.graphqlUrl, "POST", data, {
|
|
701
1277
|
requiresAuth,
|
|
702
1278
|
operation,
|
|
703
1279
|
transportOptions
|
|
704
|
-
);
|
|
1280
|
+
});
|
|
705
1281
|
}
|
|
706
1282
|
/**
|
|
707
1283
|
* Runs the shared validate-then-dispatch pipeline for an operation.
|
|
@@ -717,7 +1293,7 @@ class GraphQLOperation extends BaseOperation {
|
|
|
717
1293
|
* @param options - The declarative validation and auth contract.
|
|
718
1294
|
* @returns The unwrapped response data, as described by {@link GraphQLOperation.request}.
|
|
719
1295
|
* @throws An {@link AniLinkValidationError} when a requirement or type check
|
|
720
|
-
* fails, or a normalized
|
|
1296
|
+
* fails, or a normalized {@link AniLinkError} when the request fails.
|
|
721
1297
|
*/
|
|
722
1298
|
async execute(query, variables, options) {
|
|
723
1299
|
const { requirements, mappings, requiresAuth, transportOptions } = options;
|
|
@@ -729,7 +1305,7 @@ class GraphQLOperation extends BaseOperation {
|
|
|
729
1305
|
if (mappings && variables !== void 0) {
|
|
730
1306
|
validateVariables(variables, mappings);
|
|
731
1307
|
}
|
|
732
|
-
return await this.request(query, variables, requiresAuth,
|
|
1308
|
+
return await this.request(query, variables, { requiresAuth, transportOptions });
|
|
733
1309
|
}
|
|
734
1310
|
}
|
|
735
1311
|
|
|
@@ -766,8 +1342,8 @@ class CustomRequest extends AniListOperation {
|
|
|
766
1342
|
* @param variables - The variables for the document. This parameter is optional.
|
|
767
1343
|
* @param options - Optional per-request transport settings merged over the instance-level ones for this call only.
|
|
768
1344
|
* @returns A promise that resolves to the unwrapped response data for single-root-field documents, or the full `{ data }` envelope otherwise.
|
|
769
|
-
* @throws An
|
|
770
|
-
* @throws An `AniLinkError` when the request fails.
|
|
1345
|
+
* @throws An {@link AniLinkValidationError} when the query is empty or does not declare a `query` or `mutation` operation.
|
|
1346
|
+
* @throws An `AniLinkError` when the request fails. When AniList returns partial success (some fields resolve while others fail inside an HTTP 200 envelope), the thrown `AniLinkGraphQLError` exposes the resolved portion via its `partialData` field, so the fields that did resolve remain recoverable from the error.
|
|
771
1347
|
* @see https://docs.anilist.co/reference/query
|
|
772
1348
|
* @see https://docs.anilist.co/reference/mutation
|
|
773
1349
|
*/
|
|
@@ -777,7 +1353,7 @@ class CustomRequest extends AniListOperation {
|
|
|
777
1353
|
"custom() requires a GraphQL document declaring a query or mutation operation"
|
|
778
1354
|
]);
|
|
779
1355
|
}
|
|
780
|
-
return await this.request(query, variables,
|
|
1356
|
+
return await this.request(query, variables, { transportOptions: options });
|
|
781
1357
|
}
|
|
782
1358
|
}
|
|
783
1359
|
|
|
@@ -836,34 +1412,70 @@ function resolvePositiveInt(value, fallback) {
|
|
|
836
1412
|
function resolveCappedInt(value, max, fallback) {
|
|
837
1413
|
return Math.min(resolvePositiveInt(value, fallback), max);
|
|
838
1414
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
1415
|
+
function bridgeAbortSignal(external) {
|
|
1416
|
+
const controller = new AbortController();
|
|
1417
|
+
if (external === void 0) {
|
|
1418
|
+
return { signal: controller.signal, dispose: () => controller.abort() };
|
|
1419
|
+
}
|
|
1420
|
+
if (external.aborted) {
|
|
1421
|
+
controller.abort();
|
|
1422
|
+
return { signal: controller.signal, dispose: () => {
|
|
1423
|
+
} };
|
|
1424
|
+
}
|
|
1425
|
+
const onAbort = () => controller.abort();
|
|
1426
|
+
external.addEventListener("abort", onAbort, { once: true });
|
|
1427
|
+
return {
|
|
1428
|
+
signal: controller.signal,
|
|
1429
|
+
dispose: () => {
|
|
1430
|
+
controller.abort();
|
|
1431
|
+
external.removeEventListener("abort", onAbort);
|
|
1432
|
+
}
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
async function fetchWithLookAhead(fetch, extractHasMore, extractNextKeyOrStartNumber, firstKeyOrMaxEntries, maxEntriesOrConcurrency, concurrencyOrSignal, maybeSignal) {
|
|
1436
|
+
if (extractNextKeyOrStartNumber === void 0) {
|
|
1437
|
+
return fetchNumericWithLookAhead(
|
|
1438
|
+
fetch,
|
|
1439
|
+
extractHasMore,
|
|
1440
|
+
firstKeyOrMaxEntries,
|
|
1441
|
+
maxEntriesOrConcurrency,
|
|
1442
|
+
concurrencyOrSignal,
|
|
1443
|
+
maybeSignal
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
if (typeof extractNextKeyOrStartNumber === "function") {
|
|
1447
|
+
return fetchCursorChain(
|
|
1448
|
+
fetch,
|
|
1449
|
+
extractHasMore,
|
|
1450
|
+
extractNextKeyOrStartNumber,
|
|
1451
|
+
firstKeyOrMaxEntries,
|
|
1452
|
+
maxEntriesOrConcurrency);
|
|
853
1453
|
}
|
|
1454
|
+
return fetchNumericWithLookAhead(
|
|
1455
|
+
fetch,
|
|
1456
|
+
extractHasMore,
|
|
1457
|
+
extractNextKeyOrStartNumber,
|
|
1458
|
+
firstKeyOrMaxEntries,
|
|
1459
|
+
maxEntriesOrConcurrency,
|
|
1460
|
+
typeof concurrencyOrSignal === "number" ? void 0 : concurrencyOrSignal
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
async function fetchNumericWithLookAhead(fetch, extractHasMore, startNumber, maxEntries, concurrency, signal) {
|
|
854
1464
|
const responses = [];
|
|
855
1465
|
const pending = [];
|
|
856
1466
|
let launched = 0;
|
|
857
1467
|
let count = 0;
|
|
858
1468
|
let truncated = false;
|
|
859
|
-
let pendingCursorKey = extractNextKey === void 0 ? void 0 : firstKey;
|
|
860
|
-
const effectiveConcurrency = extractNextKey === void 0 ? concurrency : 1;
|
|
861
1469
|
while (count < maxEntries) {
|
|
862
|
-
|
|
1470
|
+
if (signal?.aborted) {
|
|
1471
|
+
await Promise.allSettled(pending.slice(count));
|
|
1472
|
+
responses.length = count;
|
|
1473
|
+
return { responses, count, truncated: false };
|
|
1474
|
+
}
|
|
1475
|
+
while (launched < maxEntries && launched - count < concurrency) {
|
|
863
1476
|
const slot = launched;
|
|
864
|
-
const key = extractNextKey === void 0 ? numericStart + slot : pendingCursorKey;
|
|
865
1477
|
launched += 1;
|
|
866
|
-
const request = fetch(
|
|
1478
|
+
const request = fetch(startNumber + slot).then((response) => {
|
|
867
1479
|
responses[slot] = response;
|
|
868
1480
|
});
|
|
869
1481
|
pending[slot] = request;
|
|
@@ -871,18 +1483,22 @@ async function fetchWithLookAhead(fetch, extractHasMore, ...rest) {
|
|
|
871
1483
|
});
|
|
872
1484
|
}
|
|
873
1485
|
if (count >= launched) break;
|
|
874
|
-
|
|
1486
|
+
try {
|
|
1487
|
+
await pending[count];
|
|
1488
|
+
} catch (err) {
|
|
1489
|
+
if (signal?.aborted) {
|
|
1490
|
+
await Promise.allSettled(pending.slice(count + 1));
|
|
1491
|
+
responses.length = count;
|
|
1492
|
+
return { responses, count, truncated: false };
|
|
1493
|
+
}
|
|
1494
|
+
throw err;
|
|
1495
|
+
}
|
|
875
1496
|
count += 1;
|
|
876
|
-
|
|
877
|
-
const hasMore = extractHasMore(consumed);
|
|
878
|
-
if (!hasMore) {
|
|
1497
|
+
if (!extractHasMore(responses[count - 1])) {
|
|
879
1498
|
await Promise.allSettled(pending.slice(count));
|
|
880
1499
|
responses.length = count;
|
|
881
1500
|
return { responses, count, truncated: false };
|
|
882
1501
|
}
|
|
883
|
-
if (extractNextKey !== void 0) {
|
|
884
|
-
pendingCursorKey = extractNextKey(consumed);
|
|
885
|
-
}
|
|
886
1502
|
if (count >= maxEntries) {
|
|
887
1503
|
await Promise.allSettled(pending.slice(count));
|
|
888
1504
|
truncated = true;
|
|
@@ -891,6 +1507,32 @@ async function fetchWithLookAhead(fetch, extractHasMore, ...rest) {
|
|
|
891
1507
|
}
|
|
892
1508
|
return { responses, count, truncated };
|
|
893
1509
|
}
|
|
1510
|
+
async function fetchCursorChain(fetch, extractHasMore, extractNextKey, firstKey, maxEntries, signal) {
|
|
1511
|
+
const responses = [];
|
|
1512
|
+
let key = firstKey;
|
|
1513
|
+
while (responses.length < maxEntries && key !== void 0) {
|
|
1514
|
+
let entry;
|
|
1515
|
+
try {
|
|
1516
|
+
entry = await fetch(key);
|
|
1517
|
+
} catch (err) {
|
|
1518
|
+
throw err;
|
|
1519
|
+
}
|
|
1520
|
+
responses.push(entry);
|
|
1521
|
+
if (!extractHasMore(entry)) {
|
|
1522
|
+
return { responses, count: responses.length, truncated: false };
|
|
1523
|
+
}
|
|
1524
|
+
key = extractNextKey(entry);
|
|
1525
|
+
}
|
|
1526
|
+
return {
|
|
1527
|
+
responses,
|
|
1528
|
+
count: responses.length,
|
|
1529
|
+
// A degenerate guard (maxEntries <= 0) fetched nothing and cut
|
|
1530
|
+
// nothing short: `truncated` reports whether the guard ended a run
|
|
1531
|
+
// that still had data, matching the numeric driver's `while (count <
|
|
1532
|
+
// maxEntries)` early exit.
|
|
1533
|
+
truncated: responses.length >= maxEntries && maxEntries > 0
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
894
1536
|
|
|
895
1537
|
const DEFAULT_PER_PAGE = 50;
|
|
896
1538
|
const MAX_PER_PAGE = DEFAULT_PER_PAGE;
|
|
@@ -898,6 +1540,10 @@ const DEFAULT_MAX_PAGES = 100;
|
|
|
898
1540
|
const DEFAULT_PER_CHUNK = 500;
|
|
899
1541
|
const MAX_PER_CHUNK = DEFAULT_PER_CHUNK;
|
|
900
1542
|
const DEFAULT_MAX_CHUNKS = 100;
|
|
1543
|
+
const safeCallback = (callback, name, payload) => {
|
|
1544
|
+
safeInvoke(callback, name, void 0, payload);
|
|
1545
|
+
};
|
|
1546
|
+
const DEFAULT_CONCURRENCY = 3;
|
|
901
1547
|
const MAX_CONCURRENCY = 8;
|
|
902
1548
|
function extractHasMore(response) {
|
|
903
1549
|
if (typeof response !== "object" || response === null) return false;
|
|
@@ -914,59 +1560,130 @@ async function paginate(fetchPage, itemsKey, options) {
|
|
|
914
1560
|
const perPage = resolveCappedInt(options?.perPage, MAX_PER_PAGE, DEFAULT_PER_PAGE);
|
|
915
1561
|
const startPage = resolvePositiveInt(options?.startPage, 1);
|
|
916
1562
|
const maxPages = resolvePositiveInt(options?.maxPages, DEFAULT_MAX_PAGES);
|
|
917
|
-
const concurrency = resolveCappedInt(
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
startPage,
|
|
922
|
-
maxPages,
|
|
923
|
-
concurrency
|
|
1563
|
+
const concurrency = resolveCappedInt(
|
|
1564
|
+
options?.concurrency,
|
|
1565
|
+
MAX_CONCURRENCY,
|
|
1566
|
+
DEFAULT_CONCURRENCY
|
|
924
1567
|
);
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1568
|
+
const { signal, dispose } = bridgeAbortSignal(options?.signal);
|
|
1569
|
+
try {
|
|
1570
|
+
const { responses, count, truncated } = await fetchWithLookAhead(
|
|
1571
|
+
(number) => fetchPage(number, perPage, signal),
|
|
1572
|
+
extractHasMore,
|
|
1573
|
+
startPage,
|
|
1574
|
+
maxPages,
|
|
1575
|
+
concurrency,
|
|
1576
|
+
signal
|
|
1577
|
+
);
|
|
1578
|
+
const items = [];
|
|
1579
|
+
const pages = [];
|
|
1580
|
+
for (const response of responses) {
|
|
1581
|
+
const pageItems = response[itemsKey];
|
|
1582
|
+
pages.push({ pageInfo: response.pageInfo, items: pageItems });
|
|
1583
|
+
items.push(...pageItems);
|
|
1584
|
+
safeCallback(options?.onPage, "onPage", {
|
|
1585
|
+
pageInfo: response.pageInfo,
|
|
1586
|
+
items: pageItems
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
return { items, pages, pageCount: count, truncated };
|
|
1590
|
+
} finally {
|
|
1591
|
+
dispose();
|
|
1592
|
+
}
|
|
933
1593
|
}
|
|
934
1594
|
async function* paginatePages(fetchPage, options) {
|
|
935
1595
|
const perPage = resolveCappedInt(options?.perPage, MAX_PER_PAGE, DEFAULT_PER_PAGE);
|
|
936
1596
|
const startPage = resolvePositiveInt(options?.startPage, 1);
|
|
937
1597
|
const maxPages = resolvePositiveInt(options?.maxPages, DEFAULT_MAX_PAGES);
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1598
|
+
const concurrency = resolveCappedInt(
|
|
1599
|
+
options?.concurrency,
|
|
1600
|
+
MAX_CONCURRENCY,
|
|
1601
|
+
DEFAULT_CONCURRENCY
|
|
1602
|
+
);
|
|
1603
|
+
const { signal, dispose } = bridgeAbortSignal(options?.signal);
|
|
1604
|
+
if (signal.aborted) {
|
|
1605
|
+
dispose();
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
const pending = /* @__PURE__ */ new Map();
|
|
1609
|
+
let nextToLaunch = startPage;
|
|
1610
|
+
let nextToYield = startPage;
|
|
1611
|
+
let terminal = false;
|
|
1612
|
+
const launchWindow = () => {
|
|
1613
|
+
while (!terminal && nextToLaunch - startPage < maxPages && pending.size < concurrency) {
|
|
1614
|
+
const page = nextToLaunch;
|
|
1615
|
+
nextToLaunch += 1;
|
|
1616
|
+
const request = fetchPage(page, perPage, signal);
|
|
1617
|
+
pending.set(page, request);
|
|
1618
|
+
void request.catch(() => {
|
|
1619
|
+
});
|
|
946
1620
|
}
|
|
947
|
-
|
|
1621
|
+
};
|
|
1622
|
+
try {
|
|
1623
|
+
while (nextToYield - startPage < maxPages) {
|
|
1624
|
+
launchWindow();
|
|
1625
|
+
const page = nextToYield;
|
|
1626
|
+
const request = pending.get(page);
|
|
1627
|
+
if (request === void 0) {
|
|
1628
|
+
break;
|
|
1629
|
+
}
|
|
1630
|
+
let response;
|
|
1631
|
+
try {
|
|
1632
|
+
response = await request;
|
|
1633
|
+
} catch (err) {
|
|
1634
|
+
if (signal.aborted) {
|
|
1635
|
+
break;
|
|
1636
|
+
}
|
|
1637
|
+
throw err;
|
|
1638
|
+
}
|
|
1639
|
+
pending.delete(page);
|
|
1640
|
+
nextToYield += 1;
|
|
1641
|
+
yield response;
|
|
1642
|
+
if (!response.pageInfo.hasNextPage) {
|
|
1643
|
+
terminal = true;
|
|
1644
|
+
await Promise.allSettled([...pending.values()]);
|
|
1645
|
+
break;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
} finally {
|
|
1649
|
+
dispose();
|
|
1650
|
+
pending.clear();
|
|
948
1651
|
}
|
|
949
1652
|
}
|
|
950
1653
|
async function paginateChunks(fetchChunk, itemsKey, options) {
|
|
951
1654
|
const perChunk = resolveCappedInt(options?.perChunk, MAX_PER_CHUNK, DEFAULT_PER_CHUNK);
|
|
952
1655
|
const startChunk = resolvePositiveInt(options?.startChunk, 1);
|
|
953
1656
|
const maxChunks = resolvePositiveInt(options?.maxChunks, DEFAULT_MAX_CHUNKS);
|
|
954
|
-
const concurrency = resolveCappedInt(
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
startChunk,
|
|
959
|
-
maxChunks,
|
|
960
|
-
concurrency
|
|
1657
|
+
const concurrency = resolveCappedInt(
|
|
1658
|
+
options?.concurrency,
|
|
1659
|
+
MAX_CONCURRENCY,
|
|
1660
|
+
DEFAULT_CONCURRENCY
|
|
961
1661
|
);
|
|
962
|
-
const
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1662
|
+
const { signal, dispose } = bridgeAbortSignal(options?.signal);
|
|
1663
|
+
try {
|
|
1664
|
+
const { responses, count, truncated } = await fetchWithLookAhead(
|
|
1665
|
+
(number) => fetchChunk(number, perChunk, signal),
|
|
1666
|
+
extractHasMore,
|
|
1667
|
+
startChunk,
|
|
1668
|
+
maxChunks,
|
|
1669
|
+
concurrency,
|
|
1670
|
+
signal
|
|
1671
|
+
);
|
|
1672
|
+
const items = [];
|
|
1673
|
+
const chunks = [];
|
|
1674
|
+
for (const response of responses) {
|
|
1675
|
+
const chunkItems = response[itemsKey];
|
|
1676
|
+
chunks.push({ hasNextChunk: response.hasNextChunk, items: chunkItems });
|
|
1677
|
+
items.push(...chunkItems);
|
|
1678
|
+
safeCallback(options?.onChunk, "onChunk", {
|
|
1679
|
+
hasNextChunk: response.hasNextChunk,
|
|
1680
|
+
items: chunkItems
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
return { items, chunks, chunkCount: count, truncated };
|
|
1684
|
+
} finally {
|
|
1685
|
+
dispose();
|
|
1686
|
+
}
|
|
970
1687
|
}
|
|
971
1688
|
|
|
972
1689
|
const MediaSortMappings = [
|
|
@@ -1635,12 +2352,16 @@ const ActivityMappings = {
|
|
|
1635
2352
|
};
|
|
1636
2353
|
class ActivityQuery extends AniListOperation {
|
|
1637
2354
|
/**
|
|
1638
|
-
*
|
|
2355
|
+
* {@link ActivityQuery.activity} sends a query request to get activities.
|
|
1639
2356
|
*
|
|
1640
|
-
* @param variables -
|
|
1641
|
-
* @returns The
|
|
2357
|
+
* @param variables - Values from {@link ActivityVariables} for the query.
|
|
2358
|
+
* @returns The {@link Activity} returned by the query.
|
|
1642
2359
|
* @see https://docs.anilist.co/reference/union/activityunion
|
|
1643
|
-
* @param options - Optional
|
|
2360
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
2361
|
+
* @example
|
|
2362
|
+
* ```typescript
|
|
2363
|
+
* const result = await new ActivityQuery().activity({ userId: 1 });
|
|
2364
|
+
* ```
|
|
1644
2365
|
*/
|
|
1645
2366
|
async activity(variables, options) {
|
|
1646
2367
|
const query = `
|
|
@@ -1671,12 +2392,16 @@ const ActivityReplyMappings = {
|
|
|
1671
2392
|
};
|
|
1672
2393
|
class ActivityReplyQuery extends AniListOperation {
|
|
1673
2394
|
/**
|
|
1674
|
-
*
|
|
2395
|
+
* {@link ActivityReplyQuery.activityReply} sends a query request to get activity replies.
|
|
1675
2396
|
*
|
|
1676
|
-
* @param variables -
|
|
1677
|
-
* @returns The
|
|
2397
|
+
* @param variables - Values from {@link ActivityReplyVariables} for the query.
|
|
2398
|
+
* @returns The {@link ActivityReply} returned by the query.
|
|
1678
2399
|
* @see https://docs.anilist.co/reference/object/activityreply
|
|
1679
|
-
* @param options - Optional
|
|
2400
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
2401
|
+
* @example
|
|
2402
|
+
* ```typescript
|
|
2403
|
+
* const result = await new ActivityReplyQuery().activityReply({ activityId: 1 });
|
|
2404
|
+
* ```
|
|
1680
2405
|
*/
|
|
1681
2406
|
async activityReply(variables, options) {
|
|
1682
2407
|
const query = `
|
|
@@ -1711,10 +2436,14 @@ class ActivityRepliesQuery extends AniListOperation {
|
|
|
1711
2436
|
/**
|
|
1712
2437
|
* `activityReplies` is a method that sends a query request to get activity replies.
|
|
1713
2438
|
*
|
|
1714
|
-
* @param variables -
|
|
1715
|
-
* @returns The
|
|
2439
|
+
* @param variables - Values from {@link ActivityRepliesVariables} for the query.
|
|
2440
|
+
* @returns The {@link ActivityRepliesPageResponse} for the requested page, with pagination metadata.
|
|
1716
2441
|
* @see https://docs.anilist.co/reference/object/activityreply
|
|
1717
|
-
* @param options - Optional
|
|
2442
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
2443
|
+
* @example
|
|
2444
|
+
* ```typescript
|
|
2445
|
+
* const result = await new ActivityRepliesQuery().activityReplies({ page: 1, perPage: 10 });
|
|
2446
|
+
* ```
|
|
1718
2447
|
*/
|
|
1719
2448
|
async activityReplies(variables, options) {
|
|
1720
2449
|
const query = `
|
|
@@ -1775,10 +2504,14 @@ class ActivitiesQuery extends AniListOperation {
|
|
|
1775
2504
|
/**
|
|
1776
2505
|
* `activities` is a method that sends a query request to get activities.
|
|
1777
2506
|
*
|
|
1778
|
-
* @param variables -
|
|
1779
|
-
* @returns The
|
|
2507
|
+
* @param variables - Values from {@link ActivitiesVariables} for the query.
|
|
2508
|
+
* @returns The {@link ActivitiesPageResponse} for the requested page, with pagination metadata.
|
|
1780
2509
|
* @see https://docs.anilist.co/reference/union/activityunion
|
|
1781
|
-
* @param options - Optional
|
|
2510
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
2511
|
+
* @example
|
|
2512
|
+
* ```typescript
|
|
2513
|
+
* const result = await new ActivitiesQuery().activities({ page: 1, perPage: 10 });
|
|
2514
|
+
* ```
|
|
1782
2515
|
*/
|
|
1783
2516
|
async activities(variables, options) {
|
|
1784
2517
|
const query = `
|
|
@@ -2142,12 +2875,16 @@ const AiringScheduleMappings = {
|
|
|
2142
2875
|
};
|
|
2143
2876
|
class AiringScheduleQuery extends AniListOperation {
|
|
2144
2877
|
/**
|
|
2145
|
-
*
|
|
2878
|
+
* {@link AiringScheduleQuery.airingSchedule} sends a query request to get airing schedules.
|
|
2146
2879
|
*
|
|
2147
|
-
* @param variables -
|
|
2148
|
-
* @returns The
|
|
2880
|
+
* @param variables - Values from {@link AiringScheduleVariables} for the query.
|
|
2881
|
+
* @returns The {@link AiringScheduleResponse} returned by the query.
|
|
2149
2882
|
* @see https://docs.anilist.co/reference/object/airingschedule
|
|
2150
|
-
* @param options - Optional
|
|
2883
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
2884
|
+
* @example
|
|
2885
|
+
* ```typescript
|
|
2886
|
+
* const result = await new AiringScheduleQuery().airingSchedule({ mediaId: 1 });
|
|
2887
|
+
* ```
|
|
2151
2888
|
*/
|
|
2152
2889
|
async airingSchedule(variables, options) {
|
|
2153
2890
|
const query = `
|
|
@@ -2199,10 +2936,14 @@ class AiringSchedulesQuery extends AniListOperation {
|
|
|
2199
2936
|
/**
|
|
2200
2937
|
* `airingSchedules` is a method that sends a query request to get airing schedules.
|
|
2201
2938
|
*
|
|
2202
|
-
* @param variables -
|
|
2203
|
-
* @returns The
|
|
2939
|
+
* @param variables - Values from {@link AiringSchedulesVariables} for the query.
|
|
2940
|
+
* @returns The {@link AiringSchedulesPageResponse} returned by the query.
|
|
2204
2941
|
* @see https://docs.anilist.co/reference/object/airingschedule
|
|
2205
|
-
* @param options - Optional
|
|
2942
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
2943
|
+
* @example
|
|
2944
|
+
* ```typescript
|
|
2945
|
+
* const result = await new AiringSchedulesQuery().airingSchedules({ page: 1, perPage: 10 });
|
|
2946
|
+
* ```
|
|
2206
2947
|
*/
|
|
2207
2948
|
async airingSchedules(variables, options) {
|
|
2208
2949
|
const query = `
|
|
@@ -2230,11 +2971,15 @@ class AiringSchedulesQuery extends AniListOperation {
|
|
|
2230
2971
|
|
|
2231
2972
|
class AniChartUserQuery extends AniListOperation {
|
|
2232
2973
|
/**
|
|
2233
|
-
*
|
|
2974
|
+
* {@link AniChartUserQuery.aniChartUser} sends a query request to get AniChart users.
|
|
2234
2975
|
*
|
|
2235
|
-
* @returns The
|
|
2976
|
+
* @returns The {@link AniChartUserResponse} returned by the query.
|
|
2236
2977
|
* @see https://docs.anilist.co/reference/object/anichartuser
|
|
2237
|
-
* @param options - Optional
|
|
2978
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
2979
|
+
* @example
|
|
2980
|
+
* ```typescript
|
|
2981
|
+
* const result = await new AniChartUserQuery("authToken").aniChartUser();
|
|
2982
|
+
* ```
|
|
2238
2983
|
*/
|
|
2239
2984
|
async aniChartUser(options) {
|
|
2240
2985
|
const query = `
|
|
@@ -2295,12 +3040,16 @@ const CharacterMappings = {
|
|
|
2295
3040
|
};
|
|
2296
3041
|
class CharacterQuery extends AniListOperation {
|
|
2297
3042
|
/**
|
|
2298
|
-
*
|
|
3043
|
+
* {@link CharacterQuery.character} sends a query request to get characters.
|
|
2299
3044
|
*
|
|
2300
|
-
* @param variables -
|
|
2301
|
-
* @returns The
|
|
3045
|
+
* @param variables - Values from {@link CharacterVariables} for the query.
|
|
3046
|
+
* @returns The {@link CharacterResponse} returned by the query.
|
|
2302
3047
|
* @see https://docs.anilist.co/reference/object/character
|
|
2303
|
-
* @param options - Optional
|
|
3048
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
3049
|
+
* @example
|
|
3050
|
+
* ```typescript
|
|
3051
|
+
* const result = await new CharacterQuery().character({ id: 1 });
|
|
3052
|
+
* ```
|
|
2304
3053
|
*/
|
|
2305
3054
|
async character(variables, options) {
|
|
2306
3055
|
const query = `
|
|
@@ -2337,10 +3086,14 @@ class CharactersQuery extends AniListOperation {
|
|
|
2337
3086
|
/**
|
|
2338
3087
|
* `characters` is a method that sends a query request to get characters.
|
|
2339
3088
|
*
|
|
2340
|
-
* @param variables -
|
|
2341
|
-
* @returns The
|
|
3089
|
+
* @param variables - Values from {@link CharactersVariables} for the query.
|
|
3090
|
+
* @returns The {@link CharactersPageResponse} returned by the query.
|
|
2342
3091
|
* @see https://docs.anilist.co/reference/object/character
|
|
2343
|
-
* @param options - Optional
|
|
3092
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
3093
|
+
* @example
|
|
3094
|
+
* ```typescript
|
|
3095
|
+
* const result = await new CharactersQuery().characters({ page: 1, perPage: 10 });
|
|
3096
|
+
* ```
|
|
2344
3097
|
*/
|
|
2345
3098
|
async characters(variables, options) {
|
|
2346
3099
|
const query = `
|
|
@@ -2400,12 +3153,16 @@ const ExternalLinkSourceCollectionMappings = {
|
|
|
2400
3153
|
};
|
|
2401
3154
|
class ExternalLinkSourceCollectionQuery extends AniListOperation {
|
|
2402
3155
|
/**
|
|
2403
|
-
*
|
|
3156
|
+
* {@link ExternalLinkSourceCollectionQuery.externalLinkSourceCollection} sends a query request to get external link source collections.
|
|
2404
3157
|
*
|
|
2405
|
-
* @param variables -
|
|
2406
|
-
* @returns The
|
|
3158
|
+
* @param variables - Optional values from {@link ExternalLinkSourceCollectionVariables}; defaults to an empty object.
|
|
3159
|
+
* @returns The {@link ExternalLinkSourceCollectionResponse} returned by the query.
|
|
2407
3160
|
* @see https://docs.anilist.co/reference/query
|
|
2408
|
-
* @param options - Optional
|
|
3161
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
3162
|
+
* @example
|
|
3163
|
+
* ```typescript
|
|
3164
|
+
* const result = await new ExternalLinkSourceCollectionQuery().externalLinkSourceCollection({});
|
|
3165
|
+
* ```
|
|
2409
3166
|
*/
|
|
2410
3167
|
async externalLinkSourceCollection(variables = {}, options) {
|
|
2411
3168
|
const query = `
|
|
@@ -2818,12 +3575,16 @@ const FollowerMappings = {
|
|
|
2818
3575
|
};
|
|
2819
3576
|
class FollowerQuery extends AniListOperation {
|
|
2820
3577
|
/**
|
|
2821
|
-
*
|
|
3578
|
+
* {@link FollowerQuery.follower} sends a query request to get followers.
|
|
2822
3579
|
*
|
|
2823
|
-
* @param variables -
|
|
2824
|
-
* @returns The
|
|
3580
|
+
* @param variables - Values from {@link FollowerVariables} for the query.
|
|
3581
|
+
* @returns The {@link UserResponse} returned by the query.
|
|
2825
3582
|
* @see https://docs.anilist.co/reference/object/user
|
|
2826
|
-
* @param options - Optional
|
|
3583
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
3584
|
+
* @example
|
|
3585
|
+
* ```typescript
|
|
3586
|
+
* const result = await new FollowerQuery().follower({ userId: 1 });
|
|
3587
|
+
* ```
|
|
2827
3588
|
*/
|
|
2828
3589
|
async follower(variables, options) {
|
|
2829
3590
|
const query = `
|
|
@@ -2862,10 +3623,14 @@ class FollowersQuery extends AniListOperation {
|
|
|
2862
3623
|
/**
|
|
2863
3624
|
* `followers` is a method that sends a query request to get followers.
|
|
2864
3625
|
*
|
|
2865
|
-
* @param variables -
|
|
2866
|
-
* @returns The
|
|
3626
|
+
* @param variables - Values from {@link FollowersVariables} for the query.
|
|
3627
|
+
* @returns The {@link FollowersPageResponse} returned by the query.
|
|
2867
3628
|
* @see https://docs.anilist.co/reference/object/user
|
|
2868
|
-
* @param options - Optional
|
|
3629
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
3630
|
+
* @example
|
|
3631
|
+
* ```typescript
|
|
3632
|
+
* const result = await new FollowersQuery().followers({ userId: 1, page: 1, perPage: 10 });
|
|
3633
|
+
* ```
|
|
2869
3634
|
*/
|
|
2870
3635
|
async followers(variables, options) {
|
|
2871
3636
|
const query = `
|
|
@@ -2909,12 +3674,16 @@ const FollowingMappings = {
|
|
|
2909
3674
|
};
|
|
2910
3675
|
class FollowingQuery extends AniListOperation {
|
|
2911
3676
|
/**
|
|
2912
|
-
*
|
|
3677
|
+
* {@link FollowingQuery.following} sends a query request to get following users.
|
|
2913
3678
|
*
|
|
2914
|
-
* @param variables -
|
|
2915
|
-
* @returns The
|
|
3679
|
+
* @param variables - Values from {@link FollowingVariables} for the query.
|
|
3680
|
+
* @returns The {@link UserResponse} returned by the query.
|
|
2916
3681
|
* @see https://docs.anilist.co/reference/object/user
|
|
2917
|
-
* @param options - Optional
|
|
3682
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
3683
|
+
* @example
|
|
3684
|
+
* ```typescript
|
|
3685
|
+
* const result = await new FollowingQuery().following({ userId: 1 });
|
|
3686
|
+
* ```
|
|
2918
3687
|
*/
|
|
2919
3688
|
async following(variables, options) {
|
|
2920
3689
|
const query = `
|
|
@@ -2953,10 +3722,14 @@ class FollowingsQuery extends AniListOperation {
|
|
|
2953
3722
|
/**
|
|
2954
3723
|
* `followings` is a method that sends a query request to get followings.
|
|
2955
3724
|
*
|
|
2956
|
-
* @param variables -
|
|
2957
|
-
* @returns The
|
|
3725
|
+
* @param variables - Values from {@link FollowingsVariables} for the query.
|
|
3726
|
+
* @returns The {@link FollowingsPageResponse} returned by the query.
|
|
2958
3727
|
* @see https://docs.anilist.co/reference/object/user
|
|
2959
|
-
* @param options - Optional
|
|
3728
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
3729
|
+
* @example
|
|
3730
|
+
* ```typescript
|
|
3731
|
+
* const result = await new FollowingsQuery().followings({ userId: 1, page: 1, perPage: 10 });
|
|
3732
|
+
* ```
|
|
2960
3733
|
*/
|
|
2961
3734
|
async followings(variables, options) {
|
|
2962
3735
|
const query = `
|
|
@@ -2991,11 +3764,15 @@ class FollowingsQuery extends AniListOperation {
|
|
|
2991
3764
|
|
|
2992
3765
|
class GenreCollectionQuery extends AniListOperation {
|
|
2993
3766
|
/**
|
|
2994
|
-
*
|
|
3767
|
+
* {@link GenreCollectionQuery.genreCollection} sends a query request to get genre collections.
|
|
2995
3768
|
*
|
|
2996
|
-
* @returns The
|
|
3769
|
+
* @returns The list of genre strings returned by AniList.
|
|
2997
3770
|
* @see https://docs.anilist.co/reference/query
|
|
2998
|
-
* @param options - Optional
|
|
3771
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
3772
|
+
* @example
|
|
3773
|
+
* ```typescript
|
|
3774
|
+
* const genres = await new GenreCollectionQuery().genreCollection();
|
|
3775
|
+
* ```
|
|
2999
3776
|
*/
|
|
3000
3777
|
async genreCollection(options) {
|
|
3001
3778
|
const query = `
|
|
@@ -3017,10 +3794,14 @@ class LikesQuery extends AniListOperation {
|
|
|
3017
3794
|
/**
|
|
3018
3795
|
* `likes` is a method that sends a query request to get likes.
|
|
3019
3796
|
*
|
|
3020
|
-
* @param variables -
|
|
3021
|
-
* @returns The
|
|
3797
|
+
* @param variables - Values from {@link LikesVariables} for the query.
|
|
3798
|
+
* @returns The {@link LikesPageResponse} for the requested page, with pagination metadata.
|
|
3022
3799
|
* @see https://docs.anilist.co/reference/union/likeableunion
|
|
3023
|
-
* @param options - Optional
|
|
3800
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
3801
|
+
* @example
|
|
3802
|
+
* ```typescript
|
|
3803
|
+
* const result = await new LikesQuery().likes({ likeableId: 1, type: "ACTIVITY" });
|
|
3804
|
+
* ```
|
|
3024
3805
|
*/
|
|
3025
3806
|
async likes(variables, options) {
|
|
3026
3807
|
const query = `
|
|
@@ -3055,12 +3836,16 @@ class LikesQuery extends AniListOperation {
|
|
|
3055
3836
|
|
|
3056
3837
|
class MarkdownQuery extends AniListOperation {
|
|
3057
3838
|
/**
|
|
3058
|
-
*
|
|
3839
|
+
* {@link MarkdownQuery.markdown} sends a query request to convert Markdown text to HTML.
|
|
3059
3840
|
*
|
|
3060
|
-
* @param variables -
|
|
3061
|
-
* @returns The
|
|
3841
|
+
* @param variables - Values from {@link MarkdownVariables} for the query.
|
|
3842
|
+
* @returns The converted HTML string returned by AniList.
|
|
3062
3843
|
* @see https://docs.anilist.co/reference/object/parsedmarkdown
|
|
3063
|
-
* @param options - Optional
|
|
3844
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
3845
|
+
* @example
|
|
3846
|
+
* ```typescript
|
|
3847
|
+
* const html = await new MarkdownQuery().markdown({ markdown: "# AniList" });
|
|
3848
|
+
* ```
|
|
3064
3849
|
*/
|
|
3065
3850
|
async markdown(variables, options) {
|
|
3066
3851
|
const query = `
|
|
@@ -3194,7 +3979,7 @@ const MediaListCollectionMappings = {
|
|
|
3194
3979
|
};
|
|
3195
3980
|
class MediaListCollectionQuery extends AniListOperation {
|
|
3196
3981
|
/**
|
|
3197
|
-
*
|
|
3982
|
+
* {@link MediaListCollectionQuery.mediaListCollection} sends a query request to get media list collection data.
|
|
3198
3983
|
*
|
|
3199
3984
|
* Chunk semantics: AniList returns large user lists in chunks. Set `chunk` (1-based) and
|
|
3200
3985
|
* `perChunk` (entries per chunk) to fetch a single chunk; the response's `hasNextChunk` flag
|
|
@@ -3212,10 +3997,17 @@ class MediaListCollectionQuery extends AniListOperation {
|
|
|
3212
3997
|
* );
|
|
3213
3998
|
* ```
|
|
3214
3999
|
*
|
|
3215
|
-
* @param variables -
|
|
3216
|
-
* @returns The
|
|
4000
|
+
* @param variables - Values from {@link MediaListCollectionVariables} for the query.
|
|
4001
|
+
* @returns The {@link MediaListCollectionResponse} from the query request, including `lists` and `hasNextChunk`.
|
|
3217
4002
|
* @see https://docs.anilist.co/reference/object/medialistcollection
|
|
3218
|
-
* @param options - Optional
|
|
4003
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
4004
|
+
* @example
|
|
4005
|
+
* ```typescript
|
|
4006
|
+
* const result = await new MediaListCollectionQuery().mediaListCollection({
|
|
4007
|
+
* type: "ANIME",
|
|
4008
|
+
* userId: 1,
|
|
4009
|
+
* });
|
|
4010
|
+
* ```
|
|
3219
4011
|
*/
|
|
3220
4012
|
async mediaListCollection(variables, options) {
|
|
3221
4013
|
const query = MediaListCollectionQuerySchema;
|
|
@@ -3298,12 +4090,16 @@ const MediaListMappings = {
|
|
|
3298
4090
|
};
|
|
3299
4091
|
class MediaListQuery extends AniListOperation {
|
|
3300
4092
|
/**
|
|
3301
|
-
*
|
|
4093
|
+
* {@link MediaListQuery.mediaList} sends a query request to get media list data.
|
|
3302
4094
|
*
|
|
3303
|
-
* @param variables -
|
|
3304
|
-
* @returns The
|
|
4095
|
+
* @param variables - Values from {@link MediaListVariables} for the query.
|
|
4096
|
+
* @returns The {@link MediaListResponse} returned by the query.
|
|
3305
4097
|
* @see https://docs.anilist.co/reference/object/medialist
|
|
3306
|
-
* @param options - Optional
|
|
4098
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
4099
|
+
* @example
|
|
4100
|
+
* ```typescript
|
|
4101
|
+
* const result = await new MediaListQuery().mediaList({ id: 1 });
|
|
4102
|
+
* ```
|
|
3307
4103
|
*/
|
|
3308
4104
|
async mediaList(variables, options) {
|
|
3309
4105
|
const query = `
|
|
@@ -3356,10 +4152,14 @@ class MediaListsQuery extends AniListOperation {
|
|
|
3356
4152
|
/**
|
|
3357
4153
|
* `mediaLists` is a method that sends a query request to get media lists.
|
|
3358
4154
|
*
|
|
3359
|
-
* @param variables -
|
|
3360
|
-
* @returns The
|
|
4155
|
+
* @param variables - Values from {@link MediaListsVariables} for the query.
|
|
4156
|
+
* @returns The {@link MediaListsPageResponse} returned by the query.
|
|
3361
4157
|
* @see https://docs.anilist.co/reference/object/medialist
|
|
3362
|
-
* @param options - Optional
|
|
4158
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
4159
|
+
* @example
|
|
4160
|
+
* ```typescript
|
|
4161
|
+
* const result = await new MediaListsQuery().mediaLists({ userId: 1, page: 1, perPage: 10 });
|
|
4162
|
+
* ```
|
|
3363
4163
|
*/
|
|
3364
4164
|
async mediaLists(variables, options) {
|
|
3365
4165
|
const query = `
|
|
@@ -3487,12 +4287,16 @@ const MediaMappings = {
|
|
|
3487
4287
|
};
|
|
3488
4288
|
class MediaQuery extends AniListOperation {
|
|
3489
4289
|
/**
|
|
3490
|
-
*
|
|
4290
|
+
* {@link MediaQuery.media} sends a query request to get media data.
|
|
3491
4291
|
*
|
|
3492
|
-
* @param variables -
|
|
3493
|
-
* @returns The
|
|
4292
|
+
* @param variables - Values from {@link MediaVariables} for the query.
|
|
4293
|
+
* @returns The {@link MediaResponse} returned by the query.
|
|
3494
4294
|
* @see https://docs.anilist.co/reference/object/media
|
|
3495
|
-
* @param options - Optional
|
|
4295
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
4296
|
+
* @example
|
|
4297
|
+
* ```typescript
|
|
4298
|
+
* const result = await new MediaQuery().media({ id: 1 });
|
|
4299
|
+
* ```
|
|
3496
4300
|
*/
|
|
3497
4301
|
async media(variables, options) {
|
|
3498
4302
|
const query = `
|
|
@@ -3521,12 +4325,16 @@ const MediaTagCollectionMappings = {
|
|
|
3521
4325
|
};
|
|
3522
4326
|
class MediaTagCollectionQuery extends AniListOperation {
|
|
3523
4327
|
/**
|
|
3524
|
-
*
|
|
4328
|
+
* {@link MediaTagCollectionQuery.mediaTagCollection} sends a query request to get media tag collection data.
|
|
3525
4329
|
*
|
|
3526
|
-
* @param variables -
|
|
3527
|
-
* @returns The
|
|
4330
|
+
* @param variables - Optional values from {@link MediaTagCollectionVariables}; defaults to an empty object.
|
|
4331
|
+
* @returns The {@link MediaTagCollectionResponse} returned by the query.
|
|
3528
4332
|
* @see https://docs.anilist.co/reference/object/mediatag
|
|
3529
|
-
* @param options - Optional
|
|
4333
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
4334
|
+
* @example
|
|
4335
|
+
* ```typescript
|
|
4336
|
+
* const result = await new MediaTagCollectionQuery().mediaTagCollection({});
|
|
4337
|
+
* ```
|
|
3530
4338
|
*/
|
|
3531
4339
|
async mediaTagCollection(variables = {}, options) {
|
|
3532
4340
|
const query = `
|
|
@@ -3587,12 +4395,16 @@ const MediaTrendMappings = {
|
|
|
3587
4395
|
};
|
|
3588
4396
|
class MediaTrendQuery extends AniListOperation {
|
|
3589
4397
|
/**
|
|
3590
|
-
*
|
|
4398
|
+
* {@link MediaTrendQuery.mediaTrend} sends a query request to get media trend data.
|
|
3591
4399
|
*
|
|
3592
|
-
* @param variables -
|
|
3593
|
-
* @returns The
|
|
4400
|
+
* @param variables - Values from {@link MediaTrendVariables} for the query.
|
|
4401
|
+
* @returns The {@link MediaTrendResponse} returned by the query.
|
|
3594
4402
|
* @see https://docs.anilist.co/reference/object/mediatrend
|
|
3595
|
-
* @param options - Optional
|
|
4403
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
4404
|
+
* @example
|
|
4405
|
+
* ```typescript
|
|
4406
|
+
* const result = await new MediaTrendQuery().mediaTrend({ mediaId: 1 });
|
|
4407
|
+
* ```
|
|
3596
4408
|
*/
|
|
3597
4409
|
async mediaTrend(variables, options) {
|
|
3598
4410
|
const query = `
|
|
@@ -3650,10 +4462,14 @@ class MediaTrendsQuery extends AniListOperation {
|
|
|
3650
4462
|
/**
|
|
3651
4463
|
* `mediaTrends` is a method that sends a query request to get media trends.
|
|
3652
4464
|
*
|
|
3653
|
-
* @param variables -
|
|
3654
|
-
* @returns The
|
|
4465
|
+
* @param variables - Values from {@link MediaTrendsVariables} for the query.
|
|
4466
|
+
* @returns The {@link MediaTrendsPageResponse} returned by the query.
|
|
3655
4467
|
* @see https://docs.anilist.co/reference/object/mediatrend
|
|
3656
|
-
* @param options - Optional
|
|
4468
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
4469
|
+
* @example
|
|
4470
|
+
* ```typescript
|
|
4471
|
+
* const result = await new MediaTrendsQuery().mediaTrends({ mediaId: 1, page: 1, perPage: 10 });
|
|
4472
|
+
* ```
|
|
3657
4473
|
*/
|
|
3658
4474
|
async mediaTrends(variables, options) {
|
|
3659
4475
|
const query = `
|
|
@@ -3755,11 +4571,15 @@ const MediasMappings = {
|
|
|
3755
4571
|
};
|
|
3756
4572
|
class MediasQuery extends AniListOperation {
|
|
3757
4573
|
/**
|
|
3758
|
-
* Returns a
|
|
3759
|
-
* @param variables -
|
|
3760
|
-
* @returns
|
|
4574
|
+
* Returns a {@link MediasPageResponse} object.
|
|
4575
|
+
* @param variables - Values from {@link MediasVariables} for the query.
|
|
4576
|
+
* @returns The {@link MediasPageResponse} returned by the query.
|
|
3761
4577
|
* @see https://docs.anilist.co/reference/object/media
|
|
3762
|
-
* @param options - Optional
|
|
4578
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
4579
|
+
* @example
|
|
4580
|
+
* ```typescript
|
|
4581
|
+
* const result = await new MediasQuery().medias({ search: "Cowboy Bebop", page: 1 });
|
|
4582
|
+
* ```
|
|
3763
4583
|
*/
|
|
3764
4584
|
async medias(variables, options) {
|
|
3765
4585
|
const query = `
|
|
@@ -3967,12 +4787,16 @@ const NotificationMappings = {
|
|
|
3967
4787
|
};
|
|
3968
4788
|
class NotificationQuery extends AniListOperation {
|
|
3969
4789
|
/**
|
|
3970
|
-
*
|
|
4790
|
+
* {@link NotificationQuery.notification} sends a query request to get notification data.
|
|
3971
4791
|
*
|
|
3972
|
-
* @param variables -
|
|
3973
|
-
* @returns The
|
|
4792
|
+
* @param variables - Values from {@link NotificationVariables} for the query.
|
|
4793
|
+
* @returns The {@link NotificationResponse} returned by the query.
|
|
3974
4794
|
* @see https://docs.anilist.co/reference/union/notificationunion
|
|
3975
|
-
* @param options - Optional
|
|
4795
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
4796
|
+
* @example
|
|
4797
|
+
* ```typescript
|
|
4798
|
+
* const result = await new NotificationQuery("authToken").notification({});
|
|
4799
|
+
* ```
|
|
3976
4800
|
*/
|
|
3977
4801
|
async notification(variables, options) {
|
|
3978
4802
|
const query = `
|
|
@@ -4002,10 +4826,14 @@ class NotificationsQuery extends AniListOperation {
|
|
|
4002
4826
|
/**
|
|
4003
4827
|
* `notifications` is a method that sends a query request to get notifications.
|
|
4004
4828
|
*
|
|
4005
|
-
* @param variables -
|
|
4006
|
-
* @returns The
|
|
4829
|
+
* @param variables - Values from {@link NotificationsVariables} for the query.
|
|
4830
|
+
* @returns The {@link NotificationsPageResponse} returned by the query.
|
|
4007
4831
|
* @see https://docs.anilist.co/reference/union/notificationunion
|
|
4008
|
-
* @param options - Optional
|
|
4832
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
4833
|
+
* @example
|
|
4834
|
+
* ```typescript
|
|
4835
|
+
* const result = await new NotificationsQuery().notifications({ page: 1, perPage: 10 });
|
|
4836
|
+
* ```
|
|
4009
4837
|
*/
|
|
4010
4838
|
async notifications(variables, options) {
|
|
4011
4839
|
const query = `
|
|
@@ -4060,12 +4888,16 @@ const RecommendationMappings = {
|
|
|
4060
4888
|
};
|
|
4061
4889
|
class RecommendationQuery extends AniListOperation {
|
|
4062
4890
|
/**
|
|
4063
|
-
*
|
|
4891
|
+
* {@link RecommendationQuery.recommendation} sends a query request to get recommendation data.
|
|
4064
4892
|
*
|
|
4065
|
-
* @param variables -
|
|
4066
|
-
* @returns The
|
|
4893
|
+
* @param variables - Values from {@link RecommendationVariables} for the query.
|
|
4894
|
+
* @returns The {@link RecommendationResponse} returned by the query.
|
|
4067
4895
|
* @see https://docs.anilist.co/reference/object/recommendation
|
|
4068
|
-
* @param options - Optional
|
|
4896
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
4897
|
+
* @example
|
|
4898
|
+
* ```typescript
|
|
4899
|
+
* const result = await new RecommendationQuery().recommendation({ mediaId: 1 });
|
|
4900
|
+
* ```
|
|
4069
4901
|
*/
|
|
4070
4902
|
async recommendation(variables, options) {
|
|
4071
4903
|
const query = `
|
|
@@ -4107,10 +4939,14 @@ class RecommendationsQuery extends AniListOperation {
|
|
|
4107
4939
|
/**
|
|
4108
4940
|
* `recommendations` is a method that sends a query request to get recommendations.
|
|
4109
4941
|
*
|
|
4110
|
-
* @param variables -
|
|
4111
|
-
* @returns The
|
|
4942
|
+
* @param variables - Values from {@link RecommendationsVariables} for the query.
|
|
4943
|
+
* @returns The {@link RecommendationsPageResponse} returned by the query.
|
|
4112
4944
|
* @see https://docs.anilist.co/reference/object/recommendation
|
|
4113
|
-
* @param options - Optional
|
|
4945
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
4946
|
+
* @example
|
|
4947
|
+
* ```typescript
|
|
4948
|
+
* const result = await new RecommendationsQuery().recommendations({ mediaId: 1, page: 1 });
|
|
4949
|
+
* ```
|
|
4114
4950
|
*/
|
|
4115
4951
|
async recommendations(variables, options) {
|
|
4116
4952
|
const query = `
|
|
@@ -4168,12 +5004,16 @@ const ReviewMappings = {
|
|
|
4168
5004
|
};
|
|
4169
5005
|
class ReviewQuery extends AniListOperation {
|
|
4170
5006
|
/**
|
|
4171
|
-
*
|
|
5007
|
+
* {@link ReviewQuery.review} sends a query request to get review data.
|
|
4172
5008
|
*
|
|
4173
|
-
* @param variables -
|
|
4174
|
-
* @returns The
|
|
5009
|
+
* @param variables - Values from {@link ReviewVariables} for the query.
|
|
5010
|
+
* @returns The {@link ReviewResponse} returned by the query.
|
|
4175
5011
|
* @see https://docs.anilist.co/reference/object/review
|
|
4176
|
-
* @param options - Optional
|
|
5012
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
5013
|
+
* @example
|
|
5014
|
+
* ```typescript
|
|
5015
|
+
* const result = await new ReviewQuery().review({ mediaId: 1 });
|
|
5016
|
+
* ```
|
|
4177
5017
|
*/
|
|
4178
5018
|
async review(variables, options) {
|
|
4179
5019
|
const query = `
|
|
@@ -4211,10 +5051,14 @@ class ReviewsQuery extends AniListOperation {
|
|
|
4211
5051
|
/**
|
|
4212
5052
|
* `reviews` is a method that sends a query request to get reviews.
|
|
4213
5053
|
*
|
|
4214
|
-
* @param variables -
|
|
4215
|
-
* @returns The
|
|
5054
|
+
* @param variables - Values from {@link ReviewsVariables} for the query.
|
|
5055
|
+
* @returns The {@link ReviewsPageResponse} returned by the query.
|
|
4216
5056
|
* @see https://docs.anilist.co/reference/object/review
|
|
4217
|
-
* @param options - Optional
|
|
5057
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
5058
|
+
* @example
|
|
5059
|
+
* ```typescript
|
|
5060
|
+
* const result = await new ReviewsQuery().reviews({ mediaId: 1, page: 1 });
|
|
5061
|
+
* ```
|
|
4218
5062
|
*/
|
|
4219
5063
|
async reviews(variables, options) {
|
|
4220
5064
|
const query = `
|
|
@@ -4312,12 +5156,16 @@ const SiteStatisticsMappings = {
|
|
|
4312
5156
|
};
|
|
4313
5157
|
class SiteStatisticsQuery extends AniListOperation {
|
|
4314
5158
|
/**
|
|
4315
|
-
*
|
|
5159
|
+
* {@link SiteStatisticsQuery.siteStatistics} sends a query request to get site statistics data.
|
|
4316
5160
|
*
|
|
4317
|
-
* @param variables -
|
|
4318
|
-
* @returns The
|
|
5161
|
+
* @param variables - Optional values from {@link SiteStatisticsVariables}; defaults to an empty object.
|
|
5162
|
+
* @returns The {@link SiteStatisticsResponse} returned by the query.
|
|
4319
5163
|
* @see https://docs.anilist.co/reference/object/sitestatistics
|
|
4320
|
-
* @param options - Optional
|
|
5164
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
5165
|
+
* @example
|
|
5166
|
+
* ```typescript
|
|
5167
|
+
* const result = await new SiteStatisticsQuery().siteStatistics({});
|
|
5168
|
+
* ```
|
|
4321
5169
|
*/
|
|
4322
5170
|
async siteStatistics(variables = {}, options) {
|
|
4323
5171
|
const query = `
|
|
@@ -4407,12 +5255,16 @@ const StaffMappings = {
|
|
|
4407
5255
|
};
|
|
4408
5256
|
class StaffQuery extends AniListOperation {
|
|
4409
5257
|
/**
|
|
4410
|
-
*
|
|
5258
|
+
* {@link StaffQuery.staff} sends a query request to get staff data.
|
|
4411
5259
|
*
|
|
4412
|
-
* @param variables -
|
|
4413
|
-
* @returns The
|
|
5260
|
+
* @param variables - Values from {@link StaffVariables} for the query.
|
|
5261
|
+
* @returns The {@link StaffResponse} returned by the query.
|
|
4414
5262
|
* @see https://docs.anilist.co/reference/object/staff
|
|
4415
|
-
* @param options - Optional
|
|
5263
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
5264
|
+
* @example
|
|
5265
|
+
* ```typescript
|
|
5266
|
+
* const result = await new StaffQuery().staff({ id: 1 });
|
|
5267
|
+
* ```
|
|
4416
5268
|
*/
|
|
4417
5269
|
async staff(variables, options) {
|
|
4418
5270
|
const query = `
|
|
@@ -4457,10 +5309,14 @@ class StaffsQuery extends AniListOperation {
|
|
|
4457
5309
|
/**
|
|
4458
5310
|
* `staffs` is a method that sends a query request to get staffs.
|
|
4459
5311
|
*
|
|
4460
|
-
* @param variables -
|
|
4461
|
-
* @returns The
|
|
5312
|
+
* @param variables - Values from {@link StaffsVariables} for the query.
|
|
5313
|
+
* @returns The {@link StaffsPageResponse} returned by the query.
|
|
4462
5314
|
* @see https://docs.anilist.co/reference/object/staff
|
|
4463
|
-
* @param options - Optional
|
|
5315
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
5316
|
+
* @example
|
|
5317
|
+
* ```typescript
|
|
5318
|
+
* const result = await new StaffsQuery().staffs({ search: "Hayao Miyazaki", page: 1 });
|
|
5319
|
+
* ```
|
|
4464
5320
|
*/
|
|
4465
5321
|
async staffs(variables, options) {
|
|
4466
5322
|
const query = `
|
|
@@ -4563,12 +5419,16 @@ const StudioMappings = {
|
|
|
4563
5419
|
};
|
|
4564
5420
|
class StudioQuery extends AniListOperation {
|
|
4565
5421
|
/**
|
|
4566
|
-
*
|
|
5422
|
+
* {@link StudioQuery.studio} sends a query request to get studio data.
|
|
4567
5423
|
*
|
|
4568
|
-
* @param variables -
|
|
4569
|
-
* @returns The
|
|
5424
|
+
* @param variables - Values from {@link StudioVariables} for the query.
|
|
5425
|
+
* @returns The {@link StudioResponse} returned by the query.
|
|
4570
5426
|
* @see https://docs.anilist.co/reference/object/studio
|
|
4571
|
-
* @param options - Optional
|
|
5427
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
5428
|
+
* @example
|
|
5429
|
+
* ```typescript
|
|
5430
|
+
* const result = await new StudioQuery().studio({ id: 1 });
|
|
5431
|
+
* ```
|
|
4572
5432
|
*/
|
|
4573
5433
|
async studio(variables, options) {
|
|
4574
5434
|
const query = `
|
|
@@ -4617,10 +5477,14 @@ class StudiosQuery extends AniListOperation {
|
|
|
4617
5477
|
/**
|
|
4618
5478
|
* `studios` is a method that sends a query request to get studios.
|
|
4619
5479
|
*
|
|
4620
|
-
* @param variables -
|
|
4621
|
-
* @returns The
|
|
5480
|
+
* @param variables - Values from {@link StudiosVariables} for the query.
|
|
5481
|
+
* @returns The {@link StudiosPageResponse} returned by the query.
|
|
4622
5482
|
* @see https://docs.anilist.co/reference/object/studio
|
|
4623
|
-
* @param options - Optional
|
|
5483
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
5484
|
+
* @example
|
|
5485
|
+
* ```typescript
|
|
5486
|
+
* const result = await new StudiosQuery().studios({ search: "Bones", page: 1 });
|
|
5487
|
+
* ```
|
|
4624
5488
|
*/
|
|
4625
5489
|
async studios(variables, options) {
|
|
4626
5490
|
const query = `
|
|
@@ -4714,12 +5578,16 @@ const ThreadCommentMappings = {
|
|
|
4714
5578
|
};
|
|
4715
5579
|
class ThreadCommentQuery extends AniListOperation {
|
|
4716
5580
|
/**
|
|
4717
|
-
*
|
|
5581
|
+
* {@link ThreadCommentQuery.threadComment} sends a query request to get thread comment data.
|
|
4718
5582
|
*
|
|
4719
|
-
* @param variables -
|
|
4720
|
-
* @returns The
|
|
5583
|
+
* @param variables - Values from {@link ThreadCommentVariables} for the query.
|
|
5584
|
+
* @returns The {@link ThreadCommentResponse} returned by the query.
|
|
4721
5585
|
* @see https://docs.anilist.co/reference/object/threadcomment
|
|
4722
|
-
* @param options - Optional
|
|
5586
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
5587
|
+
* @example
|
|
5588
|
+
* ```typescript
|
|
5589
|
+
* const result = await new ThreadCommentQuery().threadComment({ threadId: 1 });
|
|
5590
|
+
* ```
|
|
4723
5591
|
*/
|
|
4724
5592
|
async threadComment(variables, options) {
|
|
4725
5593
|
const query = `
|
|
@@ -4756,10 +5624,14 @@ class ThreadCommentsQuery extends AniListOperation {
|
|
|
4756
5624
|
/**
|
|
4757
5625
|
* `threadComments` is a method that sends a query request to get thread comments.
|
|
4758
5626
|
*
|
|
4759
|
-
* @param variables -
|
|
4760
|
-
* @returns The
|
|
5627
|
+
* @param variables - Values from {@link ThreadCommentsVariables} for the query.
|
|
5628
|
+
* @returns The {@link ThreadCommentsPageResponse} returned by the query.
|
|
4761
5629
|
* @see https://docs.anilist.co/reference/object/threadcomment
|
|
4762
|
-
* @param options - Optional
|
|
5630
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
5631
|
+
* @example
|
|
5632
|
+
* ```typescript
|
|
5633
|
+
* const result = await new ThreadCommentsQuery().threadComments({ threadId: 1, page: 1 });
|
|
5634
|
+
* ```
|
|
4763
5635
|
*/
|
|
4764
5636
|
async threadComments(variables, options) {
|
|
4765
5637
|
const query = `
|
|
@@ -4806,12 +5678,16 @@ const ThreadMappings = {
|
|
|
4806
5678
|
};
|
|
4807
5679
|
class ThreadQuery extends AniListOperation {
|
|
4808
5680
|
/**
|
|
4809
|
-
*
|
|
5681
|
+
* {@link ThreadQuery.thread} sends a query request to get thread data.
|
|
4810
5682
|
*
|
|
4811
|
-
* @param variables -
|
|
4812
|
-
* @returns The
|
|
5683
|
+
* @param variables - Values from {@link ThreadVariables} for the query.
|
|
5684
|
+
* @returns The {@link ThreadResponse} returned by the query.
|
|
4813
5685
|
* @see https://docs.anilist.co/reference/object/thread
|
|
4814
|
-
* @param options - Optional
|
|
5686
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
5687
|
+
* @example
|
|
5688
|
+
* ```typescript
|
|
5689
|
+
* const result = await new ThreadQuery().thread({ id: 1 });
|
|
5690
|
+
* ```
|
|
4815
5691
|
*/
|
|
4816
5692
|
async thread(variables, options) {
|
|
4817
5693
|
const query = `
|
|
@@ -4853,10 +5729,14 @@ class ThreadsQuery extends AniListOperation {
|
|
|
4853
5729
|
/**
|
|
4854
5730
|
* `threads` is a method that sends a query request to get threads.
|
|
4855
5731
|
*
|
|
4856
|
-
* @param variables -
|
|
4857
|
-
* @returns The
|
|
5732
|
+
* @param variables - Values from {@link ThreadsVariables} for the query.
|
|
5733
|
+
* @returns The {@link ThreadsPageResponse} returned by the query.
|
|
4858
5734
|
* @see https://docs.anilist.co/reference/object/thread
|
|
4859
|
-
* @param options - Optional
|
|
5735
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
5736
|
+
* @example
|
|
5737
|
+
* ```typescript
|
|
5738
|
+
* const result = await new ThreadsQuery().threads({ page: 1, perPage: 10 });
|
|
5739
|
+
* ```
|
|
4860
5740
|
*/
|
|
4861
5741
|
async threads(variables, options) {
|
|
4862
5742
|
const query = `
|
|
@@ -4896,12 +5776,16 @@ const UserMappings = {
|
|
|
4896
5776
|
};
|
|
4897
5777
|
class UserQuery extends AniListOperation {
|
|
4898
5778
|
/**
|
|
4899
|
-
*
|
|
5779
|
+
* {@link UserQuery.user} sends a query request to get user data.
|
|
4900
5780
|
*
|
|
4901
|
-
* @param variables -
|
|
4902
|
-
* @returns The
|
|
5781
|
+
* @param variables - Values from {@link UserVariables} for the query.
|
|
5782
|
+
* @returns The {@link UserResponse} returned by the query.
|
|
4903
5783
|
* @see https://docs.anilist.co/reference/object/user
|
|
4904
|
-
* @param options - Optional
|
|
5784
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
5785
|
+
* @example
|
|
5786
|
+
* ```typescript
|
|
5787
|
+
* const result = await new UserQuery().user({ id: 1 });
|
|
5788
|
+
* ```
|
|
4905
5789
|
*/
|
|
4906
5790
|
async user(variables, options) {
|
|
4907
5791
|
const query = `
|
|
@@ -4936,10 +5820,14 @@ class UsersQuery extends AniListOperation {
|
|
|
4936
5820
|
/**
|
|
4937
5821
|
* `users` is a method that sends a query request to get users.
|
|
4938
5822
|
*
|
|
4939
|
-
* @param variables -
|
|
4940
|
-
* @returns The
|
|
5823
|
+
* @param variables - Values from {@link UsersVariables} for the query.
|
|
5824
|
+
* @returns The {@link UsersPageResponse} returned by the query.
|
|
4941
5825
|
* @see https://docs.anilist.co/reference/object/user
|
|
4942
|
-
* @param options - Optional
|
|
5826
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
5827
|
+
* @example
|
|
5828
|
+
* ```typescript
|
|
5829
|
+
* const result = await new UsersQuery().users({ search: "AniList", page: 1 });
|
|
5830
|
+
* ```
|
|
4943
5831
|
*/
|
|
4944
5832
|
async users(variables, options) {
|
|
4945
5833
|
const query = `
|
|
@@ -4974,12 +5862,16 @@ const ViewerMappings = {
|
|
|
4974
5862
|
};
|
|
4975
5863
|
class ViewerQuery extends AniListOperation {
|
|
4976
5864
|
/**
|
|
4977
|
-
*
|
|
5865
|
+
* {@link ViewerQuery.viewer} sends a query request to get viewer data.
|
|
4978
5866
|
*
|
|
4979
|
-
* @param variables -
|
|
4980
|
-
* @returns The
|
|
5867
|
+
* @param variables - Optional values from {@link ViewerVariables}; defaults to an empty object.
|
|
5868
|
+
* @returns The {@link UserResponse} returned by the query.
|
|
4981
5869
|
* @see https://docs.anilist.co/reference/object/user
|
|
4982
|
-
* @param options - Optional
|
|
5870
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
5871
|
+
* @example
|
|
5872
|
+
* ```typescript
|
|
5873
|
+
* const result = await new ViewerQuery("authToken").viewer({});
|
|
5874
|
+
* ```
|
|
4983
5875
|
*/
|
|
4984
5876
|
async viewer(variables = {}, options) {
|
|
4985
5877
|
const query = `
|
|
@@ -5002,18 +5894,22 @@ const DeleteMediaListEntryMappings = {
|
|
|
5002
5894
|
};
|
|
5003
5895
|
class DeleteMediaListEntryMutation extends AniListOperation {
|
|
5004
5896
|
/**
|
|
5005
|
-
*
|
|
5897
|
+
* {@link DeleteMediaListEntryMutation.deleteMediaListEntry} sends a mutation request to delete a media list entry.
|
|
5006
5898
|
*
|
|
5007
5899
|
* The response is `{ deleted: boolean }`. A `true` value means the entry was deleted by this
|
|
5008
5900
|
* call; a `false` value means the entry was not present (already deleted or never existed).
|
|
5009
5901
|
* The mutation is therefore safe to retry after a partial failure: a `false` result confirms
|
|
5010
5902
|
* the target is gone rather than reporting an error.
|
|
5011
5903
|
*
|
|
5012
|
-
* @param variables -
|
|
5013
|
-
* @returns
|
|
5014
|
-
* @throws
|
|
5904
|
+
* @param variables - Values from {@link DeleteMediaListEntryVariables} for the mutation.
|
|
5905
|
+
* @returns The {@link DeleteMediaListEntryResponse} returned by the mutation.
|
|
5906
|
+
* @throws Throws if no authentication token is configured, `id` is missing or invalid, or the mutation request fails.
|
|
5015
5907
|
* @see https://docs.anilist.co/reference/object/deleted
|
|
5016
|
-
* @param options - Optional
|
|
5908
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
5909
|
+
* @example
|
|
5910
|
+
* ```typescript
|
|
5911
|
+
* const result = await new DeleteMediaListEntryMutation("your-token").deleteMediaListEntry({ id: 1 });
|
|
5912
|
+
* ```
|
|
5017
5913
|
*/
|
|
5018
5914
|
async deleteMediaListEntry(variables, options) {
|
|
5019
5915
|
const mutation = `
|
|
@@ -5044,18 +5940,22 @@ const DeleteCustomListMappings = {
|
|
|
5044
5940
|
};
|
|
5045
5941
|
class DeleteCustomListMutation extends AniListOperation {
|
|
5046
5942
|
/**
|
|
5047
|
-
*
|
|
5943
|
+
* {@link DeleteCustomListMutation.deleteCustomList} sends a mutation request to delete a custom list.
|
|
5048
5944
|
*
|
|
5049
5945
|
* The response is `{ deleted: boolean }`. A `true` value means the custom list was deleted by
|
|
5050
5946
|
* this call; a `false` value means the list was not present (already deleted or never existed).
|
|
5051
5947
|
* The mutation is therefore safe to retry after a partial failure: a `false` result confirms
|
|
5052
5948
|
* the target is gone rather than reporting an error.
|
|
5053
5949
|
*
|
|
5054
|
-
* @param variables -
|
|
5055
|
-
* @returns
|
|
5056
|
-
* @throws
|
|
5950
|
+
* @param variables - Values from {@link DeleteCustomListVariables} for the mutation.
|
|
5951
|
+
* @returns The {@link DeleteResult} returned by the mutation.
|
|
5952
|
+
* @throws Throws if no authentication token is configured, `customList` or `type` is missing or invalid, or the mutation request fails.
|
|
5057
5953
|
* @see https://docs.anilist.co/reference/object/deleted
|
|
5058
|
-
* @param options - Optional
|
|
5954
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
5955
|
+
* @example
|
|
5956
|
+
* ```typescript
|
|
5957
|
+
* const result = await new DeleteCustomListMutation("your-token").deleteCustomList({ customList: "watching", type: "ANIME" });
|
|
5958
|
+
* ```
|
|
5059
5959
|
*/
|
|
5060
5960
|
async deleteCustomList(variables, options) {
|
|
5061
5961
|
const mutation = `
|
|
@@ -5088,13 +5988,17 @@ const SaveTextActivityMappings = {
|
|
|
5088
5988
|
};
|
|
5089
5989
|
class SaveTextActivityMutation extends AniListOperation {
|
|
5090
5990
|
/**
|
|
5091
|
-
*
|
|
5991
|
+
* {@link SaveTextActivityMutation.saveTextActivity} sends a mutation request to save a text activity.
|
|
5092
5992
|
*
|
|
5093
|
-
* @param variables -
|
|
5094
|
-
* @returns
|
|
5095
|
-
* @throws
|
|
5096
|
-
*
|
|
5097
|
-
* @param options - Optional
|
|
5993
|
+
* @param variables - Values from {@link SaveTextActivityVariables} for the mutation.
|
|
5994
|
+
* @returns The {@link Activity} returned by the mutation.
|
|
5995
|
+
* @throws Throws if no authentication token is configured, `id` or `text` is missing or invalid, or the mutation request fails.
|
|
5996
|
+
* @see https://docs.anilist.co/reference/union/activityunion
|
|
5997
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
5998
|
+
* @example
|
|
5999
|
+
* ```typescript
|
|
6000
|
+
* const result = await new SaveTextActivityMutation("your-token").saveTextActivity({ id: 1, text: "Hello, world!" });
|
|
6001
|
+
* ```
|
|
5098
6002
|
*/
|
|
5099
6003
|
async saveTextActivity(variables, options) {
|
|
5100
6004
|
const mutation = `
|
|
@@ -5130,13 +6034,17 @@ const SaveMessageActivityMappings = {
|
|
|
5130
6034
|
};
|
|
5131
6035
|
class SaveMessageActivityMutation extends AniListOperation {
|
|
5132
6036
|
/**
|
|
5133
|
-
*
|
|
6037
|
+
* {@link SaveMessageActivityMutation.saveMessageActivity} sends a mutation request to save a message activity.
|
|
5134
6038
|
*
|
|
5135
|
-
* @param variables -
|
|
5136
|
-
* @returns
|
|
5137
|
-
* @throws
|
|
5138
|
-
*
|
|
5139
|
-
* @param options - Optional
|
|
6039
|
+
* @param variables - Values from {@link SaveMessageActivityVariables} for the mutation.
|
|
6040
|
+
* @returns The {@link Activity} returned by the mutation.
|
|
6041
|
+
* @throws Throws if no authentication token is configured, `id` or `message` is missing or invalid, or the mutation request fails.
|
|
6042
|
+
* @see https://docs.anilist.co/reference/union/activityunion
|
|
6043
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6044
|
+
* @example
|
|
6045
|
+
* ```typescript
|
|
6046
|
+
* const result = await new SaveMessageActivityMutation("your-token").saveMessageActivity({ id: 1, message: "Hello, world!" });
|
|
6047
|
+
* ```
|
|
5140
6048
|
*/
|
|
5141
6049
|
async saveMessageActivity(variables, options) {
|
|
5142
6050
|
const mutation = `
|
|
@@ -5168,13 +6076,17 @@ const SaveListActivityMappings = {
|
|
|
5168
6076
|
};
|
|
5169
6077
|
class SaveListActivityMutation extends AniListOperation {
|
|
5170
6078
|
/**
|
|
5171
|
-
*
|
|
6079
|
+
* {@link SaveListActivityMutation.saveListActivity} sends a mutation request to save a list activity.
|
|
5172
6080
|
*
|
|
5173
|
-
* @param variables -
|
|
5174
|
-
* @returns
|
|
5175
|
-
* @throws
|
|
5176
|
-
*
|
|
5177
|
-
* @param options - Optional
|
|
6081
|
+
* @param variables - Values from {@link SaveListActivityVariables} for the mutation.
|
|
6082
|
+
* @returns The {@link Activity} returned by the mutation.
|
|
6083
|
+
* @throws Throws if no authentication token is configured, `id` is missing or invalid, or the mutation request fails.
|
|
6084
|
+
* @see https://docs.anilist.co/reference/union/activityunion
|
|
6085
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6086
|
+
* @example
|
|
6087
|
+
* ```typescript
|
|
6088
|
+
* const result = await new SaveListActivityMutation("your-token").saveListActivity({ id: 1 });
|
|
6089
|
+
* ```
|
|
5178
6090
|
*/
|
|
5179
6091
|
async saveListActivity(variables, options) {
|
|
5180
6092
|
const mutation = `
|
|
@@ -5203,18 +6115,22 @@ const DeleteActivityMappings = {
|
|
|
5203
6115
|
};
|
|
5204
6116
|
class DeleteActivityMutation extends AniListOperation {
|
|
5205
6117
|
/**
|
|
5206
|
-
*
|
|
6118
|
+
* {@link DeleteActivityMutation.deleteActivity} sends a mutation request to delete an activity.
|
|
5207
6119
|
*
|
|
5208
6120
|
* The response is `{ deleted: boolean }`. A `true` value means the activity was deleted by
|
|
5209
6121
|
* this call; a `false` value means the activity was not present (already deleted or never
|
|
5210
6122
|
* existed). The mutation is therefore safe to retry after a partial failure: a `false` result
|
|
5211
6123
|
* confirms the target is gone rather than reporting an error.
|
|
5212
6124
|
*
|
|
5213
|
-
* @param variables -
|
|
5214
|
-
* @returns
|
|
5215
|
-
* @throws
|
|
6125
|
+
* @param variables - Values from {@link DeleteActivityVariables} for the mutation.
|
|
6126
|
+
* @returns The {@link DeleteResult} returned by the mutation.
|
|
6127
|
+
* @throws Throws if no authentication token is configured, `id` is missing or invalid, or the mutation request fails.
|
|
5216
6128
|
* @see https://docs.anilist.co/reference/object/deleted
|
|
5217
|
-
* @param options - Optional
|
|
6129
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6130
|
+
* @example
|
|
6131
|
+
* ```typescript
|
|
6132
|
+
* const result = await new DeleteActivityMutation("your-token").deleteActivity({ id: 1 });
|
|
6133
|
+
* ```
|
|
5218
6134
|
*/
|
|
5219
6135
|
async deleteActivity(variables, options) {
|
|
5220
6136
|
const mutation = `
|
|
@@ -5246,13 +6162,17 @@ const ToggleActivitySubscriptionMappings = {
|
|
|
5246
6162
|
};
|
|
5247
6163
|
class ToggleActivitySubscriptionMutation extends AniListOperation {
|
|
5248
6164
|
/**
|
|
5249
|
-
*
|
|
6165
|
+
* {@link ToggleActivitySubscriptionMutation.toggleActivitySubscription} sends a mutation request to subscribe to an activity.
|
|
5250
6166
|
*
|
|
5251
|
-
* @param variables -
|
|
5252
|
-
* @returns
|
|
5253
|
-
* @throws
|
|
5254
|
-
*
|
|
5255
|
-
* @param options - Optional
|
|
6167
|
+
* @param variables - Values from {@link ToggleActivitySubscriptionVariables} for the mutation.
|
|
6168
|
+
* @returns The {@link Activity} returned by the mutation.
|
|
6169
|
+
* @throws Throws if no authentication token is configured, `activityId` or `subscribe` is missing or invalid, or the mutation request fails.
|
|
6170
|
+
* @see https://docs.anilist.co/reference/union/activityunion
|
|
6171
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6172
|
+
* @example
|
|
6173
|
+
* ```typescript
|
|
6174
|
+
* const result = await new ToggleActivitySubscriptionMutation("your-token").toggleActivitySubscription({ activityId: 1, subscribe: true });
|
|
6175
|
+
* ```
|
|
5256
6176
|
*/
|
|
5257
6177
|
async toggleActivitySubscription(variables, options) {
|
|
5258
6178
|
const mutation = `
|
|
@@ -5284,13 +6204,17 @@ const ToggleActivityPinMappings = {
|
|
|
5284
6204
|
};
|
|
5285
6205
|
class ToggleActivityPinMutation extends AniListOperation {
|
|
5286
6206
|
/**
|
|
5287
|
-
*
|
|
6207
|
+
* {@link ToggleActivityPinMutation.toggleActivityPin} sends a mutation request to pin an activity.
|
|
5288
6208
|
*
|
|
5289
|
-
* @param variables -
|
|
5290
|
-
* @returns
|
|
5291
|
-
* @throws
|
|
5292
|
-
*
|
|
5293
|
-
* @param options - Optional
|
|
6209
|
+
* @param variables - Values from {@link ToggleActivityPinVariables} for the mutation.
|
|
6210
|
+
* @returns The {@link Activity} returned by the mutation.
|
|
6211
|
+
* @throws Throws if no authentication token is configured, `id` or `pinned` is missing or invalid, or the mutation request fails.
|
|
6212
|
+
* @see https://docs.anilist.co/reference/union/activityunion
|
|
6213
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6214
|
+
* @example
|
|
6215
|
+
* ```typescript
|
|
6216
|
+
* const result = await new ToggleActivityPinMutation("your-token").toggleActivityPin({ id: 1, pinned: true });
|
|
6217
|
+
* ```
|
|
5294
6218
|
*/
|
|
5295
6219
|
async toggleActivityPin(variables, options) {
|
|
5296
6220
|
const mutation = `
|
|
@@ -5324,13 +6248,17 @@ const SaveActivityReplyMappings = {
|
|
|
5324
6248
|
};
|
|
5325
6249
|
class SaveActivityReplyMutation extends AniListOperation {
|
|
5326
6250
|
/**
|
|
5327
|
-
*
|
|
6251
|
+
* {@link SaveActivityReplyMutation.saveActivityReply} sends a mutation request to save an activity reply.
|
|
5328
6252
|
*
|
|
5329
|
-
* @param variables -
|
|
5330
|
-
* @returns
|
|
5331
|
-
* @throws
|
|
5332
|
-
*
|
|
5333
|
-
* @param options - Optional
|
|
6253
|
+
* @param variables - Values from {@link SaveActivityReplyVariables} for the mutation.
|
|
6254
|
+
* @returns The {@link ActivityReply} returned by the mutation.
|
|
6255
|
+
* @throws Throws if no authentication token is configured, `id` or `text` is missing or invalid, or the mutation request fails.
|
|
6256
|
+
* @see https://docs.anilist.co/reference/object/activityreply
|
|
6257
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6258
|
+
* @example
|
|
6259
|
+
* ```typescript
|
|
6260
|
+
* const result = await new SaveActivityReplyMutation("your-token").saveActivityReply({ id: 1, text: "Hello, world!" });
|
|
6261
|
+
* ```
|
|
5334
6262
|
*/
|
|
5335
6263
|
async saveActivityReply(variables, options) {
|
|
5336
6264
|
const mutation = `
|
|
@@ -5360,18 +6288,22 @@ const DeleteActivityReplyMappings = {
|
|
|
5360
6288
|
};
|
|
5361
6289
|
class DeleteActivityReplyMutation extends AniListOperation {
|
|
5362
6290
|
/**
|
|
5363
|
-
*
|
|
6291
|
+
* {@link DeleteActivityReplyMutation.deleteActivityReply} sends a mutation request to delete an activity reply.
|
|
5364
6292
|
*
|
|
5365
6293
|
* The response is `{ deleted: boolean }`. A `true` value means the reply was deleted by this
|
|
5366
6294
|
* call; a `false` value means the reply was not present (already deleted or never existed).
|
|
5367
6295
|
* The mutation is therefore safe to retry after a partial failure: a `false` result confirms
|
|
5368
6296
|
* the target is gone rather than reporting an error.
|
|
5369
6297
|
*
|
|
5370
|
-
* @param variables -
|
|
5371
|
-
* @returns
|
|
5372
|
-
* @throws
|
|
6298
|
+
* @param variables - Values from {@link DeleteActivityReplyVariables} for the mutation.
|
|
6299
|
+
* @returns The {@link DeleteResult} returned by the mutation.
|
|
6300
|
+
* @throws Throws if no authentication token is configured, `id` is missing or invalid, or the mutation request fails.
|
|
5373
6301
|
* @see https://docs.anilist.co/reference/object/deleted
|
|
5374
|
-
* @param options - Optional
|
|
6302
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6303
|
+
* @example
|
|
6304
|
+
* ```typescript
|
|
6305
|
+
* const result = await new DeleteActivityReplyMutation("your-token").deleteActivityReply({ id: 1 });
|
|
6306
|
+
* ```
|
|
5375
6307
|
*/
|
|
5376
6308
|
async deleteActivityReply(variables, options) {
|
|
5377
6309
|
const mutation = `
|
|
@@ -5402,13 +6334,18 @@ const ToggleLikeMappings = {
|
|
|
5402
6334
|
};
|
|
5403
6335
|
class ToggleLikeMutation extends AniListOperation {
|
|
5404
6336
|
/**
|
|
5405
|
-
*
|
|
6337
|
+
* {@link ToggleLikeMutation.toggleLike} sends a mutation request to toggle a like.
|
|
5406
6338
|
*
|
|
5407
|
-
* @
|
|
5408
|
-
* @
|
|
5409
|
-
* @
|
|
5410
|
-
*
|
|
5411
|
-
* @
|
|
6339
|
+
* @deprecated Prefer `ToggleLikeV2Mutation.toggleLikeV2`, which returns the richer `Likeable` union (activity, activity reply, thread, or thread comment) instead of a bare user.
|
|
6340
|
+
* @param variables - Values from {@link ToggleLikeVariables} for the mutation.
|
|
6341
|
+
* @returns The {@link BasicUser} returned by the mutation.
|
|
6342
|
+
* @throws Throws if no authentication token is configured, `id` or `type` is missing or invalid, or the mutation request fails.
|
|
6343
|
+
* @see https://docs.anilist.co/reference/object/user
|
|
6344
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6345
|
+
* @example
|
|
6346
|
+
* ```typescript
|
|
6347
|
+
* const result = await new ToggleLikeMutation("your-token").toggleLike({ id: 1, type: "ACTIVITY" });
|
|
6348
|
+
* ```
|
|
5412
6349
|
*/
|
|
5413
6350
|
async toggleLike(variables, options) {
|
|
5414
6351
|
const mutation = `
|
|
@@ -5440,14 +6377,17 @@ const ToggleLikeV2Mappings = {
|
|
|
5440
6377
|
};
|
|
5441
6378
|
class ToggleLikeV2Mutation extends AniListOperation {
|
|
5442
6379
|
/**
|
|
5443
|
-
*
|
|
6380
|
+
* {@link ToggleLikeV2Mutation.toggleLikeV2} sends a mutation request to toggle a like.
|
|
5444
6381
|
*
|
|
5445
|
-
* @param variables -
|
|
5446
|
-
* @returns
|
|
5447
|
-
*
|
|
5448
|
-
* @
|
|
5449
|
-
*
|
|
5450
|
-
* @
|
|
6382
|
+
* @param variables - Values from {@link ToggleLikeV2Variables} for the mutation.
|
|
6383
|
+
* @returns The {@link Likeable} returned by the mutation: an activity, activity reply, thread, or thread comment.
|
|
6384
|
+
* @throws Throws if no authentication token is configured, `id` or `type` is missing or invalid, or the mutation request fails.
|
|
6385
|
+
* @see https://docs.anilist.co/reference/union/likeableunion
|
|
6386
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6387
|
+
* @example
|
|
6388
|
+
* ```typescript
|
|
6389
|
+
* const result = await new ToggleLikeV2Mutation("your-token").toggleLikeV2({ id: 1, type: "ACTIVITY" });
|
|
6390
|
+
* ```
|
|
5451
6391
|
*/
|
|
5452
6392
|
async toggleLikeV2(variables, options) {
|
|
5453
6393
|
const mutation = `
|
|
@@ -5477,13 +6417,17 @@ const ToggleFollowMappings = {
|
|
|
5477
6417
|
};
|
|
5478
6418
|
class ToggleFollowMutation extends AniListOperation {
|
|
5479
6419
|
/**
|
|
5480
|
-
*
|
|
6420
|
+
* {@link ToggleFollowMutation.toggleFollow} sends a mutation request to toggle a follow.
|
|
5481
6421
|
*
|
|
5482
|
-
* @param variables -
|
|
5483
|
-
* @returns
|
|
5484
|
-
* @throws
|
|
5485
|
-
*
|
|
5486
|
-
* @param options - Optional
|
|
6422
|
+
* @param variables - Values from {@link ToggleFollowVariables} for the mutation.
|
|
6423
|
+
* @returns The {@link UserResponse} returned by the mutation.
|
|
6424
|
+
* @throws Throws if no authentication token is configured, `userId` is missing or invalid, or the mutation request fails.
|
|
6425
|
+
* @see https://docs.anilist.co/reference/object/user
|
|
6426
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6427
|
+
* @example
|
|
6428
|
+
* ```typescript
|
|
6429
|
+
* const result = await new ToggleFollowMutation("your-token").toggleFollow({ userId: 1 });
|
|
6430
|
+
* ```
|
|
5487
6431
|
*/
|
|
5488
6432
|
async toggleFollow(variables, options) {
|
|
5489
6433
|
const mutation = `
|
|
@@ -5593,13 +6537,17 @@ const ToggleFavouriteMappings = {
|
|
|
5593
6537
|
};
|
|
5594
6538
|
class ToggleFavouriteMutation extends AniListOperation {
|
|
5595
6539
|
/**
|
|
5596
|
-
*
|
|
6540
|
+
* {@link ToggleFavouriteMutation.toggleFavourite} sends a mutation request to toggle a favourite.
|
|
5597
6541
|
*
|
|
5598
|
-
* @param variables -
|
|
5599
|
-
* @returns
|
|
5600
|
-
* @throws
|
|
6542
|
+
* @param variables - Values from {@link ToggleFavouriteVariables} for the mutation.
|
|
6543
|
+
* @returns The {@link Favourites} returned by the mutation.
|
|
6544
|
+
* @throws Throws if no authentication token is configured, at least one favourite ID is missing or invalid, or the mutation request fails.
|
|
5601
6545
|
* @see https://docs.anilist.co/reference/object/favourites
|
|
5602
|
-
* @param options - Optional
|
|
6546
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6547
|
+
* @example
|
|
6548
|
+
* ```typescript
|
|
6549
|
+
* const result = await new ToggleFavouriteMutation("your-token").toggleFavourite({ animeId: 1, mangaId: 1, characterId: 1, staffId: 1, studioId: 1 });
|
|
6550
|
+
* ```
|
|
5603
6551
|
*/
|
|
5604
6552
|
async toggleFavourite(variables, options) {
|
|
5605
6553
|
const mutation = `
|
|
@@ -5638,13 +6586,17 @@ const UpdateFavouriteOrderMappings = {
|
|
|
5638
6586
|
};
|
|
5639
6587
|
class UpdateFavouriteOrderMutation extends AniListOperation {
|
|
5640
6588
|
/**
|
|
5641
|
-
*
|
|
6589
|
+
* {@link UpdateFavouriteOrderMutation.updateFavouriteOrder} sends a mutation request to update the order of favourites.
|
|
5642
6590
|
*
|
|
5643
|
-
* @param variables -
|
|
5644
|
-
* @returns
|
|
5645
|
-
* @throws
|
|
6591
|
+
* @param variables - Values from {@link UpdateFavouriteOrderVariables} for the mutation.
|
|
6592
|
+
* @returns The {@link Favourites} returned by the mutation.
|
|
6593
|
+
* @throws Throws if no authentication token is configured, an order array lacks its corresponding ID array, a variable has an invalid type, or the mutation request fails.
|
|
5646
6594
|
* @see https://docs.anilist.co/reference/object/favourites
|
|
5647
|
-
* @param options - Optional
|
|
6595
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6596
|
+
* @example
|
|
6597
|
+
* ```typescript
|
|
6598
|
+
* const result = await new UpdateFavouriteOrderMutation("your-token").updateFavouriteOrder({ animeIds: [1], mangaIds: [], characterIds: [], staffIds: [], studioIds: [], animeOrder: [1], mangaOrder: [], characterOrder: [], staffOrder: [], studioOrder: [] });
|
|
6599
|
+
* ```
|
|
5648
6600
|
*/
|
|
5649
6601
|
async updateFavouriteOrder(variables, options) {
|
|
5650
6602
|
if (!variables.animeIds && variables.animeOrder || !variables.mangaIds && variables.mangaOrder || !variables.characterIds && variables.characterOrder || !variables.staffIds && variables.staffOrder || !variables.studioIds && variables.studioOrder) {
|
|
@@ -5678,13 +6630,17 @@ const SaveReviewMappings = {
|
|
|
5678
6630
|
};
|
|
5679
6631
|
class SaveReviewMutation extends AniListOperation {
|
|
5680
6632
|
/**
|
|
5681
|
-
*
|
|
6633
|
+
* {@link SaveReviewMutation.saveReview} sends a mutation request to save a review.
|
|
5682
6634
|
*
|
|
5683
|
-
* @param variables -
|
|
5684
|
-
* @returns
|
|
5685
|
-
* @throws
|
|
6635
|
+
* @param variables - Values from {@link SaveReviewVariables} for the mutation.
|
|
6636
|
+
* @returns The {@link ReviewResponse} returned by the mutation.
|
|
6637
|
+
* @throws Throws if no authentication token is configured, `id` or `mediaId` is missing, a variable has an invalid type, or the mutation request fails.
|
|
5686
6638
|
* @see https://docs.anilist.co/reference/object/review
|
|
5687
|
-
* @param options - Optional
|
|
6639
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6640
|
+
* @example
|
|
6641
|
+
* ```typescript
|
|
6642
|
+
* const result = await new SaveReviewMutation("your-token").saveReview({ id: 1, mediaId: 1, body: "Example review", summary: "Example", score: 8, private: false });
|
|
6643
|
+
* ```
|
|
5688
6644
|
*/
|
|
5689
6645
|
async saveReview(variables, options) {
|
|
5690
6646
|
const mutation = `
|
|
@@ -5717,13 +6673,17 @@ const RateReviewMappings = {
|
|
|
5717
6673
|
};
|
|
5718
6674
|
class RateReviewMutation extends AniListOperation {
|
|
5719
6675
|
/**
|
|
5720
|
-
*
|
|
6676
|
+
* {@link RateReviewMutation.rateReview} sends a mutation request to rate a review.
|
|
5721
6677
|
*
|
|
5722
|
-
* @param variables -
|
|
5723
|
-
* @returns
|
|
5724
|
-
* @throws
|
|
6678
|
+
* @param variables - Values from {@link RateReviewVariables} for the mutation.
|
|
6679
|
+
* @returns The {@link ReviewResponse} returned by the mutation.
|
|
6680
|
+
* @throws Throws if no authentication token is configured, `reviewId` or `rating` is missing or invalid, or the mutation request fails.
|
|
5725
6681
|
* @see https://docs.anilist.co/reference/object/review
|
|
5726
|
-
* @param options - Optional
|
|
6682
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6683
|
+
* @example
|
|
6684
|
+
* ```typescript
|
|
6685
|
+
* const result = await new RateReviewMutation("your-token").rateReview({ reviewId: 1, rating: "UP_VOTE" });
|
|
6686
|
+
* ```
|
|
5727
6687
|
*/
|
|
5728
6688
|
async rateReview(variables, options) {
|
|
5729
6689
|
const mutation = `
|
|
@@ -5753,18 +6713,22 @@ const DeleteReviewMappings = {
|
|
|
5753
6713
|
};
|
|
5754
6714
|
class DeleteReviewMutation extends AniListOperation {
|
|
5755
6715
|
/**
|
|
5756
|
-
*
|
|
6716
|
+
* {@link DeleteReviewMutation.deleteReview} sends a mutation request to delete a review.
|
|
5757
6717
|
*
|
|
5758
6718
|
* The response is `{ deleted: boolean }`. A `true` value means the review was deleted by this
|
|
5759
6719
|
* call; a `false` value means the review was not present (already deleted or never existed).
|
|
5760
6720
|
* The mutation is therefore safe to retry after a partial failure: a `false` result confirms
|
|
5761
6721
|
* the target is gone rather than reporting an error.
|
|
5762
6722
|
*
|
|
5763
|
-
* @param variables -
|
|
5764
|
-
* @returns
|
|
5765
|
-
* @throws
|
|
6723
|
+
* @param variables - Values from {@link DeleteReviewVariables} for the mutation.
|
|
6724
|
+
* @returns The {@link DeleteResult} returned by the mutation.
|
|
6725
|
+
* @throws Throws if no authentication token is configured, `id` is missing or invalid, or the mutation request fails.
|
|
5766
6726
|
* @see https://docs.anilist.co/reference/object/deleted
|
|
5767
|
-
* @param options - Optional
|
|
6727
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6728
|
+
* @example
|
|
6729
|
+
* ```typescript
|
|
6730
|
+
* const result = await new DeleteReviewMutation("your-token").deleteReview({ id: 1 });
|
|
6731
|
+
* ```
|
|
5768
6732
|
*/
|
|
5769
6733
|
async deleteReview(variables, options) {
|
|
5770
6734
|
const mutation = `
|
|
@@ -5803,13 +6767,17 @@ const SaveRecommendationMappings = {
|
|
|
5803
6767
|
};
|
|
5804
6768
|
class SaveRecommendationMutation extends AniListOperation {
|
|
5805
6769
|
/**
|
|
5806
|
-
*
|
|
6770
|
+
* {@link SaveRecommendationMutation.saveRecommendation} sends a mutation request to save a recommendation.
|
|
5807
6771
|
*
|
|
5808
|
-
* @param variables -
|
|
5809
|
-
* @returns
|
|
5810
|
-
* @throws
|
|
6772
|
+
* @param variables - Values from {@link SaveRecommendationVariables} for the mutation.
|
|
6773
|
+
* @returns The {@link RecommendationResponse} returned by the mutation.
|
|
6774
|
+
* @throws Throws if no authentication token is configured, `mediaId`, `mediaRecommendationId`, or `rating` is missing or invalid, or the mutation request fails.
|
|
5811
6775
|
* @see https://docs.anilist.co/reference/object/recommendation
|
|
5812
|
-
* @param options - Optional
|
|
6776
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6777
|
+
* @example
|
|
6778
|
+
* ```typescript
|
|
6779
|
+
* const result = await new SaveRecommendationMutation("your-token").saveRecommendation({ mediaId: 1, mediaRecommendationId: 2, rating: "RATE_UP" });
|
|
6780
|
+
* ```
|
|
5813
6781
|
*/
|
|
5814
6782
|
async saveRecommendation(variables, options) {
|
|
5815
6783
|
const mutation = `
|
|
@@ -5846,13 +6814,17 @@ const SaveThreadMappings = {
|
|
|
5846
6814
|
};
|
|
5847
6815
|
class SaveThreadMutation extends AniListOperation {
|
|
5848
6816
|
/**
|
|
5849
|
-
*
|
|
6817
|
+
* {@link SaveThreadMutation.saveThread} sends a mutation request to save a thread.
|
|
5850
6818
|
*
|
|
5851
|
-
* @param variables -
|
|
5852
|
-
* @returns
|
|
5853
|
-
* @throws
|
|
5854
|
-
*
|
|
5855
|
-
* @param options - Optional
|
|
6819
|
+
* @param variables - Values from {@link SaveThreadVariables} for the mutation.
|
|
6820
|
+
* @returns The {@link ThreadResponse} returned by the mutation.
|
|
6821
|
+
* @throws Throws if no authentication token is configured, `id` or `title` is missing, a variable has an invalid type, or the mutation request fails.
|
|
6822
|
+
* @see https://docs.anilist.co/reference/object/thread
|
|
6823
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6824
|
+
* @example
|
|
6825
|
+
* ```typescript
|
|
6826
|
+
* const result = await new SaveThreadMutation("your-token").saveThread({ id: 1, title: "Example thread", body: "Hello, world!", categories: [], mediaCategories: [], sticky: false, locked: false, asHtml: true });
|
|
6827
|
+
* ```
|
|
5856
6828
|
*/
|
|
5857
6829
|
async saveThread(variables, options) {
|
|
5858
6830
|
const mutation = `
|
|
@@ -5882,18 +6854,22 @@ const DeleteThreadMappings = {
|
|
|
5882
6854
|
};
|
|
5883
6855
|
class DeleteThreadMutation extends AniListOperation {
|
|
5884
6856
|
/**
|
|
5885
|
-
*
|
|
6857
|
+
* {@link DeleteThreadMutation.deleteThread} sends a mutation request to delete a thread.
|
|
5886
6858
|
*
|
|
5887
6859
|
* The response is `{ deleted: boolean }`. A `true` value means the thread was deleted by this
|
|
5888
6860
|
* call; a `false` value means the thread was not present (already deleted or never existed).
|
|
5889
6861
|
* The mutation is therefore safe to retry after a partial failure: a `false` result confirms
|
|
5890
6862
|
* the target is gone rather than reporting an error.
|
|
5891
6863
|
*
|
|
5892
|
-
* @param variables -
|
|
5893
|
-
* @returns
|
|
5894
|
-
* @throws
|
|
6864
|
+
* @param variables - Values from {@link DeleteThreadVariables} for the mutation.
|
|
6865
|
+
* @returns The {@link DeleteResult} returned by the mutation.
|
|
6866
|
+
* @throws Throws if no authentication token is configured, `id` is missing or invalid, or the mutation request fails.
|
|
5895
6867
|
* @see https://docs.anilist.co/reference/object/deleted
|
|
5896
|
-
* @param options - Optional
|
|
6868
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6869
|
+
* @example
|
|
6870
|
+
* ```typescript
|
|
6871
|
+
* const result = await new DeleteThreadMutation("your-token").deleteThread({ id: 1 });
|
|
6872
|
+
* ```
|
|
5897
6873
|
*/
|
|
5898
6874
|
async deleteThread(variables, options) {
|
|
5899
6875
|
const mutation = `
|
|
@@ -5925,13 +6901,17 @@ const ToggleThreadSubscriptionMappings = {
|
|
|
5925
6901
|
};
|
|
5926
6902
|
class ToggleThreadSubscriptionMutation extends AniListOperation {
|
|
5927
6903
|
/**
|
|
5928
|
-
*
|
|
6904
|
+
* {@link ToggleThreadSubscriptionMutation.toggleThreadSubscription} sends a mutation request to subscribe to a thread.
|
|
5929
6905
|
*
|
|
5930
|
-
* @param variables -
|
|
5931
|
-
* @returns
|
|
5932
|
-
* @throws
|
|
5933
|
-
*
|
|
5934
|
-
* @param options - Optional
|
|
6906
|
+
* @param variables - Values from {@link ToggleThreadSubscriptionVariables} for the mutation.
|
|
6907
|
+
* @returns The {@link ThreadResponse} returned by the mutation.
|
|
6908
|
+
* @throws Throws if no authentication token is configured, `threadId` or `subscribe` is missing or invalid, or the mutation request fails.
|
|
6909
|
+
* @see https://docs.anilist.co/reference/object/thread
|
|
6910
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6911
|
+
* @example
|
|
6912
|
+
* ```typescript
|
|
6913
|
+
* const result = await new ToggleThreadSubscriptionMutation("your-token").toggleThreadSubscription({ threadId: 1, subscribe: true });
|
|
6914
|
+
* ```
|
|
5935
6915
|
*/
|
|
5936
6916
|
async toggleThreadSubscription(variables, options) {
|
|
5937
6917
|
const mutation = `
|
|
@@ -5966,13 +6946,17 @@ const SaveThreadCommentMappings = {
|
|
|
5966
6946
|
};
|
|
5967
6947
|
class SaveThreadCommentMutation extends AniListOperation {
|
|
5968
6948
|
/**
|
|
5969
|
-
*
|
|
6949
|
+
* {@link SaveThreadCommentMutation.saveThreadComment} sends a mutation request to save a thread comment.
|
|
5970
6950
|
*
|
|
5971
|
-
* @param variables -
|
|
5972
|
-
* @returns
|
|
5973
|
-
* @throws
|
|
5974
|
-
*
|
|
5975
|
-
* @param options - Optional
|
|
6951
|
+
* @param variables - Values from {@link SaveThreadCommentVariables} for the mutation.
|
|
6952
|
+
* @returns The {@link ThreadCommentResponse} returned by the mutation.
|
|
6953
|
+
* @throws Throws if no authentication token is configured, `id` or `threadId` is missing, a variable has an invalid type, or the mutation request fails.
|
|
6954
|
+
* @see https://docs.anilist.co/reference/object/threadcomment
|
|
6955
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
6956
|
+
* @example
|
|
6957
|
+
* ```typescript
|
|
6958
|
+
* const result = await new SaveThreadCommentMutation("your-token").saveThreadComment({ id: 1, threadId: 1, parentCommentId: 0, comment: "Hello, world!", locked: false, asHtml: true });
|
|
6959
|
+
* ```
|
|
5976
6960
|
*/
|
|
5977
6961
|
async saveThreadComment(variables, options) {
|
|
5978
6962
|
const mutation = `
|
|
@@ -6002,18 +6986,22 @@ const DeleteThreadCommentMappings = {
|
|
|
6002
6986
|
};
|
|
6003
6987
|
class DeleteThreadCommentMutation extends AniListOperation {
|
|
6004
6988
|
/**
|
|
6005
|
-
*
|
|
6989
|
+
* {@link DeleteThreadCommentMutation.deleteThreadComment} sends a mutation request to delete a thread comment.
|
|
6006
6990
|
*
|
|
6007
6991
|
* The response is `{ deleted: boolean }`. A `true` value means the comment was deleted by this
|
|
6008
6992
|
* call; a `false` value means the comment was not present (already deleted or never existed).
|
|
6009
6993
|
* The mutation is therefore safe to retry after a partial failure: a `false` result confirms
|
|
6010
6994
|
* the target is gone rather than reporting an error.
|
|
6011
6995
|
*
|
|
6012
|
-
* @param variables -
|
|
6013
|
-
* @returns
|
|
6014
|
-
* @throws
|
|
6996
|
+
* @param variables - Values from {@link DeleteThreadCommentVariables} for the mutation.
|
|
6997
|
+
* @returns The {@link DeleteResult} returned by the mutation.
|
|
6998
|
+
* @throws Throws if no authentication token is configured, `id` is missing or invalid, or the mutation request fails.
|
|
6015
6999
|
* @see https://docs.anilist.co/reference/object/deleted
|
|
6016
|
-
* @param options - Optional
|
|
7000
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
7001
|
+
* @example
|
|
7002
|
+
* ```typescript
|
|
7003
|
+
* const result = await new DeleteThreadCommentMutation("your-token").deleteThreadComment({ id: 1 });
|
|
7004
|
+
* ```
|
|
6017
7005
|
*/
|
|
6018
7006
|
async deleteThreadComment(variables, options) {
|
|
6019
7007
|
const mutation = `
|
|
@@ -6046,13 +7034,17 @@ const UpdateAniChartSettingsMappings = {
|
|
|
6046
7034
|
};
|
|
6047
7035
|
class UpdateAniChartSettingsMutation extends AniListOperation {
|
|
6048
7036
|
/**
|
|
6049
|
-
*
|
|
7037
|
+
* {@link UpdateAniChartSettingsMutation.updateAniChartSettings} sends a mutation request to update the AniChart settings.
|
|
6050
7038
|
*
|
|
6051
|
-
* @param variables -
|
|
6052
|
-
* @returns
|
|
6053
|
-
* @throws
|
|
7039
|
+
* @param variables - Values from {@link UpdateAniChartSettingsVariables} for the mutation.
|
|
7040
|
+
* @returns The updated AniChart settings string returned by the mutation.
|
|
7041
|
+
* @throws Throws if no authentication token is configured, a setting has an invalid type, or the mutation request fails.
|
|
6054
7042
|
* @see https://docs.anilist.co/reference/object/anichartuser
|
|
6055
|
-
* @param options - Optional
|
|
7043
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
7044
|
+
* @example
|
|
7045
|
+
* ```typescript
|
|
7046
|
+
* const result = await new UpdateAniChartSettingsMutation("your-token").updateAniChartSettings({ titleLanguage: "romaji", outgoingLinkProvider: "ANILIST", theme: "dark", sort: "POPULARITY" });
|
|
7047
|
+
* ```
|
|
6056
7048
|
*/
|
|
6057
7049
|
async updateAniChartSettings(variables, options) {
|
|
6058
7050
|
const mutation = `
|
|
@@ -6076,13 +7068,17 @@ const UpdateAniChartHighlightsMappings = {
|
|
|
6076
7068
|
};
|
|
6077
7069
|
class UpdateAniChartHighlightsMutation extends AniListOperation {
|
|
6078
7070
|
/**
|
|
6079
|
-
*
|
|
7071
|
+
* {@link UpdateAniChartHighlightsMutation.updateAniChartHighlights} sends a mutation request to update the AniChart highlights.
|
|
6080
7072
|
*
|
|
6081
|
-
* @param variables -
|
|
6082
|
-
* @returns
|
|
6083
|
-
* @throws
|
|
7073
|
+
* @param variables - Values from {@link UpdateAniChartHighlightsVariables} for the mutation.
|
|
7074
|
+
* @returns The updated AniChart highlights string returned by the mutation.
|
|
7075
|
+
* @throws Throws if no authentication token is configured, `highlights` is missing or invalid, or the mutation request fails.
|
|
6084
7076
|
* @see https://docs.anilist.co/reference/object/anichartuser
|
|
6085
|
-
* @param options - Optional
|
|
7077
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
7078
|
+
* @example
|
|
7079
|
+
* ```typescript
|
|
7080
|
+
* const result = await new UpdateAniChartHighlightsMutation("your-token").updateAniChartHighlights({ highlights: { mediaId: 1, highlight: true } });
|
|
7081
|
+
* ```
|
|
6086
7082
|
*/
|
|
6087
7083
|
async updateAniChartHighlights(variables, options) {
|
|
6088
7084
|
const mutation = `
|
|
@@ -6123,13 +7119,17 @@ const UpdateMediaListEntriesMappings = {
|
|
|
6123
7119
|
};
|
|
6124
7120
|
class UpdateMediaListEntriesMutation extends AniListOperation {
|
|
6125
7121
|
/**
|
|
6126
|
-
*
|
|
7122
|
+
* {@link UpdateMediaListEntriesMutation.updateMediaListEntries} sends a mutation request to update media list entries.
|
|
6127
7123
|
*
|
|
6128
|
-
* @param variables -
|
|
6129
|
-
* @returns
|
|
6130
|
-
* @throws
|
|
6131
|
-
*
|
|
6132
|
-
* @param options - Optional
|
|
7124
|
+
* @param variables - Values from {@link UpdateMediaListEntriesVariables} for the mutation.
|
|
7125
|
+
* @returns The updated {@link MediaListResponse} entries returned by the mutation.
|
|
7126
|
+
* @throws Throws if no authentication token is configured, `ids` is missing or invalid, or the mutation request fails.
|
|
7127
|
+
* @see https://docs.anilist.co/reference/object/medialist
|
|
7128
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
7129
|
+
* @example
|
|
7130
|
+
* ```typescript
|
|
7131
|
+
* const result = await new UpdateMediaListEntriesMutation("your-token").updateMediaListEntries({ ids: [1], status: "CURRENT", progress: 1 });
|
|
7132
|
+
* ```
|
|
6133
7133
|
*/
|
|
6134
7134
|
async updateMediaListEntries(variables, options) {
|
|
6135
7135
|
const mutation = `
|
|
@@ -6245,13 +7245,17 @@ const UpdateUserMappings = {
|
|
|
6245
7245
|
};
|
|
6246
7246
|
class UpdateUserMutation extends AniListOperation {
|
|
6247
7247
|
/**
|
|
6248
|
-
*
|
|
7248
|
+
* {@link UpdateUserMutation.updateUser} sends a mutation request to update a user.
|
|
6249
7249
|
*
|
|
6250
|
-
* @param variables -
|
|
6251
|
-
* @returns
|
|
6252
|
-
* @throws
|
|
7250
|
+
* @param variables - Values from {@link UpdateUserVariables} for the mutation.
|
|
7251
|
+
* @returns The {@link UpdateUserResponse} returned by the mutation.
|
|
7252
|
+
* @throws Throws if no authentication token is configured, a variable has an invalid type, or the mutation request fails.
|
|
6253
7253
|
* @see https://docs.anilist.co/reference/object/user
|
|
6254
|
-
* @param options - Optional
|
|
7254
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
7255
|
+
* @example
|
|
7256
|
+
* ```typescript
|
|
7257
|
+
* const result = await new UpdateUserMutation("your-token").updateUser({ about: "Updated profile" });
|
|
7258
|
+
* ```
|
|
6255
7259
|
*/
|
|
6256
7260
|
async updateUser(variables, options) {
|
|
6257
7261
|
const mutation = `
|
|
@@ -6337,13 +7341,17 @@ const SaveMediaListEntryMappings = {
|
|
|
6337
7341
|
};
|
|
6338
7342
|
class SaveMediaListEntryMutation extends AniListOperation {
|
|
6339
7343
|
/**
|
|
6340
|
-
*
|
|
7344
|
+
* {@link SaveMediaListEntryMutation.saveMediaListEntry} sends a mutation request to save a media list entry.
|
|
6341
7345
|
*
|
|
6342
|
-
* @param variables -
|
|
6343
|
-
* @returns
|
|
6344
|
-
* @throws
|
|
7346
|
+
* @param variables - Values from {@link SaveMediaListEntryVariables} for the mutation.
|
|
7347
|
+
* @returns The {@link MediaListResponse} returned by the mutation.
|
|
7348
|
+
* @throws Throws if no authentication token is configured, `mediaId` is missing or invalid, or the mutation request fails.
|
|
6345
7349
|
* @see https://docs.anilist.co/reference/object/medialist
|
|
6346
|
-
* @param options - Optional
|
|
7350
|
+
* @param options - Optional {@link RequestOptions} merged over the instance-level settings for this call only.
|
|
7351
|
+
* @example
|
|
7352
|
+
* ```typescript
|
|
7353
|
+
* const result = await new SaveMediaListEntryMutation("your-token").saveMediaListEntry({ mediaId: 1, status: "COMPLETED" });
|
|
7354
|
+
* ```
|
|
6347
7355
|
*/
|
|
6348
7356
|
async saveMediaListEntry(variables, options) {
|
|
6349
7357
|
const mutation = `
|
|
@@ -6387,7 +7395,7 @@ class SaveMediaListEntryMutation extends AniListOperation {
|
|
|
6387
7395
|
}
|
|
6388
7396
|
|
|
6389
7397
|
function op(name, operationClass) {
|
|
6390
|
-
return { name, operationClass };
|
|
7398
|
+
return { name, operationClass, methodName: name };
|
|
6391
7399
|
}
|
|
6392
7400
|
function opAs(name, operationClass, methodName) {
|
|
6393
7401
|
return { name, operationClass, methodName };
|
|
@@ -6473,118 +7481,571 @@ const ANILIST_OPERATION_REGISTRY = {
|
|
|
6473
7481
|
]
|
|
6474
7482
|
};
|
|
6475
7483
|
|
|
6476
|
-
function
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
methodName: entry.methodName,
|
|
6480
|
-
instance: new entry.operationClass(authToken, options)
|
|
6481
|
-
}));
|
|
6482
|
-
}
|
|
6483
|
-
function bindEntries(entries) {
|
|
6484
|
-
const bound = {};
|
|
6485
|
-
for (const { name, instance, methodName } of entries) {
|
|
6486
|
-
const method = instance[methodName ?? name];
|
|
7484
|
+
function validateCategoryMethods(category) {
|
|
7485
|
+
for (const entry of ANILIST_OPERATION_REGISTRY[category]) {
|
|
7486
|
+
const method = entry.operationClass.prototype[entry.methodName];
|
|
6487
7487
|
if (typeof method !== "function") {
|
|
6488
7488
|
throw new TypeError(
|
|
6489
|
-
`Operation "${name}" does not expose a "${methodName
|
|
7489
|
+
`Operation "${entry.name}" does not expose a "${entry.methodName}" method to bind.`
|
|
6490
7490
|
);
|
|
6491
7491
|
}
|
|
6492
|
-
bound[name] = method.bind(instance);
|
|
6493
7492
|
}
|
|
6494
|
-
|
|
7493
|
+
}
|
|
7494
|
+
function buildLazyGroup(category, authToken, options) {
|
|
7495
|
+
const entries = ANILIST_OPERATION_REGISTRY[category];
|
|
7496
|
+
const descriptors = {};
|
|
7497
|
+
for (const entry of entries) {
|
|
7498
|
+
let bound;
|
|
7499
|
+
descriptors[entry.name] = {
|
|
7500
|
+
enumerable: true,
|
|
7501
|
+
configurable: false,
|
|
7502
|
+
get() {
|
|
7503
|
+
if (bound === void 0) {
|
|
7504
|
+
const instance = new entry.operationClass(
|
|
7505
|
+
authToken,
|
|
7506
|
+
options
|
|
7507
|
+
);
|
|
7508
|
+
const method = instance[entry.methodName];
|
|
7509
|
+
if (typeof method !== "function") {
|
|
7510
|
+
throw new TypeError(
|
|
7511
|
+
`Operation "${entry.name}" does not expose a "${entry.methodName}" method to bind.`
|
|
7512
|
+
);
|
|
7513
|
+
}
|
|
7514
|
+
bound = method.bind(instance);
|
|
7515
|
+
}
|
|
7516
|
+
return bound;
|
|
7517
|
+
}
|
|
7518
|
+
};
|
|
7519
|
+
}
|
|
7520
|
+
return Object.defineProperties({}, descriptors);
|
|
6495
7521
|
}
|
|
6496
7522
|
function buildAniListWiring(authToken, options) {
|
|
6497
|
-
const
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
7523
|
+
for (const category of ["query", "page", "mutation"]) {
|
|
7524
|
+
validateCategoryMethods(category);
|
|
7525
|
+
}
|
|
7526
|
+
const queryFacade = buildLazyGroup("query", authToken, options);
|
|
7527
|
+
const pageFacade = buildLazyGroup("page", authToken, options);
|
|
7528
|
+
const mutationFacade = buildLazyGroup("mutation", authToken, options);
|
|
7529
|
+
Object.defineProperty(queryFacade, "page", {
|
|
7530
|
+
value: pageFacade,
|
|
7531
|
+
enumerable: true,
|
|
7532
|
+
configurable: false,
|
|
7533
|
+
writable: false
|
|
7534
|
+
});
|
|
7535
|
+
let customBound;
|
|
7536
|
+
return Object.defineProperties(
|
|
7537
|
+
{
|
|
7538
|
+
query: queryFacade,
|
|
7539
|
+
mutation: mutationFacade,
|
|
7540
|
+
paginate,
|
|
7541
|
+
paginatePages,
|
|
7542
|
+
paginateChunks,
|
|
7543
|
+
fuzzyDate,
|
|
7544
|
+
flattenMediaListCollection
|
|
6504
7545
|
},
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
7546
|
+
{
|
|
7547
|
+
custom: {
|
|
7548
|
+
enumerable: true,
|
|
7549
|
+
configurable: false,
|
|
7550
|
+
get() {
|
|
7551
|
+
if (customBound === void 0) {
|
|
7552
|
+
const customInstance = new CustomRequest(authToken, options);
|
|
7553
|
+
customBound = customInstance.custom.bind(customInstance);
|
|
7554
|
+
}
|
|
7555
|
+
return customBound;
|
|
7556
|
+
}
|
|
7557
|
+
}
|
|
7558
|
+
}
|
|
7559
|
+
);
|
|
6512
7560
|
}
|
|
6513
7561
|
|
|
6514
7562
|
function buildAniListApi(authToken, options) {
|
|
6515
7563
|
return buildAniListWiring(authToken, options);
|
|
6516
7564
|
}
|
|
6517
7565
|
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
7566
|
+
const TRANSPORT_OPTION_KEYS = [
|
|
7567
|
+
"timeout",
|
|
7568
|
+
"signal",
|
|
7569
|
+
"exposeRawAxiosError",
|
|
7570
|
+
"retry",
|
|
7571
|
+
"paceWithRateLimit",
|
|
7572
|
+
"rateLimitFloor",
|
|
7573
|
+
"circuitBreaker",
|
|
7574
|
+
"retryBudget",
|
|
7575
|
+
"maxSockets",
|
|
7576
|
+
"maxFreeSockets",
|
|
7577
|
+
"onError",
|
|
7578
|
+
"onRetry",
|
|
7579
|
+
"onRequestStart",
|
|
7580
|
+
"onResponse",
|
|
7581
|
+
"onPace",
|
|
7582
|
+
"onHookError",
|
|
7583
|
+
"onCircuitOpen",
|
|
7584
|
+
"onCircuitClose",
|
|
7585
|
+
"ignorePaceDeadline",
|
|
7586
|
+
"responseCache"
|
|
7587
|
+
];
|
|
7588
|
+
|
|
7589
|
+
const resolveTransportOptions = (credentials, providerFields) => {
|
|
7590
|
+
const allowedKeys = /* @__PURE__ */ new Set([...TRANSPORT_OPTION_KEYS, ...providerFields]);
|
|
7591
|
+
const options = {};
|
|
7592
|
+
for (const [key, value] of Object.entries(credentials)) {
|
|
7593
|
+
if (providerFields.includes(key)) continue;
|
|
7594
|
+
if (!allowedKeys.has(key)) {
|
|
7595
|
+
throw new TypeError(
|
|
7596
|
+
`Unknown credential key "${key}". Valid transport options are: ${TRANSPORT_OPTION_KEYS.join(
|
|
7597
|
+
", "
|
|
7598
|
+
)}. Provider auth fields are: ${providerFields.join(", ")}.`
|
|
7599
|
+
);
|
|
7600
|
+
}
|
|
7601
|
+
options[key] = value;
|
|
7602
|
+
}
|
|
7603
|
+
return Object.keys(options).length === 0 ? void 0 : options;
|
|
7604
|
+
};
|
|
7605
|
+
function resolveAniListCredentials(credentials) {
|
|
7606
|
+
if (credentials === void 0) return {};
|
|
7607
|
+
return {
|
|
7608
|
+
auth: credentials.authToken,
|
|
7609
|
+
options: resolveTransportOptions(credentials, ["authToken"])
|
|
7610
|
+
};
|
|
7611
|
+
}
|
|
7612
|
+
function resolveMalCredentials(credentials) {
|
|
7613
|
+
if (credentials === void 0) return {};
|
|
7614
|
+
const headers = credentials.clientId === void 0 ? void 0 : { "X-MAL-CLIENT-ID": credentials.clientId };
|
|
7615
|
+
return {
|
|
7616
|
+
auth: credentials.accessToken === void 0 && headers === void 0 ? void 0 : { token: credentials.accessToken, headers },
|
|
7617
|
+
options: resolveTransportOptions(credentials, [
|
|
7618
|
+
"accessToken",
|
|
7619
|
+
"refreshToken",
|
|
7620
|
+
"clientId",
|
|
7621
|
+
"clientSecret"
|
|
7622
|
+
])
|
|
7623
|
+
};
|
|
6522
7624
|
}
|
|
6523
7625
|
|
|
6524
|
-
const
|
|
6525
|
-
const
|
|
6526
|
-
const
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
7626
|
+
const buildQueryString = (params) => {
|
|
7627
|
+
const segments = [];
|
|
7628
|
+
for (const [key, value] of Object.entries(params)) {
|
|
7629
|
+
if (value === void 0 || value === null) {
|
|
7630
|
+
continue;
|
|
7631
|
+
}
|
|
7632
|
+
const encodedKey = encodeURIComponent(key);
|
|
7633
|
+
if (Array.isArray(value)) {
|
|
7634
|
+
for (const item of value) {
|
|
7635
|
+
if (item !== void 0 && item !== null) {
|
|
7636
|
+
segments.push(`${encodedKey}=${encodeURIComponent(String(item))}`);
|
|
7637
|
+
}
|
|
7638
|
+
}
|
|
7639
|
+
} else {
|
|
7640
|
+
segments.push(`${encodedKey}=${encodeURIComponent(String(value))}`);
|
|
7641
|
+
}
|
|
6533
7642
|
}
|
|
6534
|
-
return
|
|
7643
|
+
return segments.length > 0 ? `?${segments.join("&")}` : "";
|
|
7644
|
+
};
|
|
7645
|
+
class RestOperation extends BaseOperation {
|
|
7646
|
+
/**
|
|
7647
|
+
* Sends one REST call through the shared transport pipeline.
|
|
7648
|
+
*
|
|
7649
|
+
* GET and DELETE calls pass their parameters as a query string; POST, PUT,
|
|
7650
|
+
* and PATCH calls send them as a JSON body. Responses are returned verbatim
|
|
7651
|
+
* — REST providers have no GraphQL-style envelope, so no unwrapping
|
|
7652
|
+
* happens.
|
|
7653
|
+
*
|
|
7654
|
+
* @typeParam T - The expected parsed response body.
|
|
7655
|
+
* @param path - The endpoint path beginning with `/` (for example `/anime/{id}`); placeholders are substituted from `pathParams` before interpolation into the URL.
|
|
7656
|
+
* @param options - The declarative request contract: method, auth requirement, content type, query/body/pathParams, and per-request transport settings.
|
|
7657
|
+
* @returns The parsed response body as-is.
|
|
7658
|
+
* @throws An {@link AniLinkAuthError} when `requiresAuth` is true and no token is set, or a normalized {@link AniLinkError} (typically `AniLinkRestError`) when the request fails.
|
|
7659
|
+
*/
|
|
7660
|
+
async execute(path, options = {}) {
|
|
7661
|
+
const {
|
|
7662
|
+
method = "GET",
|
|
7663
|
+
requiresAuth = false,
|
|
7664
|
+
contentType,
|
|
7665
|
+
query,
|
|
7666
|
+
body,
|
|
7667
|
+
pathParams = {},
|
|
7668
|
+
transportOptions
|
|
7669
|
+
} = options;
|
|
7670
|
+
const interpolatedPath = path.replace(/\{(\w+)\}/g, (match, name) => {
|
|
7671
|
+
const value = pathParams[name];
|
|
7672
|
+
return value === void 0 ? match : encodeURIComponent(String(value));
|
|
7673
|
+
});
|
|
7674
|
+
const url = `${this.baseUrl}${interpolatedPath}${buildQueryString(query ?? {})}`;
|
|
7675
|
+
const carriesBody = method === "POST" || method === "PUT" || method === "PATCH";
|
|
7676
|
+
const effectiveContentType = contentType ?? "application/json";
|
|
7677
|
+
return await this.dispatch(url, method, carriesBody ? body : void 0, {
|
|
7678
|
+
requiresAuth,
|
|
7679
|
+
transportOptions,
|
|
7680
|
+
contentType: effectiveContentType,
|
|
7681
|
+
protocol: "rest"
|
|
7682
|
+
});
|
|
7683
|
+
}
|
|
7684
|
+
}
|
|
7685
|
+
|
|
7686
|
+
const MAL_API_BASE_URL = "https://api.myanimelist.net/v2";
|
|
7687
|
+
const MAL_AUTHORIZE_URL = "https://myanimelist.net/v1/oauth2/authorize";
|
|
7688
|
+
const MAL_TOKEN_URL = "https://myanimelist.net/v1/oauth2/token";
|
|
7689
|
+
const MAL_API_REFERENCE = "https://myanimelist.net/apiconfig/references/api/v2";
|
|
7690
|
+
|
|
7691
|
+
class MalAnimeOperation extends RestOperation {
|
|
7692
|
+
/** The base URL for MyAnimeList API v2, from {@link MAL_API_BASE_URL}. */
|
|
7693
|
+
baseUrl = MAL_API_BASE_URL;
|
|
7694
|
+
/**
|
|
7695
|
+
* {@link MalAnimeOperation.get} gets one anime by its MyAnimeList ID.
|
|
7696
|
+
*
|
|
7697
|
+
* It calls `GET /anime/{id}` through `RestOperation.execute` and returns a {@link MalAnime} shaped by {@link MalRequestOptions.fields}. The facade alias is `MyAnimeListAnimeApi.get`.
|
|
7698
|
+
*
|
|
7699
|
+
* @param id - The MyAnimeList anime ID.
|
|
7700
|
+
* @param options - Optional field selection and transport settings; a {@link MalRequestOptions} merged over the instance defaults.
|
|
7701
|
+
* @returns The requested {@link MalAnime}.
|
|
7702
|
+
* @throws A normalized `AniLinkError` when the request fails.
|
|
7703
|
+
* @example
|
|
7704
|
+
* ```typescript
|
|
7705
|
+
* const api = new AniLink({ mal: { accessToken: "mal-token" } }).mal;
|
|
7706
|
+
* const anime = await api.anime.get(21, { fields: ["id", "title"] });
|
|
7707
|
+
* ```
|
|
7708
|
+
* @see https://myanimelist.net/apiconfig/references/api/v2#tag/anime/operation/anime_anime_id_get
|
|
7709
|
+
*/
|
|
7710
|
+
async get(id, options = {}) {
|
|
7711
|
+
const { fields, ...transportOptions } = options;
|
|
7712
|
+
return await this.execute("/anime/{id}", {
|
|
7713
|
+
transportOptions,
|
|
7714
|
+
query: fields === void 0 ? void 0 : { fields: Array.isArray(fields) ? fields.join(",") : fields },
|
|
7715
|
+
pathParams: { id }
|
|
7716
|
+
});
|
|
7717
|
+
}
|
|
7718
|
+
/**
|
|
7719
|
+
* Encodes a {@link MalAnimeListStatusUpdate} as the form-urlencoded body
|
|
7720
|
+
* MAL's list-status endpoints require.
|
|
7721
|
+
*
|
|
7722
|
+
* MAL documents `PATCH /anime/{id}/my_list_status` with an
|
|
7723
|
+
* `application/x-www-form-urlencoded` request body, not JSON. Array values
|
|
7724
|
+
* (`tags`) are joined into the comma-separated string MAL expects.
|
|
7725
|
+
*
|
|
7726
|
+
* @param payload - The list-status fields to update.
|
|
7727
|
+
* @returns The encoded body string, safe to pass as the request `data`.
|
|
7728
|
+
*/
|
|
7729
|
+
encodeListStatusBody(payload) {
|
|
7730
|
+
const params = new URLSearchParams();
|
|
7731
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
7732
|
+
if (value === void 0) continue;
|
|
7733
|
+
if (Array.isArray(value)) {
|
|
7734
|
+
if (Array.isArray(value)) {
|
|
7735
|
+
params.set(key, value.join(","));
|
|
7736
|
+
}
|
|
7737
|
+
} else {
|
|
7738
|
+
params.set(key, String(value));
|
|
7739
|
+
}
|
|
7740
|
+
}
|
|
7741
|
+
return params.toString();
|
|
7742
|
+
}
|
|
7743
|
+
/**
|
|
7744
|
+
* {@link MalAnimeOperation.updateMyListStatus} updates the authenticated user's anime list status.
|
|
7745
|
+
*
|
|
7746
|
+
* It calls `PATCH /anime/{id}/my_list_status` through `RestOperation.execute` with `requiresAuth` and a form-urlencoded {@link MalAnimeListStatusUpdate} body (MAL rejects JSON on this endpoint), returning the updated {@link MalAnimeListStatus}. The facade alias is `MyAnimeListAnimeApi.updateMyListStatus` and it requires `MalCredentials.accessToken`.
|
|
7747
|
+
*
|
|
7748
|
+
* @param id - The MyAnimeList anime ID.
|
|
7749
|
+
* @param payload - The list-status fields to update; a {@link MalAnimeListStatusUpdate} of only the fields to change.
|
|
7750
|
+
* @param options - Optional field selection and transport settings; a {@link MalRequestOptions} merged over the instance defaults.
|
|
7751
|
+
* @returns The updated {@link MalAnimeListStatus}.
|
|
7752
|
+
* @throws An `AniLinkAuthError` without an access token, or a normalized request error.
|
|
7753
|
+
* @example
|
|
7754
|
+
* ```typescript
|
|
7755
|
+
* const api = new AniLink({ mal: { accessToken: "mal-token" } }).mal;
|
|
7756
|
+
* const status = await api.anime.updateMyListStatus(21, {
|
|
7757
|
+
* status: "watching",
|
|
7758
|
+
* num_watched_episodes: 10,
|
|
7759
|
+
* score: 9,
|
|
7760
|
+
* });
|
|
7761
|
+
* ```
|
|
7762
|
+
* @see https://myanimelist.net/apiconfig/references/api/v2#tag/user-animelist/operation/anime_anime_id_my_list_status_put
|
|
7763
|
+
*/
|
|
7764
|
+
async updateMyListStatus(id, payload, options = {}) {
|
|
7765
|
+
const { fields, ...transportOptions } = options;
|
|
7766
|
+
return await this.execute("/anime/{id}/my_list_status", {
|
|
7767
|
+
method: "PATCH",
|
|
7768
|
+
requiresAuth: true,
|
|
7769
|
+
transportOptions,
|
|
7770
|
+
contentType: "application/x-www-form-urlencoded",
|
|
7771
|
+
body: this.encodeListStatusBody(payload),
|
|
7772
|
+
query: fields === void 0 ? void 0 : { fields: Array.isArray(fields) ? fields.join(",") : fields },
|
|
7773
|
+
pathParams: { id }
|
|
7774
|
+
});
|
|
7775
|
+
}
|
|
7776
|
+
/**
|
|
7777
|
+
* {@link MalAnimeOperation.deleteFromList} removes an anime from the authenticated user's list.
|
|
7778
|
+
*
|
|
7779
|
+
* It calls `DELETE /anime/{id}/my_list_status` through `RestOperation.execute` with `requiresAuth` and resolves with no body. The facade alias is `MyAnimeListAnimeApi.deleteFromList` and it requires `MalCredentials.accessToken`.
|
|
7780
|
+
*
|
|
7781
|
+
* @param id - The MyAnimeList anime ID.
|
|
7782
|
+
* @param options - Optional transport settings; a {@link MalRequestOptions} merged over the instance defaults.
|
|
7783
|
+
* @returns Resolves once the entry is deleted; the response carries no body.
|
|
7784
|
+
* @throws An `AniLinkAuthError` without an access token, or a normalized request error.
|
|
7785
|
+
* @example
|
|
7786
|
+
* ```typescript
|
|
7787
|
+
* const api = new AniLink({ mal: { accessToken: "mal-token" } }).mal;
|
|
7788
|
+
* await api.anime.deleteFromList(21);
|
|
7789
|
+
* ```
|
|
7790
|
+
* @see https://myanimelist.net/apiconfig/references/api/v2#tag/user-animelist/operation/anime_anime_id_my_list_status_delete
|
|
7791
|
+
*/
|
|
7792
|
+
async deleteFromList(id, options = {}) {
|
|
7793
|
+
const { fields: _ignoredFields, ...transportOptions } = options;
|
|
7794
|
+
await this.execute("/anime/{id}/my_list_status", {
|
|
7795
|
+
method: "DELETE",
|
|
7796
|
+
requiresAuth: true,
|
|
7797
|
+
transportOptions,
|
|
7798
|
+
pathParams: { id }
|
|
7799
|
+
});
|
|
7800
|
+
}
|
|
7801
|
+
}
|
|
7802
|
+
|
|
7803
|
+
class MalMangaOperation extends RestOperation {
|
|
7804
|
+
/** The base URL for MyAnimeList API v2, from {@link MAL_API_BASE_URL}. */
|
|
7805
|
+
baseUrl = MAL_API_BASE_URL;
|
|
7806
|
+
/**
|
|
7807
|
+
* {@link MalMangaOperation.get} gets one manga by its MyAnimeList ID.
|
|
7808
|
+
*
|
|
7809
|
+
* It calls `GET /manga/{id}` through `RestOperation.execute` and returns a {@link MalManga} shaped by {@link MalRequestOptions.fields}. The facade alias is `MyAnimeListMangaApi.get`.
|
|
7810
|
+
*
|
|
7811
|
+
* @param id - The MyAnimeList manga ID.
|
|
7812
|
+
* @param options - Optional field selection and transport settings; a {@link MalRequestOptions} merged over the instance defaults.
|
|
7813
|
+
* @returns The requested {@link MalManga}.
|
|
7814
|
+
* @throws A normalized `AniLinkError` when the request fails.
|
|
7815
|
+
* @example
|
|
7816
|
+
* ```typescript
|
|
7817
|
+
* const api = new AniLink({ mal: { accessToken: "mal-token" } }).mal;
|
|
7818
|
+
* const manga = await api.manga.get(1, { fields: ["id", "title"] });
|
|
7819
|
+
* ```
|
|
7820
|
+
* @see https://myanimelist.net/apiconfig/references/api/v2#tag/manga/operation/manga_manga_id_get
|
|
7821
|
+
*/
|
|
7822
|
+
async get(id, options = {}) {
|
|
7823
|
+
const { fields, ...transportOptions } = options;
|
|
7824
|
+
return await this.execute("/manga/{id}", {
|
|
7825
|
+
transportOptions,
|
|
7826
|
+
query: fields === void 0 ? void 0 : { fields: Array.isArray(fields) ? fields.join(",") : fields },
|
|
7827
|
+
pathParams: { id }
|
|
7828
|
+
});
|
|
7829
|
+
}
|
|
7830
|
+
/**
|
|
7831
|
+
* Encodes a {@link MalMangaListStatusUpdate} as the form-urlencoded body
|
|
7832
|
+
* MAL's list-status endpoints require.
|
|
7833
|
+
*
|
|
7834
|
+
* MAL documents `PATCH /manga/{id}/my_list_status` with an
|
|
7835
|
+
* `application/x-www-form-urlencoded` request body, not JSON. Array values
|
|
7836
|
+
* (`tags`) are joined into the comma-separated string MAL expects.
|
|
7837
|
+
*
|
|
7838
|
+
* @param payload - The list-status fields to update.
|
|
7839
|
+
* @returns The encoded body string, safe to pass as the request `data`.
|
|
7840
|
+
*/
|
|
7841
|
+
encodeListStatusBody(payload) {
|
|
7842
|
+
const params = new URLSearchParams();
|
|
7843
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
7844
|
+
if (value === void 0) continue;
|
|
7845
|
+
if (Array.isArray(value)) {
|
|
7846
|
+
params.set(key, value.join(","));
|
|
7847
|
+
} else {
|
|
7848
|
+
params.set(key, String(value));
|
|
7849
|
+
}
|
|
7850
|
+
}
|
|
7851
|
+
return params.toString();
|
|
7852
|
+
}
|
|
7853
|
+
/**
|
|
7854
|
+
* {@link MalMangaOperation.updateMyListStatus} updates the authenticated user's manga list status.
|
|
7855
|
+
*
|
|
7856
|
+
* It calls `PATCH /manga/{id}/my_list_status` through `RestOperation.execute` with `requiresAuth` and a form-urlencoded {@link MalMangaListStatusUpdate} body (MAL rejects JSON on this endpoint), returning the updated {@link MalMangaListStatus}. The facade alias is `MyAnimeListMangaApi.updateMyListStatus` and it requires `MalCredentials.accessToken`.
|
|
7857
|
+
*
|
|
7858
|
+
* @param id - The MyAnimeList manga ID.
|
|
7859
|
+
* @param payload - The list-status fields to update; a {@link MalMangaListStatusUpdate} of only the fields to change.
|
|
7860
|
+
* @param options - Optional field selection and transport settings; a {@link MalRequestOptions} merged over the instance defaults.
|
|
7861
|
+
* @returns The updated {@link MalMangaListStatus}.
|
|
7862
|
+
* @throws An `AniLinkAuthError` without an access token, or a normalized request error.
|
|
7863
|
+
* @example
|
|
7864
|
+
* ```typescript
|
|
7865
|
+
* const api = new AniLink({ mal: { accessToken: "mal-token" } }).mal;
|
|
7866
|
+
* const status = await api.manga.updateMyListStatus(1, {
|
|
7867
|
+
* status: "reading",
|
|
7868
|
+
* num_chapters_read: 10,
|
|
7869
|
+
* score: 9,
|
|
7870
|
+
* });
|
|
7871
|
+
* ```
|
|
7872
|
+
* @see https://myanimelist.net/apiconfig/references/api/v2#tag/user-mangalist/operation/manga_manga_id_my_list_status_put
|
|
7873
|
+
*/
|
|
7874
|
+
async updateMyListStatus(id, payload, options = {}) {
|
|
7875
|
+
const { fields, ...transportOptions } = options;
|
|
7876
|
+
return await this.execute("/manga/{id}/my_list_status", {
|
|
7877
|
+
method: "PATCH",
|
|
7878
|
+
requiresAuth: true,
|
|
7879
|
+
transportOptions,
|
|
7880
|
+
contentType: "application/x-www-form-urlencoded",
|
|
7881
|
+
body: this.encodeListStatusBody(payload),
|
|
7882
|
+
query: fields === void 0 ? void 0 : { fields: Array.isArray(fields) ? fields.join(",") : fields },
|
|
7883
|
+
pathParams: { id }
|
|
7884
|
+
});
|
|
7885
|
+
}
|
|
7886
|
+
/**
|
|
7887
|
+
* {@link MalMangaOperation.deleteFromList} removes a manga from the authenticated user's list.
|
|
7888
|
+
*
|
|
7889
|
+
* It calls `DELETE /manga/{id}/my_list_status` through `RestOperation.execute` with `requiresAuth` and resolves with no body. The facade alias is `MyAnimeListMangaApi.deleteFromList` and it requires `MalCredentials.accessToken`.
|
|
7890
|
+
*
|
|
7891
|
+
* @param id - The MyAnimeList manga ID.
|
|
7892
|
+
* @param options - Optional transport settings; a {@link MalRequestOptions} merged over the instance defaults.
|
|
7893
|
+
* @returns Resolves once the entry is deleted; the response carries no body.
|
|
7894
|
+
* @throws An `AniLinkAuthError` without an access token, or a normalized request error.
|
|
7895
|
+
* @example
|
|
7896
|
+
* ```typescript
|
|
7897
|
+
* const api = new AniLink({ mal: { accessToken: "mal-token" } }).mal;
|
|
7898
|
+
* await api.manga.deleteFromList(1);
|
|
7899
|
+
* ```
|
|
7900
|
+
* @see https://myanimelist.net/apiconfig/references/api/v2#tag/user-mangalist/operation/manga_manga_id_my_list_status_delete
|
|
7901
|
+
*/
|
|
7902
|
+
async deleteFromList(id, options = {}) {
|
|
7903
|
+
const { fields: _ignoredFields, ...transportOptions } = options;
|
|
7904
|
+
await this.execute("/manga/{id}/my_list_status", {
|
|
7905
|
+
method: "DELETE",
|
|
7906
|
+
requiresAuth: true,
|
|
7907
|
+
transportOptions,
|
|
7908
|
+
pathParams: { id }
|
|
7909
|
+
});
|
|
7910
|
+
}
|
|
7911
|
+
}
|
|
7912
|
+
|
|
7913
|
+
class MalUserOperation extends RestOperation {
|
|
7914
|
+
/** The base URL for MyAnimeList API v2, from {@link MAL_API_BASE_URL}. */
|
|
7915
|
+
baseUrl = MAL_API_BASE_URL;
|
|
7916
|
+
/**
|
|
7917
|
+
* {@link MalUserOperation.me} gets the currently authenticated MyAnimeList user.
|
|
7918
|
+
*
|
|
7919
|
+
* It calls `GET /users/@me` through `RestOperation.execute` with `requiresAuth` and returns a {@link MalUser} shaped by {@link MalRequestOptions.fields}. The facade alias is `MyAnimeListUserApi.me` and it requires `MalCredentials.accessToken`.
|
|
7920
|
+
*
|
|
7921
|
+
* @param options - Optional field selection and transport settings; a {@link MalRequestOptions} merged over the instance defaults.
|
|
7922
|
+
* @returns The authenticated {@link MalUser}.
|
|
7923
|
+
* @throws An `AniLinkAuthError` without an access token, or a normalized request error.
|
|
7924
|
+
* @example
|
|
7925
|
+
* ```typescript
|
|
7926
|
+
* const api = new AniLink({ mal: { accessToken: "mal-token" } }).mal;
|
|
7927
|
+
* const user = await api.user.me({ fields: ["id", "name"] });
|
|
7928
|
+
* ```
|
|
7929
|
+
* @see https://myanimelist.net/apiconfig/references/api/v2#tag/users/operation/users_user_id_get
|
|
7930
|
+
*/
|
|
7931
|
+
async me(options = {}) {
|
|
7932
|
+
const { fields, ...transportOptions } = options;
|
|
7933
|
+
return await this.execute("/users/@me", {
|
|
7934
|
+
requiresAuth: true,
|
|
7935
|
+
transportOptions,
|
|
7936
|
+
query: fields === void 0 ? void 0 : { fields: Array.isArray(fields) ? fields.join(",") : fields }
|
|
7937
|
+
});
|
|
7938
|
+
}
|
|
7939
|
+
}
|
|
7940
|
+
|
|
7941
|
+
function buildMyAnimeListApi(credentials) {
|
|
7942
|
+
const { auth, options } = resolveMalCredentials(credentials);
|
|
7943
|
+
const anime = new MalAnimeOperation(auth, options);
|
|
7944
|
+
const manga = new MalMangaOperation(auth, options);
|
|
7945
|
+
const user = new MalUserOperation(auth, options);
|
|
7946
|
+
return {
|
|
7947
|
+
anime: {
|
|
7948
|
+
get: anime.get.bind(anime),
|
|
7949
|
+
updateMyListStatus: anime.updateMyListStatus.bind(anime),
|
|
7950
|
+
deleteFromList: anime.deleteFromList.bind(anime)
|
|
7951
|
+
},
|
|
7952
|
+
manga: {
|
|
7953
|
+
get: manga.get.bind(manga),
|
|
7954
|
+
updateMyListStatus: manga.updateMyListStatus.bind(manga),
|
|
7955
|
+
deleteFromList: manga.deleteFromList.bind(manga)
|
|
7956
|
+
},
|
|
7957
|
+
user: { me: user.me.bind(user) }
|
|
7958
|
+
};
|
|
7959
|
+
}
|
|
7960
|
+
|
|
7961
|
+
const buildAniListClient = (credentials, legacyOptions) => {
|
|
7962
|
+
const resolved = resolveAniListCredentials(credentials);
|
|
7963
|
+
return buildAniListApi(resolved.auth, resolved.options ?? legacyOptions);
|
|
6535
7964
|
};
|
|
6536
|
-
const
|
|
7965
|
+
const buildMalClient = (credentials) => buildMyAnimeListApi(credentials);
|
|
7966
|
+
const PROVIDER_FACTORIES = {
|
|
7967
|
+
anilist: buildAniListClient,
|
|
7968
|
+
mal: buildMalClient
|
|
7969
|
+
};
|
|
7970
|
+
function buildProviderClients(credentials = {}, legacyOptions) {
|
|
7971
|
+
const clientHookError = credentials.onHookError;
|
|
7972
|
+
const anilistSlot = clientHookError !== void 0 && credentials.anilist?.onHookError === void 0 ? { ...credentials.anilist, onHookError: clientHookError } : credentials.anilist;
|
|
7973
|
+
const malSlot = clientHookError !== void 0 && credentials.mal?.onHookError === void 0 ? { ...credentials.mal, onHookError: clientHookError } : credentials.mal;
|
|
7974
|
+
return {
|
|
7975
|
+
anilist: PROVIDER_FACTORIES.anilist(anilistSlot, legacyOptions),
|
|
7976
|
+
mal: PROVIDER_FACTORIES.mal(malSlot)
|
|
7977
|
+
};
|
|
7978
|
+
}
|
|
7979
|
+
|
|
7980
|
+
const sanitizeTokenError = (error, label) => {
|
|
7981
|
+
if (error instanceof AniLinkRestError) {
|
|
7982
|
+
const relabeled = new AniLinkRestError(error.status, error.data, void 0, {
|
|
7983
|
+
rateLimit: error.rateLimit,
|
|
7984
|
+
contentType: error.contentType,
|
|
7985
|
+
requestId: error.requestId
|
|
7986
|
+
});
|
|
7987
|
+
relabeled.message = `${label} failed with status ${error.status}.`;
|
|
7988
|
+
return relabeled;
|
|
7989
|
+
}
|
|
6537
7990
|
if (error instanceof AniLinkApiError) {
|
|
6538
|
-
|
|
6539
|
-
|
|
7991
|
+
const relabeled = new AniLinkApiError(error.status, error.data, void 0, {
|
|
7992
|
+
rateLimit: error.rateLimit,
|
|
7993
|
+
contentType: error.contentType,
|
|
7994
|
+
requestId: error.requestId
|
|
7995
|
+
});
|
|
7996
|
+
relabeled.message = `${label} failed with status ${error.status}.`;
|
|
7997
|
+
return relabeled;
|
|
6540
7998
|
}
|
|
6541
7999
|
if (error instanceof AniLinkError) {
|
|
6542
8000
|
return error;
|
|
6543
8001
|
}
|
|
6544
8002
|
if (axios.isCancel(error)) {
|
|
6545
|
-
return new AniLinkNetworkError(
|
|
6546
|
-
AniLinkErrorCodes.ABORTED,
|
|
6547
|
-
"The token request was cancelled."
|
|
6548
|
-
);
|
|
8003
|
+
return new AniLinkNetworkError(AniLinkErrorCodes.ABORTED, `${label} was cancelled.`);
|
|
6549
8004
|
}
|
|
6550
8005
|
if (axios.isAxiosError(error)) {
|
|
6551
8006
|
if (error.response?.status !== void 0) {
|
|
6552
8007
|
const status = error.response.status;
|
|
6553
8008
|
const apiError = new AniLinkApiError(status, error.response.data);
|
|
6554
|
-
apiError.message =
|
|
8009
|
+
apiError.message = `${label} failed with status ${status}.`;
|
|
6555
8010
|
return apiError;
|
|
6556
8011
|
}
|
|
6557
8012
|
if (error.code === "ECONNABORTED" || error.code === "ETIMEDOUT") {
|
|
6558
|
-
return new AniLinkNetworkError(
|
|
6559
|
-
AniLinkErrorCodes.TIMEOUT,
|
|
6560
|
-
"The token request timed out."
|
|
6561
|
-
);
|
|
8013
|
+
return new AniLinkNetworkError(AniLinkErrorCodes.TIMEOUT, `${label} timed out.`);
|
|
6562
8014
|
}
|
|
6563
8015
|
return new AniLinkNetworkError(
|
|
6564
8016
|
AniLinkErrorCodes.NETWORK,
|
|
6565
|
-
|
|
8017
|
+
`${label} failed due to a network error.`
|
|
6566
8018
|
);
|
|
6567
8019
|
}
|
|
6568
|
-
return new AniLinkError(
|
|
8020
|
+
return new AniLinkError(`${label} failed.`, AniLinkErrorCodes.UNKNOWN);
|
|
8021
|
+
};
|
|
8022
|
+
|
|
8023
|
+
const AUTH_TOKEN_TIMEOUT_MS = 1e4;
|
|
8024
|
+
const ANILIST_TOKEN_URL = "https://anilist.co/api/v2/oauth/token";
|
|
8025
|
+
const ANILIST_AUTHORIZE_URL = "https://anilist.co/api/v2/oauth/authorize";
|
|
8026
|
+
const buildAuthorizationUrl = (clientId, redirectUri, state) => {
|
|
8027
|
+
let url = `${ANILIST_AUTHORIZE_URL}?client_id=${encodeURIComponent(
|
|
8028
|
+
clientId
|
|
8029
|
+
)}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code`;
|
|
8030
|
+
if (state !== void 0) {
|
|
8031
|
+
url += `&state=${encodeURIComponent(state)}`;
|
|
8032
|
+
}
|
|
8033
|
+
return url;
|
|
6569
8034
|
};
|
|
8035
|
+
const normalizeTokenRequestError = (error) => sanitizeTokenError(error, "AniList token request");
|
|
6570
8036
|
const requestToken = async (params, signal) => {
|
|
6571
8037
|
const options = {
|
|
6572
|
-
// Token exchanges block the login flow, so they fail faster than
|
|
6573
|
-
// GraphQL operations unless the caller tunes the timeout explicitly.
|
|
6574
8038
|
timeout: AUTH_TOKEN_TIMEOUT_MS,
|
|
6575
|
-
signal
|
|
8039
|
+
signal,
|
|
8040
|
+
exposeRawAxiosError: false
|
|
6576
8041
|
};
|
|
6577
8042
|
try {
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
new URLSearchParams(params).toString(),
|
|
6582
|
-
void 0,
|
|
6583
|
-
false,
|
|
8043
|
+
const body = new URLSearchParams(params).toString();
|
|
8044
|
+
return await sendRequest(ANILIST_TOKEN_URL, "POST", body, void 0, {
|
|
8045
|
+
requiresAuth: false,
|
|
6584
8046
|
options,
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
);
|
|
8047
|
+
contentType: "application/x-www-form-urlencoded"
|
|
8048
|
+
});
|
|
6588
8049
|
} catch (error) {
|
|
6589
8050
|
throw normalizeTokenRequestError(error);
|
|
6590
8051
|
}
|
|
@@ -6610,22 +8071,236 @@ const refreshAccessToken = async (clientId, clientSecret, refreshToken, signal)
|
|
|
6610
8071
|
);
|
|
6611
8072
|
const getTokenExpiry = (response, now = Date.now()) => new Date(now + response.expires_in * 1e3);
|
|
6612
8073
|
|
|
8074
|
+
const isPlainObject = (value) => {
|
|
8075
|
+
const proto = Object.getPrototypeOf(value);
|
|
8076
|
+
return proto === Object.prototype || proto === null;
|
|
8077
|
+
};
|
|
8078
|
+
const stableStringify = (value, seen = /* @__PURE__ */ new Set()) => {
|
|
8079
|
+
if (value === null || typeof value !== "object") {
|
|
8080
|
+
return JSON.stringify(value) ?? "undefined";
|
|
8081
|
+
}
|
|
8082
|
+
if (seen.has(value)) {
|
|
8083
|
+
return '"[Circular]"';
|
|
8084
|
+
}
|
|
8085
|
+
seen.add(value);
|
|
8086
|
+
try {
|
|
8087
|
+
if (Array.isArray(value)) {
|
|
8088
|
+
return `[${value.map((item) => stableStringify(item, seen)).join(",")}]`;
|
|
8089
|
+
}
|
|
8090
|
+
if (!isPlainObject(value)) {
|
|
8091
|
+
return JSON.stringify(value) ?? "undefined";
|
|
8092
|
+
}
|
|
8093
|
+
const keys = Object.keys(value).sort();
|
|
8094
|
+
return `{${keys.map(
|
|
8095
|
+
(key) => `${JSON.stringify(key)}:${stableStringify(value[key], seen)}`
|
|
8096
|
+
).join(",")}}`;
|
|
8097
|
+
} finally {
|
|
8098
|
+
seen.delete(value);
|
|
8099
|
+
}
|
|
8100
|
+
};
|
|
8101
|
+
class ResponseCache {
|
|
8102
|
+
entries = /* @__PURE__ */ new Map();
|
|
8103
|
+
ttlMs;
|
|
8104
|
+
maxEntries;
|
|
8105
|
+
/**
|
|
8106
|
+
* Creates a response cache.
|
|
8107
|
+
*
|
|
8108
|
+
* @param options - Cache configuration; `ttlMs` defaults to 60_000, `maxEntries` to 128.
|
|
8109
|
+
*/
|
|
8110
|
+
constructor(options) {
|
|
8111
|
+
const rawTtl = options?.ttlMs ?? 6e4;
|
|
8112
|
+
if (!Number.isFinite(rawTtl) || rawTtl < 0) {
|
|
8113
|
+
throw new TypeError("ttlMs must be a finite, non-negative number");
|
|
8114
|
+
}
|
|
8115
|
+
this.ttlMs = rawTtl;
|
|
8116
|
+
const rawMax = options?.maxEntries ?? 128;
|
|
8117
|
+
if (!Number.isFinite(rawMax) || rawMax <= 0 || !Number.isInteger(rawMax)) {
|
|
8118
|
+
throw new TypeError("maxEntries must be a finite, positive integer");
|
|
8119
|
+
}
|
|
8120
|
+
this.maxEntries = rawMax;
|
|
8121
|
+
}
|
|
8122
|
+
/**
|
|
8123
|
+
* Builds the cache key for a request.
|
|
8124
|
+
*
|
|
8125
|
+
* The serialized body is SHA-256 hashed (truncated to 16 hex chars)
|
|
8126
|
+
* before it enters the key, so a credential-bearing GET body is never
|
|
8127
|
+
* duplicated into the key string in plaintext — the key map retains
|
|
8128
|
+
* entries for up to the TTL, outliving the error paths the rest of the
|
|
8129
|
+
* library scrubs. The hash is deterministic, so equal bodies still share
|
|
8130
|
+
* one entry and different bodies still get different entries.
|
|
8131
|
+
*
|
|
8132
|
+
* @param method - The HTTP method.
|
|
8133
|
+
* @param url - The request URL.
|
|
8134
|
+
* @param data - The request body, when present.
|
|
8135
|
+
* @param authKey - An authentication-safe credential identity, so cached
|
|
8136
|
+
* responses never cross bearer-token identities.
|
|
8137
|
+
* @returns The cache key.
|
|
8138
|
+
*/
|
|
8139
|
+
static buildKey(method, url, data, authKey) {
|
|
8140
|
+
const body = data === void 0 ? "none" : `sha256:${createHash("sha256").update(stableStringify(data)).digest("hex").slice(0, 16)}`;
|
|
8141
|
+
return `${method}:${url}:${body}:${authKey ?? "none"}`;
|
|
8142
|
+
}
|
|
8143
|
+
/**
|
|
8144
|
+
* Reads a cached response for the given request, or `undefined` when the
|
|
8145
|
+
* entry is absent or expired. Expired entries are evicted on read. The
|
|
8146
|
+
* returned value is a deep clone of the cached entry, so a caller that
|
|
8147
|
+
* mutates it cannot corrupt the cached copy or affect subsequent reads.
|
|
8148
|
+
*
|
|
8149
|
+
* @param method - The HTTP method.
|
|
8150
|
+
* @param url - The request URL.
|
|
8151
|
+
* @param data - The request body, when present.
|
|
8152
|
+
* @param authKey - An authentication-safe credential identity, so cached
|
|
8153
|
+
* responses never cross bearer-token identities.
|
|
8154
|
+
* @returns A deep clone of the cached response body, or `undefined`.
|
|
8155
|
+
*/
|
|
8156
|
+
get(method, url, data, authKey) {
|
|
8157
|
+
const key = ResponseCache.buildKey(method, url, data, authKey);
|
|
8158
|
+
const entry = this.entries.get(key);
|
|
8159
|
+
if (entry === void 0) {
|
|
8160
|
+
return void 0;
|
|
8161
|
+
}
|
|
8162
|
+
if (Date.now() >= entry.expiresAt) {
|
|
8163
|
+
this.entries.delete(key);
|
|
8164
|
+
return void 0;
|
|
8165
|
+
}
|
|
8166
|
+
this.entries.delete(key);
|
|
8167
|
+
this.entries.set(key, entry);
|
|
8168
|
+
return structuredClone(entry.data);
|
|
8169
|
+
}
|
|
8170
|
+
/**
|
|
8171
|
+
* Stores a response in the cache, evicting the LRU entry when the cap is
|
|
8172
|
+
* reached. Only `GET` responses are cached; other methods are no-ops.
|
|
8173
|
+
* The value is deep-copied on write; the cache never aliases the
|
|
8174
|
+
* caller's object.
|
|
8175
|
+
*
|
|
8176
|
+
* @param method - The HTTP method.
|
|
8177
|
+
* @param url - The request URL.
|
|
8178
|
+
* @param data - The request body, when present.
|
|
8179
|
+
* @param authKey - An authentication-safe credential identity, so cached
|
|
8180
|
+
* responses never cross bearer-token identities.
|
|
8181
|
+
* @param response - The response body to cache.
|
|
8182
|
+
*/
|
|
8183
|
+
set(method, url, data, authKey, response) {
|
|
8184
|
+
if (method !== "GET") return;
|
|
8185
|
+
const key = ResponseCache.buildKey(method, url, data, authKey);
|
|
8186
|
+
let snapshot;
|
|
8187
|
+
try {
|
|
8188
|
+
snapshot = structuredClone(response);
|
|
8189
|
+
} catch {
|
|
8190
|
+
return;
|
|
8191
|
+
}
|
|
8192
|
+
if (this.entries.has(key)) {
|
|
8193
|
+
this.entries.delete(key);
|
|
8194
|
+
} else if (this.entries.size >= this.maxEntries) {
|
|
8195
|
+
this.evictLru();
|
|
8196
|
+
}
|
|
8197
|
+
this.entries.set(key, {
|
|
8198
|
+
data: snapshot,
|
|
8199
|
+
expiresAt: Date.now() + this.ttlMs
|
|
8200
|
+
});
|
|
8201
|
+
}
|
|
8202
|
+
/**
|
|
8203
|
+
* Removes the cached entry for the given request, if present. Use this
|
|
8204
|
+
* for targeted invalidation after a mutation that changes the resource
|
|
8205
|
+
* (for example a `POST` that updates the entity a cached `GET` returned).
|
|
8206
|
+
* Only `GET` entries are tracked, so non-`GET` methods are a no-op and
|
|
8207
|
+
* return `false`.
|
|
8208
|
+
*
|
|
8209
|
+
* @param method - The HTTP method.
|
|
8210
|
+
* @param url - The request URL.
|
|
8211
|
+
* @param data - The request body, when present.
|
|
8212
|
+
* @param authKey - An authentication-safe credential identity, so cached
|
|
8213
|
+
* responses never cross bearer-token identities.
|
|
8214
|
+
* @returns `true` when an entry was removed, `false` when it was absent
|
|
8215
|
+
* or the method is not cached.
|
|
8216
|
+
*/
|
|
8217
|
+
delete(method, url, data, authKey) {
|
|
8218
|
+
if (method !== "GET") return false;
|
|
8219
|
+
const key = ResponseCache.buildKey(method, url, data, authKey);
|
|
8220
|
+
return this.entries.delete(key);
|
|
8221
|
+
}
|
|
8222
|
+
/**
|
|
8223
|
+
* Evicts the least-recently-used entry.
|
|
8224
|
+
*/
|
|
8225
|
+
evictLru() {
|
|
8226
|
+
const oldestKey = this.entries.keys().next().value;
|
|
8227
|
+
if (oldestKey !== void 0) {
|
|
8228
|
+
this.entries.delete(oldestKey);
|
|
8229
|
+
}
|
|
8230
|
+
}
|
|
8231
|
+
/**
|
|
8232
|
+
* Clears all cached entries.
|
|
8233
|
+
*/
|
|
8234
|
+
clear() {
|
|
8235
|
+
this.entries.clear();
|
|
8236
|
+
}
|
|
8237
|
+
}
|
|
8238
|
+
|
|
8239
|
+
const MAL_AUTH_TIMEOUT_MS = 1e4;
|
|
8240
|
+
const buildMalAuthorizationUrl = (clientId, codeChallenge, state) => {
|
|
8241
|
+
const params = new URLSearchParams({
|
|
8242
|
+
response_type: "code",
|
|
8243
|
+
client_id: clientId,
|
|
8244
|
+
code_challenge: codeChallenge,
|
|
8245
|
+
code_challenge_method: "S256"
|
|
8246
|
+
});
|
|
8247
|
+
if (state !== void 0) params.set("state", state);
|
|
8248
|
+
return `${MAL_AUTHORIZE_URL}?${params.toString().replaceAll("+", "%20")}`;
|
|
8249
|
+
};
|
|
8250
|
+
const normalizeMalTokenError = (error) => sanitizeTokenError(error, "MAL token request");
|
|
8251
|
+
const requestMalToken = async (params, options) => {
|
|
8252
|
+
try {
|
|
8253
|
+
const body = new URLSearchParams(params).toString();
|
|
8254
|
+
return await sendRequest(MAL_TOKEN_URL, "POST", body, void 0, {
|
|
8255
|
+
requiresAuth: false,
|
|
8256
|
+
options: {
|
|
8257
|
+
...options,
|
|
8258
|
+
timeout: options?.timeout ?? MAL_AUTH_TIMEOUT_MS,
|
|
8259
|
+
exposeRawAxiosError: false
|
|
8260
|
+
},
|
|
8261
|
+
contentType: "application/x-www-form-urlencoded"
|
|
8262
|
+
});
|
|
8263
|
+
} catch (error) {
|
|
8264
|
+
throw normalizeMalTokenError(error);
|
|
8265
|
+
}
|
|
8266
|
+
};
|
|
8267
|
+
const getMalAccessToken = (request) => requestMalToken(
|
|
8268
|
+
{
|
|
8269
|
+
client_id: request.clientId,
|
|
8270
|
+
code: request.code,
|
|
8271
|
+
code_verifier: request.codeVerifier,
|
|
8272
|
+
grant_type: "authorization_code",
|
|
8273
|
+
...request.clientSecret === void 0 ? {} : { client_secret: request.clientSecret }
|
|
8274
|
+
},
|
|
8275
|
+
request.options
|
|
8276
|
+
);
|
|
8277
|
+
const refreshMalAccessToken = (request) => requestMalToken(
|
|
8278
|
+
{
|
|
8279
|
+
client_id: request.clientId,
|
|
8280
|
+
grant_type: "refresh_token",
|
|
8281
|
+
refresh_token: request.refreshToken,
|
|
8282
|
+
...request.clientSecret === void 0 ? {} : { client_secret: request.clientSecret }
|
|
8283
|
+
},
|
|
8284
|
+
request.options
|
|
8285
|
+
);
|
|
8286
|
+
const getMalTokenExpiry = (response, now = Date.now()) => new Date(now + response.expires_in * 1e3);
|
|
8287
|
+
|
|
6613
8288
|
class AniLink {
|
|
6614
8289
|
/**
|
|
6615
|
-
*
|
|
8290
|
+
* The AniList GraphQL API surface, a {@link AniListApi} composed from the
|
|
8291
|
+
* query, mutation, custom, and helper groups.
|
|
6616
8292
|
* @public
|
|
6617
8293
|
*/
|
|
6618
8294
|
anilist;
|
|
6619
|
-
/**
|
|
6620
|
-
* MyAnimeList API methods. Populated once the MAL provider module ships;
|
|
6621
|
-
* the constructor already accepts and stores MAL credentials so adding
|
|
6622
|
-
* the provider requires no further constructor change.
|
|
6623
|
-
*/
|
|
8295
|
+
/** The MyAnimeList REST API methods, a {@link MyAnimeListApi} exposed under the `mal` namespace. */
|
|
6624
8296
|
mal;
|
|
6625
8297
|
/**
|
|
6626
|
-
* Creates a new AniLink instance. The `authToken` parameter is optional and only
|
|
8298
|
+
* Creates a new {@link AniLink} instance. The `authToken` parameter is optional and only
|
|
8299
|
+
* required for authenticated queries and mutations; without it only public queries are
|
|
8300
|
+
* available. Multiple instances can hold different `authToken`s, each exposing an
|
|
8301
|
+
* {@link AniListApi} under `anilist` and a {@link MyAnimeListApi} under `mal`.
|
|
6627
8302
|
*
|
|
6628
|
-
* Alternatively, pass a per-provider
|
|
8303
|
+
* Alternatively, pass a per-provider {@link AniLinkCredentials} object: each provider
|
|
6629
8304
|
* owns its own credentials shape, and credentials given under one key are
|
|
6630
8305
|
* never applied to another provider's requests.
|
|
6631
8306
|
* @param {string | AniLinkCredentials} [authToken] - The authentication token to use for AniList API requests, or a per-provider credentials object (`{ anilist?: …, mal?: … }`).
|
|
@@ -6652,13 +8327,17 @@ class AniLink {
|
|
|
6652
8327
|
* ```
|
|
6653
8328
|
*/
|
|
6654
8329
|
constructor(authToken, options) {
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
8330
|
+
let clients;
|
|
8331
|
+
if (typeof authToken === "string") {
|
|
8332
|
+
clients = buildProviderClients({ anilist: { authToken } }, options);
|
|
8333
|
+
} else if (authToken === void 0) {
|
|
8334
|
+
clients = buildProviderClients({}, options);
|
|
8335
|
+
} else {
|
|
8336
|
+
clients = buildProviderClients(authToken);
|
|
6658
8337
|
}
|
|
6659
|
-
|
|
6660
|
-
this.
|
|
8338
|
+
this.anilist = clients.anilist;
|
|
8339
|
+
this.mal = clients.mal;
|
|
6661
8340
|
}
|
|
6662
8341
|
}
|
|
6663
8342
|
|
|
6664
|
-
export { ANILIST_AUTHORIZE_URL, ANILIST_TOKEN_URL, AniLink, AniLinkApiError, AniLinkAuthError, AniLinkError, AniLinkErrorCodes, AniLinkGraphQLError, AniLinkNetworkError, AniLinkRestError, AniLinkValidationError, buildAuthorizationUrl, getAccessToken, getTokenExpiry, paginate, paginateChunks, paginatePages, refreshAccessToken };
|
|
8343
|
+
export { ANILIST_AUTHORIZE_URL, ANILIST_TOKEN_URL, AniLink, AniLinkApiError, AniLinkAuthError, AniLinkError, AniLinkErrorCodes, AniLinkGraphQLError, AniLinkNetworkError, AniLinkRestError, AniLinkValidationError, MAL_API_BASE_URL, MAL_API_REFERENCE, MAL_AUTHORIZE_URL, MAL_TOKEN_URL, ResponseCache, buildAuthorizationUrl, buildMalAuthorizationUrl, buildMyAnimeListApi, buildProviderClients, destroyCachedAgents, getAccessToken, getMalAccessToken, getMalTokenExpiry, getTokenExpiry, paginate, paginateChunks, paginatePages, refreshAccessToken, refreshMalAccessToken };
|