From 8ce80893df024f74a411a811e712c3aad5e6707c Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Wed, 31 Jul 2024 09:53:22 +0200 Subject: [PATCH 1/4] feat: support `client["/endpoint"].GET()` style calls --- .changeset/stale-donuts-smoke.md | 5 +++ docs/openapi-fetch/index.md | 13 ++++++ packages/openapi-fetch/src/index.d.ts | 24 +++++++--- packages/openapi-fetch/src/index.js | 55 +++++++++-------------- packages/openapi-fetch/test/index.test.ts | 48 ++++++++++++++------ 5 files changed, 94 insertions(+), 51 deletions(-) create mode 100644 .changeset/stale-donuts-smoke.md diff --git a/.changeset/stale-donuts-smoke.md b/.changeset/stale-donuts-smoke.md new file mode 100644 index 000000000..6fca93c01 --- /dev/null +++ b/.changeset/stale-donuts-smoke.md @@ -0,0 +1,5 @@ +--- +"openapi-fetch": minor +--- + +Add support for `client["/endpoint"].GET()` style calls diff --git a/docs/openapi-fetch/index.md b/docs/openapi-fetch/index.md index 619c7d9c1..5da2dfb63 100644 --- a/docs/openapi-fetch/index.md +++ b/docs/openapi-fetch/index.md @@ -169,6 +169,19 @@ const { data, error, response } = await client.GET("/url"); | `error` | `5xx`, `4xx`, or `default` response if not OK; otherwise `undefined` | | `response` | [The original Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) which contains `status`, `headers`, etc. | +### Path-property style + +Alternatively to passing the path as parameter, you can select it as a property on the client: + +```ts +client["/blogposts/{post_id}"].GET({ + params: { post_id: "my-post" }, + query: { version: 2 }, +}); +``` + +This is strictly equivalent to `.GET("/blogposts/{post_id}", { ... } )`. + ## Support | Platform | Support | diff --git a/packages/openapi-fetch/src/index.d.ts b/packages/openapi-fetch/src/index.d.ts index 2ba91147f..438c589a5 100644 --- a/packages/openapi-fetch/src/index.d.ts +++ b/packages/openapi-fetch/src/index.d.ts @@ -156,18 +156,30 @@ export type MaybeOptionalInit, Location ex ? FetchOptions> | undefined : FetchOptions>; +// The final init param to accept. +// - Determines if the param is optional or not. +// - Performs arbitrary [key: string] addition. +// Note: the addition It MUST happen after all the inference happens (otherwise TS can’t infer if init is required or not). +type InitParam = HasRequiredKeys extends never + ? [(Init & { [key: string]: unknown })?] + : [Init & { [key: string]: unknown }]; + export type ClientMethod< Paths extends Record>, Method extends HttpMethod, Media extends MediaType, > = , Init extends MaybeOptionalInit>( url: Path, - ...init: HasRequiredKeys extends never - ? [(Init & { [key: string]: unknown })?] // note: the arbitrary [key: string]: addition MUST happen here after all the inference happens (otherwise TS can’t infer if it’s required or not) - : [Init & { [key: string]: unknown }] + ...init: InitParam ) => Promise>; -export interface Client { +export type ClientForPath, Media extends MediaType> = { + [Method in keyof PathInfo as Uppercase]: >( + ...init: InitParam + ) => Promise>; +}; + +export type Client>, Media extends MediaType = MediaType> = { /** Call a GET endpoint */ GET: ClientMethod; /** Call a PUT endpoint */ @@ -188,7 +200,9 @@ export interface Client { use(...middleware: Middleware[]): void; /** Unregister middleware */ eject(...middleware: Middleware[]): void; -} +} & { + [Path in keyof Paths]: ClientForPath; +}; export default function createClient( clientOptions?: ClientOptions, diff --git a/packages/openapi-fetch/src/index.js b/packages/openapi-fetch/src/index.js index e878ef0b8..524d74829 100644 --- a/packages/openapi-fetch/src/index.js +++ b/packages/openapi-fetch/src/index.js @@ -176,39 +176,15 @@ export default function createClient(clientOptions) { return { error, response }; } - return { - /** Call a GET endpoint */ - async GET(url, init) { - return coreFetch(url, { ...init, method: "GET" }); - }, - /** Call a PUT endpoint */ - async PUT(url, init) { - return coreFetch(url, { ...init, method: "PUT" }); - }, - /** Call a POST endpoint */ - async POST(url, init) { - return coreFetch(url, { ...init, method: "POST" }); - }, - /** Call a DELETE endpoint */ - async DELETE(url, init) { - return coreFetch(url, { ...init, method: "DELETE" }); - }, - /** Call a OPTIONS endpoint */ - async OPTIONS(url, init) { - return coreFetch(url, { ...init, method: "OPTIONS" }); - }, - /** Call a HEAD endpoint */ - async HEAD(url, init) { - return coreFetch(url, { ...init, method: "HEAD" }); - }, - /** Call a PATCH endpoint */ - async PATCH(url, init) { - return coreFetch(url, { ...init, method: "PATCH" }); - }, - /** Call a TRACE endpoint */ - async TRACE(url, init) { - return coreFetch(url, { ...init, method: "TRACE" }); - }, + const methods = ["GET", "PUT", "POST", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"]; + + const methodMembers = Object.fromEntries( + methods.map((method) => [method, (url, init) => coreFetch(url, { ...init, method })]), + ); + + const coreClient = { + ...methodMembers, + /** Register middleware */ use(...middleware) { for (const m of middleware) { @@ -231,6 +207,19 @@ export default function createClient(clientOptions) { } }, }; + + const handler = { + get: (coreClient, property) => { + if (property in coreClient) { + return coreClient[property]; + } + + // Assume the property is an URL. + return Object.fromEntries(methods.map((method) => [method, (init) => coreFetch(property, { ...init, method })])); + }, + }; + + return new Proxy(coreClient, handler); } // utils diff --git a/packages/openapi-fetch/test/index.test.ts b/packages/openapi-fetch/test/index.test.ts index 0055dcba7..8097dbaa2 100644 --- a/packages/openapi-fetch/test/index.test.ts +++ b/packages/openapi-fetch/test/index.test.ts @@ -21,19 +21,6 @@ afterEach(() => server.resetHandlers()); afterAll(() => server.close()); describe("client", () => { - it("generates all proper functions", () => { - const client = createClient(); - - expect(client).toHaveProperty("GET"); - expect(client).toHaveProperty("PUT"); - expect(client).toHaveProperty("POST"); - expect(client).toHaveProperty("DELETE"); - expect(client).toHaveProperty("OPTIONS"); - expect(client).toHaveProperty("HEAD"); - expect(client).toHaveProperty("PATCH"); - expect(client).toHaveProperty("TRACE"); - }); - describe("TypeScript checks", () => { it("marks data or error as undefined, but never both", async () => { const client = createClient({ @@ -1870,6 +1857,41 @@ describe("client", () => { ); }); }); + + describe("URL as property style call", () => { + it("performs a call without params", async () => { + const client = createClient({ baseUrl }); + const { getRequest } = useMockRequestHandler({ + baseUrl, + method: "get", + path: "/anyMethod", + }); + await client["/anyMethod"].GET(); + expect(getRequest().method).toBe("GET"); + }); + + it("performs a call with params", async () => { + const client = createClient({ baseUrl }); + const { getRequestUrl } = useMockRequestHandler({ + baseUrl, + method: "get", + path: "/blogposts/:post_id", + status: 200, + body: { message: "OK" }, + }); + + await client["/blogposts/{post_id}"].GET({ + // expect error on number instead of string. + // @ts-expect-error + params: { path: { post_id: 1234 } }, + }); + + await client["/blogposts/{post_id}"].GET({ + params: { path: { post_id: "1234" } }, + }); + expect(getRequestUrl().pathname).toBe("/blogposts/1234"); + }); + }); }); // test that the library behaves as expected inside commonly-used patterns From b8502ad816f63d6bf1860734694727da7ec89eee Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Fri, 2 Aug 2024 11:02:22 +0200 Subject: [PATCH 2/4] feat: only-pay-what-you-use alternative --- docs/openapi-fetch/api.md | 37 ++++++++ docs/openapi-fetch/index.md | 10 ++- packages/openapi-fetch/src/index.d.ts | 21 ++++- packages/openapi-fetch/src/index.js | 103 ++++++++++++++++++---- packages/openapi-fetch/test/index.test.ts | 99 +++++++++++++++++++-- 5 files changed, 240 insertions(+), 30 deletions(-) diff --git a/docs/openapi-fetch/api.md b/docs/openapi-fetch/api.md index 0a337aaae..542f632b0 100644 --- a/docs/openapi-fetch/api.md +++ b/docs/openapi-fetch/api.md @@ -40,6 +40,43 @@ client.GET("/my-url", options); | `middleware` | `Middleware[]` | [See docs](/openapi-fetch/middleware-auth) | | (Fetch options) | | Any valid fetch option (`headers`, `mode`, `cache`, `signal`, …) ([docs](https://developer.mozilla.org/en-US/docs/Web/API/fetch#options)) | +## wrapAsPathBasedClient + +**wrapAsPathBasedClient** wraps the result of `createClient()` to return a [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)-based client that allows path-indexed calls: + +```ts +const client = createClient(clientOptions); +const pathBasedClient = wrapAsPathBasedClient(client); + +pathBasedClient["/my-url"].GET(fetchOptions); +``` + +The `fetchOptions` are the same than for the base client. + +A path based client can lead to better type inference but comes at a runtime cost due to the use of a Proxy. + +**createPathBasedClient** is a convenience method combining `createClient` and `wrapAsPathBasedClient` if you only want to use the path based call style: + +```ts +const client = createPathBasedClient(clientOptions); + +client["/my-url"].GET(fetchOptions); +``` + +Note that it does not allow you to attach middlewares. If you need middlewares, you need to use the full form: + +```ts +const client = createClient(clientOptions); + +client.use(...); + +const pathBasedClient = wrapAsPathBasedClient(client); + +client.use(...); // the client reference is shared, so the middlewares will propagate. + +pathBasedClient["/my-url"].GET(fetchOptions); +``` + ## querySerializer OpenAPI supports [different ways of serializing objects and arrays](https://swagger.io/docs/specification/serialization/#query) for parameters (strings, numbers, and booleans—primitives—always behave the same way). By default, this library serializes arrays using `style: "form", explode: true`, and objects using `style: "deepObject", explode: true`, but you can customize that behavior with the `querySerializer` option (either on `createClient()` to control every request, or on individual requests for just one). diff --git a/docs/openapi-fetch/index.md b/docs/openapi-fetch/index.md index 5da2dfb63..1f57879fa 100644 --- a/docs/openapi-fetch/index.md +++ b/docs/openapi-fetch/index.md @@ -171,16 +171,22 @@ const { data, error, response } = await client.GET("/url"); ### Path-property style -Alternatively to passing the path as parameter, you can select it as a property on the client: +If you prefer selecting the path as a property, you can create a path based client: ```ts +import { createPathBasedClient } from "openapi-fetch"; +import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript + +const client = createPathBasedClient({ baseUrl: "https://myapi.dev/v1" }); + client["/blogposts/{post_id}"].GET({ params: { post_id: "my-post" }, query: { version: 2 }, }); ``` -This is strictly equivalent to `.GET("/blogposts/{post_id}", { ... } )`. +Note that this has performance implications and does not allow to attach middlewares directly. +See [`wrapAsPathBasedClient`](/openapi-fetch/api#wrapAsPathBasedClient) for more. ## Support diff --git a/packages/openapi-fetch/src/index.d.ts b/packages/openapi-fetch/src/index.d.ts index 438c589a5..2dde5350a 100644 --- a/packages/openapi-fetch/src/index.d.ts +++ b/packages/openapi-fetch/src/index.d.ts @@ -179,7 +179,7 @@ export type ClientForPath, Media extends ) => Promise>; }; -export type Client>, Media extends MediaType = MediaType> = { +export interface Client { /** Call a GET endpoint */ GET: ClientMethod; /** Call a PUT endpoint */ @@ -200,14 +200,27 @@ export type Client>, Media e use(...middleware: Middleware[]): void; /** Unregister middleware */ eject(...middleware: Middleware[]): void; -} & { - [Path in keyof Paths]: ClientForPath; -}; +} export default function createClient( clientOptions?: ClientOptions, ): Client; +export type PathBasedClient< + Paths extends Record>, + Media extends MediaType = MediaType, +> = { + [Path in keyof Paths]: ClientForPath; +}; + +export declare function wrapAsPathBasedClient( + client: Client, +): PathBasedClient; + +export declare function createPathBasedClient( + clientOptions?: ClientOptions, +): PathBasedClient; + /** Serialize primitive params to string */ export declare function serializePrimitiveParam( name: string, diff --git a/packages/openapi-fetch/src/index.js b/packages/openapi-fetch/src/index.js index 524d74829..04b3cf57c 100644 --- a/packages/openapi-fetch/src/index.js +++ b/packages/openapi-fetch/src/index.js @@ -176,15 +176,39 @@ export default function createClient(clientOptions) { return { error, response }; } - const methods = ["GET", "PUT", "POST", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"]; - - const methodMembers = Object.fromEntries( - methods.map((method) => [method, (url, init) => coreFetch(url, { ...init, method })]), - ); - - const coreClient = { - ...methodMembers, - + return { + /** Call a GET endpoint */ + async GET(url, init) { + return coreFetch(url, { ...init, method: "GET" }); + }, + /** Call a PUT endpoint */ + async PUT(url, init) { + return coreFetch(url, { ...init, method: "PUT" }); + }, + /** Call a POST endpoint */ + async POST(url, init) { + return coreFetch(url, { ...init, method: "POST" }); + }, + /** Call a DELETE endpoint */ + async DELETE(url, init) { + return coreFetch(url, { ...init, method: "DELETE" }); + }, + /** Call a OPTIONS endpoint */ + async OPTIONS(url, init) { + return coreFetch(url, { ...init, method: "OPTIONS" }); + }, + /** Call a HEAD endpoint */ + async HEAD(url, init) { + return coreFetch(url, { ...init, method: "HEAD" }); + }, + /** Call a PATCH endpoint */ + async PATCH(url, init) { + return coreFetch(url, { ...init, method: "PATCH" }); + }, + /** Call a TRACE endpoint */ + async TRACE(url, init) { + return coreFetch(url, { ...init, method: "TRACE" }); + }, /** Register middleware */ use(...middleware) { for (const m of middleware) { @@ -207,19 +231,60 @@ export default function createClient(clientOptions) { } }, }; +} - const handler = { - get: (coreClient, property) => { - if (property in coreClient) { - return coreClient[property]; - } +class UrlCallForwarder { + constructor(client, url) { + this.client = client; + this.url = url; + } - // Assume the property is an URL. - return Object.fromEntries(methods.map((method) => [method, (init) => coreFetch(property, { ...init, method })])); - }, - }; + GET(init) { + return this.client.GET(this.url, init); + } + PUT(init) { + return this.client.PUT(this.url, init); + } + POST(init) { + return this.client.POST(this.url, init); + } + DELETE(init) { + return this.client.DELETE(this.url, init); + } + OPTIONS(init) { + return this.client.OPTIONS(this.url, init); + } + HEAD(init) { + return this.client.HEAD(this.url, init); + } + PATCH(init) { + return this.client.PATCH(this.url, init); + } + TRACE(init) { + return this.client.TRACE(this.url, init); + } +} - return new Proxy(coreClient, handler); +const clientProxyHandler = { + // Assume the property is an URL. + get: (coreClient, url) => new UrlCallForwarder(coreClient, url), +}; + +/** + * Wrap openapi-fetch client to support a path based API. + * @type {import("./index.js").wrapAsPathBasedClient} + */ +export function wrapAsPathBasedClient(coreClient) { + return new Proxy(coreClient, clientProxyHandler); +} + +/** + * Convenience method to an openapi-fetch path based client. + * Strictly equivalent to `wrapAsPathBasedClient(createClient(...))`. + * @type {import("./index.js").createPathBasedClient} + */ +export function createPathBasedClient(clientOptions) { + return wrapAsPathBasedClient(createClient(clientOptions)); } // utils diff --git a/packages/openapi-fetch/test/index.test.ts b/packages/openapi-fetch/test/index.test.ts index 8097dbaa2..fe79c4e15 100644 --- a/packages/openapi-fetch/test/index.test.ts +++ b/packages/openapi-fetch/test/index.test.ts @@ -4,6 +4,7 @@ import createClient, { type Middleware, type MiddlewareCallbackParams, type QuerySerializerOptions, + createPathBasedClient, } from "../src/index.js"; import { server, baseUrl, useMockRequestHandler, toAbsoluteURL } from "./fixtures/mock-server.js"; import type { paths } from "./fixtures/api.js"; @@ -21,6 +22,19 @@ afterEach(() => server.resetHandlers()); afterAll(() => server.close()); describe("client", () => { + it("generates all proper functions", () => { + const client = createClient(); + + expect(client).toHaveProperty("GET"); + expect(client).toHaveProperty("PUT"); + expect(client).toHaveProperty("POST"); + expect(client).toHaveProperty("DELETE"); + expect(client).toHaveProperty("OPTIONS"); + expect(client).toHaveProperty("HEAD"); + expect(client).toHaveProperty("PATCH"); + expect(client).toHaveProperty("TRACE"); + }); + describe("TypeScript checks", () => { it("marks data or error as undefined, but never both", async () => { const client = createClient({ @@ -1858,9 +1872,10 @@ describe("client", () => { }); }); - describe("URL as property style call", () => { + describe("path based client", () => { it("performs a call without params", async () => { - const client = createClient({ baseUrl }); + const client = createPathBasedClient({ baseUrl }); + const { getRequest } = useMockRequestHandler({ baseUrl, method: "get", @@ -1871,13 +1886,23 @@ describe("client", () => { }); it("performs a call with params", async () => { - const client = createClient({ baseUrl }); + const client = createPathBasedClient({ baseUrl }); const { getRequestUrl } = useMockRequestHandler({ baseUrl, method: "get", path: "/blogposts/:post_id", status: 200, - body: { message: "OK" }, + body: { title: "Blog post title" }, + }); + + // Wrong method + // @ts-expect-error + await client["/blogposts/{post_id}"].POST({ + params: { + // Unknown property `path`. + // @ts-expect-error + path: { post_id: "1234" }, + }, }); await client["/blogposts/{post_id}"].GET({ @@ -1886,10 +1911,74 @@ describe("client", () => { params: { path: { post_id: 1234 } }, }); - await client["/blogposts/{post_id}"].GET({ + const { data, error } = await client["/blogposts/{post_id}"].GET({ params: { path: { post_id: "1234" } }, }); + expect(getRequestUrl().pathname).toBe("/blogposts/1234"); + + // Check typing of data. + if (error) { + // Fail, but we need the if above for type inference. + expect(error).toBeUndefined(); + } else { + // @ts-expect-error + data.not_a_blogpost_property; + // Check typing of result value. + expect(data.title).toBe("Blog post title"); + } + }); + + it("performs a POST call", async () => { + const client = createPathBasedClient({ baseUrl }); + const { getRequest } = useMockRequestHandler({ + baseUrl, + method: "post", + path: "/anyMethod", + }); + await client["/anyMethod"].POST(); + expect(getRequest().method).toBe("POST"); + }); + + it("performs a PUT call with a request body", async () => { + const mockData = { status: "success" }; + + const client = createPathBasedClient({ baseUrl }); + const { getRequestUrl } = useMockRequestHandler({ + baseUrl, + method: "put", + path: "/blogposts", + status: 201, + body: mockData, + }); + + await client["/blogposts"].PUT({ + body: { + title: "New Post", + body: "

Best post yet

", + // Should be a number, not a Date. + // @ts-expect-error + publish_date: new Date("2023-03-31T12:00:00Z"), + }, + }); + + const { data, error, response } = await client["/blogposts"].PUT({ + body: { + title: "New Post", + body: "

Best post yet

", + publish_date: new Date("2023-03-31T12:00:00Z").getTime(), + }, + }); + + // assert correct URL was called + expect(getRequestUrl().pathname).toBe("/blogposts"); + + // assert correct data was returned + expect(data).toEqual(mockData); + expect(response.status).toBe(201); + + // assert error is empty + expect(error).toBeUndefined(); }); }); }); From f13f95d5fe4f10f8f51328f9af1118c3fe0469a2 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Fri, 2 Aug 2024 12:09:33 +0200 Subject: [PATCH 3/4] Use prototype chain to memoize PathCallForwarder (and rename) --- packages/openapi-fetch/src/index.js | 35 ++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/packages/openapi-fetch/src/index.js b/packages/openapi-fetch/src/index.js index 04b3cf57c..ded445de6 100644 --- a/packages/openapi-fetch/src/index.js +++ b/packages/openapi-fetch/src/index.js @@ -233,7 +233,7 @@ export default function createClient(clientOptions) { }; } -class UrlCallForwarder { +class PathCallForwarder { constructor(client, url) { this.client = client; this.url = url; @@ -265,17 +265,42 @@ class UrlCallForwarder { } } -const clientProxyHandler = { +class PathClientProxyHandler { + constructor() { + this.client = null; + } + // Assume the property is an URL. - get: (coreClient, url) => new UrlCallForwarder(coreClient, url), -}; + get(coreClient, url) { + const forwarder = new PathCallForwarder(coreClient, url); + this.client[url] = forwarder; + return forwarder; + } +} /** * Wrap openapi-fetch client to support a path based API. * @type {import("./index.js").wrapAsPathBasedClient} */ export function wrapAsPathBasedClient(coreClient) { - return new Proxy(coreClient, clientProxyHandler); + const handler = new PathClientProxyHandler(); + const proxy = new Proxy(coreClient, handler); + + // Put the proxy on the prototype chain of the actual client. + // This means if we do not have a memoized PathCallForwarder, + // we fall back to the proxy to synthesize it. + // However, the proxy itself is not on the hot-path (if we fetch the same + // endpoint multiple times, only the first call will hit the proxy). + function Client() {} + Client.prototype = proxy; + + const client = new Client(); + + // Feed the client back to the proxy handler so it can store the generated + // PathCallForwarder. + handler.client = client; + + return client; } /** From f5702940f28731148736696b169d1c39ad879d4b Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Fri, 2 Aug 2024 14:09:11 +0200 Subject: [PATCH 4/4] Add benchmarks --- packages/openapi-fetch/test/index.bench.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/openapi-fetch/test/index.bench.js b/packages/openapi-fetch/test/index.bench.js index 99463b3ba..fdf780870 100644 --- a/packages/openapi-fetch/test/index.bench.js +++ b/packages/openapi-fetch/test/index.bench.js @@ -2,10 +2,9 @@ import axios from "axios"; import { http, HttpResponse } from "msw"; import { setupServer } from "msw/node"; import { Fetcher } from "openapi-typescript-fetch"; -import { nanoid } from "nanoid"; import superagent from "superagent"; import { afterAll, bench, describe } from "vitest"; -import createClient from "../dist/index.js"; +import createClient, { createPathBasedClient } from "../dist/index.js"; import * as openapiTSCodegen from "./fixtures/openapi-typescript-codegen.min.js"; const BASE_URL = "https://api.test.local"; @@ -41,6 +40,10 @@ describe("setup", () => { createClient({ baseUrl: BASE_URL }); }); + bench("openapi-fetch (path based)", async () => { + createPathBasedClient({ baseUrl: BASE_URL }); + }); + bench("openapi-typescript-fetch", async () => { const fetcher = Fetcher.for(); fetcher.configure({ @@ -60,6 +63,7 @@ describe("setup", () => { describe("get (only URL)", () => { const openapiFetch = createClient({ baseUrl: BASE_URL }); + const openapiFetchPath = createPathBasedClient({ baseUrl: BASE_URL }); const openapiTSFetch = Fetcher.for(); openapiTSFetch.configure({ baseUrl: BASE_URL, @@ -74,6 +78,10 @@ describe("get (only URL)", () => { await openapiFetch.GET("/url"); }); + bench("openapi-fetch (path based)", async () => { + await openapiFetchPath["/url"].GET(); + }); + bench("openapi-typescript-fetch", async () => { await openapiTSFetchGET(); }); @@ -96,6 +104,10 @@ describe("get (headers)", () => { baseUrl: BASE_URL, headers: { "x-base-header": 123 }, }); + const openapiFetchPath = createPathBasedClient({ + baseUrl: BASE_URL, + headers: { "x-base-header": 123 }, + }); const openapiTSFetch = Fetcher.for(); openapiTSFetch.configure({ baseUrl: BASE_URL, @@ -113,6 +125,12 @@ describe("get (headers)", () => { }); }); + bench("openapi-fetch (path based)", async () => { + await openapiFetchPath["/url"].GET({ + headers: { "x-header-1": 123, "x-header-2": 456 }, + }); + }); + bench("openapi-typescript-fetch", async () => { await openapiTSFetchGET(null, { headers: { "x-header-1": 123, "x-header-2": 456 },