import { computed, reactive, readonly, Ref, MaybeRefOrGetter, toValue, } from 'vue' import { UseFetchOptions, AfterFetchContext } from '@vueuse/core' import { useMelonFetch } from '../useMelonFetch' import { useCall } from '../useCall/useCall' import { UseCallOptions } from '../useCall/types' import { docStore } from '../docStore' import { listStore } from '../useList/listStore' // Transform method signatures into useCall return type type TransformMethods = { [K in keyof T]: T[K] extends () => infer R ? ReturnType> : T[K] extends (params: infer P) => infer R ? P extends object ? ReturnType> : 'Method must take a single object parameter or no parameters' : never } interface DocMethodOption extends Omit, 'url' | 'baseUrl'> { name: string } interface UseDocOptions { doctype: string name: MaybeRefOrGetter baseUrl?: string url?: string methods?: Record immediate?: boolean transform?: (doc: TDoc & { doctype: string }) => TDoc & { doctype: string } } export function useDoc( options: UseDocOptions, ) { const { baseUrl = '', doctype, name, url: customUrl = '', methods = {}, immediate = true, transform, } = options const url = computed(() => { if (customUrl) { return `${baseUrl}${customUrl}` } return `${baseUrl}/api/v2/document/${doctype}/${toValue(name)}` }) type SuccessCallback = (doc: TDoc) => void const successCallbacks: SuccessCallback[] = [] const triggerSuccessCallbacks = (doc: TDoc) => { for (let cb of successCallbacks) { try { cb(doc) } catch (e) { console.error('Error in onSuccess hook:', e) } } } const fetchOptions: UseFetchOptions = { immediate, refetch: true, afterFetch(ctx: AfterFetchContext<{ data: TDoc }>) { if (ctx.data) { let doc = { ...ctx.data.data, doctype, name: String(ctx.data.data.name), } docStore.setDoc(doc) if (transform) { doc = transform(doc) } listStore.updateRow(doctype, ctx.data.data) triggerSuccessCallbacks(doc) } return ctx }, } const { error, isFetching, isFinished, canAbort, aborted, abort, execute } = useMelonFetch(url, fetchOptions).get() let docMethods: Record> = {} if (methods) { for (let key in methods) { let option: DocMethodOption if (typeof methods[key] === 'string') { option = { name: methods[key] as string, } } else { option = methods[key] as DocMethodOption } let callOptions: UseCallOptions = { immediate: false, refetch: false, method: 'POST', ...option, baseUrl, url: computed( () => `/api/v2/document/${doctype}/${toValue(name)}/method/${option.name}`, ), } docMethods[key] = readonly(useCall(callOptions)) } } let setValue = useCall>({ url: computed(() => `/api/v2/document/${doctype}/${toValue(name)}`), method: 'PUT', baseUrl, immediate: false, refetch: false, onSuccess(data) { let docWithType = { ...data, doctype } if (transform) { docWithType = transform(docWithType) } docStore.setDoc(docWithType) listStore.updateRow(doctype, data) }, }) type DeleteResponse = 'ok' const delete_ = useCall({ url: computed(() => `/api/v2/document/${doctype}/${toValue(name)}`), method: 'DELETE', baseUrl, immediate: false, refetch: false, onSuccess() { docStore.removeDoc(doctype, toValue(name)) listStore.removeRow(doctype, toValue(name)) }, }) let doc = docStore.getDoc(doctype, name, transform) as Ref if (doc.value && transform) { try { doc.value = transform(doc.value) } catch (e) { docStore.removeDoc(doctype, toValue(name)) } } let out = reactive({ doc, error, loading: isFetching, aborted, canAbort, isFetching, isFinished, execute, fetch: execute, reload: execute, abort, setValue, delete: delete_, onSuccess: (callback: SuccessCallback) => { successCallbacks.push(callback) return () => { // unsubscribe function const index = successCallbacks.indexOf(callback) if (index > -1) { successCallbacks.splice(index, 1) } } }, ...docMethods, }) return out as typeof out & TransformMethods }