Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions packages/kida/src/internals/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,14 @@ export function assignKey<
key: K,
value: V
) {
return {
...object || {},
[key]: value
} as PickNonEmptyValue<T> & Record<K, V>
return (
object?.[key] === value
? object
: {
...object,
[key]: value
}
) as PickNonEmptyValue<T> & Record<K, V>
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/query/.size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "All publics",
"path": "dist/index.js",
"import": "*",
"limit": "3.98 kB"
"limit": "4.01 kB"
},
{
"name": "Minimal set",
Expand Down
59 changes: 58 additions & 1 deletion packages/query/src/cache.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { CacheStorage } from './CacheStorage.js'
import {
queryKey,
keys,
dataCacheFacade
dataCacheFacade,
errorCacheFacade
} from './cache.js'

describe('query', () => {
Expand Down Expand Up @@ -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')
Expand All @@ -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')
})
})
})
})
63 changes: 39 additions & 24 deletions packages/query/src/cache.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import {
type NewValue,
assignKey,
batch,
isFunction
} from '@nano_kit/store'
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'

Expand Down Expand Up @@ -68,33 +73,43 @@ export function operationKey<
return queryKey<P, R>(name, filter) as ExtrasCacheKeyBuilder<P, E, R>
}

/**
* 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<P extends unknown[], R>(
function cacheGetterSetter<F extends 'data' | 'error', P extends unknown[], R>(
this: CacheStorage,
field: F,
key: CacheKey<P, R>,
...value: [NewValue<R | null>]
...value: [NewData<P, CacheEntry<P, R>[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<P, R>[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<P, R>[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
}

/**
Expand All @@ -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
}
10 changes: 8 additions & 2 deletions packages/query/src/cache.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { NewValue } from '@nano_kit/store'
import type {
CacheKey,
CacheShardKey
Expand Down Expand Up @@ -33,7 +32,14 @@ export type ExtrasCacheKeyBuilder<
(...params: Partial<P>) => ExtrasCacheKey<P, E, R>
) & CacheShardKey<P, R>

export type NewData<P extends readonly unknown[], R> = R | ((data: R, params: P) => R)

export interface CacheDataFacade {
<P extends unknown[], R>(key: CacheKey<P, R>): R | null
<P extends unknown[], R>(key: CacheShardKey<P, R> | CacheKey<P, R>, value: NewValue<R | null>): void
<P extends unknown[], R>(key: CacheShardKey<P, R> | CacheKey<P, R>, value: NewData<P, R | null>): () => void
}

export interface CacheErrorFacade {
(key: CacheKey): string | null
<P extends unknown[]>(key: CacheShardKey<P> | CacheKey<P>, value: NewData<P, string | null>): () => void
}