diff --git a/.changeset/light-lamps-chew.md b/.changeset/light-lamps-chew.md new file mode 100644 index 0000000..5481a1c --- /dev/null +++ b/.changeset/light-lamps-chew.md @@ -0,0 +1,5 @@ +--- +'openapi-fetch': patch +--- + +Fix TypeScript lib error, simplify generated types diff --git a/schema.ts b/schema.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/index.ts b/src/index.ts index 210ffba..c16da2b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,8 +13,22 @@ export interface BaseParams { query?: Record; } -export const methods = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'] as const; -export type Method = (typeof methods)[number]; +export type Method = 'get' | 'put' | 'post' | 'delete' | 'options' | 'head' | 'patch' | 'trace'; + +/** Gets a union of paths which have method */ +type PathsWith = { + [Path in keyof T]: T[Path] extends { [K in M]: unknown } ? Path : never; +}[keyof T]; + +type PathParams = T extends { parameters: any } ? { params: T['parameters'] } : { params?: BaseParams }; +type MethodParams = T extends { + parameters: any; +} + ? { params: T['parameters'] } + : { params?: BaseParams }; +type Params = PathParams & MethodParams; +type RequestBody = T extends { requestBody: any } ? { body: Unwrap } : { body?: never }; +type FetchOptions = Params & RequestBody & Omit; type TruncatedResponse = Omit; /** Infer request/response from content type */ @@ -28,90 +42,77 @@ type Unwrap = T extends { ? T['content']['*/*'] : T; -export default function createClient(defaultOptions?: ClientOptions) { +type Success = T extends { 200: any } ? T[200] : T extends { 201: any } ? T[201] : T extends { 202: any } ? T[202] : T extends { default: any } ? T['default'] : unknown; +type Error = T extends { 500: any } + ? T[500] + : T extends { 404: any } + ? T[404] + : T extends { 402: any } + ? T[402] + : T extends { 401: any } + ? T[401] + : T extends { 400: any } + ? T[400] + : T extends { 422: any } + ? T[422] + : T extends { 418: any } + ? T[418] + : T extends { 417: any } + ? T[417] + : T extends { 416: any } + ? T[416] + : T extends { 415: any } + ? T[415] + : T extends { 414: any } + ? T[414] + : T extends { 413: any } + ? T[413] + : T extends { 412: any } + ? T[412] + : T extends { 411: any } + ? T[411] + : T extends { 410: any } + ? T[410] + : T extends { 409: any } + ? T[409] + : T extends { 408: any } + ? T[408] + : T extends { 407: any } + ? T[407] + : T extends { 406: any } + ? T[406] + : T extends { 405: any } + ? T[405] + : T extends { default: any } + ? T['default'] + : unknown; +type FetchResponse = + | { + data: T extends { responses: any } ? NonNullable>> : unknown; + error?: never; + response: TruncatedResponse; + } + | { + data?: never; + error: T extends { responses: any } ? NonNullable>> : unknown; + response: TruncatedResponse; + }; + +export default function createClient(options?: ClientOptions) { const defaultHeaders = new Headers({ ...DEFAULT_HEADERS, - ...(defaultOptions?.headers ?? {}), + ...(options?.headers ?? {}), }); - /** Gets a union of paths which have method */ - type PathsWith = { - [Path in keyof T]: T[Path] extends { [K in M]: unknown } ? Path : never; - }[keyof T]; + async function coreFetch(url: U, fetchOptions: FetchOptions): Promise> { + let { headers, body, params = {}, ...init } = fetchOptions || {}; - type FetchResponse = - | { - data: T extends { responses: any } ? NonNullable>> : unknown; - error?: never; - response: TruncatedResponse; - } - | { - data?: never; - error: T extends { responses: any } ? NonNullable>> : unknown; - response: TruncatedResponse; - }; - - type PathParams = T extends { parameters: any } ? { params: T['parameters'] } : { params?: BaseParams }; - type MethodParams = T extends { - parameters: any; - } - ? { params: T['parameters'] } - : { params?: BaseParams }; - type Params = PathParams & MethodParams; - type RequestBody = T extends { requestBody: any } ? { body: Unwrap } : { body?: never }; - type FetchOptions = Params & RequestBody & Omit; - type Success = T extends { 200: any } ? T[200] : T extends { 201: any } ? T[201] : T extends { 202: any } ? T[202] : T extends { default: any } ? T['default'] : unknown; - type Error = T extends { 500: any } - ? T[500] - : T extends { 404: any } - ? T[404] - : T extends { 402: any } - ? T[402] - : T extends { 401: any } - ? T[401] - : T extends { 400: any } - ? T[400] - : T extends { 422: any } - ? T[422] - : T extends { 418: any } - ? T[418] - : T extends { 417: any } - ? T[417] - : T extends { 416: any } - ? T[416] - : T extends { 415: any } - ? T[415] - : T extends { 414: any } - ? T[414] - : T extends { 413: any } - ? T[413] - : T extends { 412: any } - ? T[412] - : T extends { 411: any } - ? T[411] - : T extends { 410: any } - ? T[410] - : T extends { 409: any } - ? T[409] - : T extends { 408: any } - ? T[408] - : T extends { 407: any } - ? T[407] - : T extends { 406: any } - ? T[406] - : T extends { 405: any } - ? T[405] - : T extends { default: any } - ? T['default'] - : unknown; - - async function coreFetch(url: U, options: FetchOptions): Promise> { - let { headers, body, params = {}, ...init } = options || {}; // URL - let finalURL = `${defaultOptions?.baseUrl ?? ''}${url as string}`; + let finalURL = `${options?.baseUrl ?? ''}${url as string}`; const { path, query } = (params as BaseParams | undefined) ?? {}; if (path) for (const [k, v] of Object.entries(path)) finalURL = finalURL.replace(`{${k}}`, encodeURIComponent(`${v}`.trim())); if (query) finalURL = `${finalURL}?${new URLSearchParams(query as any).toString()}`; + // headers const baseHeaders = new Headers(defaultHeaders); // clone defaults (don’t overwrite!) const headerOverrides = new Headers(headers); @@ -119,10 +120,11 @@ export default function createClient(defaultOptions?: ClientOptions) { if (v === undefined || v === null) baseHeaders.delete(k); // allow `undefined` | `null` to erase value else baseHeaders.set(k, v); } + // fetch! const res = await fetch(finalURL, { redirect: 'follow', - ...defaultOptions, + ...options, ...init, headers: baseHeaders, body: typeof body === 'string' ? body : JSON.stringify(body), @@ -142,36 +144,36 @@ export default function createClient(defaultOptions?: ClientOptions) { return { /** Call a GET endpoint */ - async get, M extends keyof T[U]>(url: U, options: FetchOptions) { - return coreFetch(url, { ...options, method: 'GET' }); + async get, M extends keyof T[U]>(url: U, init: FetchOptions) { + return coreFetch(url, { ...init, method: 'GET' }); }, /** Call a PUT endpoint */ - async put, M extends keyof T[U]>(url: U, options: FetchOptions) { - return coreFetch(url, { ...options, method: 'PUT' }); + async put, M extends keyof T[U]>(url: U, init: FetchOptions) { + return coreFetch(url, { ...init, method: 'PUT' }); }, /** Call a POST endpoint */ - async post, M extends keyof T[U]>(url: U, options: FetchOptions) { - return coreFetch(url, { ...options, method: 'POST' }); + async post, M extends keyof T[U]>(url: U, init: FetchOptions) { + return coreFetch(url, { ...init, method: 'POST' }); }, /** Call a DELETE endpoint */ - async del, M extends keyof T[U]>(url: U, options: FetchOptions) { - return coreFetch(url, { ...options, method: 'DELETE' }); + async del, M extends keyof T[U]>(url: U, init: FetchOptions) { + return coreFetch(url, { ...init, method: 'DELETE' }); }, /** Call a OPTIONS endpoint */ - async options, M extends keyof T[U]>(url: U, options: FetchOptions) { - return coreFetch(url, { ...options, method: 'OPTIONS' }); + async options, M extends keyof T[U]>(url: U, init: FetchOptions) { + return coreFetch(url, { ...init, method: 'OPTIONS' }); }, /** Call a HEAD endpoint */ - async head, M extends keyof T[U]>(url: U, options: FetchOptions) { - return coreFetch(url, { ...options, method: 'HEAD' }); + async head, M extends keyof T[U]>(url: U, init: FetchOptions) { + return coreFetch(url, { ...init, method: 'HEAD' }); }, /** Call a PATCH endpoint */ - async patch, M extends keyof T[U]>(url: U, options: FetchOptions) { - return coreFetch(url, { ...options, method: 'PATCH' }); + async patch, M extends keyof T[U]>(url: U, init: FetchOptions) { + return coreFetch(url, { ...init, method: 'PATCH' }); }, /** Call a TRACE endpoint */ - async trace, M extends keyof T[U]>(url: U, options: FetchOptions) { - return coreFetch(url, { ...options, method: 'TRACE' }); + async trace, M extends keyof T[U]>(url: U, init: FetchOptions) { + return coreFetch(url, { ...init, method: 'TRACE' }); }, }; } diff --git a/tsconfig.json b/tsconfig.json index 0f1fb77..85b111c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "allowSyntheticDefaultImports": true, "declaration": true, + "downlevelIteration": false, "esModuleInterop": true, "module": "ESNext", "moduleResolution": "nodenext",