From 34f0b321a201724be29ddecaa1d8d1d6e13276f2 Mon Sep 17 00:00:00 2001 From: hugeletters Date: Sun, 5 Nov 2023 18:16:58 +0100 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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); }); }); });