feat(dx): [WIP] auto-generate correctly typed client for any API route 🎉

This commit is contained in:
upsiflu 2025-02-11 20:38:01 +01:00
parent 01ed8cfb12
commit 30493ade84
2 changed files with 94 additions and 0 deletions

View File

@ -9,6 +9,7 @@ import { humanSize, truncate } from '~/utils/filters'
import { useI18n } from 'vue-i18n'
import { sortBy } from 'lodash-es'
import { useStore } from '~/store'
import { useClient } from '~/ui/composables/useClient'
import axios from 'axios'
@ -41,6 +42,7 @@ const props = withDefaults(defineProps<Props>(), {
const { t } = useI18n()
const store = useStore()
const { get, post } = useClient('libraries')
const upload = ref()
const currentTab = ref('uploads')
@ -80,6 +82,23 @@ const options = {
const privacyLevel = defineModel<keyof typeof options>({ required: true })
// New implementation with `useClient`:
watch(privacyLevel, (newValue) =>
get({
query: {
privacy_level: newValue,
scope: 'me'
}
})
.then((data) =>
library.value = data?.results.find(({name}) => name === privacyLevel.value)
),
{ immediate: true }
)
// Old implementation:
/*
const library = ref<Library>()
watch(privacyLevel, async(newValue) => { try {
const response = await axios.get<paths['/api/v2/libraries/']['get']['responses']['200']['content']['application/json']>('libraries/', {
@ -93,6 +112,7 @@ watch(privacyLevel, async(newValue) => { try {
} catch (error) {
useErrorHandler(error as Error)
}}, { immediate: true })
*/
//
// File counts

View File

@ -0,0 +1,74 @@
import { type paths } from '~/generated/types.ts'
import { type Simplify } from 'type-fest'
import axios from 'axios'
import useErrorHandler from '~/composables/useErrorHandler'
const prefix = "/api/v2/" as const;
type Prefix = typeof prefix;
type RemovePrefixAndSuffix<T extends string> =
T extends `${Prefix}${infer Infix}/` ? Infix : never
type Path = RemovePrefixAndSuffix<Simplify<keyof paths>>
type Get<TPath extends Path> = paths[`${Prefix}${TPath}/`]['get']
type Post<TPath extends Path> = paths[`${Prefix}${TPath}/`]['post']
type GetRequestParameters<TPath extends Path> =
Get<TPath> extends { parameters: any }
? Get<TPath>['parameters']
: never
/**
* 200
*/
type OK<TPath extends Path> =
Get<TPath> extends { responses: { ['200'] : { content: { ['application/json']: any } } } }
? Get<TPath>['responses']['200']['content']['application/json']
: never
type PostRequestJson<TPath extends Path> =
Post<TPath> extends { requestBody: { content: { ['application/json']: any } } }
? Post<TPath>['requestBody']['content']['application/json']
: never
/**
* 201
*/
type Created<TPath extends Path> =
Post<TPath> extends { responses: { ['201']: { content: { ['application/json']: any } } } }
? Post<TPath>['responses']['201']['content']['application/json']
: never
/**
*
* Returns `get` and `post` clients for the chosen path (endpoint).
```ts
const { get, post } = useClient('manage/tags');
const result0 = post({ name: 'test' })
const result1 = get({ query: { q: 'test' } })
```
*
* @param path The path to create a client for. Check the `paths` type in '~/generated/types.ts' to find all available paths.
*/
export const useClient = <TPath extends Path>( path: TPath ) => ({
get: async (parameters: GetRequestParameters<TPath>) =>
await axios.get<OK<TPath>>(`${prefix}${path}/`, {
params: parameters.query || parameters
})
.then(({ data }) => { return data })
.catch (useErrorHandler),
post: async (requestBody: PostRequestJson<TPath> ) =>
await axios.post<Created<TPath>>(`${prefix}${path}/`, {
params: requestBody
})
.then(({ data }) => { return data })
.catch (useErrorHandler)
// TODO: Add put, patch, delete, head, options, trace
})