Skip to content

Commit

Permalink
feat: typed api method handler data param
Browse files Browse the repository at this point in the history
  • Loading branch information
johannschopplich committed Sep 3, 2023
1 parent d81adb1 commit a99216f
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 23 deletions.
16 changes: 5 additions & 11 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import { ofetch } from 'ofetch'
import { joinURL } from 'ufo'
import type { QueryObject } from 'ufo'
import type { FetchOptions } from 'ofetch'
import type { ApiBuilder, ApiMethodHandler, ResponseType } from './types'
import { mergeFetchOptions } from './utils'

export type { ApiBuilder }

const payloadMethods = [
'POST',
'PUT',
'DELETE',
'PATCH',
] as const
const payloadMethods = ['POST', 'PUT', 'DELETE', 'PATCH']

/**
* Minimal, type-safe REST client using JS proxies
Expand All @@ -32,12 +26,12 @@ export function createClient<R extends ResponseType = 'json'>(
return p(joinURL(url, key))

const handler: ApiMethodHandler = <T = any, R extends ResponseType = 'json'>(
data?: RequestInit['body'] | Record<string, any>,
data?: any,
opts: FetchOptions<R> = {},
) => {
if (method === 'GET' && data)
opts.query = data as QueryObject
else if (payloadMethods.includes(method as typeof payloadMethods[number]))
if (method === 'GET')
opts.query = data
else if (payloadMethods.includes(method))
opts.body = data

opts.method = method
Expand Down
24 changes: 15 additions & 9 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,26 @@ export interface ResponseMap {
}

export type ResponseType = keyof ResponseMap | 'json'
export type MappedType<R extends ResponseType, JsonType = any> = R extends keyof ResponseMap ? ResponseMap[R] : JsonType
export type MappedType<
R extends ResponseType,
JsonType = any,
> = R extends keyof ResponseMap ? ResponseMap[R] : JsonType

export type ApiMethodHandler = <T = any, R extends ResponseType = 'json'>(
data?: RequestInit['body'] | Record<string, any>,
opts?: Omit<FetchOptions<R>, 'baseURL' | 'method'>
export type ApiMethodHandler<Data = never> = <
T = any,
R extends ResponseType = 'json',
>(
data?: Data,
opts?: Omit<FetchOptions<R>, 'baseURL' | 'method'>,
) => Promise<MappedType<R, T>>

export type ApiBuilder = {
[key: string]: ApiBuilder
(...segmentsOrIds: (string | number)[]): ApiBuilder
} & {
get: ApiMethodHandler
post: ApiMethodHandler
put: ApiMethodHandler
delete: ApiMethodHandler
patch: ApiMethodHandler
get: ApiMethodHandler<FetchOptions['query']>
post: ApiMethodHandler<FetchOptions['body']>
put: ApiMethodHandler<FetchOptions['body']>
delete: ApiMethodHandler<FetchOptions['body']>
patch: ApiMethodHandler<FetchOptions['body']>
}
13 changes: 10 additions & 3 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,20 @@ describe('unrested', () => {
})

it('default options', async () => {
const { headers } = await client.bar.post()
expect(headers).to.include({ 'x-foo': 'bar' })
const { headers } = await client.bar.post(undefined, {
headers: {
'Content-Type': 'application/json',
},
})
expect(headers).to.include({
'x-foo': 'bar',
'content-type': 'application/json',
})
})

it('override default options', async () => {
const { headers } = await client.bar.post(
{},
undefined,
{ headers: { 'X-Foo': 'baz' } },
)
expect(headers).to.include({ 'x-foo': 'baz' })
Expand Down

0 comments on commit a99216f

Please sign in to comment.