fix(web): clear cache when asset changes (#26257)

* fix(web): clear cache when asset changes

* formatting
This commit is contained in:
Michel Heusschen
2026-02-17 11:43:08 +01:00
committed by GitHub
parent de7b42eb23
commit ceef65154d
3 changed files with 31 additions and 18 deletions

View File

@@ -1,25 +1,23 @@
import { authManager } from '$lib/managers/auth-manager.svelte';
import { eventManager } from '$lib/managers/event-manager.svelte';
import { getAssetInfo, getAssetOcr, type AssetOcrResponseDto, type AssetResponseDto } from '@immich/sdk';
import { getAssetInfo, getAssetOcr } from '@immich/sdk';
const defaultSerializer = <K>(params: K) => JSON.stringify(params);
class AsyncCache<V> {
class AsyncCache<K, V> {
#cache = new Map<string, V>();
async getOrFetch<K>(
params: K,
fetcher: (params: K) => Promise<V>,
keySerializer: (params: K) => string = defaultSerializer,
updateCache: boolean,
): Promise<V> {
const cacheKey = keySerializer(params);
constructor(private fetcher: (params: K) => Promise<V>) {}
async getOrFetch(params: K, updateCache: boolean): Promise<V> {
const cacheKey = defaultSerializer(params);
const cached = this.#cache.get(cacheKey);
if (cached) {
return cached;
}
const value = await fetcher(params);
const value = await this.fetcher(params);
if (value && updateCache) {
this.#cache.set(cacheKey, value);
}
@@ -27,30 +25,43 @@ class AsyncCache<V> {
return value;
}
clearKey(params: K) {
const cacheKey = defaultSerializer(params);
this.#cache.delete(cacheKey);
}
clear() {
this.#cache.clear();
}
}
class AssetCacheManager {
#assetCache = new AsyncCache<AssetResponseDto>();
#ocrCache = new AsyncCache<AssetOcrResponseDto[]>();
#assetCache = new AsyncCache(getAssetInfo);
#ocrCache = new AsyncCache(getAssetOcr);
constructor() {
eventManager.on({
AssetEditsApplied: () => {
this.#assetCache.clear();
this.#ocrCache.clear();
AssetEditsApplied: (assetId) => {
this.invalidateAsset(assetId);
},
AssetUpdate: (asset) => {
this.invalidateAsset(asset.id);
},
});
}
async getAsset(assetIdentifier: { key?: string; slug?: string; id: string }, updateCache = true) {
return this.#assetCache.getOrFetch(assetIdentifier, getAssetInfo, defaultSerializer, updateCache);
async getAsset({ id, key, slug }: { id: string; key?: string; slug?: string }, updateCache = true) {
return this.#assetCache.getOrFetch({ id, key, slug }, updateCache);
}
async getAssetOcr(id: string) {
return this.#ocrCache.getOrFetch({ id }, getAssetOcr, (params) => params.id, true);
return this.#ocrCache.getOrFetch({ id }, true);
}
invalidateAsset(id: string) {
const { key, slug } = authManager.params;
this.#assetCache.clearKey({ id, key, slug });
this.#ocrCache.clearKey({ id });
}
clearAssetCache() {

View File

@@ -5,6 +5,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
// Mock the SDK
vi.mock('@immich/sdk', () => ({
getAssetInfo: vi.fn(),
getAssetOcr: vi.fn(),
}));

View File

@@ -77,6 +77,7 @@ websocket
.on('on_new_release', (event) => eventManager.emit('ReleaseEvent', event))
.on('on_session_delete', () => authManager.logout())
.on('on_user_delete', (id) => eventManager.emit('UserAdminDeleted', { id }))
.on('on_asset_update', (asset) => eventManager.emit('AssetUpdate', asset))
.on('on_person_thumbnail', (id) => eventManager.emit('PersonThumbnailReady', { id }))
.on('on_notification', () => notificationManager.refresh())
.on('connect_error', (e) => console.log('Websocket Connect Error', e));