Skip to content

Commit dfa251e

Browse files
committed
fix(cache): update cache eviction strategy
1 parent f0913e3 commit dfa251e

File tree

3 files changed

+50
-20
lines changed

3 files changed

+50
-20
lines changed

src/services/common/base-client.ts

+14-9
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,16 @@ export type BaseTemplate<P extends RecursiveRecord = RecursiveRecord, O extends
8888
transform?: (param: P) => P;
8989
};
9090

91+
export type CacheResponse<T> = {
92+
previous?: CacheStoreEntity<TypedResponse<T>>;
93+
current?: CacheStoreEntity<TypedResponse<T>>;
94+
isCache?: boolean;
95+
evict?: () => string | boolean | undefined | Promise<string | boolean | undefined>;
96+
};
97+
9198
export type TypedResponse<T> = Omit<Response, 'json'> & {
9299
json(): Promise<T>;
93-
cache?: {
94-
previous?: CacheStoreEntity<TypedResponse<T>>;
95-
current?: CacheStoreEntity<TypedResponse<T>>;
96-
isCache?: boolean;
97-
};
100+
cache?: CacheResponse<T>;
98101
};
99102

100103
export type ResponseOrTypedResponse<T = unknown> = T extends never ? Response : TypedResponse<T>;
@@ -203,28 +206,30 @@ export const getCachedFunction = <
203206
): ClientEndpointCache<Parameter, ResponseBody> => {
204207
const cachedFn = async (param: Parameter, init: BaseInit, cacheOptions: BaseCacheOption) => {
205208
const _key = typeof key === 'function' ? key(param, init) : key;
209+
const evict = () => cache.delete(_key);
206210
const cached = await cache.get(_key);
207211
if (cached && !cacheOptions?.force) {
208212
let templateRetention = typeof retention === 'number' ? retention : undefined;
209213
if (typeof retention === 'object') templateRetention = retention.retention;
210214
const _retention = cacheOptions?.retention ?? templateRetention ?? cache.retention;
211-
if (!_retention) return cloneResponse<ResponseType>(cached.value, { previous: cached, current: cached, isCache: true });
215+
if (!_retention) return cloneResponse<ResponseType>(cached.value, { previous: cached, current: cached, isCache: true, evict });
212216
const expires = cached.cachedAt + _retention;
213-
if (expires > Date.now()) return cloneResponse(cached.value, { previous: cached, current: cached, isCache: true });
217+
if (expires > Date.now()) return cloneResponse(cached.value, { previous: cached, current: cached, isCache: true, evict });
214218
}
215219

216220
try {
217221
const result: TypedResponse<ResponseBody> = await clientFn(param, init);
218222
const cacheEntry: CacheStoreEntity<ResponseType> = {
219223
cachedAt: Date.now(),
220224
value: cloneResponse(result) as ResponseType,
225+
key: _key,
221226
};
222227
await cache.set(_key, cacheEntry);
223-
result.cache = { previous: cached, current: cacheEntry, isCache: false };
228+
result.cache = { previous: cached, current: cacheEntry, isCache: false, evict };
224229
return result;
225230
} catch (error) {
226231
if (cacheOptions?.evictOnError ?? (typeof retention === 'object' ? retention?.evictOnError : undefined) ?? cache.evictOnError) {
227-
cache.delete(_key);
232+
evict();
228233
}
229234
throw error;
230235
}

src/services/trakt.service.ts

+35-11
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import type { TraktWatchlistGetQuery } from '~/models/trakt/trakt-watchlist.mode
1919
import type { SettingsAuth, UserSetting } from '~/models/trakt-service.model';
2020
import type { TvdbApiResponse } from '~/models/tvdb/tvdb-client.model';
2121

22-
import { type BaseCacheOption, getCachedFunction, type TypedResponse } from '~/services/common/base-client';
22+
import { type BaseCacheOption, type CacheResponse, getCachedFunction, type TypedResponse } from '~/services/common/base-client';
2323
import { LoadingBarService } from '~/services/loading-bar.service';
2424
import { tmdbApi } from '~/services/tmdb-client/api/tmdb-api.endpoints';
2525
import { TmdbClient } from '~/services/tmdb-client/clients/tmdb-client';
@@ -36,6 +36,20 @@ import { useUserSettingsStore } from '~/stores/settings/user.store';
3636
import { createTab } from '~/utils/browser/browser.utils';
3737
import { CacheRetention, ChromeCacheStore } from '~/utils/cache.utils';
3838

39+
export const shouldEvict = (date?: string | number | Date, cache?: CacheResponse<unknown>): boolean => {
40+
// no date or cache skip
41+
if (!date || !cache?.evict) return false;
42+
// date in the past skip
43+
if (new Date(date) <= new Date()) return false;
44+
// cached today skip
45+
if (cache?.current?.cachedAt) {
46+
const _cachedAt = new Date(cache.current.cachedAt).toLocaleDateString();
47+
const _today = new Date().toLocaleDateString();
48+
if (_cachedAt === _today) return false;
49+
}
50+
return true;
51+
};
52+
3953
export class TraktService {
4054
private static traktClient: TraktClient;
4155
private static tmdbClient: TmdbClient;
@@ -352,40 +366,50 @@ export class TraktService {
352366
static show = {
353367
async summary(id: string | number) {
354368
const response = await TraktService.traktClient.shows.summary.cached({ id, extended: 'full' });
355-
return response.json() as Promise<TraktShowExtended>;
369+
const data = await response.json();
370+
if (shouldEvict(data?.first_aired, response?.cache)) response.cache?.evict?.();
371+
return data as TraktShowExtended;
356372
},
357373

358374
async season(id: string | number, season: number) {
359375
const response = await TraktService.traktClient.seasons.season.cached({ id, season });
360-
return response.json() as Promise<TraktEpisodeShort[]>;
376+
const data = await response.json();
377+
if (data.some(e => shouldEvict(e?.first_aired, response?.cache))) response.cache?.evict?.();
378+
return data as TraktEpisodeShort[];
361379
},
362380

363381
async seasons(id: string | number) {
364382
const response = await TraktService.traktClient.seasons.summary.cached({ id, extended: 'full' });
365-
return response.json() as Promise<TraktSeasonExtended[]>;
383+
const data = await response.json();
384+
if (data.some(s => shouldEvict(s?.first_aired, response?.cache))) response.cache?.evict?.();
385+
return data as TraktSeasonExtended[];
366386
},
367387

368388
async episode({ id, season, episode }: { id: string | number; season: number; episode: number }) {
369389
const response = await TraktService.traktClient.episodes.summary.cached({ id, season, episode, extended: 'full' });
370-
return response.json() as Promise<TraktEpisodeExtended>;
390+
const data = await response.json();
391+
if (shouldEvict(data?.first_aired, response?.cache)) response.cache?.evict?.();
392+
return data as TraktEpisodeExtended;
371393
},
372394
};
373395

374-
static async activity() {
375-
const response = await this.traktClient.sync.lastActivities();
376-
return response.json();
377-
}
378-
379396
static async movie(id: string | number) {
380397
const response = await this.traktClient.movies.summary.cached({ id, extended: 'full' });
381-
return response.json() as Promise<TraktMovieExtended>;
398+
const data = await response.json();
399+
if (shouldEvict(data?.released, response?.cache)) response.cache?.evict?.();
400+
return data as TraktMovieExtended;
382401
}
383402

384403
static async person(id: string | number) {
385404
const response = await this.traktClient.people.summary.cached({ id, extended: 'full' });
386405
return response.json() as Promise<TraktPersonExtended>;
387406
}
388407

408+
static async activity() {
409+
const response = await this.traktClient.sync.lastActivities();
410+
return response.json();
411+
}
412+
389413
static evict = {
390414
tmdb: () => TraktService.tmdbClient.clearCache(),
391415
trakt: () => TraktService.traktClient.clearCache(),

src/utils/cache.utils.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { ResponseOrTypedResponse, TypedResponse } from '~/services/common/b
33
import { storage, type StorageArea } from '~/utils/browser/browser-storage.utils';
44

55
export type CacheStoreEntity<V = unknown, T = string> = {
6+
key: string;
67
value: V;
78
type?: T;
89
cachedAt: number;

0 commit comments

Comments
 (0)