UNPKG

swr-vue

Version:

Vue composables for Data fetching

787 lines (786 loc) 24.4 kB
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 };