swr-vue
Version:
Vue composables for Data fetching
787 lines (786 loc) • 24.4 kB
JavaScript
import { isRef, reactive, unref, ref, watch, getCurrentScope, onScopeDispose, getCurrentInstance, onMounted, nextTick, computed, toRefs, inject, provide, shallowReadonly, onUnmounted, customRef } from "vue";
var _a;
const isClient = typeof window !== "undefined";
const isString = (val) => typeof val === "string";
const noop = () => {
};
isClient && ((_a = window == null ? void 0 : window.navigator) == null ? void 0 : _a.userAgent) && /iP(ad|hone|od)/.test(window.navigator.userAgent);
function resolveUnref(r) {
return typeof r === "function" ? r() : unref(r);
}
function identity(arg) {
return arg;
}
function tryOnScopeDispose(fn) {
if (getCurrentScope()) {
onScopeDispose(fn);
return true;
}
return false;
}
function toReactive(objectRef) {
if (!isRef(objectRef))
return reactive(objectRef);
const proxy = new Proxy({}, {
get(_, p, receiver) {
return unref(Reflect.get(objectRef.value, p, receiver));
},
set(_, p, value) {
if (isRef(objectRef.value[p]) && !isRef(value))
objectRef.value[p].value = value;
else
objectRef.value[p] = value;
return true;
},
deleteProperty(_, p) {
return Reflect.deleteProperty(objectRef.value, p);
},
has(_, p) {
return Reflect.has(objectRef.value, p);
},
ownKeys() {
return Object.keys(objectRef.value);
},
getOwnPropertyDescriptor() {
return {
enumerable: true,
configurable: true
};
}
});
return reactive(proxy);
}
function tryOnMounted(fn, sync = true) {
if (getCurrentInstance())
onMounted(fn);
else if (sync)
fn();
else
nextTick(fn);
}
function useIntervalFn(cb, interval = 1e3, options = {}) {
const {
immediate = true,
immediateCallback = false
} = options;
let timer = null;
const isActive = ref(false);
function clean() {
if (timer) {
clearInterval(timer);
timer = null;
}
}
function pause() {
isActive.value = false;
clean();
}
function resume() {
if (unref(interval) <= 0)
return;
isActive.value = true;
if (immediateCallback)
cb();
clean();
timer = setInterval(cb, resolveUnref(interval));
}
if (immediate && isClient)
resume();
if (isRef(interval)) {
const stopWatch = watch(interval, () => {
if (isActive.value && isClient)
resume();
});
tryOnScopeDispose(stopWatch);
}
tryOnScopeDispose(pause);
return {
isActive,
pause,
resume
};
}
function whenever(source, cb, options) {
return watch(source, (v, ov, onInvalidate) => {
if (v)
cb(v, ov, onInvalidate);
}, options);
}
const createUnrefFn = (fn) => {
return function(...args) {
return fn.apply(this, args.map((i) => unref(i)));
};
};
function unrefElement(elRef) {
var _a2;
const plain = resolveUnref(elRef);
return (_a2 = plain == null ? void 0 : plain.$el) != null ? _a2 : plain;
}
const defaultWindow = isClient ? window : void 0;
isClient ? window.document : void 0;
isClient ? window.navigator : void 0;
isClient ? window.location : void 0;
function useEventListener(...args) {
let target;
let event;
let listener;
let options;
if (isString(args[0])) {
[event, listener, options] = args;
target = defaultWindow;
} else {
[target, event, listener, options] = args;
}
if (!target)
return noop;
let cleanup = noop;
const stopWatch = watch(() => unrefElement(target), (el) => {
cleanup();
if (!el)
return;
el.addEventListener(event, listener, options);
cleanup = () => {
el.removeEventListener(event, listener, options);
cleanup = noop;
};
}, { immediate: true, flush: "post" });
const stop = () => {
stopWatch();
cleanup();
};
tryOnScopeDispose(stop);
return stop;
}
function useSupported(callback, sync = false) {
const isSupported = ref();
const update = () => isSupported.value = Boolean(callback());
update();
tryOnMounted(update, sync);
return isSupported;
}
const _global = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
const globalKey = "__vueuse_ssr_handlers__";
_global[globalKey] = _global[globalKey] || {};
_global[globalKey];
function useNetwork(options = {}) {
const { window: window2 = defaultWindow } = options;
const navigator2 = window2 == null ? void 0 : window2.navigator;
const isSupported = useSupported(() => navigator2 && "connection" in navigator2);
const isOnline = ref(true);
const saveData = ref(false);
const offlineAt = ref(void 0);
const onlineAt = ref(void 0);
const downlink = ref(void 0);
const downlinkMax = ref(void 0);
const rtt = ref(void 0);
const effectiveType = ref(void 0);
const type = ref("unknown");
const connection = isSupported.value && navigator2.connection;
function updateNetworkInformation() {
if (!navigator2)
return;
isOnline.value = navigator2.onLine;
offlineAt.value = isOnline.value ? void 0 : Date.now();
onlineAt.value = isOnline.value ? Date.now() : void 0;
if (connection) {
downlink.value = connection.downlink;
downlinkMax.value = connection.downlinkMax;
effectiveType.value = connection.effectiveType;
rtt.value = connection.rtt;
saveData.value = connection.saveData;
type.value = connection.type;
}
}
if (window2) {
useEventListener(window2, "offline", () => {
isOnline.value = false;
offlineAt.value = Date.now();
});
useEventListener(window2, "online", () => {
isOnline.value = true;
onlineAt.value = Date.now();
});
}
if (connection)
useEventListener(connection, "change", updateNetworkInformation, false);
updateNetworkInformation();
return {
isSupported,
isOnline,
saveData,
offlineAt,
onlineAt,
downlink,
downlinkMax,
effectiveType,
rtt,
type
};
}
var SwipeDirection;
(function(SwipeDirection2) {
SwipeDirection2["UP"] = "UP";
SwipeDirection2["RIGHT"] = "RIGHT";
SwipeDirection2["DOWN"] = "DOWN";
SwipeDirection2["LEFT"] = "LEFT";
SwipeDirection2["NONE"] = "NONE";
})(SwipeDirection || (SwipeDirection = {}));
var __defProp = Object.defineProperty;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
const _TransitionPresets = {
easeInSine: [0.12, 0, 0.39, 0],
easeOutSine: [0.61, 1, 0.88, 1],
easeInOutSine: [0.37, 0, 0.63, 1],
easeInQuad: [0.11, 0, 0.5, 0],
easeOutQuad: [0.5, 1, 0.89, 1],
easeInOutQuad: [0.45, 0, 0.55, 1],
easeInCubic: [0.32, 0, 0.67, 0],
easeOutCubic: [0.33, 1, 0.68, 1],
easeInOutCubic: [0.65, 0, 0.35, 1],
easeInQuart: [0.5, 0, 0.75, 0],
easeOutQuart: [0.25, 1, 0.5, 1],
easeInOutQuart: [0.76, 0, 0.24, 1],
easeInQuint: [0.64, 0, 0.78, 0],
easeOutQuint: [0.22, 1, 0.36, 1],
easeInOutQuint: [0.83, 0, 0.17, 1],
easeInExpo: [0.7, 0, 0.84, 0],
easeOutExpo: [0.16, 1, 0.3, 1],
easeInOutExpo: [0.87, 0, 0.13, 1],
easeInCirc: [0.55, 0, 1, 0.45],
easeOutCirc: [0, 0.55, 0.45, 1],
easeInOutCirc: [0.85, 0, 0.15, 1],
easeInBack: [0.36, 0, 0.66, -0.56],
easeOutBack: [0.34, 1.56, 0.64, 1],
easeInOutBack: [0.68, -0.6, 0.32, 1.6]
};
__spreadValues({
linear: identity
}, _TransitionPresets);
function useWindowFocus({ window: window2 = defaultWindow } = {}) {
if (!window2)
return ref(false);
const focused = ref(window2.document.hasFocus());
useEventListener(window2, "blur", () => {
focused.value = false;
});
useEventListener(window2, "focus", () => {
focused.value = true;
});
return focused;
}
const isFunction = (value) => typeof value === "function";
const isUndefined = (value) => typeof value === "undefined";
const table = /* @__PURE__ */ new WeakMap();
let counter = 0;
const stableHash = createUnrefFn((arg) => {
const type = typeof arg;
const constructor = arg && arg.constructor;
const isDate = constructor === Date;
let result;
let index;
if (Object(arg) === arg && !isDate && constructor !== RegExp) {
result = table.get(arg);
if (result)
return result;
counter += 1;
result = `${counter}~`;
table.set(arg, result);
if (constructor === Array) {
result = "@";
for (index = 0; index < arg.length; index += 1) {
result += `${stableHash(arg[index])},`;
}
table.set(arg, result);
}
if (constructor === Object) {
const keys = Object.keys(arg).sort();
result = "#";
index = keys.pop();
while (!isUndefined(index)) {
if (!isUndefined(arg[index])) {
result += `${index}:${stableHash(arg[index])},`;
}
index = keys.pop();
}
table.set(arg, result);
}
} else {
result = isDate ? arg.toJSON() : type === "symbol" ? arg.toString() : type === "string" ? JSON.stringify(arg) : `${arg}`;
}
return result;
});
const serializeKey = createUnrefFn((key) => {
let sanitizedKey = key;
if (isFunction(sanitizedKey)) {
try {
sanitizedKey = sanitizedKey();
} catch {
sanitizedKey = "";
}
}
const isEmptyArray = Array.isArray(sanitizedKey) && sanitizedKey.length === 0;
return {
key: !isEmptyArray && !!sanitizedKey ? stableHash(sanitizedKey) : "",
args: Array.isArray(sanitizedKey) ? sanitizedKey : [sanitizedKey]
};
});
const chainFns = (...fns) => {
const validFns = fns.filter((maybeFn) => !!maybeFn);
return (...params) => validFns.forEach((fn) => fn(...params));
};
const mergeConfig = (fallbackConfig, config) => {
const onSuccess = [config.onSuccess, fallbackConfig.onSuccess].filter(Boolean);
const onError = [config.onError, fallbackConfig.onError].filter(Boolean);
return {
...fallbackConfig,
...config,
onSuccess: onSuccess.length > 0 ? chainFns(...onSuccess) : void 0,
onError: onError.length > 0 ? chainFns(...onError) : void 0
};
};
const unsubscribeCallback = (key, cb, cbCache) => {
const callbacks = cbCache.get(key) || [];
const newCallbacks = callbacks.filter((currentCb) => currentCb !== cb);
cbCache.set(key, newCallbacks);
};
const subscribeCallback = (key, cb, cbCache) => {
const callbacks = cbCache.get(key) || [];
cbCache.set(key, [...callbacks, cb]);
return () => unsubscribeCallback(key, cb, cbCache);
};
const MapAdapter = Map;
const defaultConfig = {
cacheProvider: reactive(new MapAdapter()),
revalidateOnFocus: true,
revalidateOnReconnect: true,
revalidateIfStale: true,
focusThrottleInterval: 5e3,
dedupingInterval: 2e3,
refreshInterval: 0,
refreshWhenHidden: false,
refreshWhenOffline: false
};
const globalConfigKey = Symbol("SWR global config key");
const globalState = reactive(/* @__PURE__ */ new WeakMap());
const initScopeState = (cacheProvider) => {
globalState.set(cacheProvider, { revalidateCache: /* @__PURE__ */ new Map() });
};
const useScopeState = (_cacheProvider) => {
const cacheProvider = computed(() => unref(_cacheProvider));
const scopeState = computed(() => globalState.get(cacheProvider.value));
const onScopeStateChange = () => {
if (!scopeState.value)
initScopeState(cacheProvider.value);
};
watch(scopeState, onScopeStateChange, { immediate: true });
return {
scopeState,
...toRefs(toReactive(scopeState))
};
};
const createCacheState = (data) => ({
data,
error: void 0,
isValidating: false,
fetchedIn: new Date()
});
const useSWRConfig = () => {
const contextConfig = inject(
globalConfigKey,
computed(() => defaultConfig)
);
const cacheProvider = computed(() => contextConfig.value.cacheProvider);
const { revalidateCache } = useScopeState(cacheProvider);
const mutate = async (_key, updateFnOrPromise, options = {}) => {
const { key } = serializeKey(_key);
const cache = cacheProvider.value;
const cacheState = cache.get(key);
const hasCache = !isUndefined(cacheState);
const { optimisticData, rollbackOnError, revalidate = true } = options;
const { data } = hasCache ? toRefs(cacheState) : { data: ref() };
const dataInCache = data.value;
const resultPromise = isFunction(updateFnOrPromise) ? updateFnOrPromise(dataInCache) : updateFnOrPromise;
if (optimisticData) {
data.value = optimisticData;
}
try {
data.value = isUndefined(resultPromise) ? data.value : await resultPromise;
} catch (error) {
if (rollbackOnError) {
data.value = dataInCache;
}
throw error;
}
cache.set(key, hasCache ? cacheState : createCacheState(data));
const revalidationCallbackcs = revalidateCache.value.get(key) || [];
if (revalidate && revalidationCallbackcs.length) {
const [firstRevalidateCallback] = revalidationCallbackcs;
await firstRevalidateCallback();
}
return data.value;
};
return {
config: contextConfig,
mutate
};
};
const configureGlobalSWR = (config) => {
const { config: contextConfig } = useSWRConfig();
const mergedConfig = computed(() => mergeConfig(contextConfig.value, unref(config)));
provide(globalConfigKey, shallowReadonly(mergedConfig));
};
const useGlobalSWRConfig = () => {
const { config, ...rest } = useSWRConfig();
return {
...rest,
globalConfig: config
};
};
const setStateToCache = (key, cache, state) => {
cache.set(key, {
data: void 0,
error: void 0,
fetchedIn: new Date(),
isValidating: false,
...state
});
};
const refCached = (initialValue, { cacheProvider, stateKey, key }) => {
const cacheState = computed(() => cacheProvider.get(unref(key)));
return customRef((track, trigger) => ({
get() {
var _a2, _b;
track();
return (_b = (_a2 = cacheState.value) == null ? void 0 : _a2[stateKey]) != null ? _b : initialValue;
},
set(newValue) {
setStateToCache(unref(key), cacheProvider, {
...cacheState.value,
[stateKey]: newValue
});
trigger();
}
}));
};
const getFromFallback = createUnrefFn((key, fallback) => {
if (!fallback)
return void 0;
const findedKey = Object.keys(fallback).find((_key) => serializeKey(_key).key === key);
return findedKey && fallback[findedKey];
});
const useSWR = (_key, fetcher, config = {}) => {
const { config: contextConfig, mutate } = useSWRConfig();
const { revalidateCache } = useScopeState(contextConfig.value.cacheProvider);
const { isOnline } = useNetwork();
const isWindowFocused = useWindowFocus();
const mergedConfig = mergeConfig(contextConfig.value, config);
const {
cacheProvider,
revalidateOnFocus,
revalidateOnReconnect,
revalidateIfStale,
dedupingInterval,
fallback,
fallbackData,
focusThrottleInterval,
refreshInterval,
refreshWhenHidden,
refreshWhenOffline,
onSuccess,
onError
} = mergedConfig;
const { key, args: fetcherArgs } = toRefs(toReactive(computed(() => serializeKey(_key))));
const fallbackValue = isUndefined(fallbackData) ? getFromFallback(key, fallback) : fallbackData;
const valueInCache = computed(() => cacheProvider.get(key.value));
const hasCachedValue = computed(() => !!valueInCache.value);
const data = refCached(fallbackValue, { cacheProvider, stateKey: "data", key });
const error = refCached(void 0, { cacheProvider, stateKey: "error", key });
const isValidating = refCached(true, { cacheProvider, stateKey: "isValidating", key });
const fetchedIn = refCached(new Date(), { cacheProvider, stateKey: "fetchedIn", key });
const fetchData = async (opts = { dedup: true }) => {
var _a2;
const timestampToDedupExpire = (((_a2 = fetchedIn.value) == null ? void 0 : _a2.getTime()) || 0) + dedupingInterval;
const hasNotExpired = timestampToDedupExpire > Date.now();
if (opts.dedup && hasCachedValue.value && (hasNotExpired || isValidating.value && dedupingInterval !== 0))
return;
isValidating.value = true;
try {
const fetcherResponse = await fetcher.apply(fetcher, fetcherArgs.value);
data.value = fetcherResponse;
fetchedIn.value = new Date();
if (onSuccess)
onSuccess(data.value, key.value, mergedConfig);
} catch (err) {
error.value = err;
if (onError)
onError(err, key.value, mergedConfig);
} finally {
isValidating.value = false;
}
};
let unsubRevalidateCb;
const onRefresh = () => {
const shouldSkipRefreshOffline = !refreshWhenOffline && !isOnline.value;
const shouldSkipRefreshHidden = !refreshWhenHidden && document.visibilityState === "hidden";
if (shouldSkipRefreshOffline || shouldSkipRefreshHidden)
return;
fetchData();
};
const onWindowFocus = () => {
var _a2;
const fetchedInTimestamp = ((_a2 = fetchedIn.value) == null ? void 0 : _a2.getTime()) || 0;
if (fetchedInTimestamp + focusThrottleInterval > Date.now())
return;
fetchData();
};
const onRevalidate = async () => {
if (!key.value) {
return;
}
await fetchData({ dedup: false });
};
const onKeyChange = (newKey, oldKey) => {
if (!!newKey && newKey !== oldKey && (revalidateIfStale || !data.value)) {
fetchData();
}
unsubRevalidateCb == null ? void 0 : unsubRevalidateCb();
subscribeCallback(newKey, onRevalidate, revalidateCache.value);
};
if (refreshInterval) {
useIntervalFn(onRefresh, refreshInterval);
}
whenever(
() => revalidateOnFocus && (revalidateIfStale || !data.value) && isWindowFocused.value,
() => onWindowFocus()
);
whenever(
() => revalidateOnReconnect && (revalidateIfStale || !data.value) && isOnline.value,
() => fetchData()
);
watch(key, onKeyChange, { immediate: true });
onUnmounted(() => unsubRevalidateCb == null ? void 0 : unsubRevalidateCb());
if (!hasCachedValue.value) {
setStateToCache(key.value, cacheProvider, {
error: error.value,
data: data.value,
isValidating: isValidating.value,
fetchedIn: fetchedIn.value
});
}
return {
data: shallowReadonly(data),
error: shallowReadonly(error),
isValidating: shallowReadonly(isValidating),
mutate: (...params) => mutate(unref(_key), ...params)
};
};
function getDevtoolsGlobalHook() {
return getTarget().__VUE_DEVTOOLS_GLOBAL_HOOK__;
}
function getTarget() {
return typeof navigator !== "undefined" && typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {};
}
const isProxyAvailable = typeof Proxy === "function";
const HOOK_SETUP = "devtools-plugin:setup";
const HOOK_PLUGIN_SETTINGS_SET = "plugin:settings:set";
let supported;
let perf;
function isPerformanceSupported() {
var _a2;
if (supported !== void 0) {
return supported;
}
if (typeof window !== "undefined" && window.performance) {
supported = true;
perf = window.performance;
} else if (typeof global !== "undefined" && ((_a2 = global.perf_hooks) === null || _a2 === void 0 ? void 0 : _a2.performance)) {
supported = true;
perf = global.perf_hooks.performance;
} else {
supported = false;
}
return supported;
}
function now() {
return isPerformanceSupported() ? perf.now() : Date.now();
}
class ApiProxy {
constructor(plugin, hook) {
this.target = null;
this.targetQueue = [];
this.onQueue = [];
this.plugin = plugin;
this.hook = hook;
const defaultSettings = {};
if (plugin.settings) {
for (const id in plugin.settings) {
const item = plugin.settings[id];
defaultSettings[id] = item.defaultValue;
}
}
const localSettingsSaveId = `__vue-devtools-plugin-settings__${plugin.id}`;
let currentSettings = Object.assign({}, defaultSettings);
try {
const raw = localStorage.getItem(localSettingsSaveId);
const data = JSON.parse(raw);
Object.assign(currentSettings, data);
} catch (e) {
}
this.fallbacks = {
getSettings() {
return currentSettings;
},
setSettings(value) {
try {
localStorage.setItem(localSettingsSaveId, JSON.stringify(value));
} catch (e) {
}
currentSettings = value;
},
now() {
return now();
}
};
if (hook) {
hook.on(HOOK_PLUGIN_SETTINGS_SET, (pluginId, value) => {
if (pluginId === this.plugin.id) {
this.fallbacks.setSettings(value);
}
});
}
this.proxiedOn = new Proxy({}, {
get: (_target, prop) => {
if (this.target) {
return this.target.on[prop];
} else {
return (...args) => {
this.onQueue.push({
method: prop,
args
});
};
}
}
});
this.proxiedTarget = new Proxy({}, {
get: (_target, prop) => {
if (this.target) {
return this.target[prop];
} else if (prop === "on") {
return this.proxiedOn;
} else if (Object.keys(this.fallbacks).includes(prop)) {
return (...args) => {
this.targetQueue.push({
method: prop,
args,
resolve: () => {
}
});
return this.fallbacks[prop](...args);
};
} else {
return (...args) => {
return new Promise((resolve) => {
this.targetQueue.push({
method: prop,
args,
resolve
});
});
};
}
}
});
}
async setRealTarget(target) {
this.target = target;
for (const item of this.onQueue) {
this.target.on[item.method](...item.args);
}
for (const item of this.targetQueue) {
item.resolve(await this.target[item.method](...item.args));
}
}
}
function setupDevtoolsPlugin(pluginDescriptor, setupFn) {
const descriptor = pluginDescriptor;
const target = getTarget();
const hook = getDevtoolsGlobalHook();
const enableProxy = isProxyAvailable && descriptor.enableEarlyProxy;
if (hook && (target.__VUE_DEVTOOLS_PLUGIN_API_AVAILABLE__ || !enableProxy)) {
hook.emit(HOOK_SETUP, pluginDescriptor, setupFn);
} else {
const proxy = enableProxy ? new ApiProxy(descriptor, hook) : null;
const list = target.__VUE_DEVTOOLS_PLUGINS__ = target.__VUE_DEVTOOLS_PLUGINS__ || [];
list.push({
pluginDescriptor: descriptor,
setupFn,
proxy
});
if (proxy)
setupFn(proxy.proxiedTarget);
}
}
const inspectorId = "swr-vue-inspector";
function setupDevtools(app) {
const setupPluginSettings = {
id: "swr-vue-devtools-plugin",
label: "Stale-While-Revalidate Vue",
packageName: "swr-vue",
homepage: "https://edumudu.github.io/swr-vue/",
app
};
const isSwrInspector = (id) => id === inspectorId;
setupDevtoolsPlugin(setupPluginSettings, (api) => {
const handleGetInspectorTree = (payload) => {
if (!isSwrInspector(inspectorId))
return;
payload.rootNodes = [
{
id: "root",
label: "Global scope"
}
];
};
const handleGetInspectorState = (payload) => {
if (!isSwrInspector(inspectorId) || payload.nodeId !== "root")
return;
const entries = Array.from(defaultConfig.cacheProvider.entries(), ([key, value]) => ({
key,
value
}));
payload.state = {
"Global cache": entries
};
};
api.addInspector({
id: inspectorId,
label: "SWR",
icon: "archive"
});
api.on.getInspectorTree(handleGetInspectorTree);
api.on.getInspectorState(handleGetInspectorState);
watch(defaultConfig.cacheProvider, () => api.sendInspectorState(inspectorId), { deep: true });
});
}
export {
configureGlobalSWR,
setupDevtools,
useGlobalSWRConfig,
useSWR,
useSWRConfig
};