From 9dbc51d5e36233fab81128072354548facb26256 Mon Sep 17 00:00:00 2001 From: Simon S <54024766+smnsht@users.noreply.github.com> Date: Mon, 25 Mar 2024 08:23:05 +0200 Subject: [PATCH] feature(requests): add support for removing a single request cache by key(#515) (#516) New function 'deleteRequestsCache' resolves #515 --- .../docs/features/requests/requests-cache.mdx | 10 ++ packages/requests/src/index.ts | 1 + .../requests/src/lib/requests-cache.spec.ts | 121 +++++++++++++----- packages/requests/src/lib/requests-cache.ts | 38 +++--- 4 files changed, 126 insertions(+), 44 deletions(-) diff --git a/docs/docs/features/requests/requests-cache.mdx b/docs/docs/features/requests/requests-cache.mdx index 0a08c182..9be2f245 100644 --- a/docs/docs/features/requests/requests-cache.mdx +++ b/docs/docs/features/requests/requests-cache.mdx @@ -216,3 +216,13 @@ import { clearRequestsCache } from '@ngneat/elf-requests'; store.update(clearRequestsCache()); ``` + +### `deleteRequestsCache` + +```ts +import { deleteRequestsCache } from '@ngneat/elf-requests'; + +store.update(deleteRequestsCache('keyOne')); + +store.update(deleteRequestsCache(['keyOne', 'keyTwo'])); +``` \ No newline at end of file diff --git a/packages/requests/src/index.ts b/packages/requests/src/index.ts index f6cddbf8..5e55d3a5 100644 --- a/packages/requests/src/index.ts +++ b/packages/requests/src/index.ts @@ -7,6 +7,7 @@ export { selectRequestCache, selectIsRequestCached, clearRequestsCache, + deleteRequestsCache, updateRequestsCache, } from './lib/requests-cache'; diff --git a/packages/requests/src/lib/requests-cache.spec.ts b/packages/requests/src/lib/requests-cache.spec.ts index 4c5627e7..f0c138e8 100644 --- a/packages/requests/src/lib/requests-cache.spec.ts +++ b/packages/requests/src/lib/requests-cache.spec.ts @@ -2,6 +2,7 @@ import { createState, Store } from '@ngneat/elf'; import { CacheState, clearRequestsCache, + deleteRequestsCache, getRequestCache, isRequestCached, selectIsRequestCached, @@ -15,9 +16,8 @@ import { expectTypeOf } from 'expect-type'; import { createRequestsCacheOperator } from '..'; describe('requestsCache', () => { - const { state, config } = createState( - withRequestsCache<'users' | `user-${string}`>() - ); + const { state, config } = + createState(withRequestsCache<'users' | `user-${string}`>()); const store = new Store({ state, config, name: 'users' }); it('should work', () => { @@ -60,14 +60,13 @@ describe('requestsCache', () => { // It's partial not full expect(store.query(isRequestCached('users'))).toBeFalsy(); expect( - store.query(isRequestCached('users', { value: 'partial' })) + store.query(isRequestCached('users', { value: 'partial' })), ).toBeTruthy(); }); it('should updateRequestCache', () => { - const { state, config } = createState( - withRequestsCache<'users' | `user-${string}`>() - ); + const { state, config } = + createState(withRequestsCache<'users' | `user-${string}`>()); const store = new Store({ state, config, name: 'users' }); const key = 'users'; @@ -78,7 +77,7 @@ describe('requestsCache', () => { store.update(updateRequestCache(key, { value: 'partial' })); expect(store.query(isRequestCached(key))).toBeFalsy(); expect( - store.query(isRequestCached(key, { value: 'partial' })) + store.query(isRequestCached(key, { value: 'partial' })), ).toBeTruthy(); store.update(updateRequestCache(key, { value: 'none' })); @@ -87,9 +86,8 @@ describe('requestsCache', () => { }); it('should skipWhileCached', () => { - const { state, config } = createState( - withRequestsCache<'users' | `user-${string}`>() - ); + const { state, config } = + createState(withRequestsCache<'users' | `user-${string}`>()); const store = new Store({ state, config, name: 'users' }); const skipWhileUsersCached = createRequestsCacheOperator(store); @@ -113,9 +111,8 @@ describe('requestsCache', () => { }); it('should uphold ttl', () => { - const { state, config } = createState( - withRequestsCache<'users' | `user-${string}`>() - ); + const { state, config } = + createState(withRequestsCache<'users' | `user-${string}`>()); const store = new Store({ state, config, name: 'users' }); jest.useFakeTimers(); @@ -124,25 +121,25 @@ describe('requestsCache', () => { store.update(updateRequestCache(ttlRequestKey, { ttl: 1000 })); expect( - store.query(isRequestCached(ttlRequestKey, { value: 'full' })) + store.query(isRequestCached(ttlRequestKey, { value: 'full' })), ).toBeTruthy(); jest.advanceTimersByTime(2000); expect( - store.query(isRequestCached(ttlRequestKey, { value: 'full' })) + store.query(isRequestCached(ttlRequestKey, { value: 'full' })), ).toBeFalsy(); store.update(updateRequestCache(ttlRequestKey, { ttl: 1000 })); expect( - store.query(isRequestCached(ttlRequestKey, { value: 'full' })) + store.query(isRequestCached(ttlRequestKey, { value: 'full' })), ).toBeTruthy(); jest.advanceTimersByTime(2000); expect( - store.query(isRequestCached(ttlRequestKey, { value: 'full' })) + store.query(isRequestCached(ttlRequestKey, { value: 'full' })), ).toBeFalsy(); jest.useRealTimers(); @@ -175,10 +172,77 @@ describe('requestsCache', () => { }); }); +describe('deleteRequestsCache', () => { + let store: Store; + + beforeEach(() => { + const { state, config } = createState(withRequestsCache<'qux' | 'fred'>()); + + store = new Store({ state, config, name: 'users' }); + + store.update( + updateRequestsCache({ + qux: { + value: 'full', + }, + fred: { + value: 'full', + }, + }), + ); + + expect(store.getValue()).toMatchInlineSnapshot(` + Object { + "requestsCache": Object { + "fred": Object { + "value": "full", + }, + "qux": Object { + "value": "full", + }, + }, + } + `); + }); + + it('should clear single key', () => { + store.update(deleteRequestsCache('qux')); + + expect(store.getValue()).toMatchInlineSnapshot(` + Object { + "requestsCache": Object { + "fred": Object { + "value": "full", + }, + "qux": Object { + "value": "none", + }, + }, + } + `); + }); + + it('should clear all keys', () => { + store.update(deleteRequestsCache(['qux', 'fred'])); + + expect(store.getValue()).toMatchInlineSnapshot(` + Object { + "requestsCache": Object { + "fred": Object { + "value": "none", + }, + "qux": Object { + "value": "none", + }, + }, + } + `); + }); +}); + test('updateRequestsCache', () => { - const { state, config } = createState( - withRequestsCache<'foo' | 'bar' | 'baz'>() - ); + const { state, config } = + createState(withRequestsCache<'foo' | 'bar' | 'baz'>()); const store = new Store({ state, config, name: 'users' }); @@ -187,7 +251,7 @@ test('updateRequestsCache', () => { foo: { value: 'partial', }, - }) + }), ); expect(store.getValue()).toMatchSnapshot(); @@ -200,7 +264,7 @@ test('updateRequestsCache', () => { bar: { value: 'full', }, - }) + }), ); expect(store.getValue()).toMatchSnapshot(); @@ -211,24 +275,23 @@ test('updateRequestsCache', () => { }); test('updateRequestsCache with ttl', () => { - const { state, config } = createState( - withRequestsCache<'foo' | 'bar' | 'baz'>() - ); + const { state, config } = + createState(withRequestsCache<'foo' | 'bar' | 'baz'>()); const store = new Store({ state, config, name: 'users' }); jest.useFakeTimers(); store.update( - updateRequestsCache(['foo', 'bar'], { value: 'partial', ttl: 1000 }) + updateRequestsCache(['foo', 'bar'], { value: 'partial', ttl: 1000 }), ); expect( - store.query(isRequestCached('foo', { value: 'partial' })) + store.query(isRequestCached('foo', { value: 'partial' })), ).toBeTruthy(); expect( - store.query(isRequestCached('bar', { value: 'partial' })) + store.query(isRequestCached('bar', { value: 'partial' })), ).toBeTruthy(); jest.advanceTimersByTime(2000); diff --git a/packages/requests/src/lib/requests-cache.ts b/packages/requests/src/lib/requests-cache.ts index 29d4e04e..ee2ec0dc 100644 --- a/packages/requests/src/lib/requests-cache.ts +++ b/packages/requests/src/lib/requests-cache.ts @@ -33,7 +33,7 @@ export type CacheState = { }; export function withRequestsCache( - initialValue?: Record + initialValue?: Record, ): PropsFactory<{ requestsCache: Record }, EmptyConfig> { return { props: { @@ -44,17 +44,17 @@ export function withRequestsCache( } export function selectRequestCache( - key: CacheRecordKeys + key: CacheRecordKeys, ): OperatorFunction { return pipe( distinctUntilKeyChanged('requestsCache'), - select((state) => getRequestCache(key)(state)) + select((state) => getRequestCache(key)(state)), ); } export function updateRequestsCache( keys: Array>, - value: CacheState | { value: CacheState['value']; ttl?: number } + value: CacheState | { value: CacheState['value']; ttl?: number }, ): Reducer; export function updateRequestsCache( requests: Partial< @@ -62,11 +62,11 @@ export function updateRequestsCache( CacheRecordKeys, CacheState | { value: CacheState['value']; ttl?: number } > - > + >, ): Reducer; export function updateRequestsCache( requestsOrKeys: any, - value?: any + value?: any, ): Reducer { let normalized = requestsOrKeys; @@ -99,7 +99,7 @@ export function updateRequestsCache( export function updateRequestCache( key: CacheRecordKeys, - { ttl, value: v }: { ttl?: number; value?: CacheState['value'] } = {} + { ttl, value: v }: { ttl?: number; value?: CacheState['value'] } = {}, ): Reducer { const data = { value: v ?? 'full', @@ -120,7 +120,7 @@ export function updateRequestCache( } export function getRequestCache( - key: CacheRecordKeys + key: CacheRecordKeys, ): Query { return function (state: S) { const cacheValue = @@ -141,22 +141,22 @@ export function getRequestCache( export function selectIsRequestCached( key: Parameters[0], - options?: { value?: CacheState['value'] } + options?: { value?: CacheState['value'] }, ): OperatorFunction { return pipe( distinctUntilKeyChanged('requestsCache'), - select((state) => isRequestCached(key, options)(state)) + select((state) => isRequestCached(key, options)(state)), ); } export function isRequestCached( key: OrArray>, - options?: { value?: CacheState['value'] } + options?: { value?: CacheState['value'] }, ) { return function (state: S) { const type = options?.value ?? 'full'; return coerceArray(key).some( - (k) => getRequestCache(k)(state).value === type + (k) => getRequestCache(k)(state).value === type, ); }; } @@ -164,7 +164,7 @@ export function isRequestCached( export function skipWhileCached( store: Store>, key: OrArray>, - options?: { value?: CacheState['value']; returnSource?: Observable } + options?: { value?: CacheState['value']; returnSource?: Observable }, ): MonoTypeOperatorFunction { return function (source: Observable) { if (store.query(isRequestCached(key, { value: options?.value }))) { @@ -176,11 +176,11 @@ export function skipWhileCached( } export function createRequestsCacheOperator( - store: Store> + store: Store>, ) { return function ( key: CacheRecordKeys, - options?: Parameters[2] + options?: Parameters[2], ) { return skipWhileCached(store, key, options); }; @@ -194,3 +194,11 @@ export function clearRequestsCache(): Reducer { }; }; } + +export function deleteRequestsCache( + keys: OrArray>, +): Reducer { + return updateRequestsCache(coerceArray(keys), { + value: 'none', + }); +}