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 +60 -1
- package/dist/index.d.ts +60 -1
- package/dist/index.js +223 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +223 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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.
|
|
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
|
-
|
|
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;
|