@nanostores/query
Advanced tools
Comparing version 0.0.7 to 0.1.0
@@ -26,5 +26,8 @@ import { MapStore, ReadableAtom } from "nanostores"; | ||
loading: boolean; | ||
promise?: Promise<T>; | ||
}; | ||
export declare type FetcherStore<T = any, E = any> = MapStore<FetcherValue<T, E>> & { | ||
key?: string; | ||
invalidate: (...args: any[]) => void; | ||
mutate: (data?: T) => void; | ||
}; | ||
@@ -31,0 +34,0 @@ export declare type FetcherStoreCreator<T = any, E = Error> = (keys: KeyInput, settings?: CommonSettings<T>) => FetcherStore<T, E>; |
@@ -16,12 +16,13 @@ import { map, onStart, onStop, atom } from "nanostores"; | ||
subscribe("online", () => events.emit(RECONNECT)); | ||
const _refetchOnInterval = /* @__PURE__ */ new Map(), _lastFetch = /* @__PURE__ */ new Map(), _runningFetches = /* @__PURE__ */ new Set(), _latestStoreKey = /* @__PURE__ */ new Map(); | ||
const _refetchOnInterval = /* @__PURE__ */ new Map(), _lastFetch = /* @__PURE__ */ new Map(), _runningFetches = /* @__PURE__ */ new Set(); | ||
let rewrittenSettings = {}; | ||
const runFetcher = async ([key, keyParts], store, settings, force) => { | ||
var _a; | ||
_latestStoreKey.set(store, key); | ||
const isKeyStillSame = () => _latestStoreKey.get(store) === key; | ||
if (!focus) | ||
return; | ||
const isKeyStillSame = () => store.key === key; | ||
const set = (v) => { | ||
if (isKeyStillSame()) { | ||
store.set(v); | ||
events.emit(SET_KEY, key, v); | ||
events.emit(SET_CACHE, key, v, true); | ||
} | ||
@@ -33,4 +34,2 @@ }, setKey = (k, v) => { | ||
}; | ||
if (!focus) | ||
return; | ||
const { dedupeTime = 4e3, fetcher } = { | ||
@@ -42,8 +41,5 @@ ...settings, | ||
if (!force) { | ||
tick().then(() => { | ||
const cached = cache.get(key); | ||
if (store.value.data !== cached) | ||
set(cached ? { data: cached, loading: false } : loading); | ||
}); | ||
await tick(); | ||
const cached = cache.get(key); | ||
if (store.value.data !== cached) | ||
set(cached ? { data: cached, ...notLoading } : { ...loading }); | ||
const last = _lastFetch.get(key); | ||
@@ -61,10 +57,11 @@ if (last && last + dedupeTime > now) { | ||
try { | ||
const res = await fetcher(...keyParts); | ||
const promise = fetcher(...keyParts); | ||
setKey("promise", promise); | ||
const res = await promise; | ||
cache.set(key, res); | ||
set({ data: res, loading: false }); | ||
set({ data: res, ...notLoading }); | ||
_lastFetch.set(key, getNow()); | ||
} catch (error) { | ||
(_a = settings.onError) == null ? void 0 : _a.call(settings, error); | ||
setKey("error", error); | ||
setKey("loading", false); | ||
set({ data: store.value.data, error, ...notLoading }); | ||
} finally { | ||
@@ -84,4 +81,16 @@ _runningFetches.delete(key); | ||
const fetcherStore = map({ | ||
loading: true | ||
...notLoading | ||
}), settings = { ...globalSettings, ...fetcherSettings, fetcher }; | ||
fetcherStore.invalidate = () => { | ||
const { key } = fetcherStore; | ||
if (key) { | ||
invalidateKeys(key); | ||
} | ||
}; | ||
fetcherStore.mutate = (data) => { | ||
const { key } = fetcherStore; | ||
if (key) { | ||
mutateCache(key, data); | ||
} | ||
}; | ||
let keysInternalUnsub, prevKey, prevKeyParts, keyUnsub, keyStore; | ||
@@ -108,4 +117,2 @@ let evtUnsubs = []; | ||
handleNewListener(); | ||
} else { | ||
tick().then(() => fetcherStore.set(loading)); | ||
} | ||
@@ -132,13 +139,2 @@ const { | ||
evtUnsubs.push( | ||
events.on(MUTATE_CACHE, (keySelector, data) => { | ||
if (prevKey && testKeyAgainstSelector(prevKey, keySelector)) { | ||
if (data === void 0) { | ||
cache.delete(prevKey); | ||
_lastFetch.delete(prevKey); | ||
} else { | ||
cache.set(prevKey, data); | ||
} | ||
fetcherStore.setKey("data", data); | ||
} | ||
}), | ||
events.on(INVALIDATE_KEYS, (keySelector) => { | ||
@@ -149,5 +145,8 @@ if (prevKey && testKeyAgainstSelector(prevKey, keySelector)) { | ||
}), | ||
events.on(SET_KEY, (key, value) => { | ||
if (key === prevKey && fetcherStore.value !== value) | ||
fetcherStore.set(value); | ||
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 } | ||
); | ||
} | ||
}) | ||
@@ -160,8 +159,10 @@ ); | ||
}; | ||
const originListen = fetcherStore.listen; | ||
fetcherStore.listen = (listener) => { | ||
const newImplFactory = (origin) => (listener) => { | ||
handleNewListener(); | ||
return originListen(listener); | ||
return origin(listener); | ||
}; | ||
fetcherStore.listen = newImplFactory(fetcherStore.listen); | ||
fetcherStore.subscribe = newImplFactory(fetcherStore.subscribe); | ||
onStop(fetcherStore, () => { | ||
fetcherStore.value = { ...notLoading }; | ||
keysInternalUnsub == null ? void 0 : keysInternalUnsub(); | ||
@@ -177,7 +178,24 @@ evtUnsubs.forEach((fn) => fn()); | ||
}; | ||
const nukeKey = (key) => { | ||
cache.delete(key); | ||
_lastFetch.delete(key); | ||
}; | ||
const iterOverCache = (keySelector, cb) => { | ||
for (const key of cache.keys()) { | ||
if (testKeyAgainstSelector(key, keySelector)) | ||
cb(key); | ||
} | ||
}; | ||
const invalidateKeys = (keySelector) => { | ||
iterOverCache(keySelector, nukeKey); | ||
events.emit(INVALIDATE_KEYS, keySelector); | ||
}; | ||
const mutateCache = (keySelector, data) => { | ||
events.emit(MUTATE_CACHE, keySelector, data); | ||
iterOverCache(keySelector, (key) => { | ||
if (data === void 0) | ||
nukeKey(key); | ||
else | ||
cache.set(key, data); | ||
}); | ||
events.emit(SET_CACHE, keySelector, data); | ||
}; | ||
@@ -192,5 +210,5 @@ function createMutatorStore(mutator) { | ||
error: void 0, | ||
loading: true, | ||
data: void 0, | ||
mutate | ||
mutate, | ||
...loading | ||
}); | ||
@@ -224,3 +242,3 @@ const result = await newMutator({ | ||
mutate, | ||
loading: false | ||
...notLoading | ||
}); | ||
@@ -272,3 +290,3 @@ return store; | ||
}; | ||
const FOCUS = 1, RECONNECT = 2, INVALIDATE_KEYS = 3, MUTATE_CACHE = 4, SET_KEY = 5; | ||
const FOCUS = 1, RECONNECT = 2, INVALIDATE_KEYS = 3, SET_CACHE = 4; | ||
const subscribe = (name, fn) => { | ||
@@ -289,6 +307,5 @@ const isServer = typeof window === "undefined"; | ||
const getNow = () => new Date().getTime(); | ||
const tick = () => new Promise((r) => r()); | ||
const loading = Object.freeze({ loading: true }); | ||
const loading = { loading: true }, notLoading = { loading: false }; | ||
export { | ||
nanoquery | ||
}; |
{ | ||
"name": "@nanostores/query", | ||
"version": "0.0.7", | ||
"version": "0.1.0", | ||
"description": "Tiny remote data fetching library for Nano Stores", | ||
@@ -32,2 +32,9 @@ "scripts": { | ||
"types": "./dist/main.d.ts", | ||
"exports": { | ||
".": { | ||
"types": "./dist/main.d.ts", | ||
"import": "./dist/nanoquery.js", | ||
"require": "./dist/nanoquery.umd.cjs" | ||
} | ||
}, | ||
"dependencies": { | ||
@@ -34,0 +41,0 @@ "nanoevents": "7" |
@@ -8,3 +8,3 @@ # Nano Stores Query | ||
- **Small**. 1.65 Kb (minified and gzipped). | ||
- **Small**. 1.61 Kb (minified and gzipped). | ||
- **Familiar DX**. If you've used [`swr`](https://swr.vercel.app/) or | ||
@@ -41,3 +41,3 @@ [`react-query`](https://react-query-v3.tanstack.com/), you'll get the same treatment, | ||
### Query | ||
### Context | ||
@@ -173,2 +173,45 @@ First, we define the context. It allows us to share the default fetcher | ||
## _Third returned item_ | ||
(we didn't come up with a name for it 😅) | ||
`nanoquery` function returns a third item that gives you a bit more manual control over the behavior of the cache. | ||
```ts | ||
// store/fetcher.ts | ||
import { nanoquery } from '@nanostores/query'; | ||
export const [,, { invalidateKeys, mutateCache }] = nanoquery(); | ||
``` | ||
`invalidateKeys` does 2 things: | ||
1. nukes all cache for the specified keys; | ||
2. asks all the fetcher stores that used those keys to refresh data immediately, if they have active subscribers. | ||
It accepts one argument—the keys—in 3 different forms, that we call _key selector_. | ||
```ts | ||
// Single key | ||
invalidateKeys("/api/whoAmI"); | ||
// Array of keys | ||
invalidateKeys(["/api/dashboard", "/api/projects"]); | ||
/** | ||
* A function that will be called against all keys in cache. | ||
* Must return `true` if key should be invalidated. | ||
*/ | ||
invalidateKeys((key) => key.startsWith("/api/job")); | ||
``` | ||
`mutateCache` does one thing only: it mutates cache for those keys and refreshes all fetcher stores that have those keys currently. | ||
```ts | ||
/** | ||
* Accepts key in the same form as `invalidateKeys`: single, array and a function. | ||
*/ | ||
mutateCache((key) => key === "/api/whoAmI", { title: "I'm Batman!" }); | ||
``` | ||
Keep in mind: we're talking about the serialized singular form of keys here. You cannot pass stuff like `['/api', '/v1', $someStore]`, it needs to be the full key in its string form. | ||
## Recipes | ||
@@ -199,1 +242,29 @@ | ||
not give up the flexibility of component-level data fetching. | ||
### Refetching and manual mutation | ||
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? | ||
For these cases we have 3 additional things on fetcher stores: | ||
1. `fetcherStore.invalidate`. It's a function that invalidates current key for the fetcher. Doesn't accept any arguments. | ||
2. `fetcherStore.mutate`. It's a function that mutates current key for the fetcher. Accepts the new value. | ||
3. `fetcherStore.key`. Well, it holds current key in serialized form (as a string). | ||
Typically, those 3 are more than enough to make all look very good. | ||
### Dependencies, but not in keys | ||
Let's say, you have a dependency for your fetcher, but you don't wish for it to be in your fetcher keys. For example, this could be your `refreshToken`—that would be a hassle to put it _everywhere_, but you need it, because once you change your user, you don't want to have stale cache from the previous user. | ||
The idea here is to wipe the cache manually. For something as big as a new refresh token you can go and do a simple "wipe everything you find": | ||
```ts | ||
onSet($refreshToken, () => invalidateKeys(() => true)) | ||
``` | ||
But if your store is somehow dependant on other store, but it shouldn't be reflected in the key, you should do the same, but more targetly: | ||
```ts | ||
onSet($someOutsideFactor, $specificStore.invalidate) | ||
``` |
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
33458
649
267