UNPKG

svelte-simple-query

Version:

A simple yet powerful, lightweight data query library for Svelte 5, providing full control with built-in functionalities. Built with TypeScript for easy usage and strong typing.

252 lines (251 loc) 8.7 kB
const defaultFetcher = async (url) => { const res = await fetch(Query.baseURI + url, { ...Query.baseInit }); if (!res.ok) { const error = new Error('An error occurred while fetching the data.' // eslint-disable-next-line @typescript-eslint/no-explicit-any ); error.info = await res.json(); error.status = res.status; // throw error; } return res.json(); }; // Use QueryShape to define Query export const Query = { baseURI: '', baseInit: {}, fetcher: defaultFetcher, cacheTimeout: 2000, setup: (options) => { // Safely update Query // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error delete options.setup; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error delete options.bagHit; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error delete options.clear; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error delete options.clearGroup; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error delete options.group; Object.assign(Query, { ...Query, ...options }); // Safely update Query }, bagHit: {}, clear: (endpoint) => { if (endpoint) { CacheStore[endpoint] = null; state[endpoint].data = null; state[endpoint].isError = false; state[endpoint].isLoading = false; Query.bagHit[endpoint] = 0; return; } else { CacheStore = {}; Object.keys(state).forEach((key) => { state[key].data = null; state[key].isError = false; state[key].isLoading = false; Query.bagHit[key] = 0; }); return; } }, clearGroup: (group) => { Object.keys(state).forEach((key) => { if (state[key].group === group) { CacheStore[key] = null; state[key].data = null; state[key].isError = false; state[key].isLoading = false; Query.bagHit[key] = 0; } }); }, group: (group) => { return Object.keys(state) .filter((key) => { return state[key].group === group || state[key].groups?.includes(group); }) .map((key) => { return state[key]; }); } }; const state = $state({ system: {} }); let CacheStore = {}; export const useQuery = (endpoint, options) => { // if (!state[endpoint]) { state[endpoint] = { data: null, isError: false, isLoading: false, fetch: null, refetch: null, mutate: null, clear: null, endpoint, group: options?.group, groups: options?.groups }; } if (!state.system?.[endpoint]) { state.system[endpoint] = { onLoadingSlowTimeout: 0, shouldRetryWhenErrorTimeout: 0, shouldRetryWhenErrorAttempt: 0, disableLoading: false }; } // const TheQuery = { ...Query }; if (options) { TheQuery.setup(options); } const fetchData = async () => { try { if (TheQuery.onLoadingSlow) { clearTimeout(state.system[endpoint].onLoadingSlowTimeout); state.system[endpoint].onLoadingSlowTimeout = setTimeout(() => { if (state[endpoint].isLoading && TheQuery.onLoadingSlow) { TheQuery.onLoadingSlow(state[endpoint]); } }, TheQuery.loadingSlowTimeout ?? 30000); } if (CacheStore[endpoint]) { state[endpoint].data = CacheStore[endpoint].data; if (TheQuery.cacheTimeout !== -1 && new Date().getTime() - CacheStore[endpoint].time > TheQuery.cacheTimeout) { const json = await TheQuery.fetcher(endpoint); // CacheStore[endpoint] = { data: json, time: new Date().getTime() }; state[endpoint].data = json; state[endpoint].isError = false; if (TheQuery.onSuccess) TheQuery.onSuccess(state[endpoint]); } } else { if (!state.system[endpoint].disableLoading) { state[endpoint].isLoading = true; } const json = await TheQuery.fetcher(endpoint); CacheStore[endpoint] = { data: json, time: new Date().getTime() }; state[endpoint].data = json; state[endpoint].isError = false; if (TheQuery.onSuccess) TheQuery.onSuccess(state[endpoint]); } // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error) { CacheStore[endpoint] = null; state[endpoint].isError = error; state[endpoint].data = null; if (TheQuery.onError) TheQuery.onError(state[endpoint], error); if (TheQuery.shouldRetryWhenError) { clearTimeout(state.system[endpoint].shouldRetryWhenErrorTimeout); state.system[endpoint].shouldRetryWhenErrorTimeout = setTimeout(() => { if (state.system[endpoint].shouldRetryWhenErrorAttempt >= (TheQuery.retryCount ?? 5)) { state.system[endpoint].disableLoading = false; return; } state.system[endpoint].shouldRetryWhenErrorAttempt++; state[endpoint].refetch({ disableLoading: true }); }, TheQuery.retryDelay && TheQuery.retryDelay >= 1000 ? TheQuery.retryDelay : 10000); } } finally { state[endpoint].isLoading = false; } }; // if (!state[endpoint].fetch) { state[endpoint].fetch = async () => { Query.bagHit[endpoint] = (Query.bagHit[endpoint] || 0) + 1; if (Query.bagHit[endpoint] > 1) return; await fetchData(); Query.bagHit[endpoint] = 0; }; } if (!state[endpoint].refetch) { state[endpoint].refetch = async (opt) => { if (opt && opt.disableLoading) { state.system[endpoint].disableLoading = opt.disableLoading; } else { state.system[endpoint].disableLoading = false; } CacheStore[endpoint] = null; await fetchData(); }; } if (!state[endpoint].mutate) { state[endpoint].mutate = async (options) => { await mutate(endpoint, options); }; } if (!state[endpoint].clear) { state[endpoint].clear = () => { CacheStore[endpoint] = null; state[endpoint].data = null; state[endpoint].isError = false; state[endpoint].isLoading = false; Query.bagHit[endpoint] = 0; }; } // return state[endpoint]; }; export const useSingleQuery = (endpointCallBack, options) => { return new Proxy({}, { get: (_, key) => { // Clone the options each time to avoid mutation problems const clonedOptions = options ? { ...options } : undefined; return useQuery(endpointCallBack(key.toString()), clonedOptions); } }); }; export const mutate = async (endpoint, options) => { const defaultOptions = { refetch: options?.data || options?.populateCache ? false : true }; options = { ...defaultOptions, ...options }; // let data = options.data; if (options.populateCache) { data = options.populateCache(state[endpoint].data); } if (data !== undefined) { CacheStore[endpoint] = { data, time: new Date().getTime() }; state[endpoint].data = data; } if (options.refetch) { await state[endpoint].refetch(); } }; // ## To Do // autoRefetchWhenOnline => boolean => check cache first // autoRefetchWhenFocus => boolean => check cache first // Feature Web Socket