UNPKG

@apparently/query

Version:

Simple and small data fetching library for SolidJS. Inspired by SWR.

310 lines (284 loc) 8.35 kB
import { isServer as isServer$1, createComponent } from 'solid-js/web'; import { getListener, createSignal, DEV, createContext, useContext, createResource, onMount, onCleanup } from 'solid-js'; var isServer = isServer$1; var isClient = !isServer; var isDev = DEV && isClient; var createTrigger = isDev ? () => createSignal(void 0, { equals: false, name: "trigger" }) : () => createSignal(void 0, { equals: false }); function dirtyTriggerCache(key) { const trigger = this.get(key); if (trigger) trigger[1](); } function dirtyAllTriggerCache() { this.forEach((s) => s[1]()); } function trackTriggerCache(key) { if (!getListener()) return; let trigger = this.get(key); if (!trigger) { trigger = createTrigger(); this.set(key, trigger); } trigger[0](); } function createTriggerCache() { const cache = /* @__PURE__ */ new Map(); return { dirty: dirtyTriggerCache.bind(cache), dirtyAll: dirtyAllTriggerCache.bind(cache), track: trackTriggerCache.bind(cache) }; } // src/index.ts var KEYS = Symbol("track-keys"); var ReactiveMap = class { constructor(initial, equals = (a, b) => a === b) { this.equals = equals; this.map = new Map(initial); } map; cache = createTriggerCache(); has(key) { this.cache.track(key); return this.map.has(key); } get(key) { this.cache.track(key); return this.map.get(key); } get size() { this.cache.track(KEYS); return this.map.size; } keys() { this.cache.track(KEYS); return this.map.keys(); } values() { this.cache.track(KEYS); return this.map.values(); } entries() { this.cache.track(KEYS); return this.map.entries(); } set(key, value) { if (this.map.has(key)) { const oldV = this.map.get(key); if (this.equals(oldV, value)) { this.map.set(key, value); return this; } } this.map.set(key, value); this.cache.dirty(key); this.cache.dirty(KEYS); return this; } delete(key) { const r = this.map.delete(key); if (r) { this.cache.dirty(key); this.cache.dirty(KEYS); } return r; } clear() { if (this.map.size) { this.map.clear(); this.cache.dirtyAll(); } } forEach(callbackfn) { this.cache.track(KEYS); this.map.forEach((value, key) => callbackfn(value, key, this)); } [Symbol.iterator]() { return this.entries(); } get [Symbol.toStringTag]() { return this.map[Symbol.toStringTag]; } }; Object.setPrototypeOf(ReactiveMap.prototype, Map.prototype); var ReactiveWeakMap = class { constructor(initial, equals = (a, b) => a === b) { this.equals = equals; this.map = new WeakMap(initial); } map; cache = createTriggerCache(); has(key) { this.cache.track(key); return this.map.has(key); } get(key) { this.cache.track(key); return this.map.get(key); } set(key, value) { if (this.map.has(key)) { const oldV = this.map.get(key); if (this.equals(oldV, value)) { this.map.set(key, value); return this; } } this.map.set(key, value); this.cache.dirty(key); return this; } delete(key) { const r = this.map.delete(key); if (r) this.cache.dirty(key); return r; } get [Symbol.toStringTag]() { return this.map[Symbol.toStringTag]; } }; Object.setPrototypeOf(ReactiveWeakMap.prototype, WeakMap.prototype); const o=new WeakMap;let l=0;function i(t){const c=typeof t,s=t&&t.constructor,f=s==Date;let e,n;if(Object(t)===t&&!f&&s!=RegExp){if(e=o.get(t),e)return e;if(e=++l+"~",o.set(t,e),s==Array){for(e="@",n=0;n<t.length;n++)e+=i(t[n])+",";o.set(t,e);}if(s==Object){e="#";const y=Object.keys(t).sort();for(;typeof(n=y.pop())!="undefined";)typeof t[n]!="undefined"&&(e+=n+":"+i(t[n])+",");o.set(t,e);}}else e=f?t.toJSON():c=="symbol"?t.toString():c=="string"?JSON.stringify(t):""+t;return e} const QueryContext = createContext(); class ReactiveMapWithStableHash extends ReactiveMap { set(key, value) { if (i(super.get(key)) === i(value)) { return this; } return super.set(key, value); } } // TODO: // - deduplikace // - infiniteQuery (v postupu) // - error retry // - isRefething // - refetch on reconnect function useQueryContext() { return useContext(QueryContext); } const QueryConfig = props => { const cache = new ReactiveMapWithStableHash(); // Tady možná je trochu overheat, že has atd je reaktivní. return createComponent(QueryContext.Provider, { get value() { return { fetcher: props.fetcher, cache }; }, get children() { return props.children; } }); }; function useCache() { const { cache } = useQueryContext(); return cache; } function useMutate() { const { fetcher, cache } = useQueryContext(); return async (key, data) => { cache.set(key, data ? data : await fetcher(key)); }; } function useQuery(getKey, initialValue) { const { fetcher, cache } = useQueryContext(); const globalMutate = useMutate(); const mutate = data => globalMutate(getKey(), data); const refetch = () => mutate(); const cacheStorage = initialValue => { const key = getKey(); if (!cache.has(key) && initialValue) { cache.set(key, initialValue); } return [() => cache.get(key), newValue => { cache.set(key, newValue()); return cache.get(key); }]; }; const cacheFetcher = async key => { if (cache.has(key)) { (async () => cache.set(key, await fetcher(key)))(); return cache.get(key); } return await fetcher(key); }; // @ts-ignore // TODO: opravit type problem. const [resource] = createResource(getKey, cacheFetcher, { storage: cacheStorage, initialValue }); if (typeof window !== "undefined") { onMount(() => window.addEventListener("focus", refetch)); onCleanup(() => window.removeEventListener("focus", refetch)); } return [resource, { mutate }]; } // TODO: caching, suspense, klasický nekonečný scroll (bez deloadingu), vyčistit kód... //type UseInfiniteQueryGetKey<T> = (pageIndex: number, previousPageData: T | null) => string; //type UseInfiniteQuery<T> = [InitializedResource<T[][]>, { setSize: Setter<number>, size: Accessor<number>, loadingNextPage: Accessor<boolean> }]; // export function useInfiniteQuery<T>(getKey: UseInfiniteQueryGetKey<T>, initialValue?: T[]) { // const { fetcher, cache } = useQueryContext(); // const [size, setSize] = createSignal(0); // const [loadingPage, setLoadingPage] = createSignal(false); // const [data, setData] = createSignal<T[]>([]); // let max = 0; // onMount(async () => setData(await fetcher(getKey(0, null)))); // createEffect(() => { // if (data().length > max) { // max = data().length; // } // const _size = untrack(size); // console.log("Max loaded sofar:", max, "Size:", _size); // }); // async function forward() { // if (loadingPage()) { // return; // } // setLoadingPage(true); // let _data = untrack(data); // const newSize = size() + 1; // const fetchedData = await fetcher(getKey(newSize, null)); // let newData = [..._data, ...fetchedData]; // if (newSize % 4 == 0) { // newData = newData.slice(newData.length - 200, newData.length); // } // setData(newData); // setSize(newSize); // setLoadingPage(false); // } // async function back() { // if (loadingPage()) { // return; // } // if (size() <= 3) { // if (size() != 0) { // setSize(0); // } // return; // } // setLoadingPage(true); // let _data = untrack(data); // const newSize = size() - 1; // const fetchedData = await fetcher(getKey(newSize - 3, null)); // let newData = [...fetchedData, ..._data]; // if (newSize % 4 == 0) { // newData = newData.slice(0, newData.length - 200); // } // setData(newData); // setSize(newSize); // setLoadingPage(false); // } // return [data, { back, forward }]; // } export { QueryConfig, useCache, useMutate, useQuery, useQueryContext };