ani-client 2.1.1 → 2.1.2

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/index.d.mts CHANGED
@@ -29,6 +29,17 @@ interface ExternalLink {
29
29
  interface CacheAdapter {
30
30
  /** Retrieve a cached value, or `undefined` if missing / expired. */
31
31
  get<T>(key: string): T | undefined | Promise<T | undefined>;
32
+ /**
33
+ * Retrieve a cached value along with its stale status (for stale-while-revalidate).
34
+ * If implemented, the client will use this to trigger background revalidation.
35
+ */
36
+ getWithMeta?<T>(key: string): {
37
+ data: T;
38
+ stale: boolean;
39
+ } | undefined | Promise<{
40
+ data: T;
41
+ stale: boolean;
42
+ } | undefined>;
32
43
  /** Store a value in the cache. */
33
44
  set<T>(key: string, data: T): void | Promise<void>;
34
45
  /** Remove a specific entry. Returns `true` if the key existed. */
@@ -1070,6 +1081,47 @@ interface SearchThreadOptions {
1070
1081
  perPage?: number;
1071
1082
  }
1072
1083
 
1084
+ /**
1085
+ * Normalized Cache Adapter for AniListClient.
1086
+ *
1087
+ * This cache intercepts GraphQL responses, extracts objects with `__typename` and `id`,
1088
+ * and stores them flat in an entity store.
1089
+ * This ensures data consistency across all queries (e.g., `getMedia(1)` and `searchMedia`
1090
+ * share the exact same `Media` object).
1091
+ */
1092
+ declare class NormalizedCache implements CacheAdapter {
1093
+ private readonly ttl;
1094
+ private readonly maxSize;
1095
+ private readonly enabled;
1096
+ private readonly swrMs;
1097
+ private readonly queryStore;
1098
+ private readonly entityStore;
1099
+ private _hits;
1100
+ private _misses;
1101
+ private _stales;
1102
+ constructor(options?: CacheOptions);
1103
+ static key(query: string, variables: Record<string, unknown>): string;
1104
+ /** Normalizes a GraphQL response, extracting entities and returning a tree of references. */
1105
+ private normalize;
1106
+ /** Reconstructs a GraphQL response from references. */
1107
+ private denormalize;
1108
+ getWithMeta<T>(key: string): {
1109
+ data: T;
1110
+ stale: boolean;
1111
+ } | undefined;
1112
+ get<T>(key: string): T | undefined;
1113
+ set<T>(key: string, data: T): void;
1114
+ delete(key: string): boolean;
1115
+ clear(): void;
1116
+ get size(): number;
1117
+ keys(): string[];
1118
+ invalidate(pattern: string | RegExp): number;
1119
+ get stats(): CacheStats & {
1120
+ entitiesCount: number;
1121
+ };
1122
+ resetStats(): void;
1123
+ }
1124
+
1073
1125
  /** Cache performance statistics. */
1074
1126
  interface CacheStats {
1075
1127
  /** Total cache hits. */
@@ -1098,6 +1150,13 @@ declare class MemoryCache implements CacheAdapter {
1098
1150
  * With stale-while-revalidate enabled, returns stale data within the grace window
1099
1151
  * and flags it so the caller can refresh in the background.
1100
1152
  */
1153
+ /**
1154
+ * Retrieve a cached value and its stale status.
1155
+ */
1156
+ getWithMeta<T>(key: string): {
1157
+ data: T;
1158
+ stale: boolean;
1159
+ } | undefined;
1101
1160
  get<T>(key: string): T | undefined;
1102
1161
  /** Store a value in the cache. */
1103
1162
  set<T>(key: string, data: T): void;
@@ -1473,4 +1532,4 @@ declare class RateLimiter {
1473
1532
 
1474
1533
  declare function parseAniListMarkdown(text: string): string;
1475
1534
 
1476
- export { type AiringSchedule, AiringSort, AniListClient, type AniListClientOptions, AniListError, type AniListHooks, type CacheAdapter, type CacheOptions, type CacheStats, type Character, type CharacterImage, type CharacterIncludeOptions, type CharacterMediaEdge, type CharacterName, CharacterRole, CharacterSort, type DayOfWeek, type ExternalLink, type FavoriteCharacterNode, type FavoriteMediaNode, type FavoriteStaffNode, type FavoriteStudioNode, type FuzzyDate, type GetAiringOptions, type GetMediaCharactersOptions, type GetMediaStaffOptions, type GetPlanningOptions, type GetRecentChaptersOptions, type GetRecommendationsOptions, type GetSeasonOptions, type GetUserMediaListOptions, type Logger, type Media, type MediaCharacterConnection, type MediaCharacterEdge, type MediaConnection, type MediaCoverImage, type MediaEdge, MediaFormat, type MediaIncludeOptions, type MediaListEntry, MediaListSort, MediaListStatus, type MediaRecommendationNode, MediaRelationType, MediaSeason, MediaSort, MediaSource, type MediaStaffConnection, type MediaStaffEdge, type MediaStats, MediaStatus, type MediaTag, type MediaTitle, type MediaTrailer, MediaType, MemoryCache, type NextAiringEpisode, type PageInfo, type PagedResult, type RateLimitInfo, type RateLimitOptions, RateLimiter, type Recommendation, RecommendationSort, RedisCache, type RedisCacheOptions, type RedisLikeClient, type ResponseMeta, type Review, ReviewSort, type ScoreDistribution, type SearchCharacterOptions, type SearchMediaOptions, type SearchReviewOptions, type SearchStaffOptions, type SearchStudioOptions, type SearchThreadOptions, type SearchUserOptions, type Staff, type StaffImage, type StaffIncludeOptions, type StaffMediaNode, type StaffName, StaffSort, type StatusDistribution, type StreamingEpisode, type Studio, type StudioConnection, type StudioIncludeOptions, StudioSort, type Thread, type ThreadCategory, type ThreadMediaCategory, ThreadSort, type User, type UserAvatar, type UserFavorites, type UserFavoritesOptions, UserSort, type UserStatistics, type VoiceActor, type WeeklySchedule, parseAniListMarkdown };
1535
+ export { type AiringSchedule, AiringSort, AniListClient, type AniListClientOptions, AniListError, type AniListHooks, type CacheAdapter, type CacheOptions, type CacheStats, type Character, type CharacterImage, type CharacterIncludeOptions, type CharacterMediaEdge, type CharacterName, CharacterRole, CharacterSort, type DayOfWeek, type ExternalLink, type FavoriteCharacterNode, type FavoriteMediaNode, type FavoriteStaffNode, type FavoriteStudioNode, type FuzzyDate, type GetAiringOptions, type GetMediaCharactersOptions, type GetMediaStaffOptions, type GetPlanningOptions, type GetRecentChaptersOptions, type GetRecommendationsOptions, type GetSeasonOptions, type GetUserMediaListOptions, type Logger, type Media, type MediaCharacterConnection, type MediaCharacterEdge, type MediaConnection, type MediaCoverImage, type MediaEdge, MediaFormat, type MediaIncludeOptions, type MediaListEntry, MediaListSort, MediaListStatus, type MediaRecommendationNode, MediaRelationType, MediaSeason, MediaSort, MediaSource, type MediaStaffConnection, type MediaStaffEdge, type MediaStats, MediaStatus, type MediaTag, type MediaTitle, type MediaTrailer, MediaType, MemoryCache, type NextAiringEpisode, NormalizedCache, type PageInfo, type PagedResult, type RateLimitInfo, type RateLimitOptions, RateLimiter, type Recommendation, RecommendationSort, RedisCache, type RedisCacheOptions, type RedisLikeClient, type ResponseMeta, type Review, ReviewSort, type ScoreDistribution, type SearchCharacterOptions, type SearchMediaOptions, type SearchReviewOptions, type SearchStaffOptions, type SearchStudioOptions, type SearchThreadOptions, type SearchUserOptions, type Staff, type StaffImage, type StaffIncludeOptions, type StaffMediaNode, type StaffName, StaffSort, type StatusDistribution, type StreamingEpisode, type Studio, type StudioConnection, type StudioIncludeOptions, StudioSort, type Thread, type ThreadCategory, type ThreadMediaCategory, ThreadSort, type User, type UserAvatar, type UserFavorites, type UserFavoritesOptions, UserSort, type UserStatistics, type VoiceActor, type WeeklySchedule, parseAniListMarkdown };
package/dist/index.d.ts CHANGED
@@ -29,6 +29,17 @@ interface ExternalLink {
29
29
  interface CacheAdapter {
30
30
  /** Retrieve a cached value, or `undefined` if missing / expired. */
31
31
  get<T>(key: string): T | undefined | Promise<T | undefined>;
32
+ /**
33
+ * Retrieve a cached value along with its stale status (for stale-while-revalidate).
34
+ * If implemented, the client will use this to trigger background revalidation.
35
+ */
36
+ getWithMeta?<T>(key: string): {
37
+ data: T;
38
+ stale: boolean;
39
+ } | undefined | Promise<{
40
+ data: T;
41
+ stale: boolean;
42
+ } | undefined>;
32
43
  /** Store a value in the cache. */
33
44
  set<T>(key: string, data: T): void | Promise<void>;
34
45
  /** Remove a specific entry. Returns `true` if the key existed. */
@@ -1070,6 +1081,47 @@ interface SearchThreadOptions {
1070
1081
  perPage?: number;
1071
1082
  }
1072
1083
 
1084
+ /**
1085
+ * Normalized Cache Adapter for AniListClient.
1086
+ *
1087
+ * This cache intercepts GraphQL responses, extracts objects with `__typename` and `id`,
1088
+ * and stores them flat in an entity store.
1089
+ * This ensures data consistency across all queries (e.g., `getMedia(1)` and `searchMedia`
1090
+ * share the exact same `Media` object).
1091
+ */
1092
+ declare class NormalizedCache implements CacheAdapter {
1093
+ private readonly ttl;
1094
+ private readonly maxSize;
1095
+ private readonly enabled;
1096
+ private readonly swrMs;
1097
+ private readonly queryStore;
1098
+ private readonly entityStore;
1099
+ private _hits;
1100
+ private _misses;
1101
+ private _stales;
1102
+ constructor(options?: CacheOptions);
1103
+ static key(query: string, variables: Record<string, unknown>): string;
1104
+ /** Normalizes a GraphQL response, extracting entities and returning a tree of references. */
1105
+ private normalize;
1106
+ /** Reconstructs a GraphQL response from references. */
1107
+ private denormalize;
1108
+ getWithMeta<T>(key: string): {
1109
+ data: T;
1110
+ stale: boolean;
1111
+ } | undefined;
1112
+ get<T>(key: string): T | undefined;
1113
+ set<T>(key: string, data: T): void;
1114
+ delete(key: string): boolean;
1115
+ clear(): void;
1116
+ get size(): number;
1117
+ keys(): string[];
1118
+ invalidate(pattern: string | RegExp): number;
1119
+ get stats(): CacheStats & {
1120
+ entitiesCount: number;
1121
+ };
1122
+ resetStats(): void;
1123
+ }
1124
+
1073
1125
  /** Cache performance statistics. */
1074
1126
  interface CacheStats {
1075
1127
  /** Total cache hits. */
@@ -1098,6 +1150,13 @@ declare class MemoryCache implements CacheAdapter {
1098
1150
  * With stale-while-revalidate enabled, returns stale data within the grace window
1099
1151
  * and flags it so the caller can refresh in the background.
1100
1152
  */
1153
+ /**
1154
+ * Retrieve a cached value and its stale status.
1155
+ */
1156
+ getWithMeta<T>(key: string): {
1157
+ data: T;
1158
+ stale: boolean;
1159
+ } | undefined;
1101
1160
  get<T>(key: string): T | undefined;
1102
1161
  /** Store a value in the cache. */
1103
1162
  set<T>(key: string, data: T): void;
@@ -1473,4 +1532,4 @@ declare class RateLimiter {
1473
1532
 
1474
1533
  declare function parseAniListMarkdown(text: string): string;
1475
1534
 
1476
- export { type AiringSchedule, AiringSort, AniListClient, type AniListClientOptions, AniListError, type AniListHooks, type CacheAdapter, type CacheOptions, type CacheStats, type Character, type CharacterImage, type CharacterIncludeOptions, type CharacterMediaEdge, type CharacterName, CharacterRole, CharacterSort, type DayOfWeek, type ExternalLink, type FavoriteCharacterNode, type FavoriteMediaNode, type FavoriteStaffNode, type FavoriteStudioNode, type FuzzyDate, type GetAiringOptions, type GetMediaCharactersOptions, type GetMediaStaffOptions, type GetPlanningOptions, type GetRecentChaptersOptions, type GetRecommendationsOptions, type GetSeasonOptions, type GetUserMediaListOptions, type Logger, type Media, type MediaCharacterConnection, type MediaCharacterEdge, type MediaConnection, type MediaCoverImage, type MediaEdge, MediaFormat, type MediaIncludeOptions, type MediaListEntry, MediaListSort, MediaListStatus, type MediaRecommendationNode, MediaRelationType, MediaSeason, MediaSort, MediaSource, type MediaStaffConnection, type MediaStaffEdge, type MediaStats, MediaStatus, type MediaTag, type MediaTitle, type MediaTrailer, MediaType, MemoryCache, type NextAiringEpisode, type PageInfo, type PagedResult, type RateLimitInfo, type RateLimitOptions, RateLimiter, type Recommendation, RecommendationSort, RedisCache, type RedisCacheOptions, type RedisLikeClient, type ResponseMeta, type Review, ReviewSort, type ScoreDistribution, type SearchCharacterOptions, type SearchMediaOptions, type SearchReviewOptions, type SearchStaffOptions, type SearchStudioOptions, type SearchThreadOptions, type SearchUserOptions, type Staff, type StaffImage, type StaffIncludeOptions, type StaffMediaNode, type StaffName, StaffSort, type StatusDistribution, type StreamingEpisode, type Studio, type StudioConnection, type StudioIncludeOptions, StudioSort, type Thread, type ThreadCategory, type ThreadMediaCategory, ThreadSort, type User, type UserAvatar, type UserFavorites, type UserFavoritesOptions, UserSort, type UserStatistics, type VoiceActor, type WeeklySchedule, parseAniListMarkdown };
1535
+ export { type AiringSchedule, AiringSort, AniListClient, type AniListClientOptions, AniListError, type AniListHooks, type CacheAdapter, type CacheOptions, type CacheStats, type Character, type CharacterImage, type CharacterIncludeOptions, type CharacterMediaEdge, type CharacterName, CharacterRole, CharacterSort, type DayOfWeek, type ExternalLink, type FavoriteCharacterNode, type FavoriteMediaNode, type FavoriteStaffNode, type FavoriteStudioNode, type FuzzyDate, type GetAiringOptions, type GetMediaCharactersOptions, type GetMediaStaffOptions, type GetPlanningOptions, type GetRecentChaptersOptions, type GetRecommendationsOptions, type GetSeasonOptions, type GetUserMediaListOptions, type Logger, type Media, type MediaCharacterConnection, type MediaCharacterEdge, type MediaConnection, type MediaCoverImage, type MediaEdge, MediaFormat, type MediaIncludeOptions, type MediaListEntry, MediaListSort, MediaListStatus, type MediaRecommendationNode, MediaRelationType, MediaSeason, MediaSort, MediaSource, type MediaStaffConnection, type MediaStaffEdge, type MediaStats, MediaStatus, type MediaTag, type MediaTitle, type MediaTrailer, MediaType, MemoryCache, type NextAiringEpisode, NormalizedCache, type PageInfo, type PagedResult, type RateLimitInfo, type RateLimitOptions, RateLimiter, type Recommendation, RecommendationSort, RedisCache, type RedisCacheOptions, type RedisLikeClient, type ResponseMeta, type Review, ReviewSort, type ScoreDistribution, type SearchCharacterOptions, type SearchMediaOptions, type SearchReviewOptions, type SearchStaffOptions, type SearchStudioOptions, type SearchThreadOptions, type SearchUserOptions, type Staff, type StaffImage, type StaffIncludeOptions, type StaffMediaNode, type StaffName, StaffSort, type StatusDistribution, type StreamingEpisode, type Studio, type StudioConnection, type StudioIncludeOptions, StudioSort, type Thread, type ThreadCategory, type ThreadMediaCategory, ThreadSort, type User, type UserAvatar, type UserFavorites, type UserFavoritesOptions, UserSort, type UserStatistics, type VoiceActor, type WeeklySchedule, parseAniListMarkdown };
package/dist/index.js CHANGED
@@ -124,6 +124,174 @@ function sortObjectKeys(obj) {
124
124
  return sorted;
125
125
  }
126
126
 
127
+ // src/cache/normalized.ts
128
+ var NormalizedCache = class {
129
+ ttl;
130
+ maxSize;
131
+ enabled;
132
+ swrMs;
133
+ queryStore = /* @__PURE__ */ new Map();
134
+ entityStore = /* @__PURE__ */ new Map();
135
+ _hits = 0;
136
+ _misses = 0;
137
+ _stales = 0;
138
+ constructor(options = {}) {
139
+ this.ttl = options.ttl ?? 24 * 60 * 60 * 1e3;
140
+ this.maxSize = options.maxSize ?? 500;
141
+ this.enabled = options.enabled ?? true;
142
+ this.swrMs = options.staleWhileRevalidateMs ?? 0;
143
+ }
144
+ static key(query, variables) {
145
+ const normalized = normalizeQuery(query);
146
+ return `${normalized}|${JSON.stringify(sortObjectKeys(variables))}`;
147
+ }
148
+ /** Normalizes a GraphQL response, extracting entities and returning a tree of references. */
149
+ normalize(data, seen = /* @__PURE__ */ new WeakSet()) {
150
+ if (Array.isArray(data)) {
151
+ if (seen.has(data)) return null;
152
+ seen.add(data);
153
+ return data.map((item) => this.normalize(item, seen));
154
+ }
155
+ if (data !== null && typeof data === "object") {
156
+ if (seen.has(data)) return null;
157
+ seen.add(data);
158
+ const obj = data;
159
+ if (typeof obj.__typename === "string" && (typeof obj.id === "number" || typeof obj.id === "string")) {
160
+ const ref = `${obj.__typename}:${obj.id}`;
161
+ const normalizedObj = {};
162
+ for (const [k, v] of Object.entries(obj)) {
163
+ normalizedObj[k] = this.normalize(v, seen);
164
+ }
165
+ const existing = this.entityStore.get(ref) || {};
166
+ this.entityStore.set(ref, { ...existing, ...normalizedObj });
167
+ return { __ref: ref };
168
+ }
169
+ const result = {};
170
+ for (const [k, v] of Object.entries(obj)) {
171
+ result[k] = this.normalize(v, seen);
172
+ }
173
+ return result;
174
+ }
175
+ return data;
176
+ }
177
+ /** Reconstructs a GraphQL response from references. */
178
+ denormalize(data, seen = /* @__PURE__ */ new Set()) {
179
+ if (Array.isArray(data)) {
180
+ return data.map((item) => this.denormalize(item, seen));
181
+ }
182
+ if (data !== null && typeof data === "object") {
183
+ const obj = data;
184
+ if (typeof obj.__ref === "string") {
185
+ const ref = obj.__ref;
186
+ if (seen.has(ref)) {
187
+ return { __ref: ref };
188
+ }
189
+ seen.add(ref);
190
+ const entity = this.entityStore.get(ref);
191
+ if (!entity) return void 0;
192
+ const result2 = this.denormalize(entity, seen);
193
+ seen.delete(ref);
194
+ return result2;
195
+ }
196
+ const result = {};
197
+ for (const [k, v] of Object.entries(obj)) {
198
+ const denormalized = this.denormalize(v, seen);
199
+ if (denormalized === void 0) return void 0;
200
+ result[k] = denormalized;
201
+ }
202
+ return result;
203
+ }
204
+ return data;
205
+ }
206
+ getWithMeta(key) {
207
+ if (!this.enabled) return void 0;
208
+ const entry = this.queryStore.get(key);
209
+ if (!entry) {
210
+ this._misses++;
211
+ return void 0;
212
+ }
213
+ const now = Date.now();
214
+ let isStale = false;
215
+ if (now > entry.expiresAt) {
216
+ if (this.swrMs > 0 && now <= entry.expiresAt + this.swrMs) {
217
+ isStale = true;
218
+ } else {
219
+ this.queryStore.delete(key);
220
+ this._misses++;
221
+ return void 0;
222
+ }
223
+ }
224
+ const denormalized = this.denormalize(entry.data);
225
+ if (denormalized === void 0) {
226
+ this.queryStore.delete(key);
227
+ this._misses++;
228
+ return void 0;
229
+ }
230
+ this.queryStore.delete(key);
231
+ this.queryStore.set(key, entry);
232
+ if (isStale) {
233
+ this._stales++;
234
+ } else {
235
+ this._hits++;
236
+ }
237
+ return { data: denormalized, stale: isStale };
238
+ }
239
+ get(key) {
240
+ const res = this.getWithMeta(key);
241
+ return res ? res.data : void 0;
242
+ }
243
+ set(key, data) {
244
+ if (!this.enabled) return;
245
+ const normalizedData = this.normalize(data);
246
+ this.queryStore.delete(key);
247
+ if (this.maxSize > 0 && this.queryStore.size >= this.maxSize) {
248
+ const firstKey = this.queryStore.keys().next().value;
249
+ if (firstKey !== void 0) this.queryStore.delete(firstKey);
250
+ }
251
+ this.queryStore.set(key, { data: normalizedData, expiresAt: Date.now() + this.ttl });
252
+ }
253
+ delete(key) {
254
+ return this.queryStore.delete(key);
255
+ }
256
+ clear() {
257
+ this.queryStore.clear();
258
+ this.entityStore.clear();
259
+ this._hits = 0;
260
+ this._misses = 0;
261
+ this._stales = 0;
262
+ }
263
+ get size() {
264
+ return this.queryStore.size;
265
+ }
266
+ keys() {
267
+ return [...this.queryStore.keys()];
268
+ }
269
+ invalidate(pattern) {
270
+ const test = typeof pattern === "string" ? (key) => key.includes(pattern) : (key) => pattern.test(key);
271
+ const toDelete = [];
272
+ for (const key of this.queryStore.keys()) {
273
+ if (test(key)) toDelete.push(key);
274
+ }
275
+ for (const key of toDelete) this.queryStore.delete(key);
276
+ return toDelete.length;
277
+ }
278
+ get stats() {
279
+ const total = this._hits + this._misses + this._stales;
280
+ return {
281
+ hits: this._hits,
282
+ misses: this._misses,
283
+ stales: this._stales,
284
+ hitRate: total === 0 ? Number.NaN : this._hits / total,
285
+ entitiesCount: this.entityStore.size
286
+ };
287
+ }
288
+ resetStats() {
289
+ this._hits = 0;
290
+ this._misses = 0;
291
+ this._stales = 0;
292
+ }
293
+ };
294
+
127
295
  // src/cache/index.ts
128
296
  var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
129
297
  var MemoryCache = class {
@@ -151,7 +319,10 @@ var MemoryCache = class {
151
319
  * With stale-while-revalidate enabled, returns stale data within the grace window
152
320
  * and flags it so the caller can refresh in the background.
153
321
  */
154
- get(key) {
322
+ /**
323
+ * Retrieve a cached value and its stale status.
324
+ */
325
+ getWithMeta(key) {
155
326
  if (!this.enabled) return void 0;
156
327
  const entry = this.store.get(key);
157
328
  if (!entry) {
@@ -164,7 +335,7 @@ var MemoryCache = class {
164
335
  this.store.delete(key);
165
336
  this.store.set(key, entry);
166
337
  this._stales++;
167
- return entry.data;
338
+ return { data: entry.data, stale: true };
168
339
  }
169
340
  this.store.delete(key);
170
341
  this._misses++;
@@ -173,7 +344,11 @@ var MemoryCache = class {
173
344
  this.store.delete(key);
174
345
  this.store.set(key, entry);
175
346
  this._hits++;
176
- return entry.data;
347
+ return { data: entry.data, stale: false };
348
+ }
349
+ get(key) {
350
+ const res = this.getWithMeta(key);
351
+ return res ? res.data : void 0;
177
352
  }
178
353
  /** Store a value in the cache. */
179
354
  set(key, data) {
@@ -359,6 +534,7 @@ var AniListError = class _AniListError extends Error {
359
534
 
360
535
  // src/queries/fragments.ts
361
536
  var MEDIA_FIELDS_LIGHT = `
537
+ __typename
362
538
  id
363
539
  idMal
364
540
  title { romaji english native userPreferred }
@@ -385,6 +561,7 @@ var MEDIA_FIELDS_LIGHT = `
385
561
  }
386
562
  `;
387
563
  var MEDIA_FIELDS_BASE = `
564
+ __typename
388
565
  id
389
566
  idMal
390
567
  title { romaji english native userPreferred }
@@ -431,6 +608,7 @@ var RELATIONS_FIELDS = `
431
608
  edges {
432
609
  relationType(version: 2)
433
610
  node {
611
+ __typename
434
612
  id
435
613
  title { romaji english native userPreferred }
436
614
  type
@@ -461,9 +639,11 @@ var RELATIONS_FIELDS = `
461
639
  }
462
640
  `;
463
641
  var MEDIA_RECOMMENDATION_FIELDS = `
642
+ __typename
464
643
  id
465
644
  rating
466
645
  mediaRecommendation {
646
+ __typename
467
647
  id
468
648
  title { romaji english native userPreferred }
469
649
  type
@@ -495,6 +675,7 @@ var MEDIA_FIELDS = `
495
675
  ${RELATIONS_FIELDS}
496
676
  `;
497
677
  var CHARACTER_FIELDS_COMPACT = `
678
+ __typename
498
679
  id
499
680
  name { first middle last full native alternative }
500
681
  image { large medium }
@@ -509,6 +690,7 @@ var CHARACTER_FIELDS_COMPACT = `
509
690
  var CHARACTER_MEDIA_NODES = `
510
691
  media(perPage: 10) {
511
692
  nodes {
693
+ __typename
512
694
  id
513
695
  title { romaji english native userPreferred }
514
696
  type
@@ -518,6 +700,7 @@ var CHARACTER_MEDIA_NODES = `
518
700
  }
519
701
  `;
520
702
  var VOICE_ACTOR_FIELDS_COMPACT = `
703
+ __typename
521
704
  id
522
705
  name { first middle last full native userPreferred }
523
706
  languageV2
@@ -533,6 +716,7 @@ var CHARACTER_MEDIA_EDGES_WITH_VA = `
533
716
  ${VOICE_ACTOR_FIELDS_COMPACT}
534
717
  }
535
718
  node {
719
+ __typename
536
720
  id
537
721
  title { romaji english native userPreferred }
538
722
  type
@@ -551,6 +735,7 @@ var CHARACTER_FIELDS_WITH_VA = `
551
735
  ${CHARACTER_MEDIA_EDGES_WITH_VA}
552
736
  `;
553
737
  var STAFF_FIELDS = `
738
+ __typename
554
739
  id
555
740
  name { first middle last full native }
556
741
  language
@@ -570,6 +755,7 @@ var STAFF_FIELDS = `
570
755
  var STAFF_MEDIA_FIELDS = `
571
756
  staffMedia(perPage: $perPage, sort: [POPULARITY_DESC]) {
572
757
  nodes {
758
+ __typename
573
759
  id
574
760
  title { romaji english native userPreferred }
575
761
  type
@@ -608,6 +794,7 @@ var STAFF_MEDIA_FIELDS = `
608
794
  }
609
795
  `;
610
796
  var USER_FIELDS = `
797
+ __typename
611
798
  id
612
799
  name
613
800
  about(asHtml: false)
@@ -628,6 +815,7 @@ var USER_FAVORITES_FIELDS = `
628
815
  favourites {
629
816
  anime(perPage: 25) {
630
817
  nodes {
818
+ __typename
631
819
  id
632
820
  title { romaji english native userPreferred }
633
821
  coverImage { large medium }
@@ -638,6 +826,7 @@ var USER_FAVORITES_FIELDS = `
638
826
  }
639
827
  manga(perPage: 25) {
640
828
  nodes {
829
+ __typename
641
830
  id
642
831
  title { romaji english native userPreferred }
643
832
  coverImage { large medium }
@@ -648,6 +837,7 @@ var USER_FAVORITES_FIELDS = `
648
837
  }
649
838
  characters(perPage: 25) {
650
839
  nodes {
840
+ __typename
651
841
  id
652
842
  name { full native }
653
843
  image { large medium }
@@ -656,6 +846,7 @@ var USER_FAVORITES_FIELDS = `
656
846
  }
657
847
  staff(perPage: 25) {
658
848
  nodes {
849
+ __typename
659
850
  id
660
851
  name { full native }
661
852
  image { large medium }
@@ -664,6 +855,7 @@ var USER_FAVORITES_FIELDS = `
664
855
  }
665
856
  studios(perPage: 25) {
666
857
  nodes {
858
+ __typename
667
859
  id
668
860
  name
669
861
  siteUrl
@@ -672,6 +864,7 @@ var USER_FAVORITES_FIELDS = `
672
864
  }
673
865
  `;
674
866
  var MEDIA_LIST_FIELDS = `
867
+ __typename
675
868
  id
676
869
  mediaId
677
870
  status
@@ -691,6 +884,7 @@ var MEDIA_LIST_FIELDS = `
691
884
  }
692
885
  `;
693
886
  var STUDIO_FIELDS = `
887
+ __typename
694
888
  id
695
889
  name
696
890
  isAnimationStudio
@@ -699,6 +893,7 @@ var STUDIO_FIELDS = `
699
893
  media(page: 1, perPage: 25, sort: POPULARITY_DESC) {
700
894
  pageInfo { total perPage currentPage lastPage hasNextPage }
701
895
  nodes {
896
+ __typename
702
897
  id
703
898
  title { romaji english native userPreferred }
704
899
  type
@@ -709,6 +904,7 @@ var STUDIO_FIELDS = `
709
904
  }
710
905
  `;
711
906
  var THREAD_FIELDS = `
907
+ __typename
712
908
  id
713
909
  title
714
910
  body(asHtml: false)
@@ -725,20 +921,24 @@ var THREAD_FIELDS = `
725
921
  updatedAt
726
922
  siteUrl
727
923
  user {
924
+ __typename
728
925
  id
729
926
  name
730
927
  avatar { large medium }
731
928
  }
732
929
  replyUser {
930
+ __typename
733
931
  id
734
932
  name
735
933
  avatar { large medium }
736
934
  }
737
935
  categories {
936
+ __typename
738
937
  id
739
938
  name
740
939
  }
741
940
  mediaCategories {
941
+ __typename
742
942
  id
743
943
  title { romaji english native userPreferred }
744
944
  type
@@ -746,6 +946,7 @@ var THREAD_FIELDS = `
746
946
  siteUrl
747
947
  }
748
948
  likes {
949
+ __typename
749
950
  id
750
951
  name
751
952
  }
@@ -2123,7 +2324,7 @@ function mapFavorites(fav) {
2123
2324
 
2124
2325
  // src/client/index.ts
2125
2326
  var DEFAULT_API_URL = "https://graphql.anilist.co";
2126
- var LIB_VERSION = "2.1.1" ;
2327
+ var LIB_VERSION = "2.1.2" ;
2127
2328
  var AniListClient = class {
2128
2329
  apiUrl;
2129
2330
  headers;
@@ -2168,10 +2369,25 @@ var AniListClient = class {
2168
2369
  /** @internal */
2169
2370
  async request(query, variables = {}) {
2170
2371
  const cacheKey = MemoryCache.key(query, variables);
2171
- const cached = await this.cacheAdapter.get(cacheKey);
2372
+ let cached;
2373
+ let isStale = false;
2374
+ if (this.cacheAdapter.getWithMeta) {
2375
+ const res = await this.cacheAdapter.getWithMeta(cacheKey);
2376
+ if (res) {
2377
+ cached = res.data;
2378
+ isStale = res.stale;
2379
+ }
2380
+ } else {
2381
+ cached = await this.cacheAdapter.get(cacheKey);
2382
+ }
2172
2383
  if (cached !== void 0) {
2384
+ if (isStale) {
2385
+ this.executeRequest(query, variables, cacheKey).catch((err) => {
2386
+ this.logger?.error("Background revalidation failed", { error: err.message, cacheKey });
2387
+ });
2388
+ }
2173
2389
  this.hooks.onCacheHit?.(cacheKey);
2174
- this.logger?.debug("Cache hit", { cacheKey });
2390
+ this.logger?.debug("Cache hit", { cacheKey, isStale });
2175
2391
  const meta = { durationMs: 0, fromCache: true };
2176
2392
  this._lastRequestMeta = meta;
2177
2393
  this.hooks.onResponse?.(query, 0, true);
@@ -2561,6 +2777,7 @@ exports.MediaSource = MediaSource;
2561
2777
  exports.MediaStatus = MediaStatus;
2562
2778
  exports.MediaType = MediaType;
2563
2779
  exports.MemoryCache = MemoryCache;
2780
+ exports.NormalizedCache = NormalizedCache;
2564
2781
  exports.RateLimiter = RateLimiter;
2565
2782
  exports.RecommendationSort = RecommendationSort;
2566
2783
  exports.RedisCache = RedisCache;