From 3e1668348e01fb14566068ca72872e567d0d7172 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 18 Jun 2025 01:32:46 -0400 Subject: [PATCH] implement QuantumKVCache.reset --- .../backend/src/core/GlobalEventService.ts | 1 + packages/backend/src/misc/QuantumKVCache.ts | 41 ++++++++++++++++++- .../backend/test/unit/misc/QuantumKVCache.ts | 37 +++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index 53b5b06acd..b16e47362d 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -276,6 +276,7 @@ export interface InternalEventTypes { userListMemberBulkUpdated: { userListIds: MiUserList['id'][]; memberId: MiUser['id']; }; userListMemberBulkRemoved: { userListIds: MiUserList['id'][]; memberId: MiUser['id']; }; quantumCacheUpdated: { name: string, keys: string[] }; + quantumCacheReset: { name: string }; } type EventTypesToEventPayload = EventUnionFromDictionary>>; diff --git a/packages/backend/src/misc/QuantumKVCache.ts b/packages/backend/src/misc/QuantumKVCache.ts index 16810e98a0..9c87b7fab9 100644 --- a/packages/backend/src/misc/QuantumKVCache.ts +++ b/packages/backend/src/misc/QuantumKVCache.ts @@ -41,6 +41,13 @@ export interface QuantumKVOpts { */ onChanged?: (keys: string[], cache: QuantumKVCache) => void | Promise; + /** + * Optional callback when all values are removed from the cache, either locally or elsewhere in the cluster. + * This is called *after* the cache state is updated. + * Implementations may be synchronous or async. + */ + onReset?: (cache: QuantumKVCache) => void | Promise; + // TODO equality comparer } @@ -65,6 +72,7 @@ export class QuantumKVCache implements Iterable['fetcher']; public readonly bulkFetcher: QuantumKVOpts['bulkFetcher']; public readonly onChanged: QuantumKVOpts['onChanged']; + public readonly onReset: QuantumKVOpts['onReset']; /** * @param name Unique name of the cache - must be the same in all processes. @@ -83,12 +91,17 @@ export class QuantumKVCache implements Iterable implements Iterable { + this.clear(); + + await this.internalEventService.emit('quantumCacheReset', { name: this.name }); + + if (this.onReset) { + await this.onReset(this); + } + } + /** * Removes expired cache entries from the local view. * Does not send any events or update other processes. @@ -396,6 +423,7 @@ export class QuantumKVCache implements Iterable implements Iterable { + if (data.name === this.name) { + this.clear(); + + if (this.onReset) { + await this.onReset(this); + } + } + } + /** * Iterates all [key, value] pairs in memory. * This applies to the local subset view, not the cross-cluster cache state. diff --git a/packages/backend/test/unit/misc/QuantumKVCache.ts b/packages/backend/test/unit/misc/QuantumKVCache.ts index 619c64c603..037a6dbf6d 100644 --- a/packages/backend/test/unit/misc/QuantumKVCache.ts +++ b/packages/backend/test/unit/misc/QuantumKVCache.ts @@ -778,6 +778,43 @@ describe(QuantumKVCache, () => { }); }); + describe('refresh', () => { + it('should erase all items', async () => { + const cache = makeCache(); + await cache.set('foo', 'bar'); + await cache.set('alpha', 'omega'); + + await cache.reset(); + + expect(cache.size).toBe(0); + }); + + it('should call onReset', async () => { + const fakeOnReset = jest.fn(() => Promise.resolve()); + const cache = makeCache({ + onReset: fakeOnReset, + }); + await cache.set('foo', 'bar'); + await cache.set('alpha', 'omega'); + + await cache.reset(); + + expect(fakeOnReset).toHaveBeenCalled(); + }); + + it('should emit event', async () => { + const cache = makeCache({ + name: 'fake', + }); + await cache.set('foo', 'bar'); + await cache.set('alpha', 'omega'); + + await cache.reset(); + + expect(fakeInternalEventService._calls).toContainEqual(['emit', ['quantumCacheReset', { name: 'fake' }]]); + }); + }); + describe('add', () => { it('should add the item', () => { const cache = makeCache();