Skip to content

Commit

Permalink
feat: useDoc
Browse files Browse the repository at this point in the history
  • Loading branch information
netchampfaris committed Dec 22, 2024
1 parent 579bcb6 commit 3e16e77
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 70 deletions.
68 changes: 0 additions & 68 deletions src/data-fetching/useDoc.ts

This file was deleted.

83 changes: 83 additions & 0 deletions src/data-fetching/useDoc/useDoc.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* @vitest-environment node
*/
import { ref } from 'vue'
import { baseUrl, waitUntilValueChanges } from '../../mocks/utils'
import { useCall, useDoc } from '../index'

describe('useList', () => {
it('it returns expected object', async () => {
interface User {
name: string
email: string
first_name: string
last_name: string
}

let user = useDoc<User>({
baseUrl,
doctype: 'User',
name: 'user1',
})

await waitUntilValueChanges(() => user.loading, true)

// Verify initial state
expect(user.doc).toBe(null)
expect(user.error).toBe(null)
expect(typeof user.fetch).toBe('function')
expect(typeof user.reload).toBe('function')

await waitUntilValueChanges(() => user.loading, false)

// Verify final state
expect(user.doc).toStrictEqual({
name: 'user1',
email: '[email protected]',
first_name: 'User',
last_name: '1',
})
expect(user.error).toBe(null)
expect(user.loading).toBe(false)
})

it('it returns expected object with methods', async () => {
interface User {
name: string
email: string
first_name: string
last_name: string
}

interface UserMethods {
getFullName: () => string
updateEmail: (params: { email: string }) => void
}

const onSuccess = vi.fn()
let user = useDoc<User, UserMethods>({
baseUrl,
doctype: 'User',
name: 'user1',
methods: {
getFullName: 'get_full_name',
updateEmail: {
name: 'update_email',
onSuccess,
},
},
})

await waitUntilValueChanges(() => user.loading, false)

console.log(user.doc)

expect(user.getFullName).toBeDefined()
expect(user.updateEmail).toBeDefined()

user.getFullName.submit()

const newEmail = '[email protected]'
user.updateEmail.submit({ email: newEmail })
})
})
116 changes: 116 additions & 0 deletions src/data-fetching/useDoc/useDoc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { computed, MaybeRef, reactive, readonly, unref } from 'vue'
import { UseFetchOptions } from '@vueuse/core'
import { useFrappeFetch } from '../useFrappeFetch'
import { useCall } from '../useCall/useCall'
import { UseCallOptions } from '../useCall/types'

// Transform method signatures into useCall return type
type TransformMethods<T> = {
[K in keyof T]: T[K] extends () => infer R
? ReturnType<typeof useCall<R>>
: T[K] extends (params: infer P) => infer R
? P extends object
? ReturnType<typeof useCall<R, P>>
: 'Method must take a single object parameter or no parameters'
: never
}

interface DocMethodOption<T = any>
extends Omit<UseCallOptions<T>, 'url' | 'baseUrl'> {
name: string
}

interface UseDocOptions<TMethods = {}> {
doctype: string
name: string | MaybeRef<string>
baseUrl?: string
methods?: Record<string, string | DocMethodOption>
immediate?: boolean
}

interface DocTypeMeta {
name: string
fields: any[]
permissions: any
whitelisted_methods: Array<{
method: string
class: string
app: string
http_methods: Array<'GET' | 'POST' | 'PUT' | 'DELETE'>
}>
}

export function useDoc<TDoc, TMethods = {}>(options: UseDocOptions<TMethods>) {
const {
baseUrl = '',
doctype,
name,
methods = {},
immediate = true,
} = options

const url = computed(() => {
let _name = unref(name)
return `${baseUrl}/api/v2/document/${doctype}/${_name}`
})

const fetchOptions: UseFetchOptions = {
immediate,
refetch: true,
}

const {
data,
error,
isFetching,
isFinished,
canAbort,
aborted,
abort,
execute,
} = useFrappeFetch<TDoc>(url, fetchOptions).get()

let docMethods: Record<string, ReturnType<typeof useCall>> = {}
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: true,
...option,
baseUrl,
url: computed(
() =>
`/api/v2/document/${doctype}/${unref(name)}/method/${option.name}`,
),
}

docMethods[key] = readonly(useCall(callOptions))
}
}

let out = reactive({
doc: data,
error,
loading: isFetching,
aborted,
canAbort,
isFetching,
isFinished,
execute,
fetch: execute,
reload: execute,
abort,
...docMethods,
})

return out as typeof out & TransformMethods<TMethods>
}
11 changes: 11 additions & 0 deletions src/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ export const handlers = [
},
})
}),

http.get(url('/api/v2/document/User/user1'), async () => {
return HttpResponse.json({
data: {
name: 'user1',
email: '[email protected]',
first_name: 'User',
last_name: '1',
},
})
}),
]

function getUsers(listParams) {
Expand Down
9 changes: 7 additions & 2 deletions src/mocks/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ export let url = (path: string) => new URL(path, baseUrl).toString()

export function waitUntilValueChanges(
getter: () => any,
matchValue?: any,
timeout = 1000,
): Promise<void> {
return new Promise((resolve) => {
let stop = watch(getter, () => {
stop()
let stop = watch(getter, (val) => {
if (matchValue !== undefined && matchValue === val) {
stop()
} else {
stop()
}
resolve()
})
setTimeout(() => {
Expand Down

0 comments on commit 3e16e77

Please sign in to comment.