UNPKG

@hazae41/glacier

Version:

Yet another React data (re)fetching library

293 lines (290 loc) 11.2 kB
import { None } from '@hazae41/option'; import { Err, Ok } from '@hazae41/result'; import { Arrays } from '../../../../libs/arrays/arrays.mjs'; import { useRenderRef } from '../../../../libs/react/ref.mjs'; import { shouldUseCacheIfFresh, shouldUseCacheIfStale, shouldUseNetwork } from '../../../../libs/request/index.mjs'; import { AbortSignals } from '../../../../libs/signals/index.mjs'; import { Time } from '../../../../libs/time/time.mjs'; import { MissingKeyError, core, MissingFetcherError } from '../../../core/core.mjs'; import { Scrollable } from '../../../queries/scroll/helper.mjs'; import { useMemo, useState, useRef, useCallback, useEffect } from 'react'; function useScrollableQuery(factory, deps) { const query = useMemo(() => { return factory(...deps); }, deps); if (query == null) return useSkeletonScrollableQuery(); if (query.settings.fetcher == null) return useFetcherlessScrollableQuery(query.settings); return useFetcherfulScrollableQuery(query.settings); } function useSkeletonScrollableQuery() { useRenderRef(undefined); const cacheKey = useMemo(() => { // NOOP }, [undefined]); useState(0); useRef(); useRef(); useMemo(() => { // NOOP }, [cacheKey]); useCallback(() => { // NOOP }, [cacheKey]); useCallback(() => { // NOOP }, [cacheKey]); useEffect(() => { // NOOP }, [cacheKey]); useEffect(() => { // NOOP }, [cacheKey]); const mutateOrThrow = useCallback(async (mutator) => { throw new MissingKeyError(); }, [cacheKey]); const deleteOrThrow = useCallback(async () => { throw new MissingKeyError(); }, [cacheKey]); const fetchOrThrow = useCallback(async (init) => { throw new MissingKeyError(); }, [cacheKey]); const refetchOrThrow = useCallback(async (init) => { throw new MissingKeyError(); }, [cacheKey]); const scrollOrThrow = useCallback(async (init) => { throw new MissingKeyError(); }, [cacheKey]); const peekOrNull = useCallback(() => { return undefined; }, [undefined, undefined]); return { mutateOrThrow, deleteOrThrow, fetchOrThrow, refetchOrThrow, scrollOrThrow, peekOrNull }; } /** * Scroll query * @param scroller * @param fetcher * @param settings * @returns */ function useFetcherlessScrollableQuery(settings) { const settingsRef = useRenderRef(settings); const cacheKey = useMemo(() => { return Scrollable.getCacheKey(settings.key); }, [settings.key]); const [, setCounter] = useState(0); const stateRef = useRef(); const aborterRef = useRef(); useMemo(() => { stateRef.current = core.getStateSync(cacheKey); aborterRef.current = core.getAborterSync(cacheKey); }, [cacheKey]); const setState = useCallback((state) => { stateRef.current = state; setCounter(c => c + 1); }, [cacheKey]); const setAborter = useCallback((aborter) => { aborterRef.current = aborter; setCounter(c => c + 1); }, [cacheKey]); useEffect(() => { if (stateRef.current != null) return; core.getOrThrow(cacheKey, settingsRef.current).then(setState).catch(console.warn); }, [cacheKey]); useEffect(() => { const onState = () => { core.getOrThrow(cacheKey, settingsRef.current).then(setState).catch(console.warn); return new None(); }; const onAborter = () => { setAborter(core.getAborterSync(cacheKey)); return new None(); }; core.onState.on(cacheKey, onState, { passive: true }); core.onAborter.on(cacheKey, onAborter, { passive: true }); core.increment(cacheKey, settingsRef.current); return () => { core.decrementOrThrow(cacheKey, settingsRef.current); core.onState.off(cacheKey, onState); core.onAborter.off(cacheKey, onAborter); }; }, [cacheKey]); const mutateOrThrow = useCallback(async (mutator) => { return await core.mutateOrThrow(cacheKey, mutator, settingsRef.current); }, [cacheKey]); const deleteOrThrow = useCallback(async () => { return await core.deleteOrThrow(cacheKey, settingsRef.current); }, [cacheKey]); const fetchOrThrow = useCallback(async (aborter = new AbortController()) => { throw new MissingFetcherError(); }, [cacheKey]); const refetchOrThrow = useCallback(async (aborter = new AbortController()) => { throw new MissingFetcherError(); }, [cacheKey]); const scrollOrThrow = useCallback(async (aborter = new AbortController()) => { throw new MissingFetcherError(); }, [cacheKey]); const state = stateRef.current; const aborter = aborterRef.current; const ready = state != null; const fetching = aborter != null; const optimistic = state?.isFake(); const current = state?.current; const data = state?.data; const error = state?.error; const real = state?.real; const fake = state?.fake; const peekOrNull = useCallback(() => { const pages = state?.real?.data?.get(); if (pages == null) return undefined; return settings.scroller(Arrays.last(pages)); }, [state?.real?.data, settings.scroller]); return { ...settings, cacheKey, current, data, error, real, fake, ready, optimistic, aborter, fetching, mutateOrThrow, fetchOrThrow, refetchOrThrow, scrollOrThrow, deleteOrThrow, peekOrNull, }; } function useFetcherfulScrollableQuery(settings) { const settingsRef = useRenderRef(settings); const cacheKey = useMemo(() => { return Scrollable.getCacheKey(settings.key); }, [settings.key]); const [, setCounter] = useState(0); const stateRef = useRef(); const aborterRef = useRef(); useMemo(() => { stateRef.current = core.getStateSync(cacheKey); aborterRef.current = core.getAborterSync(cacheKey); }, [cacheKey]); const setState = useCallback((state) => { stateRef.current = state; setCounter(c => c + 1); }, [cacheKey]); const setAborter = useCallback((aborter) => { aborterRef.current = aborter; setCounter(c => c + 1); }, [cacheKey]); useEffect(() => { if (stateRef.current != null) return; core.getOrThrow(cacheKey, settingsRef.current).then(setState).catch(console.warn); }, [cacheKey]); useEffect(() => { const onState = () => { core.getOrThrow(cacheKey, settingsRef.current).then(setState).catch(console.warn); return new None(); }; const onAborter = () => { setAborter(core.getAborterSync(cacheKey)); return new None(); }; core.onState.on(cacheKey, onState, { passive: true }); core.onAborter.on(cacheKey, onAborter, { passive: true }); core.increment(cacheKey, settingsRef.current); return () => { core.decrementOrThrow(cacheKey, settingsRef.current); core.onState.off(cacheKey, onState); core.onAborter.off(cacheKey, onAborter); }; }, [cacheKey]); const mutateOrThrow = useCallback(async (mutator) => { return await core.mutateOrThrow(cacheKey, mutator, settingsRef.current); }, [cacheKey]); const deleteOrThrow = useCallback(async () => { return await core.deleteOrThrow(cacheKey, settingsRef.current); }, [cacheKey]); const fetchOrThrow = useCallback(async (init) => { const state = stateRef.current; const settings = settingsRef.current; if (shouldUseCacheIfFresh(init?.cache) && Time.isAfterNow(state?.real?.current.cooldown)) return new Err(state); if (shouldUseCacheIfStale(init?.cache) && Time.isAfterNow(state?.real?.current.expiration)) return new Err(state); if (!shouldUseNetwork(init?.cache)) throw new Error(`Could not fetch using the provided cache directive`); const aborter = new AbortController(); const signal = AbortSignal.any([aborter.signal, AbortSignals.getOrNever(init?.signal)]); return new Ok(await core.runOrJoin(cacheKey, aborter, () => Scrollable.fetchOrThrow(cacheKey, signal, settings))); }, [cacheKey]); const refetchOrThrow = useCallback(async (init) => { const state = stateRef.current; const settings = settingsRef.current; if (shouldUseCacheIfFresh(init?.cache) && Time.isAfterNow(state?.real?.current.cooldown)) return new Err(state); if (shouldUseCacheIfStale(init?.cache) && Time.isAfterNow(state?.real?.current.expiration)) return new Err(state); if (!shouldUseNetwork(init?.cache)) throw new Error(`Could not fetch using the provided cache directive`); const aborter = new AbortController(); const signal = AbortSignal.any([aborter.signal, AbortSignals.getOrNever(init?.signal)]); return new Ok(await core.runOrReplace(cacheKey, aborter, () => Scrollable.fetchOrThrow(cacheKey, signal, settings))); }, [cacheKey]); const scrollOrThrow = useCallback(async (init) => { const state = stateRef.current; const settings = settingsRef.current; if (shouldUseCacheIfFresh(init?.cache) && Time.isAfterNow(state?.real?.current.cooldown)) return new Err(state); if (shouldUseCacheIfStale(init?.cache) && Time.isAfterNow(state?.real?.current.expiration)) return new Err(state); if (!shouldUseNetwork(init?.cache)) throw new Error(`Could not fetch using the provided cache directive`); const aborter = new AbortController(); const signal = AbortSignal.any([aborter.signal, AbortSignals.getOrNever(init?.signal)]); return new Ok(await core.runOrReplace(cacheKey, aborter, () => Scrollable.scrollOrThrow(cacheKey, signal, settings))); }, [cacheKey]); const state = stateRef.current; const aborter = aborterRef.current; const ready = state != null; const fetching = aborter != null; const optimistic = state?.isFake(); const current = state?.current; const data = state?.data; const error = state?.error; const real = state?.real; const fake = state?.fake; const peekOrNull = useCallback(() => { const pages = state?.real?.data?.get(); if (pages == null) return undefined; return settings.scroller(Arrays.last(pages)); }, [state?.real?.data, settings.scroller]); return { ...settings, cacheKey, current, data, error, real, fake, ready, optimistic, aborter, fetching, mutateOrThrow, fetchOrThrow, refetchOrThrow, scrollOrThrow, deleteOrThrow, peekOrNull }; } export { useFetcherfulScrollableQuery, useFetcherlessScrollableQuery, useScrollableQuery, useSkeletonScrollableQuery }; //# sourceMappingURL=scroll.mjs.map