@apparently/query
Version:
Simple and small data fetching library for SolidJS. Inspired by SWR.
310 lines (284 loc) • 8.35 kB
JavaScript
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 };