UNPKG

@moznion/openapi-fetch-gen

Version:

Generate TypeScript API client from OpenAPI TypeScript interface definitions created by openapi-typescript

269 lines (217 loc) 8.61 kB
# openapi-fetch-gen [![Run Tests](https://github.com/moznion/openapi-fetch-gen/actions/workflows/test.yml/badge.svg)](https://github.com/moznion/openapi-fetch-gen/actions/workflows/test.yml) Generate TypeScript API client from OpenAPI TypeScript interface definitions created by [openapi-typescript](https://github.com/openapi-ts/openapi-typescript). This tool takes TypeScript interface definitions generated by `openapi-typescript` and creates a fully typed API client using [openapi-fetch](https://github.com/openapi-ts/openapi-typescript/tree/main/packages/openapi-fetch). ## How It Works 1. Parse the TypeScript schema file generated by `openapi-typescript` 2. Extract all API endpoints, their HTTP methods, and parameter structures from the schema 3. Generate typed wrapper functions for each endpoint 4. Export a fully-typed client that provides: - A base client instance created with `createClient` from `openapi-fetch` - Individual typed functions for each API endpoint - Type helpers for parameters and responses ## Installation ```bash npm install --save-dev @moznion/openapi-fetch-gen ``` ## Usage ### CLI ```bash npx openapi-fetch-gen --input ./schema.d.ts --output ./client.ts ``` Options: ``` -V, --version output the version number -i, --input <path> path to input OpenAPI TypeScript definition file -o, --output <path> path to output generated client file (default: "./client.ts") --use-operation-id use operationId from OpenAPI schema for method names instead of generating from path (default: false) --parse-as-mapping <json-string> a mapping of response content types to their corresponding `parseAs` fetch options in the openapi-ts library (default: "{}") -h, --help display help for command ``` #### --parse-as-mapping option This option accepts a JSON string that maps response content types to their corresponding `parseAs` fetch options in the openapi-ts library (see also: https://openapi-ts.dev/openapi-fetch/api#fetch-options). For example, if the option value is `'{"application/pdf": "blob"}'`, the generated client will look like this: ```typescript return await this.client.GET("/pdf", { params, parseAs: "blob", }); ``` Here, the difference is that `parseAs: "blob"` has been added. If a single endpoint declares multiple return content types, this function prioritizes the first matching `parseAs` parameter based on the order of the declarations. ### Example Please refer to the [examples](./examples/). `schema.d.ts` is generated from `schema.yaml` by `openapi-typescript`, and `generated_client.ts` is generated by this tool according to the `schema.d.ts`. FYI, you can use the generated client as follows: ```typescript import { Client } from "./generated_client"; async function doSomething() { const client = new Client({ baseUrl: "https://api.example.com" }); const users = await client.getUsers({ query: { page: 1, pageSize: 10, membershipType: "PREMIUM", }, }); for (const user of users.data?.items ?? []) { console.log(`User: ${user.name}, Email: ${user.email}`); } } ``` ### Default HTTP Headers Generated clients support a generic type for default HTTP headers. Example: ```typescript export class Client<HT extends Record<string, string>> { constructor(clientOptions: ClientOptions, defaultHeaders?: HT) { this.client = createClient<paths>(clientOptions); this.defaultHeaders = defaultHeaders ?? ({} as HT); } ... } ``` You can create a client instance with default headers like this: ```typescript new Client({}, {"Authorization": "Bearer your-token", "Application-Version": "1.0.0"}); ``` With this setup, endpoint methods that require these headers no longer need them to be explicitly passed each time. For example, given the following schema: ```typescript "/users/bulk/{jobId}": { get: { parameters: { query?: never; header: { /** @description Authorization Header */ Authorization: string; /** @description Application version */ "Application-Version": string; /** @description Identifier of something */ "Something-Id": string; }; path: { /** @description Bulk import job identifier */ jobId: string; }; cookie?: never; }; ``` This tool generates an endpoint method using a type-level trick like this: ```typescript async getUsersBulkJobid( params: [ Exclude< // Missed Header Keys for default headers keyof { Authorization: string; "Application-Version": string; "Something-Id": string; }, Extract< // Provided header keys by default headers' keys keyof HT, keyof { Authorization: string; "Application-Version": string; "Something-Id": string; } > >, ] extends [never] ? { header?: { Authorization: string; "Application-Version": string; "Something-Id": string; }; path: { jobId: string }; } : { header: | (Pick< // Pick the header keys that are not in the default headers { Authorization: string; "Application-Version": string; "Something-Id": string; }, Exclude< // Missed Header Keys for default headers keyof { Authorization: string; "Application-Version": string; "Something-Id": string; }, Extract< // Provided header keys by default headers' keys keyof HT, keyof { Authorization: string; "Application-Version": string; "Something-Id": string; } > > > & Partial< // Disallow default headers' keys to be in the header param Record< Extract< // Provided header keys by default headers' keys keyof HT, keyof { Authorization: string; "Application-Version": string; "Something-Id": string; } >, never > >) | { Authorization: string; "Application-Version": string; "Something-Id": string; }; path: { jobId: string }; }, ) { return await this.client.GET("/users/bulk/{jobId}", { params: { ...params, header: { ...this.defaultHeaders, ...params.header } as { Authorization: string; "Application-Version": string; "Something-Id": string; }, }, }); } ``` This signature allows you to: - **Omit** the defaulted headers and only pass additional ones (here, `Something-Id`): ```typescript client.getUsersBulkJobid({header: {"Something-Id": "foobar"}, path: {jobId: "123"}}); ``` - **Override** all headers, including the defaults: ```typescript client.getUsersBulkJobid({header: {"Authorization": "foo", "Application-Version": "bar", "Something-Id": "foobar"}, path: {jobId: "123"}}); ``` If your default headers already include **all** required headers for the endpoint (e.g. `{"Authorization": "Bearer your-token", "Application-Version": "1.0.0", "Something-Id": "123"}` as the second constructor argument), you can omit the `header` parameter entirely: ```typescript client.getUsersBulkJobid({path: {jobId: "123"}}); ``` NOTE: In this context, the "default HTTP headers" are different from the headers in `ClientOptions`. The headers in `ClientOptions` are always sent implicitly, regardless of the header parameters specified in endpoint methods. In contrast, the "default HTTP headers" mechanism is intended to reduce the need to repeatedly specify common header parameters in each endpoint method when their values are already known. ## Articles - [openapi-fetch-gen – Generate TypeScript API client from OpenAPI TypeScript interface definitions created by openapi-typescript - DEV Community](https://dev.to/moznion/openapi-fetch-gen-generate-typescript-api-client-from-openapi-typescript-interface-definitions-kjd) ## For developers ### How to publish this packages ``` $ pnpm ship ``` ## License MIT