import type { AsyncData, NuxtError, UseFetchOptions } from 'nuxt/app' import type { ComputedRef } from 'vue' type KeysOf = Array /** * `status` is `idle` during SSR (the request is client-only) and flips to * `pending` as soon as the client picks it up, which would make any * `status === 'pending'` template branch mismatch on hydration. `loading` * covers both so server and client agree on the first render. */ type AdminAsyncData = AsyncData | undefined> & { loading: ComputedRef } const cache = new Map() /** * `useFetch` wrapper for the /admin surface. Requests are client-only * (`server: false`): the admin session lives in an httpOnly cookie the * browser holds, which a server-side render does not forward to * `/api/admin/*`, so an SSR'd request 401s and hydrates as empty state. * Navigation is never blocked (`lazy: true`), and successful responses are * cached client-side for the lifetime of the app so returning to a page renders instantly from cache. * A cache hit still triggers a background refresh once the app is idle, so * stale data heals itself without blocking the UI. */ export function useAdminFetch ( url: string, options: UseFetchOptions, T> & { default: () => T }, ): AdminAsyncData export function useAdminFetch ( url: string, options?: UseFetchOptions, undefined>, ): AdminAsyncData export function useAdminFetch ( url: string, options: UseFetchOptions = {}, ): AdminAsyncData { const key = `admin:${url}` const hasCached = import.meta.client && cache.has(key) const result = useFetch(url, { key, lazy: true, server: false, getCachedData: () => (import.meta.client ? cache.get(key) as T | undefined : undefined), ...options, } as UseFetchOptions) as AsyncData | undefined> if (import.meta.client) { watch([result.data, result.status], ([value, status]) => { if (status === 'success') cache.set(key, value) }, { immediate: true }) watch(result.error, error => { if (isUnauthorised(error)) redirectToLogin() }, { immediate: true }) if (hasCached) { onNuxtReady(() => result.refresh()) } } return Object.assign(result, { loading: computed(() => result.status.value === 'idle' || result.status.value === 'pending'), }) }