278
packages/sdk/js/src/v2/gen/client/client.gen.ts
Normal file
278
packages/sdk/js/src/v2/gen/client/client.gen.ts
Normal file
@@ -0,0 +1,278 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
import { createSseClient } from "../core/serverSentEvents.gen.js"
|
||||
import type { HttpMethod } from "../core/types.gen.js"
|
||||
import { getValidRequestBody } from "../core/utils.gen.js"
|
||||
import type { Client, Config, RequestOptions, ResolvedRequestOptions } from "./types.gen.js"
|
||||
import {
|
||||
buildUrl,
|
||||
createConfig,
|
||||
createInterceptors,
|
||||
getParseAs,
|
||||
mergeConfigs,
|
||||
mergeHeaders,
|
||||
setAuthParams,
|
||||
} from "./utils.gen.js"
|
||||
|
||||
type ReqInit = Omit<RequestInit, "body" | "headers"> & {
|
||||
body?: any
|
||||
headers: ReturnType<typeof mergeHeaders>
|
||||
}
|
||||
|
||||
export const createClient = (config: Config = {}): Client => {
|
||||
let _config = mergeConfigs(createConfig(), config)
|
||||
|
||||
const getConfig = (): Config => ({ ..._config })
|
||||
|
||||
const setConfig = (config: Config): Config => {
|
||||
_config = mergeConfigs(_config, config)
|
||||
return getConfig()
|
||||
}
|
||||
|
||||
const interceptors = createInterceptors<Request, Response, unknown, ResolvedRequestOptions>()
|
||||
|
||||
const beforeRequest = async (options: RequestOptions) => {
|
||||
const opts = {
|
||||
..._config,
|
||||
...options,
|
||||
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
|
||||
headers: mergeHeaders(_config.headers, options.headers),
|
||||
serializedBody: undefined,
|
||||
}
|
||||
|
||||
if (opts.security) {
|
||||
await setAuthParams({
|
||||
...opts,
|
||||
security: opts.security,
|
||||
})
|
||||
}
|
||||
|
||||
if (opts.requestValidator) {
|
||||
await opts.requestValidator(opts)
|
||||
}
|
||||
|
||||
if (opts.body !== undefined && opts.bodySerializer) {
|
||||
opts.serializedBody = opts.bodySerializer(opts.body)
|
||||
}
|
||||
|
||||
// remove Content-Type header if body is empty to avoid sending invalid requests
|
||||
if (opts.body === undefined || opts.serializedBody === "") {
|
||||
opts.headers.delete("Content-Type")
|
||||
}
|
||||
|
||||
const url = buildUrl(opts)
|
||||
|
||||
return { opts, url }
|
||||
}
|
||||
|
||||
const request: Client["request"] = async (options) => {
|
||||
// @ts-expect-error
|
||||
const { opts, url } = await beforeRequest(options)
|
||||
const requestInit: ReqInit = {
|
||||
redirect: "follow",
|
||||
...opts,
|
||||
body: getValidRequestBody(opts),
|
||||
}
|
||||
|
||||
let request = new Request(url, requestInit)
|
||||
|
||||
for (const fn of interceptors.request.fns) {
|
||||
if (fn) {
|
||||
request = await fn(request, opts)
|
||||
}
|
||||
}
|
||||
|
||||
// fetch must be assigned here, otherwise it would throw the error:
|
||||
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
|
||||
const _fetch = opts.fetch!
|
||||
let response: Response
|
||||
|
||||
try {
|
||||
response = await _fetch(request)
|
||||
} catch (error) {
|
||||
// Handle fetch exceptions (AbortError, network errors, etc.)
|
||||
let finalError = error
|
||||
|
||||
for (const fn of interceptors.error.fns) {
|
||||
if (fn) {
|
||||
finalError = (await fn(error, undefined as any, request, opts)) as unknown
|
||||
}
|
||||
}
|
||||
|
||||
finalError = finalError || ({} as unknown)
|
||||
|
||||
if (opts.throwOnError) {
|
||||
throw finalError
|
||||
}
|
||||
|
||||
// Return error response
|
||||
return opts.responseStyle === "data"
|
||||
? undefined
|
||||
: {
|
||||
error: finalError,
|
||||
request,
|
||||
response: undefined as any,
|
||||
}
|
||||
}
|
||||
|
||||
for (const fn of interceptors.response.fns) {
|
||||
if (fn) {
|
||||
response = await fn(response, request, opts)
|
||||
}
|
||||
}
|
||||
|
||||
const result = {
|
||||
request,
|
||||
response,
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
const parseAs =
|
||||
(opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json"
|
||||
|
||||
if (response.status === 204 || response.headers.get("Content-Length") === "0") {
|
||||
let emptyData: any
|
||||
switch (parseAs) {
|
||||
case "arrayBuffer":
|
||||
case "blob":
|
||||
case "text":
|
||||
emptyData = await response[parseAs]()
|
||||
break
|
||||
case "formData":
|
||||
emptyData = new FormData()
|
||||
break
|
||||
case "stream":
|
||||
emptyData = response.body
|
||||
break
|
||||
case "json":
|
||||
default:
|
||||
emptyData = {}
|
||||
break
|
||||
}
|
||||
return opts.responseStyle === "data"
|
||||
? emptyData
|
||||
: {
|
||||
data: emptyData,
|
||||
...result,
|
||||
}
|
||||
}
|
||||
|
||||
let data: any
|
||||
switch (parseAs) {
|
||||
case "arrayBuffer":
|
||||
case "blob":
|
||||
case "formData":
|
||||
case "json":
|
||||
case "text":
|
||||
data = await response[parseAs]()
|
||||
break
|
||||
case "stream":
|
||||
return opts.responseStyle === "data"
|
||||
? response.body
|
||||
: {
|
||||
data: response.body,
|
||||
...result,
|
||||
}
|
||||
}
|
||||
|
||||
if (parseAs === "json") {
|
||||
if (opts.responseValidator) {
|
||||
await opts.responseValidator(data)
|
||||
}
|
||||
|
||||
if (opts.responseTransformer) {
|
||||
data = await opts.responseTransformer(data)
|
||||
}
|
||||
}
|
||||
|
||||
return opts.responseStyle === "data"
|
||||
? data
|
||||
: {
|
||||
data,
|
||||
...result,
|
||||
}
|
||||
}
|
||||
|
||||
const textError = await response.text()
|
||||
let jsonError: unknown
|
||||
|
||||
try {
|
||||
jsonError = JSON.parse(textError)
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
|
||||
const error = jsonError ?? textError
|
||||
let finalError = error
|
||||
|
||||
for (const fn of interceptors.error.fns) {
|
||||
if (fn) {
|
||||
finalError = (await fn(error, response, request, opts)) as string
|
||||
}
|
||||
}
|
||||
|
||||
finalError = finalError || ({} as string)
|
||||
|
||||
if (opts.throwOnError) {
|
||||
throw finalError
|
||||
}
|
||||
|
||||
// TODO: we probably want to return error and improve types
|
||||
return opts.responseStyle === "data"
|
||||
? undefined
|
||||
: {
|
||||
error: finalError,
|
||||
...result,
|
||||
}
|
||||
}
|
||||
|
||||
const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) => request({ ...options, method })
|
||||
|
||||
const makeSseFn = (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => {
|
||||
const { opts, url } = await beforeRequest(options)
|
||||
return createSseClient({
|
||||
...opts,
|
||||
body: opts.body as BodyInit | null | undefined,
|
||||
headers: opts.headers as unknown as Record<string, string>,
|
||||
method,
|
||||
onRequest: async (url, init) => {
|
||||
let request = new Request(url, init)
|
||||
for (const fn of interceptors.request.fns) {
|
||||
if (fn) {
|
||||
request = await fn(request, opts)
|
||||
}
|
||||
}
|
||||
return request
|
||||
},
|
||||
url,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
buildUrl,
|
||||
connect: makeMethodFn("CONNECT"),
|
||||
delete: makeMethodFn("DELETE"),
|
||||
get: makeMethodFn("GET"),
|
||||
getConfig,
|
||||
head: makeMethodFn("HEAD"),
|
||||
interceptors,
|
||||
options: makeMethodFn("OPTIONS"),
|
||||
patch: makeMethodFn("PATCH"),
|
||||
post: makeMethodFn("POST"),
|
||||
put: makeMethodFn("PUT"),
|
||||
request,
|
||||
setConfig,
|
||||
sse: {
|
||||
connect: makeSseFn("CONNECT"),
|
||||
delete: makeSseFn("DELETE"),
|
||||
get: makeSseFn("GET"),
|
||||
head: makeSseFn("HEAD"),
|
||||
options: makeSseFn("OPTIONS"),
|
||||
patch: makeSseFn("PATCH"),
|
||||
post: makeSseFn("POST"),
|
||||
put: makeSseFn("PUT"),
|
||||
trace: makeSseFn("TRACE"),
|
||||
},
|
||||
trace: makeMethodFn("TRACE"),
|
||||
} as Client
|
||||
}
|
||||
25
packages/sdk/js/src/v2/gen/client/index.ts
Normal file
25
packages/sdk/js/src/v2/gen/client/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
export type { Auth } from "../core/auth.gen.js"
|
||||
export type { QuerySerializerOptions } from "../core/bodySerializer.gen.js"
|
||||
export {
|
||||
formDataBodySerializer,
|
||||
jsonBodySerializer,
|
||||
urlSearchParamsBodySerializer,
|
||||
} from "../core/bodySerializer.gen.js"
|
||||
export { buildClientParams } from "../core/params.gen.js"
|
||||
export { serializeQueryKeyValue } from "../core/queryKeySerializer.gen.js"
|
||||
export { createClient } from "./client.gen.js"
|
||||
export type {
|
||||
Client,
|
||||
ClientOptions,
|
||||
Config,
|
||||
CreateClientConfig,
|
||||
Options,
|
||||
RequestOptions,
|
||||
RequestResult,
|
||||
ResolvedRequestOptions,
|
||||
ResponseStyle,
|
||||
TDataShape,
|
||||
} from "./types.gen.js"
|
||||
export { createConfig, mergeHeaders } from "./utils.gen.js"
|
||||
202
packages/sdk/js/src/v2/gen/client/types.gen.ts
Normal file
202
packages/sdk/js/src/v2/gen/client/types.gen.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
import type { Auth } from "../core/auth.gen.js"
|
||||
import type { ServerSentEventsOptions, ServerSentEventsResult } from "../core/serverSentEvents.gen.js"
|
||||
import type { Client as CoreClient, Config as CoreConfig } from "../core/types.gen.js"
|
||||
import type { Middleware } from "./utils.gen.js"
|
||||
|
||||
export type ResponseStyle = "data" | "fields"
|
||||
|
||||
export interface Config<T extends ClientOptions = ClientOptions>
|
||||
extends Omit<RequestInit, "body" | "headers" | "method">,
|
||||
CoreConfig {
|
||||
/**
|
||||
* Base URL for all requests made by this client.
|
||||
*/
|
||||
baseUrl?: T["baseUrl"]
|
||||
/**
|
||||
* Fetch API implementation. You can use this option to provide a custom
|
||||
* fetch instance.
|
||||
*
|
||||
* @default globalThis.fetch
|
||||
*/
|
||||
fetch?: typeof fetch
|
||||
/**
|
||||
* Please don't use the Fetch client for Next.js applications. The `next`
|
||||
* options won't have any effect.
|
||||
*
|
||||
* Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead.
|
||||
*/
|
||||
next?: never
|
||||
/**
|
||||
* Return the response data parsed in a specified format. By default, `auto`
|
||||
* will infer the appropriate method from the `Content-Type` response header.
|
||||
* You can override this behavior with any of the {@link Body} methods.
|
||||
* Select `stream` if you don't want to parse response data at all.
|
||||
*
|
||||
* @default 'auto'
|
||||
*/
|
||||
parseAs?: "arrayBuffer" | "auto" | "blob" | "formData" | "json" | "stream" | "text"
|
||||
/**
|
||||
* Should we return only data or multiple fields (data, error, response, etc.)?
|
||||
*
|
||||
* @default 'fields'
|
||||
*/
|
||||
responseStyle?: ResponseStyle
|
||||
/**
|
||||
* Throw an error instead of returning it in the response?
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
throwOnError?: T["throwOnError"]
|
||||
}
|
||||
|
||||
export interface RequestOptions<
|
||||
TData = unknown,
|
||||
TResponseStyle extends ResponseStyle = "fields",
|
||||
ThrowOnError extends boolean = boolean,
|
||||
Url extends string = string,
|
||||
> extends Config<{
|
||||
responseStyle: TResponseStyle
|
||||
throwOnError: ThrowOnError
|
||||
}>,
|
||||
Pick<
|
||||
ServerSentEventsOptions<TData>,
|
||||
"onSseError" | "onSseEvent" | "sseDefaultRetryDelay" | "sseMaxRetryAttempts" | "sseMaxRetryDelay"
|
||||
> {
|
||||
/**
|
||||
* Any body that you want to add to your request.
|
||||
*
|
||||
* {@link https://developer.mozilla.org/docs/Web/API/fetch#body}
|
||||
*/
|
||||
body?: unknown
|
||||
path?: Record<string, unknown>
|
||||
query?: Record<string, unknown>
|
||||
/**
|
||||
* Security mechanism(s) to use for the request.
|
||||
*/
|
||||
security?: ReadonlyArray<Auth>
|
||||
url: Url
|
||||
}
|
||||
|
||||
export interface ResolvedRequestOptions<
|
||||
TResponseStyle extends ResponseStyle = "fields",
|
||||
ThrowOnError extends boolean = boolean,
|
||||
Url extends string = string,
|
||||
> extends RequestOptions<unknown, TResponseStyle, ThrowOnError, Url> {
|
||||
serializedBody?: string
|
||||
}
|
||||
|
||||
export type RequestResult<
|
||||
TData = unknown,
|
||||
TError = unknown,
|
||||
ThrowOnError extends boolean = boolean,
|
||||
TResponseStyle extends ResponseStyle = "fields",
|
||||
> = ThrowOnError extends true
|
||||
? Promise<
|
||||
TResponseStyle extends "data"
|
||||
? TData extends Record<string, unknown>
|
||||
? TData[keyof TData]
|
||||
: TData
|
||||
: {
|
||||
data: TData extends Record<string, unknown> ? TData[keyof TData] : TData
|
||||
request: Request
|
||||
response: Response
|
||||
}
|
||||
>
|
||||
: Promise<
|
||||
TResponseStyle extends "data"
|
||||
? (TData extends Record<string, unknown> ? TData[keyof TData] : TData) | undefined
|
||||
: (
|
||||
| {
|
||||
data: TData extends Record<string, unknown> ? TData[keyof TData] : TData
|
||||
error: undefined
|
||||
}
|
||||
| {
|
||||
data: undefined
|
||||
error: TError extends Record<string, unknown> ? TError[keyof TError] : TError
|
||||
}
|
||||
) & {
|
||||
request: Request
|
||||
response: Response
|
||||
}
|
||||
>
|
||||
|
||||
export interface ClientOptions {
|
||||
baseUrl?: string
|
||||
responseStyle?: ResponseStyle
|
||||
throwOnError?: boolean
|
||||
}
|
||||
|
||||
type MethodFn = <
|
||||
TData = unknown,
|
||||
TError = unknown,
|
||||
ThrowOnError extends boolean = false,
|
||||
TResponseStyle extends ResponseStyle = "fields",
|
||||
>(
|
||||
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, "method">,
|
||||
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>
|
||||
|
||||
type SseFn = <
|
||||
TData = unknown,
|
||||
TError = unknown,
|
||||
ThrowOnError extends boolean = false,
|
||||
TResponseStyle extends ResponseStyle = "fields",
|
||||
>(
|
||||
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, "method">,
|
||||
) => Promise<ServerSentEventsResult<TData, TError>>
|
||||
|
||||
type RequestFn = <
|
||||
TData = unknown,
|
||||
TError = unknown,
|
||||
ThrowOnError extends boolean = false,
|
||||
TResponseStyle extends ResponseStyle = "fields",
|
||||
>(
|
||||
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, "method"> &
|
||||
Pick<Required<RequestOptions<TData, TResponseStyle, ThrowOnError>>, "method">,
|
||||
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>
|
||||
|
||||
type BuildUrlFn = <
|
||||
TData extends {
|
||||
body?: unknown
|
||||
path?: Record<string, unknown>
|
||||
query?: Record<string, unknown>
|
||||
url: string
|
||||
},
|
||||
>(
|
||||
options: TData & Options<TData>,
|
||||
) => string
|
||||
|
||||
export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn, SseFn> & {
|
||||
interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions>
|
||||
}
|
||||
|
||||
/**
|
||||
* The `createClientConfig()` function will be called on client initialization
|
||||
* and the returned object will become the client's initial configuration.
|
||||
*
|
||||
* You may want to initialize your client this way instead of calling
|
||||
* `setConfig()`. This is useful for example if you're using Next.js
|
||||
* to ensure your client always has the correct values.
|
||||
*/
|
||||
export type CreateClientConfig<T extends ClientOptions = ClientOptions> = (
|
||||
override?: Config<ClientOptions & T>,
|
||||
) => Config<Required<ClientOptions> & T>
|
||||
|
||||
export interface TDataShape {
|
||||
body?: unknown
|
||||
headers?: unknown
|
||||
path?: unknown
|
||||
query?: unknown
|
||||
url: string
|
||||
}
|
||||
|
||||
type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>
|
||||
|
||||
export type Options<
|
||||
TData extends TDataShape = TDataShape,
|
||||
ThrowOnError extends boolean = boolean,
|
||||
TResponse = unknown,
|
||||
TResponseStyle extends ResponseStyle = "fields",
|
||||
> = OmitKeys<RequestOptions<TResponse, TResponseStyle, ThrowOnError>, "body" | "path" | "query" | "url"> &
|
||||
([TData] extends [never] ? unknown : Omit<TData, "url">)
|
||||
289
packages/sdk/js/src/v2/gen/client/utils.gen.ts
Normal file
289
packages/sdk/js/src/v2/gen/client/utils.gen.ts
Normal file
@@ -0,0 +1,289 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
import { getAuthToken } from "../core/auth.gen.js"
|
||||
import type { QuerySerializerOptions } from "../core/bodySerializer.gen.js"
|
||||
import { jsonBodySerializer } from "../core/bodySerializer.gen.js"
|
||||
import { serializeArrayParam, serializeObjectParam, serializePrimitiveParam } from "../core/pathSerializer.gen.js"
|
||||
import { getUrl } from "../core/utils.gen.js"
|
||||
import type { Client, ClientOptions, Config, RequestOptions } from "./types.gen.js"
|
||||
|
||||
export const createQuerySerializer = <T = unknown>({ parameters = {}, ...args }: QuerySerializerOptions = {}) => {
|
||||
const querySerializer = (queryParams: T) => {
|
||||
const search: string[] = []
|
||||
if (queryParams && typeof queryParams === "object") {
|
||||
for (const name in queryParams) {
|
||||
const value = queryParams[name]
|
||||
|
||||
if (value === undefined || value === null) {
|
||||
continue
|
||||
}
|
||||
|
||||
const options = parameters[name] || args
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
const serializedArray = serializeArrayParam({
|
||||
allowReserved: options.allowReserved,
|
||||
explode: true,
|
||||
name,
|
||||
style: "form",
|
||||
value,
|
||||
...options.array,
|
||||
})
|
||||
if (serializedArray) search.push(serializedArray)
|
||||
} else if (typeof value === "object") {
|
||||
const serializedObject = serializeObjectParam({
|
||||
allowReserved: options.allowReserved,
|
||||
explode: true,
|
||||
name,
|
||||
style: "deepObject",
|
||||
value: value as Record<string, unknown>,
|
||||
...options.object,
|
||||
})
|
||||
if (serializedObject) search.push(serializedObject)
|
||||
} else {
|
||||
const serializedPrimitive = serializePrimitiveParam({
|
||||
allowReserved: options.allowReserved,
|
||||
name,
|
||||
value: value as string,
|
||||
})
|
||||
if (serializedPrimitive) search.push(serializedPrimitive)
|
||||
}
|
||||
}
|
||||
}
|
||||
return search.join("&")
|
||||
}
|
||||
return querySerializer
|
||||
}
|
||||
|
||||
/**
|
||||
* Infers parseAs value from provided Content-Type header.
|
||||
*/
|
||||
export const getParseAs = (contentType: string | null): Exclude<Config["parseAs"], "auto"> => {
|
||||
if (!contentType) {
|
||||
// If no Content-Type header is provided, the best we can do is return the raw response body,
|
||||
// which is effectively the same as the 'stream' option.
|
||||
return "stream"
|
||||
}
|
||||
|
||||
const cleanContent = contentType.split(";")[0]?.trim()
|
||||
|
||||
if (!cleanContent) {
|
||||
return
|
||||
}
|
||||
|
||||
if (cleanContent.startsWith("application/json") || cleanContent.endsWith("+json")) {
|
||||
return "json"
|
||||
}
|
||||
|
||||
if (cleanContent === "multipart/form-data") {
|
||||
return "formData"
|
||||
}
|
||||
|
||||
if (["application/", "audio/", "image/", "video/"].some((type) => cleanContent.startsWith(type))) {
|
||||
return "blob"
|
||||
}
|
||||
|
||||
if (cleanContent.startsWith("text/")) {
|
||||
return "text"
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const checkForExistence = (
|
||||
options: Pick<RequestOptions, "auth" | "query"> & {
|
||||
headers: Headers
|
||||
},
|
||||
name?: string,
|
||||
): boolean => {
|
||||
if (!name) {
|
||||
return false
|
||||
}
|
||||
if (options.headers.has(name) || options.query?.[name] || options.headers.get("Cookie")?.includes(`${name}=`)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export const setAuthParams = async ({
|
||||
security,
|
||||
...options
|
||||
}: Pick<Required<RequestOptions>, "security"> &
|
||||
Pick<RequestOptions, "auth" | "query"> & {
|
||||
headers: Headers
|
||||
}) => {
|
||||
for (const auth of security) {
|
||||
if (checkForExistence(options, auth.name)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const token = await getAuthToken(auth, options.auth)
|
||||
|
||||
if (!token) {
|
||||
continue
|
||||
}
|
||||
|
||||
const name = auth.name ?? "Authorization"
|
||||
|
||||
switch (auth.in) {
|
||||
case "query":
|
||||
if (!options.query) {
|
||||
options.query = {}
|
||||
}
|
||||
options.query[name] = token
|
||||
break
|
||||
case "cookie":
|
||||
options.headers.append("Cookie", `${name}=${token}`)
|
||||
break
|
||||
case "header":
|
||||
default:
|
||||
options.headers.set(name, token)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const buildUrl: Client["buildUrl"] = (options) =>
|
||||
getUrl({
|
||||
baseUrl: options.baseUrl as string,
|
||||
path: options.path,
|
||||
query: options.query,
|
||||
querySerializer:
|
||||
typeof options.querySerializer === "function"
|
||||
? options.querySerializer
|
||||
: createQuerySerializer(options.querySerializer),
|
||||
url: options.url,
|
||||
})
|
||||
|
||||
export const mergeConfigs = (a: Config, b: Config): Config => {
|
||||
const config = { ...a, ...b }
|
||||
if (config.baseUrl?.endsWith("/")) {
|
||||
config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1)
|
||||
}
|
||||
config.headers = mergeHeaders(a.headers, b.headers)
|
||||
return config
|
||||
}
|
||||
|
||||
const headersEntries = (headers: Headers): Array<[string, string]> => {
|
||||
const entries: Array<[string, string]> = []
|
||||
headers.forEach((value, key) => {
|
||||
entries.push([key, value])
|
||||
})
|
||||
return entries
|
||||
}
|
||||
|
||||
export const mergeHeaders = (...headers: Array<Required<Config>["headers"] | undefined>): Headers => {
|
||||
const mergedHeaders = new Headers()
|
||||
for (const header of headers) {
|
||||
if (!header) {
|
||||
continue
|
||||
}
|
||||
|
||||
const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header)
|
||||
|
||||
for (const [key, value] of iterator) {
|
||||
if (value === null) {
|
||||
mergedHeaders.delete(key)
|
||||
} else if (Array.isArray(value)) {
|
||||
for (const v of value) {
|
||||
mergedHeaders.append(key, v as string)
|
||||
}
|
||||
} else if (value !== undefined) {
|
||||
// assume object headers are meant to be JSON stringified, i.e. their
|
||||
// content value in OpenAPI specification is 'application/json'
|
||||
mergedHeaders.set(key, typeof value === "object" ? JSON.stringify(value) : (value as string))
|
||||
}
|
||||
}
|
||||
}
|
||||
return mergedHeaders
|
||||
}
|
||||
|
||||
type ErrInterceptor<Err, Res, Req, Options> = (
|
||||
error: Err,
|
||||
response: Res,
|
||||
request: Req,
|
||||
options: Options,
|
||||
) => Err | Promise<Err>
|
||||
|
||||
type ReqInterceptor<Req, Options> = (request: Req, options: Options) => Req | Promise<Req>
|
||||
|
||||
type ResInterceptor<Res, Req, Options> = (response: Res, request: Req, options: Options) => Res | Promise<Res>
|
||||
|
||||
class Interceptors<Interceptor> {
|
||||
fns: Array<Interceptor | null> = []
|
||||
|
||||
clear(): void {
|
||||
this.fns = []
|
||||
}
|
||||
|
||||
eject(id: number | Interceptor): void {
|
||||
const index = this.getInterceptorIndex(id)
|
||||
if (this.fns[index]) {
|
||||
this.fns[index] = null
|
||||
}
|
||||
}
|
||||
|
||||
exists(id: number | Interceptor): boolean {
|
||||
const index = this.getInterceptorIndex(id)
|
||||
return Boolean(this.fns[index])
|
||||
}
|
||||
|
||||
getInterceptorIndex(id: number | Interceptor): number {
|
||||
if (typeof id === "number") {
|
||||
return this.fns[id] ? id : -1
|
||||
}
|
||||
return this.fns.indexOf(id)
|
||||
}
|
||||
|
||||
update(id: number | Interceptor, fn: Interceptor): number | Interceptor | false {
|
||||
const index = this.getInterceptorIndex(id)
|
||||
if (this.fns[index]) {
|
||||
this.fns[index] = fn
|
||||
return id
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
use(fn: Interceptor): number {
|
||||
this.fns.push(fn)
|
||||
return this.fns.length - 1
|
||||
}
|
||||
}
|
||||
|
||||
export interface Middleware<Req, Res, Err, Options> {
|
||||
error: Interceptors<ErrInterceptor<Err, Res, Req, Options>>
|
||||
request: Interceptors<ReqInterceptor<Req, Options>>
|
||||
response: Interceptors<ResInterceptor<Res, Req, Options>>
|
||||
}
|
||||
|
||||
export const createInterceptors = <Req, Res, Err, Options>(): Middleware<Req, Res, Err, Options> => ({
|
||||
error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(),
|
||||
request: new Interceptors<ReqInterceptor<Req, Options>>(),
|
||||
response: new Interceptors<ResInterceptor<Res, Req, Options>>(),
|
||||
})
|
||||
|
||||
const defaultQuerySerializer = createQuerySerializer({
|
||||
allowReserved: false,
|
||||
array: {
|
||||
explode: true,
|
||||
style: "form",
|
||||
},
|
||||
object: {
|
||||
explode: true,
|
||||
style: "deepObject",
|
||||
},
|
||||
})
|
||||
|
||||
const defaultHeaders = {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
export const createConfig = <T extends ClientOptions = ClientOptions>(
|
||||
override: Config<Omit<ClientOptions, keyof T> & T> = {},
|
||||
): Config<Omit<ClientOptions, keyof T> & T> => ({
|
||||
...jsonBodySerializer,
|
||||
headers: defaultHeaders,
|
||||
parseAs: "auto",
|
||||
querySerializer: defaultQuerySerializer,
|
||||
...override,
|
||||
})
|
||||
Reference in New Issue
Block a user