From 34f0b321a201724be29ddecaa1d8d1d6e13276f2 Mon Sep 17 00:00:00 2001 From: hugeletters Date: Sun, 5 Nov 2023 18:16:58 +0100 Subject: [PATCH 01/10] get queries --- packages/openapi-fetch/src/index.d.ts | 30 +++++++++++++++++++-------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/openapi-fetch/src/index.d.ts b/packages/openapi-fetch/src/index.d.ts index ea042b8de..f90f61fa4 100644 --- a/packages/openapi-fetch/src/index.d.ts +++ b/packages/openapi-fetch/src/index.d.ts @@ -39,6 +39,15 @@ export type QuerySerializer = ( export type BodySerializer = (body: OperationRequestBodyContent) => any; export type ParseAs = "json" | "text" | "blob" | "arrayBuffer" | "stream"; +export type ParseAsResponse = K extends ParseAs + ? { + json: T; + text: Awaited>; + blob: Awaited>; + arrayBuffer: Awaited>; + stream: Response["body"]; + }[K] + : T; export interface DefaultParamsOption { params?: { @@ -62,9 +71,12 @@ export type RequestBodyOption = OperationRequestBodyContent extends never export type FetchOptions = RequestOptions & Omit; -export type FetchResponse = +export type FetchResponse> = | { - data: FilterKeys>, MediaType>; + data: ParseAsResponse< + FilterKeys>, MediaType>, + O["parseAs"] + >; error?: never; response: Response; } @@ -86,13 +98,12 @@ export default function createClient( clientOptions?: ClientOptions, ): { /** Call a GET endpoint */ - GET

>( + GET< + P extends PathsWithMethod, + O extends FetchOptions> = {}, + >( url: P, - ...init: HasRequiredKeys< - FetchOptions> - > extends never - ? [(FetchOptions> | undefined)?] - : [FetchOptions>] + ...init: HasRequiredKeys extends never ? [O?] : [O] ): Promise< FetchResponse< "get" extends infer T @@ -101,7 +112,8 @@ export default function createClient( ? Paths[P][T] : unknown : never - : never + : never, + O > >; /** Call a PUT endpoint */ From d94905ecdb186bb7275330fea31de513072b83d5 Mon Sep 17 00:00:00 2001 From: hugeletters Date: Sun, 5 Nov 2023 18:36:11 +0100 Subject: [PATCH 02/10] tets updates --- packages/openapi-fetch/test/index.test.ts | 20 +++++++++++++------ packages/openapi-fetch/test/v7-beta.test.ts | 22 +++++++++++++++------ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/packages/openapi-fetch/test/index.test.ts b/packages/openapi-fetch/test/index.test.ts index 764a17286..935e926b1 100644 --- a/packages/openapi-fetch/test/index.test.ts +++ b/packages/openapi-fetch/test/index.test.ts @@ -569,23 +569,28 @@ describe("client", () => { it("text", async () => { const client = createClient(); mockFetchOnce({ status: 200, body: "{}" }); - const { data } = await client.GET("/anyMethod", { parseAs: "text" }); + const { data }: { data?: string } = await client.GET("/anyMethod", { + parseAs: "text", + }); expect(data).toBe("{}"); }); it("arrayBuffer", async () => { const client = createClient(); mockFetchOnce({ status: 200, body: "{}" }); - const { data } = await client.GET("/anyMethod", { - parseAs: "arrayBuffer", - }); + const { data }: { data?: ArrayBuffer } = await client.GET( + "/anyMethod", + { parseAs: "arrayBuffer" }, + ); expect(data instanceof ArrayBuffer).toBe(true); }); it("blob", async () => { const client = createClient(); mockFetchOnce({ status: 200, body: "{}" }); - const { data } = await client.GET("/anyMethod", { parseAs: "blob" }); + const { data }: { data?: Blob } = await client.GET("/anyMethod", { + parseAs: "blob", + }); // eslint-disable-next-line @typescript-eslint/no-explicit-any expect((data as any).constructor.name).toBe("Blob"); }); @@ -593,7 +598,10 @@ describe("client", () => { it("stream", async () => { const client = createClient(); mockFetchOnce({ status: 200, body: "{}" }); - const { data } = await client.GET("/anyMethod", { parseAs: "stream" }); + const { data }: { data?: ReadableStream | null } = await client.GET( + "/anyMethod", + { parseAs: "stream" }, + ); expect(data instanceof Buffer).toBe(true); }); }); diff --git a/packages/openapi-fetch/test/v7-beta.test.ts b/packages/openapi-fetch/test/v7-beta.test.ts index 871bcd62c..542da7b86 100644 --- a/packages/openapi-fetch/test/v7-beta.test.ts +++ b/packages/openapi-fetch/test/v7-beta.test.ts @@ -578,23 +578,30 @@ describe("client", () => { it("text", async () => { const client = createClient(); mockFetchOnce({ status: 200, body: "{}" }); - const { data } = await client.GET("/anyMethod", { parseAs: "text" }); + const { data }: { data?: string } = await client.GET("/anyMethod", { + parseAs: "text", + }); expect(data).toBe("{}"); }); it("arrayBuffer", async () => { const client = createClient(); mockFetchOnce({ status: 200, body: "{}" }); - const { data } = await client.GET("/anyMethod", { - parseAs: "arrayBuffer", - }); + const { data }: { data?: ArrayBuffer } = await client.GET( + "/anyMethod", + { + parseAs: "arrayBuffer", + }, + ); expect(data instanceof ArrayBuffer).toBe(true); }); it("blob", async () => { const client = createClient(); mockFetchOnce({ status: 200, body: "{}" }); - const { data } = await client.GET("/anyMethod", { parseAs: "blob" }); + const { data }: { data?: Blob } = await client.GET("/anyMethod", { + parseAs: "blob", + }); // eslint-disable-next-line @typescript-eslint/no-explicit-any expect((data as any).constructor.name).toBe("Blob"); }); @@ -602,7 +609,10 @@ describe("client", () => { it("stream", async () => { const client = createClient(); mockFetchOnce({ status: 200, body: "{}" }); - const { data } = await client.GET("/anyMethod", { parseAs: "stream" }); + const { data }: { data?: ReadableStream | null } = await client.GET( + "/anyMethod", + { parseAs: "stream" }, + ); expect(data instanceof Buffer).toBe(true); }); }); From 7926e49ceebdbada179969a45273bb2d93470dbb Mon Sep 17 00:00:00 2001 From: hugeletters Date: Mon, 6 Nov 2023 02:00:13 +0100 Subject: [PATCH 03/10] stumped :| --- packages/openapi-fetch/src/index.d.ts | 98 +++++++++++++-------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/packages/openapi-fetch/src/index.d.ts b/packages/openapi-fetch/src/index.d.ts index f90f61fa4..fca48e3c8 100644 --- a/packages/openapi-fetch/src/index.d.ts +++ b/packages/openapi-fetch/src/index.d.ts @@ -117,13 +117,12 @@ export default function createClient( > >; /** Call a PUT endpoint */ - PUT

>( + PUT< + P extends PathsWithMethod, + O extends FetchOptions> = {}, + >( url: P, - ...init: HasRequiredKeys< - FetchOptions> - > extends never - ? [(FetchOptions> | undefined)?] - : [FetchOptions>] + ...init: HasRequiredKeys extends never ? [O?] : [O] ): Promise< FetchResponse< "put" extends infer T @@ -132,17 +131,17 @@ export default function createClient( ? Paths[P][T] : unknown : never - : never + : never, + O > >; /** Call a POST endpoint */ - POST

>( + POST< + P extends PathsWithMethod, + O extends FetchOptions> = {}, + >( url: P, - ...init: HasRequiredKeys< - FetchOptions> - > extends never - ? [(FetchOptions> | undefined)?] - : [FetchOptions>] + ...init: HasRequiredKeys extends never ? [O?] : [O] ): Promise< FetchResponse< "post" extends infer T @@ -151,17 +150,17 @@ export default function createClient( ? Paths[P][T] : unknown : never - : never + : never, + O > >; /** Call a DELETE endpoint */ - DELETE

>( + DELETE< + P extends PathsWithMethod, + O extends FetchOptions> = {}, + >( url: P, - ...init: HasRequiredKeys< - FetchOptions> - > extends never - ? [(FetchOptions> | undefined)?] - : [FetchOptions>] + ...init: HasRequiredKeys extends never ? [O?] : [O] ): Promise< FetchResponse< "delete" extends infer T @@ -170,17 +169,17 @@ export default function createClient( ? Paths[P][T] : unknown : never - : never + : never, + O > >; /** Call a OPTIONS endpoint */ - OPTIONS

>( + OPTIONS< + P extends PathsWithMethod, + O extends FetchOptions> = {}, + >( url: P, - ...init: HasRequiredKeys< - FetchOptions> - > extends never - ? [(FetchOptions> | undefined)?] - : [FetchOptions>] + ...init: HasRequiredKeys extends never ? [O?] : [O] ): Promise< FetchResponse< "options" extends infer T @@ -189,17 +188,17 @@ export default function createClient( ? Paths[P][T] : unknown : never - : never + : never, + O > >; /** Call a HEAD endpoint */ - HEAD

>( + HEAD< + P extends PathsWithMethod, + O extends FetchOptions> = {}, + >( url: P, - ...init: HasRequiredKeys< - FetchOptions> - > extends never - ? [(FetchOptions> | undefined)?] - : [FetchOptions>] + ...init: HasRequiredKeys extends never ? [O?] : [O] ): Promise< FetchResponse< "head" extends infer T @@ -208,17 +207,17 @@ export default function createClient( ? Paths[P][T] : unknown : never - : never + : never, + O > >; /** Call a PATCH endpoint */ - PATCH

>( + PATCH< + P extends PathsWithMethod, + O extends FetchOptions> = {}, + >( url: P, - ...init: HasRequiredKeys< - FetchOptions> - > extends never - ? [(FetchOptions> | undefined)?] - : [FetchOptions>] + ...init: HasRequiredKeys extends never ? [O?] : [O] ): Promise< FetchResponse< "patch" extends infer T @@ -227,17 +226,17 @@ export default function createClient( ? Paths[P][T] : unknown : never - : never + : never, + O > >; /** Call a TRACE endpoint */ - TRACE

>( + TRACE< + P extends PathsWithMethod, + O extends FetchOptions> = {}, + >( url: P, - ...init: HasRequiredKeys< - FetchOptions> - > extends never - ? [(FetchOptions> | undefined)?] - : [FetchOptions>] + ...init: HasRequiredKeys extends never ? [O?] : [O] ): Promise< FetchResponse< "trace" extends infer T @@ -246,7 +245,8 @@ export default function createClient( ? Paths[P][T] : unknown : never - : never + : never, + O > >; }; From 30f93837a90bae85a2a5dc02592616bf9cd84498 Mon Sep 17 00:00:00 2001 From: Drew Powers Date: Sat, 18 Nov 2023 15:30:32 -0500 Subject: [PATCH 04/10] Add parseAs inference --- packages/openapi-fetch/src/index.d.ts | 140 ++++++++-------------- packages/openapi-fetch/src/index.js | 5 +- packages/openapi-fetch/test/index.test.ts | 39 +++--- 3 files changed, 77 insertions(+), 107 deletions(-) diff --git a/packages/openapi-fetch/src/index.d.ts b/packages/openapi-fetch/src/index.d.ts index fca48e3c8..e5ee414b0 100644 --- a/packages/openapi-fetch/src/index.d.ts +++ b/packages/openapi-fetch/src/index.d.ts @@ -39,14 +39,14 @@ export type QuerySerializer = ( export type BodySerializer = (body: OperationRequestBodyContent) => any; export type ParseAs = "json" | "text" | "blob" | "arrayBuffer" | "stream"; -export type ParseAsResponse = K extends ParseAs - ? { - json: T; - text: Awaited>; - blob: Awaited>; - arrayBuffer: Awaited>; - stream: Response["body"]; - }[K] +export type ParseAsResponse = K extends "text" + ? Awaited> + : K extends "blob" + ? Awaited> + : K extends "arrayBuffer" + ? Awaited> + : K extends "stream" + ? Awaited> : T; export interface DefaultParamsOption { @@ -71,11 +71,19 @@ export type RequestBodyOption = OperationRequestBodyContent extends never export type FetchOptions = RequestOptions & Omit; -export type FetchResponse> = +/** This type helper makes the 2nd function param required if params/requestBody are required; otherwise, optional */ +export type MaybeOptionalInit< + P extends {}, + M extends keyof P, +> = HasRequiredKeys>> extends never + ? [(FetchOptions> | undefined)?] + : [FetchOptions>]; + +export type FetchResponse = | { data: ParseAsResponse< FilterKeys>, MediaType>, - O["parseAs"] + R >; error?: never; response: Response; @@ -100,153 +108,105 @@ export default function createClient( /** Call a GET endpoint */ GET< P extends PathsWithMethod, - O extends FetchOptions> = {}, + I extends MaybeOptionalInit, >( url: P, - ...init: HasRequiredKeys extends never ? [O?] : [O] + ...init: I ): Promise< FetchResponse< - "get" extends infer T - ? T extends "get" - ? T extends keyof Paths[P] - ? Paths[P][T] - : unknown - : never - : never, - O + Paths[P]["get"], + I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" > >; /** Call a PUT endpoint */ PUT< P extends PathsWithMethod, - O extends FetchOptions> = {}, + I extends MaybeOptionalInit, >( url: P, - ...init: HasRequiredKeys extends never ? [O?] : [O] + ...init: I ): Promise< FetchResponse< - "put" extends infer T - ? T extends "put" - ? T extends keyof Paths[P] - ? Paths[P][T] - : unknown - : never - : never, - O + Paths[P]["put"], + I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" > >; /** Call a POST endpoint */ POST< P extends PathsWithMethod, - O extends FetchOptions> = {}, + I extends MaybeOptionalInit, >( url: P, - ...init: HasRequiredKeys extends never ? [O?] : [O] + ...init: I ): Promise< FetchResponse< - "post" extends infer T - ? T extends "post" - ? T extends keyof Paths[P] - ? Paths[P][T] - : unknown - : never - : never, - O + Paths[P]["post"], + I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" > >; /** Call a DELETE endpoint */ DELETE< P extends PathsWithMethod, - O extends FetchOptions> = {}, + I extends MaybeOptionalInit, >( url: P, - ...init: HasRequiredKeys extends never ? [O?] : [O] + ...init: I ): Promise< FetchResponse< - "delete" extends infer T - ? T extends "delete" - ? T extends keyof Paths[P] - ? Paths[P][T] - : unknown - : never - : never, - O + Paths[P]["delete"], + I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" > >; /** Call a OPTIONS endpoint */ OPTIONS< P extends PathsWithMethod, - O extends FetchOptions> = {}, + I extends MaybeOptionalInit, >( url: P, - ...init: HasRequiredKeys extends never ? [O?] : [O] + ...init: I ): Promise< FetchResponse< - "options" extends infer T - ? T extends "options" - ? T extends keyof Paths[P] - ? Paths[P][T] - : unknown - : never - : never, - O + Paths[P]["options"], + I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" > >; /** Call a HEAD endpoint */ HEAD< P extends PathsWithMethod, - O extends FetchOptions> = {}, + I extends MaybeOptionalInit, >( url: P, - ...init: HasRequiredKeys extends never ? [O?] : [O] + ...init: I ): Promise< FetchResponse< - "head" extends infer T - ? T extends "head" - ? T extends keyof Paths[P] - ? Paths[P][T] - : unknown - : never - : never, - O + Paths[P]["head"], + I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" > >; /** Call a PATCH endpoint */ PATCH< P extends PathsWithMethod, - O extends FetchOptions> = {}, + I extends MaybeOptionalInit, >( url: P, - ...init: HasRequiredKeys extends never ? [O?] : [O] + ...init: I ): Promise< FetchResponse< - "patch" extends infer T - ? T extends "patch" - ? T extends keyof Paths[P] - ? Paths[P][T] - : unknown - : never - : never, - O + Paths[P]["patch"], + I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" > >; /** Call a TRACE endpoint */ TRACE< P extends PathsWithMethod, - O extends FetchOptions> = {}, + I extends MaybeOptionalInit, >( url: P, - ...init: HasRequiredKeys extends never ? [O?] : [O] + ...init: I ): Promise< FetchResponse< - "trace" extends infer T - ? T extends "trace" - ? T extends keyof Paths[P] - ? Paths[P][T] - : unknown - : never - : never, - O + Paths[P]["trace"], + I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" > >; }; diff --git a/packages/openapi-fetch/src/index.js b/packages/openapi-fetch/src/index.js index 23e069fa5..acd39dcf4 100644 --- a/packages/openapi-fetch/src/index.js +++ b/packages/openapi-fetch/src/index.js @@ -21,8 +21,8 @@ export default function createClient(clientOptions) { /** * Per-request fetch (keeps settings created in createClient() - * @param {string} url - * @param {import('./index.js').FetchOptions} fetchOptions + * @param {T} url + * @param {import('./index.js').FetchOptions} fetchOptions */ async function coreFetch(url, fetchOptions) { const { @@ -50,6 +50,7 @@ export default function createClient(clientOptions) { ); // fetch! + /** @type {RequestInit} */ const requestInit = { redirect: "follow", ...baseOptions, diff --git a/packages/openapi-fetch/test/index.test.ts b/packages/openapi-fetch/test/index.test.ts index 935e926b1..7125ac344 100644 --- a/packages/openapi-fetch/test/index.test.ts +++ b/packages/openapi-fetch/test/index.test.ts @@ -569,40 +569,49 @@ describe("client", () => { it("text", async () => { const client = createClient(); mockFetchOnce({ status: 200, body: "{}" }); - const { data }: { data?: string } = await client.GET("/anyMethod", { + const { data, error } = await client.GET("/anyMethod", { parseAs: "text", }); - expect(data).toBe("{}"); + if (error) { + throw new Error(`parseAs text: error`); + } + expect(data.toLowerCase()).toBe("{}"); }); it("arrayBuffer", async () => { const client = createClient(); mockFetchOnce({ status: 200, body: "{}" }); - const { data }: { data?: ArrayBuffer } = await client.GET( - "/anyMethod", - { parseAs: "arrayBuffer" }, - ); - expect(data instanceof ArrayBuffer).toBe(true); + const { data, error } = await client.GET("/anyMethod", { + parseAs: "arrayBuffer", + }); + if (error) { + throw new Error(`parseAs arrayBuffer: error`); + } + expect(data.byteLength).toBe(true); }); it("blob", async () => { const client = createClient(); mockFetchOnce({ status: 200, body: "{}" }); - const { data }: { data?: Blob } = await client.GET("/anyMethod", { + const { data, error } = await client.GET("/anyMethod", { parseAs: "blob", }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect((data as any).constructor.name).toBe("Blob"); + if (error) { + throw new Error(`parseAs blob: error`); + } + expect((data as any).constructor.name).toBe("Blob"); // eslint-disable-line @typescript-eslint/no-explicit-any }); it("stream", async () => { const client = createClient(); mockFetchOnce({ status: 200, body: "{}" }); - const { data }: { data?: ReadableStream | null } = await client.GET( - "/anyMethod", - { parseAs: "stream" }, - ); - expect(data instanceof Buffer).toBe(true); + const { data } = await client.GET("/anyMethod", { + parseAs: "stream", + }); + if (!data) { + throw new Error(`parseAs stream: error`); + } + expect(data.byteLength).toBe(8); }); }); }); From 8da1b0b06d17a03a926654b9d3c209e52020e2fb Mon Sep 17 00:00:00 2001 From: hugeletters Date: Tue, 28 Nov 2023 11:15:17 +0100 Subject: [PATCH 05/10] minor fix for stream reponses --- packages/openapi-fetch/src/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/openapi-fetch/src/index.d.ts b/packages/openapi-fetch/src/index.d.ts index e5ee414b0..10f9c0f42 100644 --- a/packages/openapi-fetch/src/index.d.ts +++ b/packages/openapi-fetch/src/index.d.ts @@ -46,7 +46,7 @@ export type ParseAsResponse = K extends "text" : K extends "arrayBuffer" ? Awaited> : K extends "stream" - ? Awaited> + ? Response["body"] : T; export interface DefaultParamsOption { From 0cf7012ec430fe33f78bf75a41b9fb47137116e0 Mon Sep 17 00:00:00 2001 From: hugeletters Date: Tue, 28 Nov 2023 11:27:16 +0100 Subject: [PATCH 06/10] add satisfies clauses to tests --- packages/openapi-fetch/test/index.test.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/openapi-fetch/test/index.test.ts b/packages/openapi-fetch/test/index.test.ts index 7125ac344..c3a545553 100644 --- a/packages/openapi-fetch/test/index.test.ts +++ b/packages/openapi-fetch/test/index.test.ts @@ -569,9 +569,9 @@ describe("client", () => { it("text", async () => { const client = createClient(); mockFetchOnce({ status: 200, body: "{}" }); - const { data, error } = await client.GET("/anyMethod", { + const { data, error } = (await client.GET("/anyMethod", { parseAs: "text", - }); + })) satisfies { data?: string }; if (error) { throw new Error(`parseAs text: error`); } @@ -581,9 +581,9 @@ describe("client", () => { it("arrayBuffer", async () => { const client = createClient(); mockFetchOnce({ status: 200, body: "{}" }); - const { data, error } = await client.GET("/anyMethod", { + const { data, error } = (await client.GET("/anyMethod", { parseAs: "arrayBuffer", - }); + })) satisfies { data?: ArrayBuffer }; if (error) { throw new Error(`parseAs arrayBuffer: error`); } @@ -593,9 +593,9 @@ describe("client", () => { it("blob", async () => { const client = createClient(); mockFetchOnce({ status: 200, body: "{}" }); - const { data, error } = await client.GET("/anyMethod", { + const { data, error } = (await client.GET("/anyMethod", { parseAs: "blob", - }); + })) satisfies { data?: Blob }; if (error) { throw new Error(`parseAs blob: error`); } @@ -605,12 +605,13 @@ describe("client", () => { it("stream", async () => { const client = createClient(); mockFetchOnce({ status: 200, body: "{}" }); - const { data } = await client.GET("/anyMethod", { + const { data } = (await client.GET("/anyMethod", { parseAs: "stream", - }); + })) satisfies { data?: ReadableStream | null }; if (!data) { throw new Error(`parseAs stream: error`); } + // todo - not sure what test to put here - previously it just checked for instanceof Buffer expect(data.byteLength).toBe(8); }); }); From 762efcab7be08753ea6765eff5b3b48954382da9 Mon Sep 17 00:00:00 2001 From: hugeletters Date: Tue, 28 Nov 2023 12:37:09 +0100 Subject: [PATCH 07/10] fix tests --- packages/openapi-fetch/test/index.test.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/openapi-fetch/test/index.test.ts b/packages/openapi-fetch/test/index.test.ts index c3a545553..478a435a9 100644 --- a/packages/openapi-fetch/test/index.test.ts +++ b/packages/openapi-fetch/test/index.test.ts @@ -587,7 +587,7 @@ describe("client", () => { if (error) { throw new Error(`parseAs arrayBuffer: error`); } - expect(data.byteLength).toBe(true); + expect(data.byteLength).toBe(2); }); it("blob", async () => { @@ -611,8 +611,13 @@ describe("client", () => { if (!data) { throw new Error(`parseAs stream: error`); } - // todo - not sure what test to put here - previously it just checked for instanceof Buffer - expect(data.byteLength).toBe(8); + + expect(data instanceof Buffer).toBe(true); + if (!(data instanceof Buffer)) { + throw Error("Data should be an instance of Buffer in Node context"); + } + + expect(data.byteLength).toBe(2); }); }); }); From e2adb209325b18f23d5ca21c32b7fdd4ce1c80b9 Mon Sep 17 00:00:00 2001 From: hugeletters Date: Tue, 28 Nov 2023 16:50:10 +0100 Subject: [PATCH 08/10] simplify filterKeys type helper --- packages/openapi-typescript-helpers/index.d.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/openapi-typescript-helpers/index.d.ts b/packages/openapi-typescript-helpers/index.d.ts index b7f851f11..d47bcb0d1 100644 --- a/packages/openapi-typescript-helpers/index.d.ts +++ b/packages/openapi-typescript-helpers/index.d.ts @@ -77,9 +77,7 @@ export type ErrorResponse = FilterKeys< // 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 = Obj[keyof Obj & Matchers]; /** 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 */ From eeebf6370d19cab528e82313a89927b24d28bac6 Mon Sep 17 00:00:00 2001 From: hugeletters Date: Tue, 28 Nov 2023 23:38:21 +0100 Subject: [PATCH 09/10] parseAs Record type --- packages/openapi-fetch/src/index.d.ts | 75 +++++------------------ packages/openapi-fetch/test/index.test.ts | 3 +- 2 files changed, 19 insertions(+), 59 deletions(-) diff --git a/packages/openapi-fetch/src/index.d.ts b/packages/openapi-fetch/src/index.d.ts index 10f9c0f42..b438e4f58 100644 --- a/packages/openapi-fetch/src/index.d.ts +++ b/packages/openapi-fetch/src/index.d.ts @@ -38,16 +38,15 @@ export type QuerySerializer = ( export type BodySerializer = (body: OperationRequestBodyContent) => any; -export type ParseAs = "json" | "text" | "blob" | "arrayBuffer" | "stream"; -export type ParseAsResponse = K extends "text" - ? Awaited> - : K extends "blob" - ? Awaited> - : K extends "arrayBuffer" - ? Awaited> - : K extends "stream" - ? Response["body"] - : T; +type BodyType = { + json: T; + text: Awaited>; + blob: Awaited>; + arrayBuffer: Awaited>; + stream: Response["body"]; +}; +export type ParseAs = keyof BodyType; +export type ParseAsResponse = K extends ParseAs ? BodyType[K] : T; export interface DefaultParamsOption { params?: { @@ -112,12 +111,7 @@ export default function createClient( >( url: P, ...init: I - ): Promise< - FetchResponse< - Paths[P]["get"], - I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" - > - >; + ): Promise>; /** Call a PUT endpoint */ PUT< P extends PathsWithMethod, @@ -125,12 +119,7 @@ export default function createClient( >( url: P, ...init: I - ): Promise< - FetchResponse< - Paths[P]["put"], - I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" - > - >; + ): Promise>; /** Call a POST endpoint */ POST< P extends PathsWithMethod, @@ -138,12 +127,7 @@ export default function createClient( >( url: P, ...init: I - ): Promise< - FetchResponse< - Paths[P]["post"], - I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" - > - >; + ): Promise>; /** Call a DELETE endpoint */ DELETE< P extends PathsWithMethod, @@ -151,12 +135,7 @@ export default function createClient( >( url: P, ...init: I - ): Promise< - FetchResponse< - Paths[P]["delete"], - I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" - > - >; + ): Promise>; /** Call a OPTIONS endpoint */ OPTIONS< P extends PathsWithMethod, @@ -164,12 +143,7 @@ export default function createClient( >( url: P, ...init: I - ): Promise< - FetchResponse< - Paths[P]["options"], - I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" - > - >; + ): Promise>; /** Call a HEAD endpoint */ HEAD< P extends PathsWithMethod, @@ -177,12 +151,7 @@ export default function createClient( >( url: P, ...init: I - ): Promise< - FetchResponse< - Paths[P]["head"], - I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" - > - >; + ): Promise>; /** Call a PATCH endpoint */ PATCH< P extends PathsWithMethod, @@ -190,12 +159,7 @@ export default function createClient( >( url: P, ...init: I - ): Promise< - FetchResponse< - Paths[P]["patch"], - I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" - > - >; + ): Promise>; /** Call a TRACE endpoint */ TRACE< P extends PathsWithMethod, @@ -203,12 +167,7 @@ export default function createClient( >( url: P, ...init: I - ): Promise< - FetchResponse< - Paths[P]["trace"], - I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" - > - >; + ): Promise>; }; /** Serialize query params to string */ diff --git a/packages/openapi-fetch/test/index.test.ts b/packages/openapi-fetch/test/index.test.ts index 478a435a9..6f1aa2833 100644 --- a/packages/openapi-fetch/test/index.test.ts +++ b/packages/openapi-fetch/test/index.test.ts @@ -599,7 +599,8 @@ describe("client", () => { if (error) { throw new Error(`parseAs blob: error`); } - expect((data as any).constructor.name).toBe("Blob"); // eslint-disable-line @typescript-eslint/no-explicit-any + + expect(data.constructor.name).toBe("Blob"); }); it("stream", async () => { From c2b2da7819ba06450d0ca94a479b6f92a02b129e Mon Sep 17 00:00:00 2001 From: hugeletters Date: Wed, 29 Nov 2023 10:57:40 +0100 Subject: [PATCH 10/10] use fetchOptions for generic --- packages/openapi-fetch/src/index.d.ts | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/openapi-fetch/src/index.d.ts b/packages/openapi-fetch/src/index.d.ts index b438e4f58..c0bc767ed 100644 --- a/packages/openapi-fetch/src/index.d.ts +++ b/packages/openapi-fetch/src/index.d.ts @@ -46,7 +46,11 @@ type BodyType = { stream: Response["body"]; }; export type ParseAs = keyof BodyType; -export type ParseAsResponse = K extends ParseAs ? BodyType[K] : T; +export type ParseAsResponse = O extends { + parseAs: ParseAs; +} + ? BodyType[O["parseAs"]] + : T; export interface DefaultParamsOption { params?: { @@ -78,11 +82,11 @@ export type MaybeOptionalInit< ? [(FetchOptions> | undefined)?] : [FetchOptions>]; -export type FetchResponse = +export type FetchResponse = | { data: ParseAsResponse< FilterKeys>, MediaType>, - R + O >; error?: never; response: Response; @@ -111,7 +115,7 @@ export default function createClient( >( url: P, ...init: I - ): Promise>; + ): Promise>; /** Call a PUT endpoint */ PUT< P extends PathsWithMethod, @@ -119,7 +123,7 @@ export default function createClient( >( url: P, ...init: I - ): Promise>; + ): Promise>; /** Call a POST endpoint */ POST< P extends PathsWithMethod, @@ -127,7 +131,7 @@ export default function createClient( >( url: P, ...init: I - ): Promise>; + ): Promise>; /** Call a DELETE endpoint */ DELETE< P extends PathsWithMethod, @@ -135,7 +139,7 @@ export default function createClient( >( url: P, ...init: I - ): Promise>; + ): Promise>; /** Call a OPTIONS endpoint */ OPTIONS< P extends PathsWithMethod, @@ -143,7 +147,7 @@ export default function createClient( >( url: P, ...init: I - ): Promise>; + ): Promise>; /** Call a HEAD endpoint */ HEAD< P extends PathsWithMethod, @@ -151,7 +155,7 @@ export default function createClient( >( url: P, ...init: I - ): Promise>; + ): Promise>; /** Call a PATCH endpoint */ PATCH< P extends PathsWithMethod, @@ -159,7 +163,7 @@ export default function createClient( >( url: P, ...init: I - ): Promise>; + ): Promise>; /** Call a TRACE endpoint */ TRACE< P extends PathsWithMethod, @@ -167,7 +171,7 @@ export default function createClient( >( url: P, ...init: I - ): Promise>; + ): Promise>; }; /** Serialize query params to string */