0
0
mirror of https://github.com/honojs/hono.git synced 2024-11-29 17:46:30 +01:00
hono/deno_dist/client/client.ts
hagishi 7ffb5b547e
feat(client): Inject common headers (#908)
* feat(client): Inject common headers

* feat: Accept a RequestOptions object as argument.

* refactor: Rename RequestOption to RequestOptions

* denoify
2023-02-20 08:22:01 +09:00

125 lines
3.4 KiB
TypeScript

import type { Hono } from '../hono.ts'
import type { ValidationTargets } from '../types.ts'
import type { UnionToIntersection } from '../utils/types.ts'
import type { Callback, Client, RequestOptions } from './types.ts'
import { replaceUrlParam, mergePath, removeIndexString, deepMerge } from './utils.ts'
const createProxy = (callback: Callback, path: string[]) => {
const proxy: unknown = new Proxy(() => {}, {
get(_obj, key) {
if (typeof key !== 'string') return undefined
return createProxy(callback, [...path, key])
},
apply(_1, _2, args) {
return callback({
path,
args,
})
},
})
return proxy
}
class ClientRequestImpl {
private url: string
private method: string
private queryParams: URLSearchParams | undefined = undefined
private pathParams: Record<string, string> = {}
private rBody: BodyInit | undefined
private cType: string | undefined = undefined
constructor(url: string, method: string) {
this.url = url
this.method = method
}
fetch = (
args?: ValidationTargets & {
param?: Record<string, string>
},
opt?: RequestOptions
) => {
if (args) {
if (args.query) {
for (const [k, v] of Object.entries(args.query)) {
this.queryParams ||= new URLSearchParams()
this.queryParams.set(k, v)
}
}
if (args.queries) {
for (const [k, v] of Object.entries(args.queries)) {
for (const v2 of v) {
this.queryParams ||= new URLSearchParams()
this.queryParams.append(k, v2)
}
}
}
if (args.form) {
const form = new FormData()
for (const [k, v] of Object.entries(args.form)) {
form.append(k, v)
}
this.rBody = form
}
if (args.json) {
this.rBody = JSON.stringify(args.json)
this.cType = 'application/json'
}
if (args.param) {
this.pathParams = args.param
}
}
let methodUpperCase = this.method.toUpperCase()
let setBody = !(methodUpperCase === 'GET' || methodUpperCase === 'HEAD')
const headerValues: Record<string, string> = opt?.headers ? opt.headers : {}
if (this.cType) headerValues['Content-Type'] = this.cType
const headers = new Headers(headerValues ?? undefined)
let url = this.url
url = removeIndexString(url)
url = replaceUrlParam(url, this.pathParams)
if (this.queryParams) {
url = url + '?' + this.queryParams.toString()
}
methodUpperCase = this.method.toUpperCase()
setBody = !(methodUpperCase === 'GET' || methodUpperCase === 'HEAD')
// Pass URL string to 1st arg for testing with MSW and node-fetch
return fetch(url, {
body: setBody ? this.rBody : undefined,
method: methodUpperCase,
headers: headers,
})
}
}
export const hc = <T extends Hono>(baseUrl: string, options?: RequestOptions) =>
createProxy(async (opts) => {
const parts = [...opts.path]
let method = ''
if (/^\$/.test(parts[parts.length - 1])) {
const last = parts.pop()
if (last) {
method = last.replace(/^\$/, '')
}
}
const path = parts.join('/')
const url = mergePath(baseUrl, path)
const req = new ClientRequestImpl(url, method)
if (method) {
options ??= {}
const args = deepMerge<RequestOptions>(options, { ...(opts.args[1] ?? {}) })
return req.fetch(opts.args[0], args)
}
return req
}, []) as UnionToIntersection<Client<T>>