From 04a024ac9d45823b18e00552eb40b3b53c034c76 Mon Sep 17 00:00:00 2001 From: Drew Powers Date: Wed, 4 Oct 2023 22:13:15 -0600 Subject: [PATCH] Update openapi-fetch types --- .changeset/beige-ligers-tell.md | 5 ++ .changeset/rich-poems-swim.md | 5 ++ docs/src/content/docs/openapi-fetch/index.md | 2 - packages/openapi-fetch/src/index.ts | 30 +++++++--- .../openapi-typescript-helpers/index.d.ts | 60 +++++++++++++++---- pnpm-lock.yaml | 14 ++--- 6 files changed, 88 insertions(+), 28 deletions(-) create mode 100644 .changeset/beige-ligers-tell.md create mode 100644 .changeset/rich-poems-swim.md diff --git a/.changeset/beige-ligers-tell.md b/.changeset/beige-ligers-tell.md new file mode 100644 index 000000000..429017725 --- /dev/null +++ b/.changeset/beige-ligers-tell.md @@ -0,0 +1,5 @@ +--- +"openapi-fetch": patch +--- + +Fix GET requests requiring 2nd param when it’s not needed diff --git a/.changeset/rich-poems-swim.md b/.changeset/rich-poems-swim.md new file mode 100644 index 000000000..e18d9b0af --- /dev/null +++ b/.changeset/rich-poems-swim.md @@ -0,0 +1,5 @@ +--- +"openapi-typescript": minor +--- + +✨ **Feature**: Added debugger that lets you profile performance and see more in-depth messages diff --git a/docs/src/content/docs/openapi-fetch/index.md b/docs/src/content/docs/openapi-fetch/index.md index 5349c1bed..5c783fa71 100644 --- a/docs/src/content/docs/openapi-fetch/index.md +++ b/docs/src/content/docs/openapi-fetch/index.md @@ -69,8 +69,6 @@ Next, generate TypeScript types from your OpenAPI schema using openapi-typescrip npx openapi-typescript ./path/to/api/v1.yaml -o ./src/lib/api/v1.d.ts ``` -> ⚠️ Be sure to validate your schemas! openapi-typescript will err on invalid schemas. - Lastly, be sure to **run typechecking** in your project. This can be done by adding `tsc --noEmit` to your npm scripts like so: ```json diff --git a/packages/openapi-fetch/src/index.ts b/packages/openapi-fetch/src/index.ts index 2919a811b..972035198 100644 --- a/packages/openapi-fetch/src/index.ts +++ b/packages/openapi-fetch/src/index.ts @@ -32,28 +32,46 @@ interface ClientOptions extends Omit { // headers override to make typing friendlier headers?: HeadersOptions; } + export type HeadersOptions = | HeadersInit | Record; + export type QuerySerializer = ( query: T extends { parameters: any } ? NonNullable : Record, ) => string; + export type BodySerializer = (body: OperationRequestBodyContent) => any; + export type ParseAs = "json" | "text" | "blob" | "arrayBuffer" | "stream"; + export interface DefaultParamsOption { params?: { query?: Record }; } + +export interface EmptyParameters { + query?: never; + header?: never; + path?: never; + cookie?: never; +} + export type ParamsOption = T extends { parameters: any } - ? { params: NonNullable } + ? T["parameters"] extends EmptyParameters + ? DefaultParamsOption + : { params: NonNullable } : DefaultParamsOption; + export type RequestBodyOption = OperationRequestBodyContent extends never ? { body?: never } : undefined extends OperationRequestBodyContent ? { body?: OperationRequestBodyContent } : { body: OperationRequestBodyContent }; + export type FetchOptions = RequestOptions & Omit; + export type FetchResponse = | { data: FilterKeys>, MediaType>; @@ -65,6 +83,7 @@ export type FetchResponse = error: FilterKeys>, MediaType>; response: Response; }; + export type RequestOptions = ParamsOption & RequestBodyOption & { querySerializer?: QuerySerializer; @@ -81,7 +100,6 @@ export default function createClient( bodySerializer: globalBodySerializer, ...options } = clientOptions; - let baseUrl = options.baseUrl ?? ""; if (baseUrl.endsWith("/")) { baseUrl = baseUrl.slice(0, -1); // remove trailing slash @@ -318,11 +336,9 @@ export function createFinalURL( finalURL = finalURL.replace(`{${k}}`, encodeURIComponent(String(v))); } } - if (options.params.query) { - const search = options.querySerializer(options.params.query as any); - if (search) { - finalURL += `?${search}`; - } + const search = options.querySerializer((options.params.query as any) ?? {}); + if (search) { + finalURL += `?${search}`; } return finalURL; } diff --git a/packages/openapi-typescript-helpers/index.d.ts b/packages/openapi-typescript-helpers/index.d.ts index c74b80161..edae05ad3 100644 --- a/packages/openapi-typescript-helpers/index.d.ts +++ b/packages/openapi-typescript-helpers/index.d.ts @@ -2,7 +2,15 @@ // HTTP types -export type HttpMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace"; +export type HttpMethod = + | "get" + | "put" + | "post" + | "delete" + | "options" + | "head" + | "patch" + | "trace"; /** 2XX statuses */ export type OkStatus = 200 | 201 | 202 | 203 | 204 | 206 | 207 | "2XX"; // prettier-ignore @@ -12,8 +20,15 @@ export type ErrorStatus = 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | // OpenAPI type helpers /** Given an OpenAPI **Paths Object**, find all paths that have the given method */ -export type PathsWithMethod, PathnameMethod extends HttpMethod> = { - [Pathname in keyof Paths]: Paths[Pathname] extends { [K in PathnameMethod]: any } ? Pathname : never; +export type PathsWithMethod< + Paths extends Record, + PathnameMethod extends HttpMethod, +> = { + [Pathname in keyof Paths]: Paths[Pathname] extends { + [K in PathnameMethod]: any; + } + ? Pathname + : never; }[keyof Paths]; /** DO NOT USE! Only used only for OperationObject type inference */ export interface OperationObject { @@ -23,28 +38,49 @@ export interface OperationObject { responses: any; } /** Internal helper used in PathsWithMethod */ -export type PathItemObject = { [M in HttpMethod]: OperationObject } & { parameters?: any }; +export type PathItemObject = { + [M in HttpMethod]: OperationObject; +} & { parameters?: any }; /** Return `responses` for an Operation Object */ -export type ResponseObjectMap = T extends { responses: any } ? T["responses"] : unknown; +export type ResponseObjectMap = T extends { responses: any } + ? T["responses"] + : unknown; /** Return `content` for a Response Object */ -export type ResponseContent = T extends { content: any } ? T["content"] : unknown; +export type ResponseContent = T extends { content: any } + ? T["content"] + : unknown; /** Return `requestBody` for an Operation Object */ -export type OperationRequestBody = T extends { requestBody?: any } ? T["requestBody"] : never; +export type OperationRequestBody = T extends { requestBody?: any } + ? T["requestBody"] + : never; /** Internal helper used in OperationRequestBodyContent */ -export type OperationRequestBodyMediaContent = undefined extends OperationRequestBody ? FilterKeys>, "content"> | undefined : FilterKeys, "content">; +export type OperationRequestBodyMediaContent = + undefined extends OperationRequestBody + ? FilterKeys>, "content"> | undefined + : FilterKeys, "content">; /** Return first `content` from a Request Object Mapping, allowing any media type */ -export type OperationRequestBodyContent = FilterKeys, MediaType> extends never - ? FilterKeys>, MediaType> | undefined +export type OperationRequestBodyContent = FilterKeys< + OperationRequestBodyMediaContent, + MediaType +> extends never + ? + | FilterKeys>, MediaType> + | undefined : FilterKeys, MediaType>; /** Return first 2XX response from a Response Object Map */ export type SuccessResponse = FilterKeys, "content">; /** Return first 5XX or 4XX response (in that order) from a Response Object Map */ -export type ErrorResponse = FilterKeys, "content">; +export type ErrorResponse = FilterKeys< + FilterKeys, + "content" +>; // Generic TS utils /** Find first match of multiple keys */ -export type FilterKeys = { [K in keyof Obj]: K extends Matchers ? Obj[K] : never }[keyof Obj]; +export type FilterKeys = { + [K in keyof Obj]: K extends Matchers ? Obj[K] : never; +}[keyof Obj]; /** Return any `[string]/[string]` media type (important because openapi-fetch allows any content response, not just JSON-like) */ export type MediaType = `${string}/${string}`; /** Filter objects that have required keys */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 641efeb0b..dc217ea81 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -209,7 +209,7 @@ importers: version: 5.2.2 vite: specifier: ^4.4.9 - version: 4.4.9(@types/node@20.8.2) + version: 4.4.9(@types/node@20.8.0) packages/openapi-fetch/examples/sveltekit: dependencies: @@ -240,7 +240,7 @@ importers: version: 5.2.2 vite: specifier: ^4.4.9 - version: 4.4.9(@types/node@20.8.2) + version: 4.4.9(@types/node@20.8.0) packages/openapi-typescript: dependencies: @@ -1789,7 +1789,7 @@ packages: svelte: 4.2.0 tiny-glob: 0.2.9 undici: 5.23.0 - vite: 4.4.9(@types/node@20.8.2) + vite: 4.4.9(@types/node@20.8.0) transitivePeerDependencies: - supports-color dev: true @@ -1805,7 +1805,7 @@ packages: '@sveltejs/vite-plugin-svelte': 2.4.5(svelte@4.2.0)(vite@4.4.9) debug: 4.3.4(supports-color@9.4.0) svelte: 4.2.0 - vite: 4.4.9(@types/node@20.8.2) + vite: 4.4.9(@types/node@20.8.0) transitivePeerDependencies: - supports-color dev: true @@ -1824,7 +1824,7 @@ packages: magic-string: 0.30.3 svelte: 4.2.0 svelte-hmr: 0.15.3(svelte@4.2.0) - vite: 4.4.9(@types/node@20.8.2) + vite: 4.4.9(@types/node@20.8.0) vitefu: 0.2.4(vite@4.4.9) transitivePeerDependencies: - supports-color @@ -2341,7 +2341,7 @@ packages: vite: ^4 dependencies: '@swc/core': 1.3.83 - vite: 4.4.9(@types/node@20.8.2) + vite: 4.4.9(@types/node@20.8.0) transitivePeerDependencies: - '@swc/helpers' dev: true @@ -8165,7 +8165,7 @@ packages: vite: optional: true dependencies: - vite: 4.4.9(@types/node@20.8.2) + vite: 4.4.9(@types/node@20.8.0) dev: true /vitest-fetch-mock@0.2.2(vitest@0.34.6):