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/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
- /** The individual validation problems, one per line. */
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
- return { ...DEFAULT_RETRY_POLICY, ...retry };
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 ?? false,
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
- const unwrapSingleRootField = (response) => {
191
- const envelope = response;
192
- const queryData = envelope?.data;
193
- if (!queryData || typeof queryData !== "object") {
194
- return void 0;
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 fields = Object.keys(queryData);
197
- if (fields.length === 1) {
198
- return queryData[fields[0]];
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
- return void 0;
201
- };
202
- const unwrapGraphQLResponse = (response) => {
203
- const envelope = response;
204
- if (Array.isArray(envelope?.errors) && envelope.errors.length > 0) {
205
- throw new AniLinkGraphQLError(envelope.errors, envelope?.data);
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 unwrapSingleRootField(response) ?? response;
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 normalizeAxiosError = (resolved, error) => {
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
- return new AniLinkApiError(
232
- error.response.status,
233
- error.response.data,
234
- getRawAxiosError(resolved, error),
235
- { rateLimit: getRateLimitInfo(error.response.headers) }
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 ? { timeoutMs: resolved.timeout } : void 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 normalizeRequestError = (resolved, error) => {
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
- const parseRetryAfter = (header, now) => {
266
- if (header === void 0 || header === null || header === "") {
267
- return null;
268
- }
269
- const seconds = Number(header);
270
- if (Number.isFinite(seconds) && seconds >= 0) {
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
- return null;
278
- };
279
- const getRetryAfterDelay = (error) => {
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 null;
544
+ return void 0;
287
545
  };
288
- const getBackoffDelay = (attempt, policy) => Math.min(policy.baseDelayMs * 2 ** attempt, policy.maxDelayMs);
289
- const applyJitter = (cap, policy) => policy.jitter === false ? cap : randomInt(0, cap + 1);
290
- const getRetryDelay = (error, rawError, attempt, policy) => {
291
- if (attempt >= policy.maxRetries) {
292
- return null;
293
- }
294
- if (error instanceof AniLinkApiError) {
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
- return null;
554
+ const unwrapped = unwrapSingleRootField(response);
555
+ return unwrapped === void 0 ? response : unwrapped;
312
556
  };
313
- const sleep = (ms, signal) => new Promise((resolve, reject) => {
314
- const timeout = setTimeout(() => {
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 circuitScopeOf = (url) => {
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
- return "";
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 === void 0) {
359
- state = { consecutiveFailures: 0, openedAt: null };
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 throwIfCircuitOpen = (circuit, breaker) => {
365
- if (circuit === void 0 || breaker === void 0 || circuit.openedAt === null) {
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
- throw new AniLinkNetworkError(
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 !== void 0) {
378
- circuit.consecutiveFailures = 0;
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
- const buildErrorContext = (url, method, attempt, normalized, nextDelayMs) => ({
391
- url,
392
- method,
393
- attempt,
394
- code: normalized.code,
395
- ...normalized instanceof AniLinkApiError ? { status: normalized.status } : {},
396
- ...nextDelayMs === void 0 ? {} : { nextDelayMs }
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
- const paceAfterSuccess = async (response, resolved) => {
399
- if (!resolved.paceWithRateLimit) {
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 info = getRateLimitInfo(response.headers);
403
- if (info !== void 0 && info.remaining < resolved.rateLimitFloor) {
404
- await sleep(Math.max(0, info.reset * 1e3 - Date.now()), resolved.signal);
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 reportFailure = (url, method, attempt, normalized, resolved, nextDelayMs) => {
408
- const context = buildErrorContext(url, method, attempt, normalized, nextDelayMs);
409
- if (nextDelayMs !== void 0) {
410
- safeInvoke(
411
- resolved.onRetry ?? resolved.onError,
412
- resolved.onRetry === void 0 ? "onError" : "onRetry",
413
- normalized,
414
- context
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.onError, "onError", normalized, context);
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
- const executeWithRetry = async (options, resolved, stateKey, rawPassthrough = false) => {
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 circuit = resolved.circuitBreaker !== void 0 && stateKey !== void 0 ? getCircuitState(stateKey, circuitScopeOf(url)) : void 0;
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
- safeInvoke(resolved.onRequestStart, "onRequestStart", hookContext);
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
- safeInvoke(resolved.onResponse, "onResponse", {
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
- recordCircuitSuccess(circuit);
450
- await paceAfterSuccess(response, resolved);
451
- return rawPassthrough ? response.data : unwrapGraphQLResponse(response.data);
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
- safeInvoke(resolved.onResponse, "onResponse", {
455
- ...hookContext,
456
- durationMs: Date.now() - startedAt
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
- const normalized = normalizeRequestError(resolved, error);
459
- recordCircuitFailure(circuit, resolved.circuitBreaker);
460
- const delay = policy === null ? null : getRetryDelay(normalized, error, attempt, policy);
461
- reportFailure(url, method, attempt + 1, normalized, resolved, delay ?? void 0);
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 sendRequest = async (url, method, data, token, requiresAuth = false, options, operation, contentType) => {
471
- if (requiresAuth && (token === null || token === void 0 || token === "")) {
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
- if (token !== null && token !== void 0 && token !== "") {
479
- headers.Authorization = `Bearer ${token}`;
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
- resolveRequestOptions(options),
484
- options,
485
- contentType !== void 0
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
- authToken;
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 token used for API requests.
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.authToken = authToken;
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.authToken;
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 requiresAuth - Whether the operation requires an authentication token.
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 `AniLinkAuthError` when `requiresAuth` is true and no token is set, or a normalized `AniLinkError` when the request fails.
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, requiresAuth = false, operation, transportOptions, contentType) {
550
- return await sendRequest(
551
- url,
552
- method,
553
- data,
554
- this.authToken,
555
- requiresAuth || void 0,
556
- mergeOptions(this.resolvedOptions, transportOptions),
557
- operation ?? resolveOperationLabel(this),
558
- contentType
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 describeValue = (value) => {
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(`Invalid ${path}: ${describeValue(value)}. Expected type: ${mapping}`);
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(`Invalid ${path}: ${describeValue(value)}. Expected type: ${mapping}`);
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 requiresAuth - Whether the operation requires an authentication token.
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 `AniLinkAuthError` when `requiresAuth` is true and no token is set, or a normalized `AniLinkError` when the request fails.
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, requiresAuth = false, operation, transportOptions) {
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 `AniLinkError` when the request fails.
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, void 0, transportOptions);
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 `AniLinkValidationError` when the query is empty or does not declare a `query` or `mutation` operation.
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, false, void 0, options);
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
- async function fetchWithLookAhead(fetch, extractHasMore, ...rest) {
840
- let extractNextKey;
841
- let firstKey;
842
- let numericStart;
843
- let maxEntries;
844
- let concurrency;
845
- if (rest.length === 4) {
846
- [extractNextKey, firstKey, maxEntries, concurrency] = rest;
847
- if (extractNextKey === void 0) {
848
- numericStart = firstKey;
849
- }
850
- } else {
851
- [numericStart, maxEntries, concurrency] = rest;
852
- firstKey = numericStart;
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
- while (launched < maxEntries && launched - count < effectiveConcurrency) {
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(key).then((response) => {
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
- await pending[count];
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
- const consumed = responses[count - 1];
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(options?.concurrency, MAX_CONCURRENCY, 1);
918
- const { responses, count, truncated } = await fetchWithLookAhead(
919
- (number) => fetchPage(number, perPage),
920
- extractHasMore,
921
- startPage,
922
- maxPages,
923
- concurrency
1563
+ const concurrency = resolveCappedInt(
1564
+ options?.concurrency,
1565
+ MAX_CONCURRENCY,
1566
+ DEFAULT_CONCURRENCY
924
1567
  );
925
- const items = [];
926
- const pages = [];
927
- for (const response of responses) {
928
- const pageItems = response[itemsKey];
929
- pages.push({ pageInfo: response.pageInfo, items: pageItems });
930
- items.push(...pageItems);
931
- }
932
- return { items, pages, pageCount: count, truncated };
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
- let page = startPage;
939
- let pageCount = 0;
940
- while (pageCount < maxPages) {
941
- const response = await fetchPage(page, perPage);
942
- pageCount += 1;
943
- yield response;
944
- if (!response.pageInfo.hasNextPage || pageCount >= maxPages) {
945
- break;
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
- page += 1;
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(options?.concurrency, MAX_CONCURRENCY, 1);
955
- const { responses, count, truncated } = await fetchWithLookAhead(
956
- (number) => fetchChunk(number, perChunk),
957
- extractHasMore,
958
- startChunk,
959
- maxChunks,
960
- concurrency
1657
+ const concurrency = resolveCappedInt(
1658
+ options?.concurrency,
1659
+ MAX_CONCURRENCY,
1660
+ DEFAULT_CONCURRENCY
961
1661
  );
962
- const items = [];
963
- const chunks = [];
964
- for (const response of responses) {
965
- const chunkItems = response[itemsKey];
966
- chunks.push({ hasNextChunk: response.hasNextChunk, items: chunkItems });
967
- items.push(...chunkItems);
968
- }
969
- return { items, chunks, chunkCount: count, truncated };
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
- * `activity` is a method that sends a query request to get activities.
2355
+ * {@link ActivityQuery.activity} sends a query request to get activities.
1639
2356
  *
1640
- * @param variables - The variables for the query.
1641
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `activityReply` is a method that sends a query request to get activity replies.
2395
+ * {@link ActivityReplyQuery.activityReply} sends a query request to get activity replies.
1675
2396
  *
1676
- * @param variables - The variables for the query.
1677
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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 - The variables for the query.
1715
- * @returns The activity replies for the requested page with pagination metadata.
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 per-request transport settings merged over the instance-level ones for this call only.
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 - The variables for the query.
1779
- * @returns The activities for the requested page with pagination metadata.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `airingSchedule` is a method that sends a query request to get airing schedules.
2878
+ * {@link AiringScheduleQuery.airingSchedule} sends a query request to get airing schedules.
2146
2879
  *
2147
- * @param variables - The variables for the query.
2148
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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 - The variables for the query.
2203
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `aniChartUser` is a method that sends a query request to get AniChart users.
2974
+ * {@link AniChartUserQuery.aniChartUser} sends a query request to get AniChart users.
2234
2975
  *
2235
- * @returns The response from the query request.
2976
+ * @returns The {@link AniChartUserResponse} returned by the query.
2236
2977
  * @see https://docs.anilist.co/reference/object/anichartuser
2237
- * @param options - Optional per-request transport settings merged over the instance-level ones for this call only.
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
- * `character` is a method that sends a query request to get characters.
3043
+ * {@link CharacterQuery.character} sends a query request to get characters.
2299
3044
  *
2300
- * @param variables - The variables for the query.
2301
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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 - The variables for the query.
2341
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `externalLinkSourceCollection` is a method that sends a query request to get external link source collections.
3156
+ * {@link ExternalLinkSourceCollectionQuery.externalLinkSourceCollection} sends a query request to get external link source collections.
2404
3157
  *
2405
- * @param variables - The variables for the query. If not provided, an empty object will be used.
2406
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `follower` is a method that sends a query request to get followers.
3578
+ * {@link FollowerQuery.follower} sends a query request to get followers.
2822
3579
  *
2823
- * @param variables - The variables for the query.
2824
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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 - The variables for the query.
2866
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `following` is a method that sends a query request to get following users.
3677
+ * {@link FollowingQuery.following} sends a query request to get following users.
2913
3678
  *
2914
- * @param variables - The variables for the query.
2915
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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 - The variables for the query.
2957
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `genreCollection` is a method that sends a query request to get genre collections.
3767
+ * {@link GenreCollectionQuery.genreCollection} sends a query request to get genre collections.
2995
3768
  *
2996
- * @returns The response from the query request.
3769
+ * @returns The list of genre strings returned by AniList.
2997
3770
  * @see https://docs.anilist.co/reference/query
2998
- * @param options - Optional per-request transport settings merged over the instance-level ones for this call only.
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 - The variables for the query.
3021
- * @returns The users who liked the item for the requested page, with pagination metadata.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `markdown` is a method that sends a query request to convert Markdown text to HTML.
3839
+ * {@link MarkdownQuery.markdown} sends a query request to convert Markdown text to HTML.
3059
3840
  *
3060
- * @param variables - The variables for the query.
3061
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `mediaListCollection` is a method that sends a query request to get media list collection data.
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 - The variables for the query.
3216
- * @returns The response from the query request, including `lists` and `hasNextChunk`.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `mediaList` is a method that sends a query request to get media list data.
4093
+ * {@link MediaListQuery.mediaList} sends a query request to get media list data.
3302
4094
  *
3303
- * @param variables - The variables for the query.
3304
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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 - The variables for the query.
3360
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `media` is a method that sends a query request to get media data.
4290
+ * {@link MediaQuery.media} sends a query request to get media data.
3491
4291
  *
3492
- * @param variables - The variables for the query.
3493
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `mediaTagCollection` is a method that sends a query request to get media tag collection data.
4328
+ * {@link MediaTagCollectionQuery.mediaTagCollection} sends a query request to get media tag collection data.
3525
4329
  *
3526
- * @param variables - The variables for the query. If not provided, an empty object will be used.
3527
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `mediaTrend` is a method that sends a query request to get media trend data.
4398
+ * {@link MediaTrendQuery.mediaTrend} sends a query request to get media trend data.
3591
4399
  *
3592
- * @param variables - The variables for the query.
3593
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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 - The variables for the query.
3654
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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 `MediaResponse` object.
3759
- * @param variables - A `MediasVariables` object representing the variables for the query.
3760
- * @returns A `MediaResponse` object.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `notification` is a method that sends a query request to get notification data.
4790
+ * {@link NotificationQuery.notification} sends a query request to get notification data.
3971
4791
  *
3972
- * @param variables - The variables for the query.
3973
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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 - The variables for the query.
4006
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `recommendation` is a method that sends a query request to get recommendation data.
4891
+ * {@link RecommendationQuery.recommendation} sends a query request to get recommendation data.
4064
4892
  *
4065
- * @param variables - The variables for the query.
4066
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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 - The variables for the query.
4111
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `review` is a method that sends a query request to get review data.
5007
+ * {@link ReviewQuery.review} sends a query request to get review data.
4172
5008
  *
4173
- * @param variables - The variables for the query.
4174
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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 - The variables for the query.
4215
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `siteStatistics` is a method that sends a query request to get site statistics data.
5159
+ * {@link SiteStatisticsQuery.siteStatistics} sends a query request to get site statistics data.
4316
5160
  *
4317
- * @param variables - The variables for the query. If not provided, an empty object will be used.
4318
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `staff` is a method that sends a query request to get staff data.
5258
+ * {@link StaffQuery.staff} sends a query request to get staff data.
4411
5259
  *
4412
- * @param variables - The variables for the query.
4413
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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 - The variables for the query.
4461
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `studio` is a method that sends a query request to get studio data.
5422
+ * {@link StudioQuery.studio} sends a query request to get studio data.
4567
5423
  *
4568
- * @param variables - The variables for the query.
4569
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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 - The variables for the query.
4621
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `threadComment` is a method that sends a query request to get thread comment data.
5581
+ * {@link ThreadCommentQuery.threadComment} sends a query request to get thread comment data.
4718
5582
  *
4719
- * @param variables - The variables for the query.
4720
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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 - The variables for the query.
4760
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `thread` is a method that sends a query request to get thread data.
5681
+ * {@link ThreadQuery.thread} sends a query request to get thread data.
4810
5682
  *
4811
- * @param variables - The variables for the query.
4812
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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 - The variables for the query.
4857
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `user` is a method that sends a query request to get user data.
5779
+ * {@link UserQuery.user} sends a query request to get user data.
4900
5780
  *
4901
- * @param variables - The variables for the query.
4902
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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 - The variables for the query.
4940
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `viewer` is a method that sends a query request to get viewer data.
5865
+ * {@link ViewerQuery.viewer} sends a query request to get viewer data.
4978
5866
  *
4979
- * @param variables - The variables for the query.
4980
- * @returns The response from the query request.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `deleteMediaListEntry` is a method that sends a mutation request to delete a media list entry.
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 - An object of type `DeleteMediaListEntryVariables` representing the variables for the mutation.
5013
- * @returns A Promise that resolves to `{ deleted }`, where `deleted` is `true` when the entry was deleted by this call and `false` when it was already absent.
5014
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `deleteCustomList` is a method that sends a mutation request to delete a custom list.
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 - An object of type `DeleteCustomListVariables` representing the variables for the mutation.
5055
- * @returns A Promise that resolves to `{ deleted }`, where `deleted` is `true` when the custom list was deleted by this call and `false` when it was already absent.
5056
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `saveTextActivity` is a method that sends a mutation request to save a text activity.
5991
+ * {@link SaveTextActivityMutation.saveTextActivity} sends a mutation request to save a text activity.
5092
5992
  *
5093
- * @param variables - An object of type `SaveTextActivityVariables` representing the variables for the mutation.
5094
- * @returns A Promise that resolves to the response from the mutation request.
5095
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
5096
- * * @see https://docs.anilist.co/reference/union/activityunion
5097
- * @param options - Optional per-request transport settings merged over the instance-level ones for this call only.
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
- * `saveMessageActivity` is a method that sends a mutation request to save a message activity.
6037
+ * {@link SaveMessageActivityMutation.saveMessageActivity} sends a mutation request to save a message activity.
5134
6038
  *
5135
- * @param variables - An object of type `SaveMessageActivityVariables` representing the variables for the mutation.
5136
- * @returns A Promise that resolves to the response from the mutation request.
5137
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
5138
- * * @see https://docs.anilist.co/reference/union/activityunion
5139
- * @param options - Optional per-request transport settings merged over the instance-level ones for this call only.
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
- * `saveListActivity` is a method that sends a mutation request to save a list activity.
6079
+ * {@link SaveListActivityMutation.saveListActivity} sends a mutation request to save a list activity.
5172
6080
  *
5173
- * @param variables - An object of type `SaveListActivityVariables` representing the variables for the mutation.
5174
- * @returns A Promise that resolves to the response from the mutation request.
5175
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
5176
- * * @see https://docs.anilist.co/reference/union/activityunion
5177
- * @param options - Optional per-request transport settings merged over the instance-level ones for this call only.
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
- * `deleteActivity` is a method that sends a mutation request to delete a activity.
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 - An object of type `DeleteActivityVariables` representing the variables for the mutation.
5214
- * @returns A Promise that resolves to `{ deleted }`, where `deleted` is `true` when the activity was deleted by this call and `false` when it was already absent.
5215
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `toggleActivitySubscription` is a method that sends a mutation request to subscribe to an activity.
6165
+ * {@link ToggleActivitySubscriptionMutation.toggleActivitySubscription} sends a mutation request to subscribe to an activity.
5250
6166
  *
5251
- * @param variables - An object of type `ToggleActivitySubscriptionVariables` representing the variables for the mutation.
5252
- * @returns A Promise that resolves to the response from the mutation request.
5253
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
5254
- * * @see https://docs.anilist.co/reference/union/activityunion
5255
- * @param options - Optional per-request transport settings merged over the instance-level ones for this call only.
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
- * `toggleActivityPin` is a method that sends a mutation request to pin an activity.
6207
+ * {@link ToggleActivityPinMutation.toggleActivityPin} sends a mutation request to pin an activity.
5288
6208
  *
5289
- * @param variables - An object of type `ToggleActivityPinVariables` representing the variables for the mutation.
5290
- * @returns A Promise that resolves to the response from the mutation request.
5291
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
5292
- * * @see https://docs.anilist.co/reference/union/activityunion
5293
- * @param options - Optional per-request transport settings merged over the instance-level ones for this call only.
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
- * `SaveActivityReply` is a method that sends a mutation request to save an activity reply.
6251
+ * {@link SaveActivityReplyMutation.saveActivityReply} sends a mutation request to save an activity reply.
5328
6252
  *
5329
- * @param variables - An object of type `SaveActivityReplyVariables` representing the variables for the mutation.
5330
- * @returns A Promise that resolves to the response from the mutation request.
5331
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
5332
- * * @see https://docs.anilist.co/reference/object/activityreply
5333
- * @param options - Optional per-request transport settings merged over the instance-level ones for this call only.
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
- * `DeleteActivityReply` is a method that sends a mutation request to delete an activity reply.
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 - An object of type `DeleteActivityReplyVariables` representing the variables for the mutation.
5371
- * @returns A Promise that resolves to `{ deleted }`, where `deleted` is `true` when the reply was deleted by this call and `false` when it was already absent.
5372
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `ToggleLike` is a method that sends a mutation request to toggle a like.
6337
+ * {@link ToggleLikeMutation.toggleLike} sends a mutation request to toggle a like.
5406
6338
  *
5407
- * @param variables - An object of type `ToggleLikeVariables` representing the variables for the mutation.
5408
- * @returns A Promise that resolves to the user who performed the like toggle.
5409
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
5410
- * * @see https://docs.anilist.co/reference/object/user
5411
- * @param options - Optional per-request transport settings merged over the instance-level ones for this call only.
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
- * `ToggleLikeV2` is a method that sends a mutation request to toggle a like.
6380
+ * {@link ToggleLikeV2Mutation.toggleLikeV2} sends a mutation request to toggle a like.
5444
6381
  *
5445
- * @param variables - An object of type `ToggleLikeV2Variables` representing the variables for the mutation.
5446
- * @returns A Promise that resolves to a `Likeable` — one of an activity, activity reply, thread,
5447
- * or thread comment, depending on which likeable entity the mutation toggled.
5448
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
5449
- * * @see https://docs.anilist.co/reference/union/likeableunion
5450
- * @param options - Optional per-request transport settings merged over the instance-level ones for this call only.
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
- * `ToggleFollow` is a method that sends a mutation request to toggle a follow.
6420
+ * {@link ToggleFollowMutation.toggleFollow} sends a mutation request to toggle a follow.
5481
6421
  *
5482
- * @param variables - An object of type `ToggleFollowVariables` representing the variables for the mutation.
5483
- * @returns A Promise that resolves to the response from the mutation request.
5484
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
5485
- * * @see https://docs.anilist.co/reference/object/user
5486
- * @param options - Optional per-request transport settings merged over the instance-level ones for this call only.
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
- * `toggleFavourite` is a method that sends a mutation request to toggle a favourite.
6540
+ * {@link ToggleFavouriteMutation.toggleFavourite} sends a mutation request to toggle a favourite.
5597
6541
  *
5598
- * @param variables - An object of type `ToggleFavouriteVariables` representing the variables for the mutation.
5599
- * @returns A Promise that resolves to the response from the mutation request.
5600
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `updateFavouriteOrder` is a method that sends a mutation request to update the order of the favourites.
6589
+ * {@link UpdateFavouriteOrderMutation.updateFavouriteOrder} sends a mutation request to update the order of favourites.
5642
6590
  *
5643
- * @param variables - An object of type `UpdateFavouriteOrderVariables` representing the variables for the mutation.
5644
- * @returns A Promise that resolves to the response from the mutation request.
5645
- * @throws Will throw an error if authentication is missing, validation fails, or the mutation request fails.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `saveReview` is a method that sends a mutation request to save a review.
6633
+ * {@link SaveReviewMutation.saveReview} sends a mutation request to save a review.
5682
6634
  *
5683
- * @param variables - An object of type `SaveReviewVariables` representing the variables for the mutation.
5684
- * @returns A Promise that resolves to the response from the mutation request.
5685
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `rateReview` is a method that sends a mutation request to rate a review.
6676
+ * {@link RateReviewMutation.rateReview} sends a mutation request to rate a review.
5721
6677
  *
5722
- * @param variables - An object of type `RateReviewVariables` representing the variables for the mutation.
5723
- * @returns A Promise that resolves to the response from the mutation request.
5724
- * @throws Will throw an error if authentication is missing, validation fails, or the mutation request fails.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `deleteReview` is a method that sends a mutation request to delete a review.
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 - An object of type `DeleteReviewVariables` representing the variables for the mutation.
5764
- * @returns A Promise that resolves to `{ deleted }`, where `deleted` is `true` when the review was deleted by this call and `false` when it was already absent.
5765
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `saveReview` is a method that sends a mutation request to save a recommendation.
6770
+ * {@link SaveRecommendationMutation.saveRecommendation} sends a mutation request to save a recommendation.
5807
6771
  *
5808
- * @param variables - An object of type `SaveRecommendationVariables` representing the variables for the mutation.
5809
- * @returns A Promise that resolves to the response from the mutation request.
5810
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `SaveThread` is a method that sends a mutation request to save a thread.
6817
+ * {@link SaveThreadMutation.saveThread} sends a mutation request to save a thread.
5850
6818
  *
5851
- * @param variables - An object of type `SaveThreadVariables` representing the variables for the mutation.
5852
- * @returns A Promise that resolves to the response from the mutation request.
5853
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
5854
- * * @see https://docs.anilist.co/reference/object/thread
5855
- * @param options - Optional per-request transport settings merged over the instance-level ones for this call only.
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
- * `deleteThread` is a method that sends a mutation request to delete a thread.
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 - An object of type `DeleteThreadVariables` representing the variables for the mutation.
5893
- * @returns A Promise that resolves to `{ deleted }`, where `deleted` is `true` when the thread was deleted by this call and `false` when it was already absent.
5894
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `toggleThreadSubscription` is a method that sends a mutation request to subscribe to an activity.
6904
+ * {@link ToggleThreadSubscriptionMutation.toggleThreadSubscription} sends a mutation request to subscribe to a thread.
5929
6905
  *
5930
- * @param variables - An object of type `ToggleThreadSubscriptionVariables` representing the variables for the mutation.
5931
- * @returns A Promise that resolves to the response from the mutation request.
5932
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
5933
- * * @see https://docs.anilist.co/reference/object/thread
5934
- * @param options - Optional per-request transport settings merged over the instance-level ones for this call only.
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
- * `saveThreadComment` is a method that sends a mutation request to save a thread comment.
6949
+ * {@link SaveThreadCommentMutation.saveThreadComment} sends a mutation request to save a thread comment.
5970
6950
  *
5971
- * @param variables - An object of type `SaveThreadCommentVariables` representing the variables for the mutation.
5972
- * @returns A Promise that resolves to the response from the mutation request.
5973
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
5974
- * * @see https://docs.anilist.co/reference/object/threadcomment
5975
- * @param options - Optional per-request transport settings merged over the instance-level ones for this call only.
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
- * `deleteThreadComment` is a method that sends a mutation request to delete a thread comment.
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 - An object of type `DeleteThreadCommentVariables` representing the variables for the mutation.
6013
- * @returns A Promise that resolves to `{ deleted }`, where `deleted` is `true` when the comment was deleted by this call and `false` when it was already absent.
6014
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `updateAniChartSettings` is a method that sends a mutation request to update the AniChart settings.
7037
+ * {@link UpdateAniChartSettingsMutation.updateAniChartSettings} sends a mutation request to update the AniChart settings.
6050
7038
  *
6051
- * @param variables - An object of type `UpdateAniChartSettingsVariables` representing the variables for the mutation.
6052
- * @returns A Promise that resolves to the updated AniChart settings string.
6053
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `updateAniChartHighlights` is a method that sends a mutation request to update the AniChart highlights.
7071
+ * {@link UpdateAniChartHighlightsMutation.updateAniChartHighlights} sends a mutation request to update the AniChart highlights.
6080
7072
  *
6081
- * @param variables - An object of type `UpdateAniChartHighlightsVariables` representing the variables for the mutation.
6082
- * @returns A Promise that resolves to the updated AniChart highlights string.
6083
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `updateMediaListEntries` is a method that sends a mutation request to update media list entries.
7122
+ * {@link UpdateMediaListEntriesMutation.updateMediaListEntries} sends a mutation request to update media list entries.
6127
7123
  *
6128
- * @param variables - An object of type `UpdateMediaListEntriesVariables` representing the variables for the mutation.
6129
- * @returns A Promise that resolves to the response from the mutation request.
6130
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
6131
- * * @see https://docs.anilist.co/reference/object/medialist
6132
- * @param options - Optional per-request transport settings merged over the instance-level ones for this call only.
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
- * `updateUser` is a method that sends a mutation request to update a user.
7248
+ * {@link UpdateUserMutation.updateUser} sends a mutation request to update a user.
6249
7249
  *
6250
- * @param variables - An object of type `UpdateUserVariables` representing the variables for the mutation.
6251
- * @returns A Promise that resolves to an object of type `UpdateUserResponse`. This object includes the updated user details
6252
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
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 per-request transport settings merged over the instance-level ones for this call only.
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
- * `saveMediaListEntry` is a method that sends a mutation request to save a media list entry.
7344
+ * {@link SaveMediaListEntryMutation.saveMediaListEntry} sends a mutation request to save a media list entry.
6341
7345
  *
6342
- * @param variables - An object of type `SaveMediaListEntryVariables` representing the variables for the mutation.
6343
- * @returns A Promise that resolves to the response from the mutation request.
6344
- * @throws Will throw an error if the mutation request fails or if the provided variables do not pass the validation checks.
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 per-request transport settings merged over the instance-level ones for this call only.
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 instantiateCategory(category, authToken, options) {
6477
- return ANILIST_OPERATION_REGISTRY[category].map((entry) => ({
6478
- name: entry.name,
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 ?? name}" method to bind.`
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
- return bound;
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 customInstance = new CustomRequest(authToken, options);
6498
- const [queryEntries, pageEntries, mutationEntries] = ["query", "page", "mutation"].map((category) => instantiateCategory(category, authToken, options));
6499
- return {
6500
- custom: customInstance.custom.bind(customInstance),
6501
- query: {
6502
- ...bindEntries(queryEntries),
6503
- page: bindEntries(pageEntries)
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
- mutation: bindEntries(mutationEntries),
6506
- paginate,
6507
- paginatePages,
6508
- paginateChunks,
6509
- fuzzyDate,
6510
- flattenMediaListCollection
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
- function resolveProviderCredentials(credentials) {
6519
- if (credentials === void 0) return void 0;
6520
- const { authToken, ...transportOptions } = credentials;
6521
- return authToken === void 0 ? { ...transportOptions } : { ...transportOptions, authToken };
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 AUTH_TOKEN_TIMEOUT_MS = 1e4;
6525
- const ANILIST_TOKEN_URL = "https://anilist.co/api/v2/oauth/token";
6526
- const ANILIST_AUTHORIZE_URL = "https://anilist.co/api/v2/oauth/authorize";
6527
- const buildAuthorizationUrl = (clientId, redirectUri, state) => {
6528
- let url = `${ANILIST_AUTHORIZE_URL}?client_id=${encodeURIComponent(
6529
- clientId
6530
- )}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code`;
6531
- if (state !== void 0) {
6532
- url += `&state=${encodeURIComponent(state)}`;
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 url;
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 normalizeTokenRequestError = (error) => {
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
- error.message = `Token request failed with status ${error.status}.`;
6539
- return error;
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 = `Token request failed with status ${status}.`;
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
- "The token request failed due to a network error."
8017
+ `${label} failed due to a network error.`
6566
8018
  );
6567
8019
  }
6568
- return new AniLinkError("The token request failed.", AniLinkErrorCodes.UNKNOWN);
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
- return await sendRequest(
6579
- ANILIST_TOKEN_URL,
6580
- "POST",
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
- void 0,
6586
- "application/x-www-form-urlencoded"
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
- * Anilist API methods.
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 required for authenticated queries and mutations. If no `authToken` is provided, only public queries will be available. You are able to create multiple AniLink instances with different `authToken`s.
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 credentials object: each 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
- if (typeof authToken === "string" || authToken === void 0) {
6656
- this.anilist = buildAniListApi(authToken, options);
6657
- return;
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
- const anilistCredentials = resolveProviderCredentials(authToken.anilist);
6660
- this.anilist = buildAniListApi(anilistCredentials?.authToken, anilistCredentials);
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 };