import type { AxiosError, AxiosResponse } from 'axios' import { Api } from 'nocodb-sdk' import type { Ref } from 'vue' import type { CreateApiOptions, UseApiProps, UseApiReturn } from './types' import { addAxiosInterceptors } from './interceptors' import { BASE_URL } from '~/lib' import { createEventHook, ref, unref, useCounter, useGlobal, useNuxtApp } from '#imports' export function createApiInstance<SecurityDataType = any>({ baseURL = BASE_URL }: CreateApiOptions = {}): Api<SecurityDataType> { const { appInfo } = $(useGlobal()) return addAxiosInterceptors( new Api<SecurityDataType>({ baseURL: baseURL ?? appInfo.ncSiteUrl, }), ) } /** * Api composable that provides loading, error and response refs, as well as event hooks for error and response. * * You can use this composable to generate a fresh api instance with its own loading and error refs. * * Any request called by useApi will be pushed into the global requests counter which toggles the global loading state. * * @example * ```js * const { api, isLoading, error, response, onError, onResponse } = useApi() * * const onSignIn = async () => { * const { token } = await api.auth.signIn(form) * } */ export function useApi<Data = any, RequestConfig = any>({ useGlobalInstance = false, apiOptions, axiosConfig, }: UseApiProps<Data> = {}): UseApiReturn<Data, RequestConfig> { /** * Local state of running requests, do not confuse with global state of running requests * This state is only counting requests made by this instance of `useApi` and not by other instances. */ const { count, inc, dec } = useCounter(0) /** is request loading */ const isLoading = ref(false) /** latest request error */ const error = ref(null) /** latest request response */ const response = ref<unknown | null>(null) const errorHook = createEventHook<AxiosError<Data, RequestConfig>>() const responseHook = createEventHook<AxiosResponse<Data, RequestConfig>>() const nuxtApp = useNuxtApp() /** api instance - with interceptors for token refresh already bound */ const api = useGlobalInstance && !!nuxtApp.$api ? nuxtApp.$api : createApiInstance(apiOptions) /** set loading to true and increment local and global request counter */ function onRequestStart() { isLoading.value = true /** local count */ inc() /** global count */ nuxtApp.$state.runningRequests.inc() } /** decrement local and global request counter and check if we can stop loading */ function onRequestFinish() { /** local count */ dec() /** global count */ nuxtApp.$state.runningRequests.dec() /** try to stop loading */ stopLoading() } /** set loading state to false *only* if no request is still running */ function stopLoading() { if (count.value === 0) { isLoading.value = false } } /** reset response and error refs */ function reset() { error.value = null response.value = null } api.instance.interceptors.request.use( (config) => { reset() onRequestStart() return { ...config, ...unref(axiosConfig), } }, (requestError) => { errorHook.trigger(requestError) error.value = requestError response.value = null onRequestFinish() return Promise.reject(requestError) }, ) api.instance.interceptors.response.use( (apiResponse) => { responseHook.trigger(apiResponse as AxiosResponse<Data, RequestConfig>) response.value = apiResponse onRequestFinish() return Promise.resolve(apiResponse) }, (apiError) => { errorHook.trigger(apiError) error.value = apiError onRequestFinish() return Promise.reject(apiError) }, ) return { api, isLoading, response: response as Ref<AxiosResponse<Data, RequestConfig>>, error, onError: errorHook.on, onResponse: responseHook.on, } }