diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts index 22201e243f..3145550a44 100644 --- a/packages/backend/src/misc/cache.ts +++ b/packages/backend/src/misc/cache.ts @@ -565,6 +565,28 @@ export class QuantumKVCache implements Iterable<[key: string, value: T]> { } } + /** + * Adds a value to the local memory cache without notifying other process. + * Neither a Redis event nor onSet callback will be fired, as the value has not actually changed. + * This should only be used when the value is known to be current, like after fetching from the database. + */ + @bindThis + public add(key: string, value: T): void { + this.memoryCache.set(key, value); + } + + /** + * Adds multiple values to the local memory cache without notifying other process. + * Neither a Redis event nor onSet callback will be fired, as the value has not actually changed. + * This should only be used when the value is known to be current, like after fetching from the database. + */ + @bindThis + public addMany(items: Iterable<[key: string, value: T]>): void { + for (const [key, value] of items) { + this.memoryCache.set(key, value); + } + } + /** * Gets a value from the local memory cache, or returns undefined if not found. */ diff --git a/packages/backend/test/unit/misc/cache.ts b/packages/backend/test/unit/misc/cache.ts index 0b658618e6..e24f6d4dcc 100644 --- a/packages/backend/test/unit/misc/cache.ts +++ b/packages/backend/test/unit/misc/cache.ts @@ -417,6 +417,68 @@ describe(QuantumKVCache, () => { }); }); + describe('add', () => { + it('should add the item', () => { + const cache = makeCache(); + cache.add('foo', 'bar'); + expect(cache.has('foo')).toBe(true); + }); + + it('should not emit event', () => { + const cache = makeCache({ + name: 'fake', + }); + + cache.add('foo', 'bar'); + + expect(fakeInternalEventService._calls.filter(c => c[0] === 'emit')).toHaveLength(0); + }); + + it('should not call onSet', () => { + const fakeOnSet = jest.fn(() => Promise.resolve()); + const cache = makeCache({ + onSet: fakeOnSet, + }); + + cache.add('foo', 'bar'); + + expect(fakeOnSet).not.toHaveBeenCalled(); + }); + }); + + describe('addMany', () => { + it('should add all items', () => { + const cache = makeCache(); + + cache.addMany([['foo', 'bar'], ['alpha', 'omega']]); + + expect(cache.has('foo')).toBe(true); + expect(cache.has('alpha')).toBe(true); + }); + + + it('should not emit event', () => { + const cache = makeCache({ + name: 'fake', + }); + + cache.addMany([['foo', 'bar'], ['alpha', 'omega']]); + + expect(fakeInternalEventService._calls.filter(c => c[0] === 'emit')).toHaveLength(0); + }); + + it('should not call onSet', () => { + const fakeOnSet = jest.fn(() => Promise.resolve()); + const cache = makeCache({ + onSet: fakeOnSet, + }); + + cache.addMany([['foo', 'bar'], ['alpha', 'omega']]); + + expect(fakeOnSet).not.toHaveBeenCalled(); + }); + }); + describe('has', () => { it('should return false when empty', () => { const cache = makeCache();