@hazae41/glacier
Version:
Yet another React data (re)fetching library
293 lines (290 loc) • 11.2 kB
JavaScript
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