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.mjs CHANGED
@@ -122,6 +122,174 @@ function sortObjectKeys(obj) {
122
122
  return sorted;
123
123
  }
124
124
 
125
+ // src/cache/normalized.ts
126
+ var NormalizedCache = class {
127
+ ttl;
128
+ maxSize;
129
+ enabled;
130
+ swrMs;
131
+ queryStore = /* @__PURE__ */ new Map();
132
+ entityStore = /* @__PURE__ */ new Map();
133
+ _hits = 0;
134
+ _misses = 0;
135
+ _stales = 0;
136
+ constructor(options = {}) {
137
+ this.ttl = options.ttl ?? 24 * 60 * 60 * 1e3;
138
+ this.maxSize = options.maxSize ?? 500;
139
+ this.enabled = options.enabled ?? true;
140
+ this.swrMs = options.staleWhileRevalidateMs ?? 0;
141
+ }
142
+ static key(query, variables) {
143
+ const normalized = normalizeQuery(query);
144
+ return `${normalized}|${JSON.stringify(sortObjectKeys(variables))}`;
145
+ }
146
+ /** Normalizes a GraphQL response, extracting entities and returning a tree of references. */
147
+ normalize(data, seen = /* @__PURE__ */ new WeakSet()) {
148
+ if (Array.isArray(data)) {
149
+ if (seen.has(data)) return null;
150
+ seen.add(data);
151
+ return data.map((item) => this.normalize(item, seen));
152
+ }
153
+ if (data !== null && typeof data === "object") {
154
+ if (seen.has(data)) return null;
155
+ seen.add(data);
156
+ const obj = data;
157
+ if (typeof obj.__typename === "string" && (typeof obj.id === "number" || typeof obj.id === "string")) {
158
+ const ref = `${obj.__typename}:${obj.id}`;
159
+ const normalizedObj = {};
160
+ for (const [k, v] of Object.entries(obj)) {
161
+ normalizedObj[k] = this.normalize(v, seen);
162
+ }
163
+ const existing = this.entityStore.get(ref) || {};
164
+ this.entityStore.set(ref, { ...existing, ...normalizedObj });
165
+ return { __ref: ref };
166
+ }
167
+ const result = {};
168
+ for (const [k, v] of Object.entries(obj)) {
169
+ result[k] = this.normalize(v, seen);
170
+ }
171
+ return result;
172
+ }
173
+ return data;
174
+ }
175
+ /** Reconstructs a GraphQL response from references. */
176
+ denormalize(data, seen = /* @__PURE__ */ new Set()) {
177
+ if (Array.isArray(data)) {
178
+ return data.map((item) => this.denormalize(item, seen));
179
+ }
180
+ if (data !== null && typeof data === "object") {
181
+ const obj = data;
182
+ if (typeof obj.__ref === "string") {
183
+ const ref = obj.__ref;
184
+ if (seen.has(ref)) {
185
+ return { __ref: ref };
186
+ }
187
+ seen.add(ref);
188
+ const entity = this.entityStore.get(ref);
189
+ if (!entity) return void 0;
190
+ const result2 = this.denormalize(entity, seen);
191
+ seen.delete(ref);
192
+ return result2;
193
+ }
194
+ const result = {};
195
+ for (const [k, v] of Object.entries(obj)) {
196
+ const denormalized = this.denormalize(v, seen);
197
+ if (denormalized === void 0) return void 0;
198
+ result[k] = denormalized;
199
+ }
200
+ return result;
201
+ }
202
+ return data;
203
+ }
204
+ getWithMeta(key) {
205
+ if (!this.enabled) return void 0;
206
+ const entry = this.queryStore.get(key);
207
+ if (!entry) {
208
+ this._misses++;
209
+ return void 0;
210
+ }
211
+ const now = Date.now();
212
+ let isStale = false;
213
+ if (now > entry.expiresAt) {
214
+ if (this.swrMs > 0 && now <= entry.expiresAt + this.swrMs) {
215
+ isStale = true;
216
+ } else {
217
+ this.queryStore.delete(key);
218
+ this._misses++;
219
+ return void 0;
220
+ }
221
+ }
222
+ const denormalized = this.denormalize(entry.data);
223
+ if (denormalized === void 0) {
224
+ this.queryStore.delete(key);
225
+ this._misses++;
226
+ return void 0;
227
+ }
228
+ this.queryStore.delete(key);
229
+ this.queryStore.set(key, entry);
230
+ if (isStale) {
231
+ this._stales++;
232
+ } else {
233
+ this._hits++;
234
+ }
235
+ return { data: denormalized, stale: isStale };
236
+ }
237
+ get(key) {
238
+ const res = this.getWithMeta(key);
239
+ return res ? res.data : void 0;
240
+ }
241
+ set(key, data) {
242
+ if (!this.enabled) return;
243
+ const normalizedData = this.normalize(data);
244
+ this.queryStore.delete(key);
245
+ if (this.maxSize > 0 && this.queryStore.size >= this.maxSize) {
246
+ const firstKey = this.queryStore.keys().next().value;
247
+ if (firstKey !== void 0) this.queryStore.delete(firstKey);
248
+ }
249
+ this.queryStore.set(key, { data: normalizedData, expiresAt: Date.now() + this.ttl });
250
+ }
251
+ delete(key) {
252
+ return this.queryStore.delete(key);
253
+ }
254
+ clear() {
255
+ this.queryStore.clear();
256
+ this.entityStore.clear();
257
+ this._hits = 0;
258
+ this._misses = 0;
259
+ this._stales = 0;
260
+ }
261
+ get size() {
262
+ return this.queryStore.size;
263
+ }
264
+ keys() {
265
+ return [...this.queryStore.keys()];
266
+ }
267
+ invalidate(pattern) {
268
+ const test = typeof pattern === "string" ? (key) => key.includes(pattern) : (key) => pattern.test(key);
269
+ const toDelete = [];
270
+ for (const key of this.queryStore.keys()) {
271
+ if (test(key)) toDelete.push(key);
272
+ }
273
+ for (const key of toDelete) this.queryStore.delete(key);
274
+ return toDelete.length;
275
+ }
276
+ get stats() {
277
+ const total = this._hits + this._misses + this._stales;
278
+ return {
279
+ hits: this._hits,
280
+ misses: this._misses,
281
+ stales: this._stales,
282
+ hitRate: total === 0 ? Number.NaN : this._hits / total,
283
+ entitiesCount: this.entityStore.size
284
+ };
285
+ }
286
+ resetStats() {
287
+ this._hits = 0;
288
+ this._misses = 0;
289
+ this._stales = 0;
290
+ }
291
+ };
292
+
125
293
  // src/cache/index.ts
126
294
  var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
127
295
  var MemoryCache = class {
@@ -149,7 +317,10 @@ var MemoryCache = class {
149
317
  * With stale-while-revalidate enabled, returns stale data within the grace window
150
318
  * and flags it so the caller can refresh in the background.
151
319
  */
152
- get(key) {
320
+ /**
321
+ * Retrieve a cached value and its stale status.
322
+ */
323
+ getWithMeta(key) {
153
324
  if (!this.enabled) return void 0;
154
325
  const entry = this.store.get(key);
155
326
  if (!entry) {
@@ -162,7 +333,7 @@ var MemoryCache = class {
162
333
  this.store.delete(key);
163
334
  this.store.set(key, entry);
164
335
  this._stales++;
165
- return entry.data;
336
+ return { data: entry.data, stale: true };
166
337
  }
167
338
  this.store.delete(key);
168
339
  this._misses++;
@@ -171,7 +342,11 @@ var MemoryCache = class {
171
342
  this.store.delete(key);
172
343
  this.store.set(key, entry);
173
344
  this._hits++;
174
- return entry.data;
345
+ return { data: entry.data, stale: false };
346
+ }
347
+ get(key) {
348
+ const res = this.getWithMeta(key);
349
+ return res ? res.data : void 0;
175
350
  }
176
351
  /** Store a value in the cache. */
177
352
  set(key, data) {
@@ -357,6 +532,7 @@ var AniListError = class _AniListError extends Error {
357
532
 
358
533
  // src/queries/fragments.ts
359
534
  var MEDIA_FIELDS_LIGHT = `
535
+ __typename
360
536
  id
361
537
  idMal
362
538
  title { romaji english native userPreferred }
@@ -383,6 +559,7 @@ var MEDIA_FIELDS_LIGHT = `
383
559
  }
384
560
  `;
385
561
  var MEDIA_FIELDS_BASE = `
562
+ __typename
386
563
  id
387
564
  idMal
388
565
  title { romaji english native userPreferred }
@@ -429,6 +606,7 @@ var RELATIONS_FIELDS = `
429
606
  edges {
430
607
  relationType(version: 2)
431
608
  node {
609
+ __typename
432
610
  id
433
611
  title { romaji english native userPreferred }
434
612
  type
@@ -459,9 +637,11 @@ var RELATIONS_FIELDS = `
459
637
  }
460
638
  `;
461
639
  var MEDIA_RECOMMENDATION_FIELDS = `
640
+ __typename
462
641
  id
463
642
  rating
464
643
  mediaRecommendation {
644
+ __typename
465
645
  id
466
646
  title { romaji english native userPreferred }
467
647
  type
@@ -493,6 +673,7 @@ var MEDIA_FIELDS = `
493
673
  ${RELATIONS_FIELDS}
494
674
  `;
495
675
  var CHARACTER_FIELDS_COMPACT = `
676
+ __typename
496
677
  id
497
678
  name { first middle last full native alternative }
498
679
  image { large medium }
@@ -507,6 +688,7 @@ var CHARACTER_FIELDS_COMPACT = `
507
688
  var CHARACTER_MEDIA_NODES = `
508
689
  media(perPage: 10) {
509
690
  nodes {
691
+ __typename
510
692
  id
511
693
  title { romaji english native userPreferred }
512
694
  type
@@ -516,6 +698,7 @@ var CHARACTER_MEDIA_NODES = `
516
698
  }
517
699
  `;
518
700
  var VOICE_ACTOR_FIELDS_COMPACT = `
701
+ __typename
519
702
  id
520
703
  name { first middle last full native userPreferred }
521
704
  languageV2
@@ -531,6 +714,7 @@ var CHARACTER_MEDIA_EDGES_WITH_VA = `
531
714
  ${VOICE_ACTOR_FIELDS_COMPACT}
532
715
  }
533
716
  node {
717
+ __typename
534
718
  id
535
719
  title { romaji english native userPreferred }
536
720
  type
@@ -549,6 +733,7 @@ var CHARACTER_FIELDS_WITH_VA = `
549
733
  ${CHARACTER_MEDIA_EDGES_WITH_VA}
550
734
  `;
551
735
  var STAFF_FIELDS = `
736
+ __typename
552
737
  id
553
738
  name { first middle last full native }
554
739
  language
@@ -568,6 +753,7 @@ var STAFF_FIELDS = `
568
753
  var STAFF_MEDIA_FIELDS = `
569
754
  staffMedia(perPage: $perPage, sort: [POPULARITY_DESC]) {
570
755
  nodes {
756
+ __typename
571
757
  id
572
758
  title { romaji english native userPreferred }
573
759
  type
@@ -606,6 +792,7 @@ var STAFF_MEDIA_FIELDS = `
606
792
  }
607
793
  `;
608
794
  var USER_FIELDS = `
795
+ __typename
609
796
  id
610
797
  name
611
798
  about(asHtml: false)
@@ -626,6 +813,7 @@ var USER_FAVORITES_FIELDS = `
626
813
  favourites {
627
814
  anime(perPage: 25) {
628
815
  nodes {
816
+ __typename
629
817
  id
630
818
  title { romaji english native userPreferred }
631
819
  coverImage { large medium }
@@ -636,6 +824,7 @@ var USER_FAVORITES_FIELDS = `
636
824
  }
637
825
  manga(perPage: 25) {
638
826
  nodes {
827
+ __typename
639
828
  id
640
829
  title { romaji english native userPreferred }
641
830
  coverImage { large medium }
@@ -646,6 +835,7 @@ var USER_FAVORITES_FIELDS = `
646
835
  }
647
836
  characters(perPage: 25) {
648
837
  nodes {
838
+ __typename
649
839
  id
650
840
  name { full native }
651
841
  image { large medium }
@@ -654,6 +844,7 @@ var USER_FAVORITES_FIELDS = `
654
844
  }
655
845
  staff(perPage: 25) {
656
846
  nodes {
847
+ __typename
657
848
  id
658
849
  name { full native }
659
850
  image { large medium }
@@ -662,6 +853,7 @@ var USER_FAVORITES_FIELDS = `
662
853
  }
663
854
  studios(perPage: 25) {
664
855
  nodes {
856
+ __typename
665
857
  id
666
858
  name
667
859
  siteUrl
@@ -670,6 +862,7 @@ var USER_FAVORITES_FIELDS = `
670
862
  }
671
863
  `;
672
864
  var MEDIA_LIST_FIELDS = `
865
+ __typename
673
866
  id
674
867
  mediaId
675
868
  status
@@ -689,6 +882,7 @@ var MEDIA_LIST_FIELDS = `
689
882
  }
690
883
  `;
691
884
  var STUDIO_FIELDS = `
885
+ __typename
692
886
  id
693
887
  name
694
888
  isAnimationStudio
@@ -697,6 +891,7 @@ var STUDIO_FIELDS = `
697
891
  media(page: 1, perPage: 25, sort: POPULARITY_DESC) {
698
892
  pageInfo { total perPage currentPage lastPage hasNextPage }
699
893
  nodes {
894
+ __typename
700
895
  id
701
896
  title { romaji english native userPreferred }
702
897
  type
@@ -707,6 +902,7 @@ var STUDIO_FIELDS = `
707
902
  }
708
903
  `;
709
904
  var THREAD_FIELDS = `
905
+ __typename
710
906
  id
711
907
  title
712
908
  body(asHtml: false)
@@ -723,20 +919,24 @@ var THREAD_FIELDS = `
723
919
  updatedAt
724
920
  siteUrl
725
921
  user {
922
+ __typename
726
923
  id
727
924
  name
728
925
  avatar { large medium }
729
926
  }
730
927
  replyUser {
928
+ __typename
731
929
  id
732
930
  name
733
931
  avatar { large medium }
734
932
  }
735
933
  categories {
934
+ __typename
736
935
  id
737
936
  name
738
937
  }
739
938
  mediaCategories {
939
+ __typename
740
940
  id
741
941
  title { romaji english native userPreferred }
742
942
  type
@@ -744,6 +944,7 @@ var THREAD_FIELDS = `
744
944
  siteUrl
745
945
  }
746
946
  likes {
947
+ __typename
747
948
  id
748
949
  name
749
950
  }
@@ -2121,7 +2322,7 @@ function mapFavorites(fav) {
2121
2322
 
2122
2323
  // src/client/index.ts
2123
2324
  var DEFAULT_API_URL = "https://graphql.anilist.co";
2124
- var LIB_VERSION = "2.1.1" ;
2325
+ var LIB_VERSION = "2.1.2" ;
2125
2326
  var AniListClient = class {
2126
2327
  apiUrl;
2127
2328
  headers;
@@ -2166,10 +2367,25 @@ var AniListClient = class {
2166
2367
  /** @internal */
2167
2368
  async request(query, variables = {}) {
2168
2369
  const cacheKey = MemoryCache.key(query, variables);
2169
- const cached = await this.cacheAdapter.get(cacheKey);
2370
+ let cached;
2371
+ let isStale = false;
2372
+ if (this.cacheAdapter.getWithMeta) {
2373
+ const res = await this.cacheAdapter.getWithMeta(cacheKey);
2374
+ if (res) {
2375
+ cached = res.data;
2376
+ isStale = res.stale;
2377
+ }
2378
+ } else {
2379
+ cached = await this.cacheAdapter.get(cacheKey);
2380
+ }
2170
2381
  if (cached !== void 0) {
2382
+ if (isStale) {
2383
+ this.executeRequest(query, variables, cacheKey).catch((err) => {
2384
+ this.logger?.error("Background revalidation failed", { error: err.message, cacheKey });
2385
+ });
2386
+ }
2171
2387
  this.hooks.onCacheHit?.(cacheKey);
2172
- this.logger?.debug("Cache hit", { cacheKey });
2388
+ this.logger?.debug("Cache hit", { cacheKey, isStale });
2173
2389
  const meta = { durationMs: 0, fromCache: true };
2174
2390
  this._lastRequestMeta = meta;
2175
2391
  this.hooks.onResponse?.(query, 0, true);
@@ -2544,6 +2760,6 @@ var AniListClient = class {
2544
2760
  }
2545
2761
  };
2546
2762
 
2547
- export { AiringSort, AniListClient, AniListError, CharacterRole, CharacterSort, MediaFormat, MediaListSort, MediaListStatus, MediaRelationType, MediaSeason, MediaSort, MediaSource, MediaStatus, MediaType, MemoryCache, RateLimiter, RecommendationSort, RedisCache, ReviewSort, StaffSort, StudioSort, ThreadSort, UserSort, parseAniListMarkdown };
2763
+ export { AiringSort, AniListClient, AniListError, CharacterRole, CharacterSort, MediaFormat, MediaListSort, MediaListStatus, MediaRelationType, MediaSeason, MediaSort, MediaSource, MediaStatus, MediaType, MemoryCache, NormalizedCache, RateLimiter, RecommendationSort, RedisCache, ReviewSort, StaffSort, StudioSort, ThreadSort, UserSort, parseAniListMarkdown };
2548
2764
  //# sourceMappingURL=index.mjs.map
2549
2765
  //# sourceMappingURL=index.mjs.map