@nanostores/query
Advanced tools
Comparing version 0.3.0 to 0.3.1
@@ -1,76 +0,10 @@ | ||
import { MapStore, ReadableAtom } from 'nanostores'; | ||
type NoKey = null | undefined | void | false; | ||
type SomeKey = string | number | true; | ||
export type KeyInput = SomeKey | Array<SomeKey | ReadableAtom<SomeKey | NoKey> | FetcherStore>; | ||
type Key = string; | ||
type KeyParts = SomeKey[]; | ||
export type KeySelector = Key | Key[] | ((key: Key) => boolean); | ||
export type Fetcher<T> = (...args: KeyParts) => Promise<T>; | ||
export type OnErrorRetry = (opts: { | ||
error: unknown; | ||
key: Key; | ||
retryCount: number; | ||
}) => number | void | false | null | undefined; | ||
type EventTypes = { | ||
onError?: (error: unknown) => void; | ||
}; | ||
type RefetchSettings = { | ||
dedupeTime?: number; | ||
revalidateOnFocus?: boolean; | ||
revalidateOnReconnect?: boolean; | ||
revalidateInterval?: number; | ||
cacheLifetime?: number; | ||
onErrorRetry?: OnErrorRetry | null | false; | ||
}; | ||
export type CommonSettings<T = unknown> = { | ||
fetcher?: Fetcher<T>; | ||
} & RefetchSettings & EventTypes; | ||
export type NanoqueryArgs = { | ||
cache?: Map<Key, { | ||
data?: unknown; | ||
error?: unknown; | ||
retryCount?: number; | ||
created?: number; | ||
expires?: number; | ||
}>; | ||
} & CommonSettings; | ||
export type FetcherValue<T = any, E = Error> = { | ||
data?: T; | ||
error?: E; | ||
loading: boolean; | ||
promise?: Promise<T>; | ||
}; | ||
export type FetcherStore<T = any, E = any> = MapStore<FetcherValue<T, E>> & { | ||
_: Symbol; | ||
key?: Key; | ||
invalidate: (...args: any[]) => void; | ||
revalidate: (...args: any[]) => void; | ||
mutate: (data?: T) => void; | ||
}; | ||
export type FetcherStoreCreator<T = any, E = Error> = (keys: KeyInput, settings?: CommonSettings<T>) => FetcherStore<T, E>; | ||
export type ManualMutator<Data = void, Result = unknown> = (args: { | ||
data: Data; | ||
invalidate: (key: KeySelector) => void; | ||
revalidate: (key: KeySelector) => void; | ||
getCacheUpdater: <T = unknown>(key: Key, shouldRevalidate?: boolean) => [(newValue?: T) => void, T | undefined]; | ||
}) => Promise<Result>; | ||
export type MutateCb<Data, Result = unknown> = Data extends void ? () => Promise<Result> : (data: Data) => Promise<Result>; | ||
export type MutatorStore<Data = void, Result = unknown, E = Error> = MapStore<{ | ||
mutate: MutateCb<Data, Result>; | ||
data?: Result; | ||
loading?: boolean; | ||
error?: E; | ||
}> & { | ||
mutate: MutateCb<Data, Result>; | ||
}; | ||
export declare const nanoquery: ({ cache, fetcher: globalFetcher, ...globalSettings }?: NanoqueryArgs) => readonly [<T = unknown, E = any>(keyInput: KeyInput, { fetcher, ...fetcherSettings }?: CommonSettings<T>) => FetcherStore<T, E>, <Data = void, Result = unknown, E_1 = any>(mutator: ManualMutator<Data, Result>, opts?: { | ||
throttleCalls?: boolean; | ||
onError?: EventTypes["onError"]; | ||
}) => MutatorStore<Data, Result, E_1>, { | ||
readonly __unsafeOverruleSettings: (data: CommonSettings) => void; | ||
readonly invalidateKeys: (keySelector: KeySelector) => void; | ||
readonly revalidateKeys: (keySelector: KeySelector) => void; | ||
readonly mutateCache: (keySelector: KeySelector, data?: unknown) => void; | ||
export type { KeyInput, KeySelector, Fetcher, OnErrorRetry, CommonSettings, NanoqueryArgs, FetcherValue, FetcherStore, FetcherStoreCreator, ManualMutator, MutateCb, MutatorStore, } from './factory'; | ||
export declare const nanoquery: ({ cache, fetcher: globalFetcher, ...globalSettings }?: import('./factory').NanoqueryArgs) => readonly [<T = unknown, E = any>(keyInput: import('./factory').KeyInput, { fetcher, ...fetcherSettings }?: import('./factory').CommonSettings<T>) => import('./factory').FetcherStore<T, E>, <Data = void, Result = unknown, E_1 = any>(mutator: import('./factory').ManualMutator<Data, Result>, opts?: { | ||
throttleCalls?: boolean | undefined; | ||
onError?: ((error: unknown) => void) | undefined; | ||
} | undefined) => import('./factory').MutatorStore<Data, Result, E_1>, { | ||
readonly __unsafeOverruleSettings: (data: import('./factory').CommonSettings<unknown>) => void; | ||
readonly invalidateKeys: (keySelector: import('./factory').KeySelector) => void; | ||
readonly revalidateKeys: (keySelector: import('./factory').KeySelector) => void; | ||
readonly mutateCache: (keySelector: import('./factory').KeySelector, data?: unknown) => void; | ||
}]; | ||
export {}; |
import { map, onStart, onStop, atom, batched, startTask } from 'nanostores'; | ||
import { createNanoEvents } from 'nanoevents'; | ||
const nanoquery = ({ | ||
cache = /* @__PURE__ */ new Map(), | ||
fetcher: globalFetcher, | ||
...globalSettings | ||
} = {}) => { | ||
const events = createNanoEvents(); | ||
let focus = true; | ||
subscribe("visibilitychange", () => { | ||
focus = !document.hidden; | ||
focus && events.emit(FOCUS); | ||
}); | ||
subscribe("online", () => events.emit(RECONNECT)); | ||
const _revalidateOnInterval = /* @__PURE__ */ new Map(), _errorInvalidateTimeouts = /* @__PURE__ */ new Map(), _runningFetches = /* @__PURE__ */ new Map(); | ||
let rewrittenSettings = {}; | ||
const getCachedValueByKey = (key) => { | ||
const fromCache = cache.get(key); | ||
if (!fromCache) | ||
return []; | ||
const cacheHit = (fromCache.expires || 0) > (/* @__PURE__ */ new Date()).getTime(); | ||
return cacheHit ? [fromCache.data, fromCache.error] : []; | ||
}; | ||
const runFetcher = async ([key, keyParts], store, settings) => { | ||
if (!focus) | ||
return; | ||
const set = (v) => { | ||
if (store.key === key) { | ||
store.set(v); | ||
events.emit(SET_CACHE, key, v, true); | ||
} | ||
const nanoqueryFactory = ([ | ||
isAppVisible, | ||
visibilityChangeSubscribe, | ||
reconnectChangeSubscribe | ||
]) => { | ||
const nanoquery = ({ | ||
cache = /* @__PURE__ */ new Map(), | ||
fetcher: globalFetcher, | ||
...globalSettings | ||
} = {}) => { | ||
const events = createNanoEvents(); | ||
let focus = true; | ||
visibilityChangeSubscribe(() => { | ||
focus = isAppVisible(); | ||
focus && events.emit(FOCUS); | ||
}); | ||
reconnectChangeSubscribe(() => events.emit(RECONNECT)); | ||
const _revalidateOnInterval = /* @__PURE__ */ new Map(), _errorInvalidateTimeouts = /* @__PURE__ */ new Map(), _runningFetches = /* @__PURE__ */ new Map(); | ||
let rewrittenSettings = {}; | ||
const getCachedValueByKey = (key) => { | ||
const fromCache = cache.get(key); | ||
if (!fromCache) | ||
return []; | ||
const cacheHit = (fromCache.expires || 0) > getNow(); | ||
return cacheHit ? [fromCache.data, fromCache.error] : []; | ||
}; | ||
const setAsLoading = (prev) => { | ||
const toSet = prev === void 0 ? {} : { data: prev }; | ||
set({ | ||
...toSet, | ||
...loading, | ||
promise: _runningFetches.get(key) | ||
}); | ||
}; | ||
let { | ||
dedupeTime = 4e3, | ||
cacheLifetime = Infinity, | ||
fetcher, | ||
onErrorRetry = defaultOnErrorRetry | ||
} = { | ||
...settings, | ||
...rewrittenSettings | ||
}; | ||
if (cacheLifetime < dedupeTime) | ||
cacheLifetime = dedupeTime; | ||
const now = (/* @__PURE__ */ new Date()).getTime(); | ||
if (_runningFetches.has(key)) { | ||
if (!store.value.loading) | ||
setAsLoading(getCachedValueByKey(key)[0]); | ||
return; | ||
} | ||
let cachedValue, cachedError; | ||
const fromCache = cache.get(key); | ||
if (fromCache?.data !== void 0 || fromCache?.error) { | ||
[cachedValue, cachedError] = getCachedValueByKey(key); | ||
if ((fromCache.created || 0) + dedupeTime > now) { | ||
if (store.value.data != cachedValue || store.value.error != cachedError) { | ||
set({ ...notLoading, data: cachedValue, error: cachedError }); | ||
const runFetcher = async ([key, keyParts], store, settings) => { | ||
if (!focus) | ||
return; | ||
const set = (v) => { | ||
if (store.key === key) { | ||
store.set(v); | ||
events.emit(SET_CACHE, key, v, true); | ||
} | ||
}; | ||
const setAsLoading = (prev) => { | ||
const toSet = prev === void 0 ? {} : { data: prev }; | ||
set({ | ||
...toSet, | ||
...loading, | ||
promise: _runningFetches.get(key) | ||
}); | ||
}; | ||
let { | ||
dedupeTime = 4e3, | ||
cacheLifetime = Infinity, | ||
fetcher, | ||
onErrorRetry = defaultOnErrorRetry | ||
} = { | ||
...settings, | ||
...rewrittenSettings | ||
}; | ||
if (cacheLifetime < dedupeTime) | ||
cacheLifetime = dedupeTime; | ||
const now = getNow(); | ||
if (_runningFetches.has(key)) { | ||
if (!store.value.loading) | ||
setAsLoading(getCachedValueByKey(key)[0]); | ||
return; | ||
} | ||
} | ||
const finishTask = startTask(); | ||
try { | ||
clearTimeout(_errorInvalidateTimeouts.get(key)); | ||
const promise = fetcher(...keyParts); | ||
_runningFetches.set(key, promise); | ||
setAsLoading(cachedValue); | ||
const res = await promise; | ||
cache.set(key, { | ||
data: res, | ||
created: (/* @__PURE__ */ new Date()).getTime(), | ||
expires: (/* @__PURE__ */ new Date()).getTime() + cacheLifetime | ||
}); | ||
set({ data: res, ...notLoading }); | ||
} catch (error) { | ||
settings.onError?.(error); | ||
const retryCount = (cache.get(key)?.retryCount || 0) + 1; | ||
cache.set(key, { | ||
error, | ||
created: (/* @__PURE__ */ new Date()).getTime(), | ||
expires: (/* @__PURE__ */ new Date()).getTime() + cacheLifetime, | ||
retryCount | ||
}); | ||
if (onErrorRetry) { | ||
const timer = onErrorRetry({ | ||
let cachedValue, cachedError; | ||
const fromCache = cache.get(key); | ||
if (fromCache?.data !== void 0 || fromCache?.error) { | ||
[cachedValue, cachedError] = getCachedValueByKey(key); | ||
if ((fromCache.created || 0) + dedupeTime > now) { | ||
if (store.value.data != cachedValue || store.value.error != cachedError) { | ||
set({ ...notLoading, data: cachedValue, error: cachedError }); | ||
} | ||
return; | ||
} | ||
} | ||
const finishTask = startTask(); | ||
try { | ||
clearTimeout(_errorInvalidateTimeouts.get(key)); | ||
const promise = fetcher(...keyParts); | ||
_runningFetches.set(key, promise); | ||
setAsLoading(cachedValue); | ||
const res = await promise; | ||
cache.set(key, { | ||
data: res, | ||
created: getNow(), | ||
expires: getNow() + cacheLifetime | ||
}); | ||
set({ data: res, ...notLoading }); | ||
} catch (error) { | ||
settings.onError?.(error); | ||
const retryCount = (cache.get(key)?.retryCount || 0) + 1; | ||
cache.set(key, { | ||
error, | ||
key, | ||
created: getNow(), | ||
expires: getNow() + cacheLifetime, | ||
retryCount | ||
}); | ||
if (timer) | ||
_errorInvalidateTimeouts.set( | ||
if (onErrorRetry) { | ||
const timer = onErrorRetry({ | ||
error, | ||
key, | ||
setTimeout(() => invalidateKeys(key), timer) | ||
); | ||
retryCount | ||
}); | ||
if (timer) | ||
_errorInvalidateTimeouts.set( | ||
key, | ||
setTimeout(() => invalidateKeys(key), timer) | ||
); | ||
} | ||
set({ data: store.value.data, error, ...notLoading }); | ||
} finally { | ||
finishTask(); | ||
_runningFetches.delete(key); | ||
} | ||
set({ data: store.value.data, error, ...notLoading }); | ||
} finally { | ||
finishTask(); | ||
_runningFetches.delete(key); | ||
} | ||
}; | ||
const createFetcherStore = (keyInput, { | ||
fetcher = globalFetcher, | ||
...fetcherSettings | ||
} = {}) => { | ||
if (process.env.NODE_ENV !== "production" && !fetcher) { | ||
throw new Error( | ||
"You need to set up either global fetcher of fetcher in createFetcherStore" | ||
); | ||
} | ||
const fetcherStore = map({ | ||
...notLoading | ||
}), settings = { ...globalSettings, ...fetcherSettings, fetcher }; | ||
fetcherStore._ = fetcherSymbol; | ||
fetcherStore.invalidate = () => { | ||
const { key } = fetcherStore; | ||
if (key) { | ||
invalidateKeys(key); | ||
} | ||
}; | ||
fetcherStore.revalidate = () => { | ||
const { key } = fetcherStore; | ||
if (key) { | ||
revalidateKeys(key); | ||
const createFetcherStore = (keyInput, { | ||
fetcher = globalFetcher, | ||
...fetcherSettings | ||
} = {}) => { | ||
if (process.env.NODE_ENV !== "production" && !fetcher) { | ||
throw new Error( | ||
"You need to set up either global fetcher of fetcher in createFetcherStore" | ||
); | ||
} | ||
const fetcherStore = map({ | ||
...notLoading | ||
}), settings = { ...globalSettings, ...fetcherSettings, fetcher }; | ||
fetcherStore._ = fetcherSymbol; | ||
fetcherStore.invalidate = () => { | ||
const { key } = fetcherStore; | ||
if (key) { | ||
invalidateKeys(key); | ||
} | ||
}; | ||
fetcherStore.revalidate = () => { | ||
const { key } = fetcherStore; | ||
if (key) { | ||
revalidateKeys(key); | ||
} | ||
}; | ||
fetcherStore.mutate = (data) => { | ||
const { key } = fetcherStore; | ||
if (key) { | ||
mutateCache(key, data); | ||
} | ||
}; | ||
let keysInternalUnsub, prevKey, prevKeyParts, keyUnsub, keyStore; | ||
let evtUnsubs = []; | ||
onStart(fetcherStore, () => { | ||
const firstRun = !keysInternalUnsub; | ||
[keyStore, keysInternalUnsub] = getKeyStore(keyInput); | ||
keyUnsub = keyStore.subscribe((currentKeys) => { | ||
if (currentKeys) { | ||
const [newKey, keyParts] = currentKeys; | ||
fetcherStore.key = newKey; | ||
runFetcher([newKey, keyParts], fetcherStore, settings); | ||
prevKey = newKey; | ||
prevKeyParts = keyParts; | ||
} else { | ||
fetcherStore.key = prevKey = prevKeyParts = void 0; | ||
fetcherStore.set({ ...notLoading }); | ||
} | ||
}); | ||
const currentKeyValue = keyStore.get(); | ||
if (currentKeyValue) { | ||
[prevKey, prevKeyParts] = currentKeyValue; | ||
if (firstRun) | ||
handleNewListener(); | ||
} | ||
const { | ||
revalidateInterval = 0, | ||
revalidateOnFocus, | ||
revalidateOnReconnect | ||
} = settings; | ||
const runRefetcher = () => { | ||
if (prevKey) | ||
runFetcher([prevKey, prevKeyParts], fetcherStore, settings); | ||
}; | ||
if (revalidateInterval > 0) { | ||
_revalidateOnInterval.set( | ||
keyInput, | ||
setInterval(runRefetcher, revalidateInterval) | ||
); | ||
} | ||
if (revalidateOnFocus) | ||
evtUnsubs.push(events.on(FOCUS, runRefetcher)); | ||
if (revalidateOnReconnect) | ||
evtUnsubs.push(events.on(RECONNECT, runRefetcher)); | ||
const cacheKeyChangeHandler = (keySelector) => { | ||
if (prevKey && testKeyAgainstSelector(prevKey, keySelector)) { | ||
runFetcher([prevKey, prevKeyParts], fetcherStore, settings); | ||
} | ||
}; | ||
evtUnsubs.push( | ||
events.on(INVALIDATE_KEYS, cacheKeyChangeHandler), | ||
events.on(REVALIDATE_KEYS, cacheKeyChangeHandler), | ||
events.on(SET_CACHE, (keySelector, data, full) => { | ||
if (prevKey && testKeyAgainstSelector(prevKey, keySelector) && fetcherStore.value !== data && fetcherStore.value.data !== data) { | ||
fetcherStore.set( | ||
full ? data : { data, ...notLoading } | ||
); | ||
} | ||
}) | ||
); | ||
}); | ||
const handleNewListener = () => { | ||
if (prevKey && prevKeyParts) | ||
runFetcher([prevKey, prevKeyParts], fetcherStore, settings); | ||
}; | ||
const originListen = fetcherStore.listen; | ||
fetcherStore.listen = (listener) => { | ||
const unsub = originListen(listener); | ||
listener(fetcherStore.value); | ||
handleNewListener(); | ||
return unsub; | ||
}; | ||
onStop(fetcherStore, () => { | ||
fetcherStore.value = { ...notLoading }; | ||
keysInternalUnsub?.(); | ||
evtUnsubs.forEach((fn) => fn()); | ||
evtUnsubs = []; | ||
keyUnsub?.(); | ||
clearInterval(_revalidateOnInterval.get(keyInput)); | ||
}); | ||
return fetcherStore; | ||
}; | ||
fetcherStore.mutate = (data) => { | ||
const { key } = fetcherStore; | ||
if (key) { | ||
mutateCache(key, data); | ||
const iterOverCache = (keySelector, cb) => { | ||
for (const key of cache.keys()) { | ||
if (testKeyAgainstSelector(key, keySelector)) | ||
cb(key); | ||
} | ||
}; | ||
let keysInternalUnsub, prevKey, prevKeyParts, keyUnsub, keyStore; | ||
let evtUnsubs = []; | ||
onStart(fetcherStore, () => { | ||
const firstRun = !keysInternalUnsub; | ||
[keyStore, keysInternalUnsub] = getKeyStore(keyInput); | ||
keyUnsub = keyStore.subscribe((currentKeys) => { | ||
if (currentKeys) { | ||
const [newKey, keyParts] = currentKeys; | ||
fetcherStore.key = newKey; | ||
runFetcher([newKey, keyParts], fetcherStore, settings); | ||
prevKey = newKey; | ||
prevKeyParts = keyParts; | ||
} else { | ||
fetcherStore.key = prevKey = prevKeyParts = void 0; | ||
fetcherStore.set({ ...notLoading }); | ||
const invalidateKeys = (keySelector) => { | ||
iterOverCache(keySelector, (key) => { | ||
cache.delete(key); | ||
}); | ||
events.emit(INVALIDATE_KEYS, keySelector); | ||
}; | ||
const revalidateKeys = (keySelector) => { | ||
iterOverCache(keySelector, (key) => { | ||
const cached = cache.get(key); | ||
if (cached) { | ||
cache.set(key, { ...cached, created: -Infinity }); | ||
} | ||
}); | ||
const currentKeyValue = keyStore.get(); | ||
if (currentKeyValue) { | ||
[prevKey, prevKeyParts] = currentKeyValue; | ||
if (firstRun) | ||
handleNewListener(); | ||
} | ||
const { | ||
revalidateInterval = 0, | ||
revalidateOnFocus, | ||
revalidateOnReconnect | ||
} = settings; | ||
const runRefetcher = () => { | ||
if (prevKey) | ||
runFetcher([prevKey, prevKeyParts], fetcherStore, settings); | ||
events.emit(REVALIDATE_KEYS, keySelector); | ||
}; | ||
const mutateCache = (keySelector, data) => { | ||
iterOverCache(keySelector, (key) => { | ||
if (data === void 0) | ||
cache.delete(key); | ||
else { | ||
cache.set(key, { | ||
data, | ||
created: getNow(), | ||
expires: getNow() + (globalSettings.cacheLifetime ?? 8e3) | ||
}); | ||
} | ||
}); | ||
events.emit(SET_CACHE, keySelector, data); | ||
}; | ||
function createMutatorStore(mutator, opts) { | ||
const { throttleCalls, onError } = opts ?? { | ||
throttleCalls: true, | ||
onError: globalSettings?.onError | ||
}; | ||
if (revalidateInterval > 0) { | ||
_revalidateOnInterval.set( | ||
keyInput, | ||
setInterval(runRefetcher, revalidateInterval) | ||
); | ||
} | ||
if (revalidateOnFocus) | ||
evtUnsubs.push(events.on(FOCUS, runRefetcher)); | ||
if (revalidateOnReconnect) | ||
evtUnsubs.push(events.on(RECONNECT, runRefetcher)); | ||
const cacheKeyChangeHandler = (keySelector) => { | ||
if (prevKey && testKeyAgainstSelector(prevKey, keySelector)) { | ||
runFetcher([prevKey, prevKeyParts], fetcherStore, settings); | ||
const mutate = async (data) => { | ||
if (throttleCalls && store.value?.loading) | ||
return; | ||
const newMutator = rewrittenSettings.fetcher ?? mutator; | ||
const keysToInvalidate = [], keysToRevalidate = []; | ||
const safeKeySet = (k, v) => { | ||
if (store.lc) { | ||
store.setKey(k, v); | ||
} | ||
}; | ||
try { | ||
store.set({ | ||
error: void 0, | ||
data: void 0, | ||
mutate, | ||
...loading | ||
}); | ||
const result = await newMutator({ | ||
data, | ||
invalidate: (key) => { | ||
keysToInvalidate.push(key); | ||
}, | ||
revalidate: (key) => { | ||
keysToRevalidate.push(key); | ||
}, | ||
getCacheUpdater: (key, shouldRevalidate = true) => [ | ||
(newVal) => { | ||
mutateCache(key, newVal); | ||
if (shouldRevalidate) { | ||
keysToRevalidate.push(key); | ||
} | ||
}, | ||
cache.get(key)?.data | ||
] | ||
}); | ||
safeKeySet("data", result); | ||
return result; | ||
} catch (error) { | ||
onError?.(error); | ||
safeKeySet("error", error); | ||
store.setKey("error", error); | ||
} finally { | ||
safeKeySet("loading", false); | ||
keysToInvalidate.forEach(invalidateKeys); | ||
keysToRevalidate.forEach(revalidateKeys); | ||
} | ||
}; | ||
evtUnsubs.push( | ||
events.on(INVALIDATE_KEYS, cacheKeyChangeHandler), | ||
events.on(REVALIDATE_KEYS, cacheKeyChangeHandler), | ||
events.on(SET_CACHE, (keySelector, data, full) => { | ||
if (prevKey && testKeyAgainstSelector(prevKey, keySelector) && fetcherStore.value !== data && fetcherStore.value.data !== data) { | ||
fetcherStore.set( | ||
full ? data : { data, ...notLoading } | ||
); | ||
} | ||
}) | ||
const store = map({ | ||
mutate, | ||
...notLoading | ||
}); | ||
onStop( | ||
store, | ||
() => store.set({ mutate, ...notLoading }) | ||
); | ||
}); | ||
const handleNewListener = () => { | ||
if (prevKey && prevKeyParts) | ||
runFetcher([prevKey, prevKeyParts], fetcherStore, settings); | ||
}; | ||
const originListen = fetcherStore.listen; | ||
fetcherStore.listen = (listener) => { | ||
const unsub = originListen(listener); | ||
listener(fetcherStore.value); | ||
handleNewListener(); | ||
return unsub; | ||
}; | ||
onStop(fetcherStore, () => { | ||
fetcherStore.value = { ...notLoading }; | ||
keysInternalUnsub?.(); | ||
evtUnsubs.forEach((fn) => fn()); | ||
evtUnsubs = []; | ||
keyUnsub?.(); | ||
clearInterval(_revalidateOnInterval.get(keyInput)); | ||
}); | ||
return fetcherStore; | ||
}; | ||
const iterOverCache = (keySelector, cb) => { | ||
for (const key of cache.keys()) { | ||
if (testKeyAgainstSelector(key, keySelector)) | ||
cb(key); | ||
store.mutate = mutate; | ||
return store; | ||
} | ||
}; | ||
const invalidateKeys = (keySelector) => { | ||
iterOverCache(keySelector, (key) => { | ||
cache.delete(key); | ||
}); | ||
events.emit(INVALIDATE_KEYS, keySelector); | ||
}; | ||
const revalidateKeys = (keySelector) => { | ||
iterOverCache(keySelector, (key) => { | ||
const cached = cache.get(key); | ||
if (cached) { | ||
cache.set(key, { ...cached, created: -Infinity }); | ||
const __unsafeOverruleSettings = (data) => { | ||
if (process.env.NODE_ENV !== "test") { | ||
console.warn( | ||
`You should only use __unsafeOverruleSettings in test environment` | ||
); | ||
} | ||
}); | ||
events.emit(REVALIDATE_KEYS, keySelector); | ||
rewrittenSettings = data; | ||
}; | ||
return [ | ||
createFetcherStore, | ||
createMutatorStore, | ||
{ __unsafeOverruleSettings, invalidateKeys, revalidateKeys, mutateCache } | ||
]; | ||
}; | ||
const mutateCache = (keySelector, data) => { | ||
iterOverCache(keySelector, (key) => { | ||
if (data === void 0) | ||
cache.delete(key); | ||
else { | ||
cache.set(key, { | ||
data, | ||
created: (/* @__PURE__ */ new Date()).getTime(), | ||
expires: (/* @__PURE__ */ new Date()).getTime() + (globalSettings.cacheLifetime ?? 8e3) | ||
}); | ||
function isSomeKey(key) { | ||
return typeof key === "string" || typeof key === "number" || key === true; | ||
} | ||
const getKeyStore = (keys) => { | ||
if (isSomeKey(keys)) | ||
return [ | ||
atom(["" + keys, [keys]]), | ||
() => { | ||
} | ||
]; | ||
const keyParts = []; | ||
const $key = atom(null); | ||
const keysAsStoresToIndexes = /* @__PURE__ */ new Map(); | ||
const setKeyStoreValue = () => { | ||
if (keyParts.some((v) => v === null || v === void 0 || v === false)) { | ||
$key.set(null); | ||
} else { | ||
$key.set([keyParts.join(""), keyParts]); | ||
} | ||
}); | ||
events.emit(SET_CACHE, keySelector, data); | ||
}; | ||
function createMutatorStore(mutator, opts) { | ||
const { throttleCalls, onError } = opts ?? { | ||
throttleCalls: true, | ||
onError: globalSettings?.onError | ||
}; | ||
const mutate = async (data) => { | ||
if (throttleCalls && store.value?.loading) | ||
return; | ||
const newMutator = rewrittenSettings.fetcher ?? mutator; | ||
const keysToInvalidate = [], keysToRevalidate = []; | ||
const safeKeySet = (k, v) => { | ||
if (store.lc) { | ||
store.setKey(k, v); | ||
} | ||
}; | ||
try { | ||
store.set({ | ||
error: void 0, | ||
data: void 0, | ||
mutate, | ||
...loading | ||
}); | ||
const result = await newMutator({ | ||
data, | ||
invalidate: (key) => { | ||
keysToInvalidate.push(key); | ||
}, | ||
revalidate: (key) => { | ||
keysToRevalidate.push(key); | ||
}, | ||
getCacheUpdater: (key, shouldRevalidate = true) => [ | ||
(newVal) => { | ||
mutateCache(key, newVal); | ||
if (shouldRevalidate) { | ||
keysToRevalidate.push(key); | ||
} | ||
}, | ||
cache.get(key)?.data | ||
] | ||
}); | ||
safeKeySet("data", result); | ||
return result; | ||
} catch (error) { | ||
onError?.(error); | ||
safeKeySet("error", error); | ||
store.setKey("error", error); | ||
} finally { | ||
safeKeySet("loading", false); | ||
keysToInvalidate.forEach(invalidateKeys); | ||
keysToRevalidate.forEach(revalidateKeys); | ||
for (let i = 0; i < keys.length; i++) { | ||
const keyOrStore = keys[i]; | ||
if (isSomeKey(keyOrStore)) { | ||
keyParts.push(keyOrStore); | ||
} else { | ||
keyParts.push(null); | ||
keysAsStoresToIndexes.set(keyOrStore, i); | ||
} | ||
}; | ||
const store = map({ | ||
mutate, | ||
...notLoading | ||
} | ||
const storesAsArray = [...keysAsStoresToIndexes.keys()]; | ||
const $storeKeys = batched(storesAsArray, (...storeValues) => { | ||
for (let i = 0; i < storeValues.length; i++) { | ||
const store = storesAsArray[i], partIndex = keysAsStoresToIndexes.get(store); | ||
keyParts[partIndex] = store._ === fetcherSymbol ? store.value && "data" in store.value ? store.key : null : storeValues[i]; | ||
} | ||
setKeyStoreValue(); | ||
}); | ||
onStop( | ||
store, | ||
() => store.set({ mutate, ...notLoading }) | ||
); | ||
store.mutate = mutate; | ||
return store; | ||
setKeyStoreValue(); | ||
return [$key, $storeKeys.subscribe(noop)]; | ||
}; | ||
function defaultOnErrorRetry({ retryCount }) { | ||
return ~~((Math.random() + 0.5) * (1 << (retryCount < 8 ? retryCount : 8))) * 2e3; | ||
} | ||
const __unsafeOverruleSettings = (data) => { | ||
if (process.env.NODE_ENV !== "test") { | ||
console.warn( | ||
`You should only use __unsafeOverruleSettings in test environment` | ||
); | ||
} | ||
rewrittenSettings = data; | ||
function noop() { | ||
} | ||
const FOCUS = 1, RECONNECT = 2, INVALIDATE_KEYS = 3, REVALIDATE_KEYS = 4, SET_CACHE = 5; | ||
const testKeyAgainstSelector = (key, selector) => { | ||
if (Array.isArray(selector)) | ||
return selector.includes(key); | ||
else if (typeof selector === "function") | ||
return selector(key); | ||
else | ||
return key === selector; | ||
}; | ||
return [ | ||
createFetcherStore, | ||
createMutatorStore, | ||
{ __unsafeOverruleSettings, invalidateKeys, revalidateKeys, mutateCache } | ||
]; | ||
const getNow = () => (/* @__PURE__ */ new Date()).getTime(); | ||
const fetcherSymbol = Symbol(); | ||
const loading = { loading: true }, notLoading = { loading: false }; | ||
return nanoquery; | ||
}; | ||
function isSomeKey(key) { | ||
return typeof key === "string" || typeof key === "number" || key === true; | ||
} | ||
const getKeyStore = (keys) => { | ||
if (isSomeKey(keys)) | ||
return [atom(["" + keys, [keys]]), () => { | ||
}]; | ||
const keyParts = []; | ||
const $key = atom(null); | ||
const keysAsStoresToIndexes = /* @__PURE__ */ new Map(); | ||
const setKeyStoreValue = () => { | ||
if (keyParts.some((v) => v === null || v === void 0 || v === false)) { | ||
$key.set(null); | ||
} else { | ||
$key.set([keyParts.join(""), keyParts]); | ||
} | ||
}; | ||
for (let i = 0; i < keys.length; i++) { | ||
const keyOrStore = keys[i]; | ||
if (isSomeKey(keyOrStore)) { | ||
keyParts.push(keyOrStore); | ||
} else { | ||
keyParts.push(null); | ||
keysAsStoresToIndexes.set(keyOrStore, i); | ||
} | ||
} | ||
const storesAsArray = [...keysAsStoresToIndexes.keys()]; | ||
const $storeKeys = batched(storesAsArray, (...storeValues) => { | ||
for (let i = 0; i < storeValues.length; i++) { | ||
const store = storesAsArray[i], partIndex = keysAsStoresToIndexes.get(store); | ||
keyParts[partIndex] = store._ === fetcherSymbol ? store.value && "data" in store.value ? store.key : null : storeValues[i]; | ||
} | ||
setKeyStoreValue(); | ||
}); | ||
setKeyStoreValue(); | ||
return [$key, $storeKeys.subscribe(noop)]; | ||
}; | ||
function defaultOnErrorRetry({ retryCount }) { | ||
return ~~((Math.random() + 0.5) * (1 << (retryCount < 8 ? retryCount : 8))) * 2e3; | ||
} | ||
function noop() { | ||
} | ||
const FOCUS = 1, RECONNECT = 2, INVALIDATE_KEYS = 3, REVALIDATE_KEYS = 4, SET_CACHE = 5; | ||
const subscribe = (name, fn) => { | ||
@@ -382,13 +404,10 @@ const isServer = typeof window === "undefined"; | ||
}; | ||
const testKeyAgainstSelector = (key, selector) => { | ||
if (Array.isArray(selector)) | ||
return selector.includes(key); | ||
else if (typeof selector === "function") | ||
return selector(key); | ||
else | ||
return key === selector; | ||
}; | ||
const fetcherSymbol = Symbol(); | ||
const loading = { loading: true }, notLoading = { loading: false }; | ||
const browserCompat = [ | ||
() => !document.hidden, | ||
(cb) => subscribe("visibilitychange", cb), | ||
(cb) => subscribe("online", cb) | ||
]; | ||
const nanoquery = nanoqueryFactory(browserCompat); | ||
export { nanoquery }; |
{ | ||
"name": "@nanostores/query", | ||
"version": "0.3.0", | ||
"version": "0.3.1", | ||
"description": "Tiny remote data fetching library for Nano Stores", | ||
"scripts": { | ||
"pub": "pnpm test && pnpm build && npm publish && git push && git push --tags", | ||
"build": "vite build", | ||
"pub": "pnpm build && npm publish && git push && git push --tags", | ||
"build": "./build.sh", | ||
"test:unit": "vitest run --typecheck", | ||
@@ -16,2 +16,3 @@ "test:size": "size-limit --silent", | ||
"react", | ||
"react-native", | ||
"preact", | ||
@@ -39,2 +40,3 @@ "vue", | ||
"import": "./dist/nanoquery.js", | ||
"react-native": "./dist/nanoquery.native.cjs", | ||
"require": "./dist/nanoquery.umd.cjs" | ||
@@ -49,2 +51,3 @@ } | ||
"@nanostores/react": "^0.7.2", | ||
"@rollup/plugin-replace": "^5.0.5", | ||
"@rollup/plugin-strip": "^3.0.4", | ||
@@ -67,4 +70,8 @@ "@size-limit/preset-small-lib": "^11.1.2", | ||
"peerDependencies": { | ||
"nanostores": ">0.10" | ||
"nanostores": ">=0.10" | ||
}, | ||
"optionalPeerDependencies": { | ||
"react-native": ">=0.70", | ||
"@react-native-community/netinfo": ">=11" | ||
}, | ||
"engines": { | ||
@@ -82,3 +89,3 @@ "node": "^14.0.0 || ^16.0.0 || >=18.0.0" | ||
}, | ||
"limit": "1811 B" | ||
"limit": "1862 B" | ||
} | ||
@@ -85,0 +92,0 @@ ], |
@@ -279,3 +279,3 @@ # Nano Stores Query | ||
We've already walked through all the primitives needed for refetching and mutation, but the interface is rather bizarre with all those string-based keys. Often all we actually want is to refetch _current_ key (say, you have this refresh button in the UI), ot mutate _current_ key, right? | ||
We've already walked through all the primitives needed for refetching and mutation, but the interface is rather bizarre with all those string-based keys. Often all we actually want is to refetch _current_ key (say, you have this refresh button in the UI), or mutate _current_ key, right? | ||
@@ -315,1 +315,5 @@ For these cases we have 3 additional things on fetcher stores: | ||
`onError` gets a single argument of whatever the fetch or mutate functions threw. | ||
### React Native | ||
React Native is fully supported. For `revalidateOnReconnect` to work, you need to install `@react-native-community/netinfo` package. It's optional: if you don't `reconnect` just won't trigger revalidation. The rest works as usual. |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
64098
12
1342
318
18
6