From d5e12a43026a68d338a5b73e4340bc0ca7731d29 Mon Sep 17 00:00:00 2001 From: dangreen Date: Fri, 19 Jun 2026 16:56:15 +0400 Subject: [PATCH] feat(query,kida): cache facade now can revert changes and passes entry params into reducers --- packages/kida/src/internals/utils.ts | 12 ++++-- packages/query/.size-limit.json | 2 +- packages/query/src/cache.spec.ts | 59 +++++++++++++++++++++++++- packages/query/src/cache.ts | 63 +++++++++++++++++----------- packages/query/src/cache.types.ts | 10 ++++- 5 files changed, 114 insertions(+), 32 deletions(-) diff --git a/packages/kida/src/internals/utils.ts b/packages/kida/src/internals/utils.ts index a5622396..b671194a 100644 --- a/packages/kida/src/internals/utils.ts +++ b/packages/kida/src/internals/utils.ts @@ -50,10 +50,14 @@ export function assignKey< key: K, value: V ) { - return { - ...object || {}, - [key]: value - } as PickNonEmptyValue & Record + return ( + object?.[key] === value + ? object + : { + ...object, + [key]: value + } + ) as PickNonEmptyValue & Record } /** diff --git a/packages/query/.size-limit.json b/packages/query/.size-limit.json index aaa3bf0b..01b7811e 100644 --- a/packages/query/.size-limit.json +++ b/packages/query/.size-limit.json @@ -3,7 +3,7 @@ "name": "All publics", "path": "dist/index.js", "import": "*", - "limit": "3.98 kB" + "limit": "4.01 kB" }, { "name": "Minimal set", diff --git a/packages/query/src/cache.spec.ts b/packages/query/src/cache.spec.ts index 4c2c23a5..bece8cf6 100644 --- a/packages/query/src/cache.spec.ts +++ b/packages/query/src/cache.spec.ts @@ -9,7 +9,8 @@ import { CacheStorage } from './CacheStorage.js' import { queryKey, keys, - dataCacheFacade + dataCacheFacade, + errorCacheFacade } from './cache.js' describe('query', () => { @@ -102,6 +103,34 @@ describe('query', () => { expect($cache(key)).toBe(42) }) + it('should revert value change', () => { + const $cache = dataCacheFacade(new CacheStorage()) + const key = queryKey<[string], number>('test')('a') + + $cache(key, 42) + + const revert = $cache(key, 100) + + expect($cache(key)).toBe(100) + + revert() + + expect($cache(key)).toBe(42) + }) + + it('should pass entry params to reducer', () => { + const storage = new CacheStorage() + const $cache = dataCacheFacade(storage) + const key = queryKey<[id: number, name: string], string>('test')(42, 'Dan') + const reducer = vi.fn((value: string | null, params: [number, string]) => `${value}:${params.join(':')}`) + + storage.settled(key, 'user', null) + $cache(key, reducer) + + expect(reducer).toHaveBeenCalledWith('user', [42, 'Dan']) + expect($cache(key)).toBe('user:42:Dan') + }) + it('should notify listeners on value change', () => { const $cache = dataCacheFacade(new CacheStorage()) const key = queryKey<[string], number>('test')('a') @@ -121,5 +150,33 @@ describe('query', () => { off() }) }) + + describe('errorCacheFacade', () => { + it('should get and set error from cache', () => { + const $error = errorCacheFacade(new CacheStorage()) + const key = queryKey<[string], number>('test')('a') + + expect($error(key)).toBe(null) + + $error(key, 'failed') + + expect($error(key)).toBe('failed') + }) + + it('should revert error change', () => { + const $error = errorCacheFacade(new CacheStorage()) + const key = queryKey<[string], number>('test')('a') + + $error(key, 'first') + + const revert = $error(key, 'second') + + expect($error(key)).toBe('second') + + revert() + + expect($error(key)).toBe('first') + }) + }) }) }) diff --git a/packages/query/src/cache.ts b/packages/query/src/cache.ts index 92f4e0ca..5d8fb78a 100644 --- a/packages/query/src/cache.ts +++ b/packages/query/src/cache.ts @@ -1,5 +1,5 @@ import { - type NewValue, + assignKey, batch, isFunction } from '@nano_kit/store' @@ -7,10 +7,15 @@ import type { CacheKeyBuilder, AnyCacheKeyBuilder, CacheDataFacade, + CacheErrorFacade, CacheKey, - ExtrasCacheKeyBuilder + ExtrasCacheKeyBuilder, + NewData } from './cache.types.js' -import type { CacheStorage } from './CacheStorage.js' +import type { + CacheEntry, + CacheStorage +} from './CacheStorage.js' export type * from './cache.types.js' @@ -68,33 +73,43 @@ export function operationKey< return queryKey(name, filter) as ExtrasCacheKeyBuilder } -/** - * Create cache getter/setter for data. - * @param cache - The cache map. - * @returns The data getter/setter. - */ -/* @__NO_SIDE_EFFECTS__ */ -export function dataCacheFacade(cache: CacheStorage) { - return dataCacheGetterSetter.bind(cache) as CacheDataFacade -} - -function dataCacheGetterSetter

( +function cacheGetterSetter( this: CacheStorage, + field: F, key: CacheKey, - ...value: [NewValue] + ...value: [NewData[F]>] ) { if (value.length) { const newValue = value[0] + let prevEntry: CacheEntry + + this.set(key, (entry = this.initial()) => { + prevEntry = entry - this.set(key, (entry = this.initial()) => ({ - ...entry, - data: isFunction(newValue) - ? (newValue as (value: unknown) => unknown)(entry.data) + const next = isFunction(newValue) + ? newValue(entry[field] as CacheEntry[F], entry.params as P) : newValue - })) - } else { - return this.$get(key).data as R | null + + return assignKey(entry, field, next) + }) + + return () => this.set( + key, + (entry = prevEntry) => assignKey(entry, field, prevEntry[field]) + ) } + + return this.$get(key)[field] as CacheEntry[F] +} + +/** + * Create cache getter/setter for data. + * @param cache - The cache map. + * @returns The data getter/setter. + */ +/* @__NO_SIDE_EFFECTS__ */ +export function dataCacheFacade(cache: CacheStorage) { + return cacheGetterSetter.bind(cache, 'data') as CacheDataFacade } /** @@ -110,9 +125,9 @@ export function loadingCacheFacade(cache: CacheStorage) { /** * Create cache getter for error state. * @param cache - The cache map. - * @returns The error state getter. + * @returns The error state getter/setter. */ /* @__NO_SIDE_EFFECTS__ */ export function errorCacheFacade(cache: CacheStorage) { - return (key: CacheKey) => cache.$get(key).error + return cacheGetterSetter.bind(cache, 'error') as CacheErrorFacade } diff --git a/packages/query/src/cache.types.ts b/packages/query/src/cache.types.ts index 69cb2ad8..85597f5f 100644 --- a/packages/query/src/cache.types.ts +++ b/packages/query/src/cache.types.ts @@ -1,4 +1,3 @@ -import type { NewValue } from '@nano_kit/store' import type { CacheKey, CacheShardKey @@ -33,7 +32,14 @@ export type ExtrasCacheKeyBuilder< (...params: Partial

) => ExtrasCacheKey ) & CacheShardKey +export type NewData

= R | ((data: R, params: P) => R) + export interface CacheDataFacade {

(key: CacheKey): R | null -

(key: CacheShardKey | CacheKey, value: NewValue): void +

(key: CacheShardKey | CacheKey, value: NewData): () => void +} + +export interface CacheErrorFacade { + (key: CacheKey): string | null +

(key: CacheShardKey

| CacheKey

, value: NewData): () => void }