diff --git a/packages/sanity/src/core/util/__tests__/rxSwr.test.ts b/packages/sanity/src/core/util/__tests__/rxSwr.test.ts new file mode 100644 index 000000000000..18d273e7fcdf --- /dev/null +++ b/packages/sanity/src/core/util/__tests__/rxSwr.test.ts @@ -0,0 +1,55 @@ +import {describe, expect, it} from '@jest/globals' +import {lastValueFrom, timer} from 'rxjs' +import {map, toArray} from 'rxjs/operators' + +import {createSWR} from '../rxSwr' + +describe('rxSwr', () => { + it('should cache the last known value and emit sync', async () => { + const swr = createSWR({maxSize: 1}) + + const observable = timer(100).pipe( + map(() => 'value!'), + swr('someKey'), + toArray(), + ) + + expect(await lastValueFrom(observable)).toEqual([{fromCache: false, value: 'value!'}]) + + // Second subscription, now with warm cache + expect(await lastValueFrom(observable)).toEqual([ + {fromCache: true, value: 'value!'}, + {fromCache: false, value: 'value!'}, + ]) + }) + + it('should discard old cache keys when exceeding maxSize', async () => { + const swr = createSWR({maxSize: 1}) + + const observable1 = timer(100).pipe( + map(() => 'observable1!'), + swr('key1'), + toArray(), + ) + + expect(await lastValueFrom(observable1)).toEqual([{fromCache: false, value: 'observable1!'}]) + + // Second subscription, now with warm cache + expect(await lastValueFrom(observable1)).toEqual([ + {fromCache: true, value: 'observable1!'}, + {fromCache: false, value: 'observable1!'}, + ]) + + const observable2 = timer(100).pipe( + map(() => 'observable2!'), + swr('key2'), + toArray(), + ) + + // Subscribing to observable2 should purge the key of observable1 + expect(await lastValueFrom(observable2)).toEqual([{fromCache: false, value: 'observable2!'}]) + + // re-subscribing to the first should now not have a cache + expect(await lastValueFrom(observable1)).toEqual([{fromCache: false, value: 'value!'}]) + }) +}) diff --git a/packages/sanity/src/core/util/rxSwr.ts b/packages/sanity/src/core/util/rxSwr.ts index 34166640738e..7a70997ba4a7 100644 --- a/packages/sanity/src/core/util/rxSwr.ts +++ b/packages/sanity/src/core/util/rxSwr.ts @@ -2,6 +2,10 @@ import QuickLRU from 'quick-lru' import {concat, defer, EMPTY, map, type Observable, of, type OperatorFunction} from 'rxjs' import {tap} from 'rxjs/operators' +/** + * The interface that any caching layer must implement + * @internal + */ interface SWRCache { /** * Note: This will throw if key does not exist. Always check for existence with `has` before calling @@ -38,6 +42,11 @@ export function createSWR(options: {maxSize: number}) { } } +/** + * For now, the only cache layer implemented is an in-memory LRU. + * @param options - LRU options + * @internal + */ function createLRUCache(options: {maxSize: number}): SWRCache { const lru = new QuickLRU(options) return {