apigen-ts 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/readme.md CHANGED
@@ -3,7 +3,7 @@
3
3
  <div align="center">
4
4
 
5
5
  [<img src="https://badges.ws/npm/v/apigen-ts" alt="version" />](https://npmjs.org/package/apigen-ts)
6
- [<img src="https://badges.ws/packagephobia/publish/apigen-ts" alt="size" />](https://packagephobia.now.sh/result?p=apigen-ts)
6
+ [<img src="https://packagephobia.com/badge?p=array-utils-ts" alt="size" />](https://packagephobia.now.sh/result?p=apigen-ts)
7
7
  [<img src="https://badges.ws/npm/dm/apigen-ts" alt="downloads" />](https://npmjs.org/package/apigen-ts)
8
8
  [<img src="https://badges.ws/github/license/vladkens/apigen-ts" alt="license" />](https://github.com/vladkens/apigen-ts/blob/main/LICENSE)
9
9
  [<img src="https://badges.ws/badge/-/buy%20me%20a%20coffee/ff813f?icon=buymeacoffee&label" alt="donate" />](https://buymeacoffee.com/vladkens)
@@ -14,25 +14,22 @@
14
14
  <img src="./logo.svg" alt="apigen-ts logo" height="80" />
15
15
  </div>
16
16
 
17
- ## Features
17
+ Turn your OpenAPI spec into a typed TypeScript client with one command.
18
18
 
19
- - Generates ready to use `ApiClient` with types (using `fetch`)
20
- - Single output file, minimal third-party code
21
- - Loads schemas from JSON / YAML, locally and remote
22
- - Ability to customize `fetch` with your custom function
23
- - Automatic formating with Prettier
24
- - Can parse dates from date-time format (`--parse-dates` flag)
25
- - Support OpenAPI v2, v3, v3.1
26
- - Can be used with npx as well
19
+ - **One file.** Outputs a single `api-client.ts` no scattered modules, no runtime deps in generated code.
20
+ - **Fully typed.** Every method returns the exact response type from your schema. No casting, no `any`.
21
+ - **Pure Node.js.** No Java, no Docker. Works with `npx` in any project.
22
+ - **Fetch-based.** Uses native `fetch`. Override it with your own function for auth, retries, or logging.
23
+ - **All OpenAPI versions.** Supports v2 (Swagger), v3, and v3.1 — auto-upgrades v2 on the fly.
24
+ - **Extras built in.** Automatic date parsing, string literal unions instead of enums, Prettier formatting.
25
+ - **Filterable.** Include or exclude endpoints by path regex or tag — essential for large schemas.
27
26
 
28
- ## Install
27
+ Unlike `openapi-typescript`, it generates a ready-to-call client — not just types. Unlike `openapi-generator-cli`, it's pure Node.js with zero Java dependency. Unlike `openapi-typescript-codegen`, it outputs a single file.
29
28
 
30
- ```sh
31
- npm install apigen-ts --save-dev
32
- ```
29
+ ## Install
33
30
 
34
31
  ```sh
35
- yarn add -D apigen-ts
32
+ npm i apigen-ts --save-dev
36
33
  ```
37
34
 
38
35
  ## Usage
@@ -40,17 +37,17 @@ yarn add -D apigen-ts
40
37
  ### 1. Generate
41
38
 
42
39
  ```sh
43
- # From file
44
- yarn apigen-ts ./openapi.json ./api-client.ts
40
+ # From a local file
41
+ npx apigen-ts ./openapi.json ./api-client.ts
45
42
 
46
- # From url
47
- yarn apigen-ts https://petstore3.swagger.io/api/v3/openapi.json ./api-client.ts
43
+ # From a URL
44
+ npx apigen-ts https://petstore3.swagger.io/api/v3/openapi.json ./api-client.ts
48
45
 
49
- # From protected url
50
- yarn apigen-ts https://secret-api.example.com ./api-client.ts -H "x-api-key: secret-key"
46
+ # From a protected URL
47
+ npx apigen-ts https://secret-api.example.com ./api-client.ts -H "x-api-key: secret-key"
51
48
  ```
52
49
 
53
- Run `yarn apigen-ts --help` for more options. Examples of generated clients [here](./examples/).
50
+ Run `npx apigen-ts --help` for all options. See [generated examples](./examples/).
54
51
 
55
52
  ### 2. Import
56
53
 
@@ -67,12 +64,12 @@ const api = new ApiClient({
67
64
 
68
65
  ```ts
69
66
  // GET /pet/{petId}
70
- await api.pet.getPetById(1) // -> Pet
67
+ await api.pet.getPetById(1) // Pet
71
68
 
72
69
  // GET /pet/findByStatus?status=sold
73
- await api.pet.findPetsByStatus({ status: "sold" }) // -> Pets[]
70
+ await api.pet.findPetsByStatus({ status: "sold" }) // Pet[]
74
71
 
75
- // PUT /user/{username}; second arg body with type User
72
+ // PUT /user/{username} second arg is typed request body
76
73
  await api.user.updateUser("username", { firstName: "John" })
77
74
  ```
78
75
 
@@ -81,81 +78,108 @@ await api.user.updateUser("username", { firstName: "John" })
81
78
  ### Login flow
82
79
 
83
80
  ```ts
84
- const { token } = await api.auth.login({ usename, password })
81
+ const { token } = await api.auth.login({ username, password })
85
82
  api.Config.headers = { Authorization: token }
86
83
 
87
- await api.protectedRoute.get() // here authenticated
84
+ await api.protectedRoute.get() // authenticated
88
85
  ```
89
86
 
90
87
  ### Automatic date parsing
91
88
 
92
89
  ```sh
93
- yarn apigen-ts ./openapi.json ./api-client.ts --parse-dates
90
+ npx apigen-ts ./openapi.json ./api-client.ts --parse-dates
94
91
  ```
95
92
 
96
93
  ```ts
97
94
  const pet = await api.pet.getPetById(1)
98
- const createdAt: Date = pet.createdAt // date parsed from string with format=date-time
95
+ const createdAt: Date = pet.createdAt // parsed from format=date-time string
99
96
  ```
100
97
 
101
- ### String union as enums
98
+ ### String unions instead of enums
102
99
 
103
- You can generate string literal union instead of native enums in case you want to run in Node.js environment with [type-stripping](https://nodejs.org/api/typescript.html#type-stripping). To achive this pass `--inline-enums` command line argument or use `inlineEnums: true` in Node.js API.
100
+ Pass `--inline-enums` to generate string literal unions useful for Node.js [type stripping](https://nodejs.org/api/typescript.html#type-stripping):
104
101
 
105
102
  ```sh
106
- yarn apigen-ts ./openapi.json ./api-client.ts --inline-enums
103
+ npx apigen-ts ./openapi.json ./api-client.ts --inline-enums
107
104
  ```
108
105
 
109
- This will generate:
110
-
111
106
  ```ts
107
+ // Generated:
112
108
  type MyEnum = "OptionA" | "OptionB"
113
109
 
114
- // instead of
115
- enum MyEnum = {
110
+ // Instead of:
111
+ enum MyEnum {
116
112
  OptionA = "OptionA",
117
- OptionB = "OptionB"
113
+ OptionB = "OptionB",
118
114
  }
119
115
  ```
120
116
 
121
- ### Errors handling
117
+ ### Filter by path
118
+
119
+ Include only the endpoints you need — useful with large schemas (e.g. Cloudflare's 8 MB monolith):
120
+
121
+ ```sh
122
+ npx apigen-ts ./openapi.json ./api-client.ts --filter-paths '^/accounts'
123
+ ```
124
+
125
+ ### Filter by tag
126
+
127
+ ```sh
128
+ # include only endpoints tagged "pets" or "store"
129
+ npx apigen-ts ./openapi.json ./api-client.ts --include-tags pets,store
130
+
131
+ # exclude endpoints tagged "internal"
132
+ npx apigen-ts ./openapi.json ./api-client.ts --exclude-tags internal
133
+ ```
134
+
135
+ When both flags are set, `--exclude-tags` wins.
136
+
137
+ ### AbortController / cancellation
138
+
139
+ Pass `--fetch-options` to add an optional last argument to every generated method, accepting any [`RequestInit`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) field (including `signal`):
140
+
141
+ ```sh
142
+ npx apigen-ts ./openapi.json ./api-client.ts --fetch-options
143
+ ```
144
+
145
+ ```ts
146
+ const controller = new AbortController()
147
+ await api.pet.getPetById(1, { signal: controller.signal })
122
148
 
123
- An exception will be thrown for all unsuccessful return codes.
149
+ // cancel the request
150
+ controller.abort()
151
+ ```
152
+
153
+ ### Error handling
154
+
155
+ Non-2xx responses throw — the caught value is the parsed response body:
124
156
 
125
157
  ```ts
126
158
  try {
127
- const pet = await api.pet.getPetById(404)
159
+ await api.pet.getPetById(404)
128
160
  } catch (e) {
129
- console.log(e) // parse error depend of your domain model, e is awaited response.json()
161
+ console.log(e) // awaited response.json()
130
162
  }
131
163
  ```
132
164
 
133
- Also you can define custom function to parse error:
165
+ Override `ParseError` to control the shape:
134
166
 
135
167
  ```ts
136
168
  class MyClient extends ApiClient {
137
169
  async ParseError(rep: Response) {
138
- // do what you want
139
170
  return { code: "API_ERROR" }
140
171
  }
141
172
  }
142
-
143
- try {
144
- const api = new MyClient()
145
- const pet = await api.pet.getPetById(404)
146
- } catch (e) {
147
- console.log(e) // e is { code: "API_ERROR" }
148
- }
149
173
  ```
150
174
 
151
- ### Base url resolving
175
+ ### Base URL resolving
152
176
 
153
- You can modify how the endpoint url is created. By default [URL constructor](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) used to resolve endpoint url like: `new URL(path, baseUrl)` which has specific resolving [rules](https://developer.mozilla.org/en-US/docs/Web/API/URL_API/Resolving_relative_references). E.g.:
177
+ By default uses the [URL constructor](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL): `new URL(path, baseUrl)`. Notable behavior:
154
178
 
155
- - `new URL("/v2/cats", "https://example.com/v1/") // -> https://example.com/v2/cats`
156
- - `new URL("v2/cats", "https://example.com/v1/") // -> https://example.com/v1/v2/cats`
179
+ - `new URL("/v2/cats", "https://example.com/v1/")` `https://example.com/v2/cats`
180
+ - `new URL("v2/cats", "https://example.com/v1/")` `https://example.com/v1/v2/cats`
157
181
 
158
- If you want to have custom endpoint url resolving rules, you can override `PrepareFetchUrl` method. For more details see [issue](https://github.com/vladkens/apigen-ts/issues/2).
182
+ Override `PrepareFetchUrl` to change this (see [#2](https://github.com/vladkens/apigen-ts/issues/2)):
159
183
 
160
184
  ```ts
161
185
  class MyClient extends ApiClient {
@@ -165,42 +189,39 @@ class MyClient extends ApiClient {
165
189
  }
166
190
 
167
191
  const api = new MyClient({ baseUrl: "https://example.com/v1" })
168
- // will call: https://example.com/v1/pet/ instead of https://example.com/pet/
169
- const pet = await api.pet.getPetById(404)
192
+ await api.pet.getPetById(1) // https://example.com/v1/pet/1
170
193
  ```
171
194
 
172
195
  ### Node.js API
173
196
 
174
- Create file like `apigen.mjs` with content:
175
-
176
197
  ```js
177
198
  import { apigen } from "apigen-ts"
178
199
 
179
200
  await apigen({
180
201
  source: "https://petstore3.swagger.io/api/v3/openapi.json",
181
202
  output: "./api-client.ts",
182
- // everything below is optional
183
- name: "MyApiClient", // default "ApiClient"
184
- parseDates: true, // default false
185
- inlineEnums: false, // default false, use string literal union instead of enum
186
- headers: { "x-api-key": "secret-key" }, // Custom HTTP headers to use when fetching schema
203
+ // optional:
204
+ name: "MyApiClient", // default: "ApiClient"
205
+ parseDates: true, // default: false
206
+ inlineEnums: false, // default: false
207
+ fetchOptions: true, // default: false
208
+ filterPaths: /^\/pets/, // only include paths matching regex
209
+ includeTags: ["pets", "store"], // only include these tags
210
+ excludeTags: ["internal"], // exclude these tags (wins over includeTags)
211
+ headers: { "x-api-key": "secret-key" },
187
212
  resolveName(ctx, op, proposal) {
188
- // proposal is [string, string] which represents module.funcName
189
- if (proposal[0] === "users") return // will use default proposal
213
+ // proposal is [namespace, methodName]
214
+ if (proposal[0] === "users") return // use default
190
215
 
191
- const [a, b] = op.name.split("/").slice(3, 5) // eg. /api/v1/store/items/search
192
- return [a, `${op.method}_${b}`] // [store, 'get_items'] -> apiClient.store.get_items()
216
+ const [a, b] = op.name.split("/").slice(3, 5) // /api/v1/store/items/search
217
+ return [a, `${op.method}_${b}`] // api.store.get_items()
193
218
  },
194
219
  })
195
220
  ```
196
221
 
197
- Then run with: `node apigen.mjs`
222
+ ## Usage with FastAPI
198
223
 
199
- ## Usage with different backend frameworks
200
-
201
- ### FastAPI
202
-
203
- By default `apigen-ts` generates noisy method names when used with FastAPI. This can be fixed by [custom resolving](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#using-the-path-operation-function-name-as-the-operationid) for operations ids.
224
+ By default, FastAPI generates verbose `operationId`s. Fix with a custom resolver:
204
225
 
205
226
  ```py
206
227
  from fastapi import FastAPI
@@ -216,19 +237,18 @@ def update_operation_ids(app: FastAPI) -> None:
216
237
  ns = route.tags[0] if route.tags else "general"
217
238
  route.operation_id = f"{ns}_{route.name}".lower()
218
239
 
219
-
220
- # this function should be after all routes added
240
+ # call after all routes are added
221
241
  update_operation_ids(app)
222
242
  ```
223
243
 
224
- _Note: If you want FastAPI to be added as preset, open PR please._
225
-
226
- ## Compare
244
+ ## Alternatives
227
245
 
228
- - [openapi-typescript-codegen](https://github.com/ferdikoomen/openapi-typescript-codegen) ([npm](https://www.npmjs.com/package/openapi-typescript-codegen)): no single file mode [#1263](https://github.com/ferdikoomen/openapi-typescript-codegen/issues/1263#issuecomment-1502890838)
229
- - [openapi-typescript](https://github.com/drwpow/openapi-typescript) ([npm](https://www.npmjs.com/package/openapi-typescript)): low level api; no named types export to use in client code
230
- - [openapi-generator-cli](https://github.com/OpenAPITools/openapi-generator-cli) ([npm](https://www.npmjs.com/package/@openapitools/openapi-generator-cli)): wrapper around java lib
231
- - [swagger-typescript-api](https://github.com/acacode/swagger-typescript-api) ([npm](https://www.npmjs.com/package/swagger-typescript-api)): complicated configuration; user-api breaking changes between versions
246
+ | Package | Issue |
247
+ | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
248
+ | [openapi-typescript-codegen](https://github.com/ferdikoomen/openapi-typescript-codegen) | No single-file output ([#1263](https://github.com/ferdikoomen/openapi-typescript-codegen/issues/1263#issuecomment-1502890838)) |
249
+ | [openapi-typescript](https://github.com/drwpow/openapi-typescript) | Low-level types only no callable client, no named type exports |
250
+ | [openapi-generator-cli](https://github.com/OpenAPITools/openapi-generator-cli) | Wraps a Java library |
251
+ | [swagger-typescript-api](https://github.com/acacode/swagger-typescript-api) | Complex config, breaking API changes between versions |
232
252
 
233
253
  ## Development
234
254
 
package/dist/cli.cjs DELETED
@@ -1,18 +0,0 @@
1
- 'use strict';
2
-
3
- var main$1 = require('./main-C0qK6dZX.cjs');
4
- require('fs/promises');
5
- require('path');
6
- require('url');
7
- require('cleye');
8
- require('@redocly/openapi-core');
9
- require('array-utils-ts');
10
- require('lodash-es');
11
- require('swagger2openapi');
12
- require('typescript');
13
- require('node:path');
14
-
15
- const main = async () => {
16
- await main$1.apigen(main$1.getCliConfig());
17
- };
18
- main();
package/dist/cli.mjs DELETED
@@ -1,16 +0,0 @@
1
- import { a as apigen, g as getCliConfig } from './main-l0LIDQ3K.mjs';
2
- import 'fs/promises';
3
- import 'path';
4
- import 'url';
5
- import 'cleye';
6
- import '@redocly/openapi-core';
7
- import 'array-utils-ts';
8
- import 'lodash-es';
9
- import 'swagger2openapi';
10
- import 'typescript';
11
- import 'node:path';
12
-
13
- const main = async () => {
14
- await apigen(getCliConfig());
15
- };
16
- main();