UNPKG

nuxt

Version:

Nuxt is a free and open-source framework with an intuitive and extendable way to create type-safe, performant and production-grade full-stack web applications and websites with Vue.js.

133 lines (132 loc) 4.6 kB
import { computed, reactive, toValue, watch } from "vue"; import { hash } from "ohash"; import { isPlainObject } from "@vue/shared"; import { useRequestFetch } from "./ssr.js"; import { useAsyncData } from "./asyncData.js"; import { alwaysRunFetchOnKeyChange, fetchDefaults } from "#build/nuxt.config.mjs"; export function useFetch(request, arg1, arg2) { const [opts = {}, autoKey] = typeof arg1 === "string" ? [{}, arg1] : [arg1, arg2]; const _request = computed(() => toValue(request)); const key = computed(() => toValue(opts.key) || "$f" + hash([autoKey, typeof _request.value === "string" ? _request.value : "", ...generateOptionSegments(opts)])); if (!opts.baseURL && typeof _request.value === "string" && (_request.value[0] === "/" && _request.value[1] === "/")) { throw new Error('[nuxt] [useFetch] the request URL must not start with "//".'); } const { server, lazy, default: defaultFn, transform, pick, watch: watchSources, immediate, getCachedData, deep, dedupe, ...fetchOptions } = opts; const _fetchOptions = reactive({ ...fetchDefaults, ...fetchOptions, cache: typeof opts.cache === "boolean" ? void 0 : opts.cache }); const _asyncDataOptions = { server, lazy, default: defaultFn, transform, pick, immediate, getCachedData, deep, dedupe, watch: watchSources === false ? [] : [...watchSources || [], _fetchOptions] }; if (import.meta.dev) { _asyncDataOptions._functionName ||= "useFetch"; } if (alwaysRunFetchOnKeyChange && !immediate) { let setImmediate = function() { _asyncDataOptions.immediate = true; }; watch(key, setImmediate, { flush: "sync", once: true }); watch([...watchSources || [], _fetchOptions], setImmediate, { flush: "sync", once: true }); } let controller; const asyncData = useAsyncData(watchSources === false ? key.value : key, () => { controller?.abort?.(new DOMException("Request aborted as another request to the same endpoint was initiated.", "AbortError")); controller = typeof AbortController !== "undefined" ? new AbortController() : {}; const timeoutLength = toValue(opts.timeout); let timeoutId; if (timeoutLength) { timeoutId = setTimeout(() => controller.abort(new DOMException("Request aborted due to timeout.", "AbortError")), timeoutLength); controller.signal.onabort = () => clearTimeout(timeoutId); } let _$fetch = opts.$fetch || globalThis.$fetch; if (import.meta.server && !opts.$fetch) { const isLocalFetch = typeof _request.value === "string" && _request.value[0] === "/" && (!toValue(opts.baseURL) || toValue(opts.baseURL)[0] === "/"); if (isLocalFetch) { _$fetch = useRequestFetch(); } } return _$fetch(_request.value, { signal: controller.signal, ..._fetchOptions }).finally(() => { clearTimeout(timeoutId); }); }, _asyncDataOptions); return asyncData; } export function useLazyFetch(request, arg1, arg2) { const [opts = {}, autoKey] = typeof arg1 === "string" ? [{}, arg1] : [arg1, arg2]; if (import.meta.dev) { opts._functionName ||= "useLazyFetch"; } return useFetch( request, { ...opts, lazy: true }, // @ts-expect-error we pass an extra argument with the resolved auto-key to prevent another from being injected autoKey ); } function generateOptionSegments(opts) { const segments = [ toValue(opts.method)?.toUpperCase() || "GET", toValue(opts.baseURL) ]; for (const _obj of [opts.params || opts.query]) { const obj = toValue(_obj); if (!obj) { continue; } const unwrapped = {}; for (const [key, value] of Object.entries(obj)) { unwrapped[toValue(key)] = toValue(value); } segments.push(unwrapped); } if (opts.body) { const value = toValue(opts.body); if (!value) { segments.push(hash(value)); } else if (value instanceof ArrayBuffer) { segments.push(hash(Object.fromEntries([...new Uint8Array(value).entries()].map(([k, v]) => [k, v.toString()])))); } else if (value instanceof FormData) { const obj = {}; for (const entry of value.entries()) { const [key, val] = entry; obj[key] = val instanceof File ? val.name : val; } segments.push(hash(obj)); } else if (isPlainObject(value)) { segments.push(hash(reactive(value))); } else { try { segments.push(hash(value)); } catch { console.warn("[useFetch] Failed to hash body", value); } } } return segments; }