@buildvision/nest-sdk-generator 2.3.0 → 3.0.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/.eslintrc.js +1 -1
- package/.github/workflows/test.yml +35 -0
- package/README.md +402 -239
- package/build/analyzer/classdeps.js +36 -4
- package/build/analyzer/controller.d.ts +2 -14
- package/build/analyzer/controller.js +38 -18
- package/build/analyzer/controllers.d.ts +2 -2
- package/build/analyzer/controllers.js +37 -5
- package/build/analyzer/extractor.d.ts +2 -41
- package/build/analyzer/extractor.js +63 -66
- package/build/analyzer/index.d.ts +2 -6
- package/build/analyzer/index.js +107 -86
- package/build/analyzer/methods.d.ts +2 -30
- package/build/analyzer/methods.js +7 -16
- package/build/analyzer/module.js +36 -4
- package/build/analyzer/params.d.ts +3 -39
- package/build/analyzer/params.js +17 -37
- package/build/analyzer/route.js +8 -9
- package/build/analyzer/sdk.d.ts +146 -0
- package/build/analyzer/sdk.js +120 -0
- package/build/analyzer/typedeps.d.ts +15 -0
- package/build/analyzer/typedeps.js +62 -10
- package/build/bin.js +72 -25
- package/build/config.d.ts +13 -0
- package/build/config.js +36 -3
- package/build/engine/analyze.d.ts +16 -0
- package/build/engine/analyze.js +786 -0
- package/build/engine/ast.d.ts +24 -0
- package/build/engine/ast.js +100 -0
- package/build/engine/file-order.d.ts +17 -0
- package/build/engine/file-order.js +97 -0
- package/build/engine/host.d.ts +63 -0
- package/build/engine/host.js +16 -0
- package/build/engine/index.d.ts +15 -0
- package/build/engine/index.js +93 -0
- package/build/engine/ts-morph-compat-host.d.ts +8 -0
- package/build/engine/ts-morph-compat-host.js +18 -0
- package/build/engine/tsgo-host.d.ts +9 -0
- package/build/engine/tsgo-host.js +136 -0
- package/build/engine/typescript-host.d.ts +26 -0
- package/build/engine/typescript-host.js +142 -0
- package/build/generator/genmodules.d.ts +1 -4
- package/build/generator/genmodules.js +23 -27
- package/build/generator/gentypes.d.ts +1 -1
- package/build/generator/gentypes.js +36 -4
- package/build/generator/index.d.ts +1 -1
- package/build/generator/index.js +91 -69
- package/build/generator/prettier.js +43 -9
- package/build/generator/sdk-interface.d.ts +1 -1
- package/build/generator/sdk-interface.js +5 -5
- package/build/logging.d.ts +3 -0
- package/build/logging.js +17 -10
- package/build/rtk-query-generator/genmodules.d.ts +1 -4
- package/build/rtk-query-generator/genmodules.js +31 -33
- package/build/rtk-query-generator/index.d.ts +1 -1
- package/build/rtk-query-generator/index.js +87 -72
- package/build/rtk-query-generator/prettier.js +43 -9
- package/build/rtk-query-generator/sdk-interface.js +3 -4
- package/build/utils.js +38 -6
- package/package.json +29 -11
- package/src/analyzer/controller.ts +3 -31
- package/src/analyzer/controllers.ts +3 -2
- package/src/analyzer/extractor.ts +17 -86
- package/src/analyzer/index.ts +5 -11
- package/src/analyzer/methods.ts +5 -36
- package/src/analyzer/params.ts +13 -55
- package/src/analyzer/sdk.ts +247 -0
- package/src/analyzer/typedeps.ts +22 -4
- package/src/bin.ts +31 -6
- package/src/config.ts +15 -0
- package/src/engine/analyze.ts +1110 -0
- package/src/engine/ast.ts +105 -0
- package/src/engine/file-order.ts +104 -0
- package/src/engine/host.ts +79 -0
- package/src/engine/index.ts +79 -0
- package/src/engine/ts-morph-compat-host.ts +20 -0
- package/src/engine/tsgo-host.ts +119 -0
- package/src/engine/typescript-host.ts +159 -0
- package/src/generator/genmodules.ts +9 -4
- package/src/generator/gentypes.ts +1 -1
- package/src/generator/index.ts +1 -1
- package/src/logging.ts +10 -1
- package/src/rtk-query-generator/genmodules.ts +17 -9
- package/src/rtk-query-generator/index.ts +2 -12
- package/src/rtk-query-generator/sdk-interface.ts +2 -3
- package/tsconfig.json +7 -6
- package/build/analyzer/decorator.d.ts +0 -10
- package/build/analyzer/decorator.js +0 -29
- package/build/rtk-query-generator/gentypes.d.ts +0 -10
- package/build/rtk-query-generator/gentypes.js +0 -62
- package/build/tsconfig.tsbuildinfo +0 -1
- package/bun.lockb +0 -0
- package/src/rtk-query-generator/gentypes.ts +0 -74
- package/tsconfig.tsbuildinfo +0 -1
package/README.md
CHANGED
|
@@ -1,353 +1,516 @@
|
|
|
1
|
-
# nest-sdk-generator
|
|
1
|
+
# @buildvision/nest-sdk-generator
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/nest-sdk-generator)
|
|
3
|
+
[](https://www.npmjs.com/package/@buildvision/nest-sdk-generator)
|
|
4
|
+
[](https://github.com/build-vision/nest-sdk-generator/actions/workflows/test.yml)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Generate a fully-typed [RTK Query](https://redux-toolkit.js.org/rtk-query/overview) API client from your NestJS controllers.
|
|
6
7
|
|
|
7
|
-
The
|
|
8
|
+
The generator reads your API's source code with the TypeScript compiler: no running server, no OpenAPI spec, no decorators to add. Every route becomes an RTK Query endpoint whose arguments and result are typed from the controller itself, including return types TypeScript infers from your services.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
```ts
|
|
11
|
+
// API: users.controller.ts
|
|
12
|
+
@Controller('users')
|
|
13
|
+
export class UsersController {
|
|
14
|
+
@Get(':id')
|
|
15
|
+
async getUser(@Param('id') id: string) {
|
|
16
|
+
return this.usersService.findOne(id) // Promise<User | null>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@Patch(':id')
|
|
20
|
+
renameUser(@Param('id') id: string, @Body('name') name: string) { ... }
|
|
21
|
+
}
|
|
22
|
+
```
|
|
15
23
|
|
|
16
|
-
|
|
24
|
+
```tsx
|
|
25
|
+
// Client: a React component
|
|
26
|
+
const { data: user } = API.Users.getUser.useQuery({ id }) // user: User | null | undefined
|
|
27
|
+
const [renameUser] = API.Users.renameUser.useMutation()
|
|
17
28
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- [How fast is it?](#how-fast-is-it)
|
|
21
|
-
- [Features](#features)
|
|
22
|
-
- [Limitations](#limitations)
|
|
23
|
-
- [Using the SDK](#using-the-sdk)
|
|
24
|
-
- [Architecture](#architecture)
|
|
25
|
-
- [Step-by-step generation tutorial](#step-by-step-generation-tutorial)
|
|
26
|
-
- [Recommandations](#recommandations)
|
|
27
|
-
- [SDK usage](#sdk-usage)
|
|
28
|
-
- [Importing API types](#importing-api-types)
|
|
29
|
-
- [External files](#external-files)
|
|
30
|
-
- [Configuration options](#configuration-options)
|
|
31
|
-
- [Magic types](#magic-types)
|
|
32
|
-
- [Frequently-asked questions](#frequently-asked-questions)
|
|
33
|
-
- [Does this replace Swagger?](#does-this-replace-swagger)
|
|
34
|
-
- [I have a GraphQL API, what can this project do for me?](#i-have-a-graphql-api-what-can-this-project-do-for-me)
|
|
35
|
-
- [Does the SDK has any performance overhead?](#does-the-sdk-has-any-performance-overhead)
|
|
36
|
-
- [How do I update the SDK once I change the API's source code?](#how-do-i-update-the-sdk-once-i-change-the-apis-source-code)
|
|
37
|
-
- [Is the SDK documented?](#is-the-sdk-documented)
|
|
38
|
-
- [Can I add header or other data on-the-fly when making requests?](#can-i-add-header-or-other-data-on-the-fly-when-making-requests)
|
|
39
|
-
- [Is there a way to log the requests or responses somewhere?](#is-there-a-way-to-log-the-requests-or-responses-somewhere)
|
|
40
|
-
- [Does the API server needs to be running to generate a SDK?](#does-the-api-server-needs-to-be-running-to-generate-a-sdk)
|
|
41
|
-
- [Can I fork this project to make my own version of it?](#can-i-fork-this-project-to-make-my-own-version-of-it)
|
|
42
|
-
- [Can I use this project in commercial software?](#can-i-use-this-project-in-commercial-software)
|
|
43
|
-
- [License](#license)
|
|
44
|
-
|
|
45
|
-
## What is nest-sdk-generator and why should I use it?
|
|
46
|
-
|
|
47
|
-
nest-sdk-generator is a tool that creates a client-side SDK based on a NestJS REST API. The SDK can be used to call the API's routes seamlessly without any friction, and also enforces type safety by typing all parameters and return values based on the API itself.
|
|
48
|
-
|
|
49
|
-
This brings several advantages, including:
|
|
50
|
-
|
|
51
|
-
- Missing or bad parameters and return types will be detected at compilation time
|
|
52
|
-
- IDE autocompletion for parameters and return values without needing to look at the doc
|
|
53
|
-
- All API routes are listed in a single block allowing to list all at once
|
|
54
|
-
- Routes are split across controllers, themselves split across modules, keeping the same hierarchy as your API even if the routes don't
|
|
55
|
-
- Global request and response handler acting as the main request actor, allowing to customize requests by providing additional values like headers if required
|
|
56
|
-
- Simple & clean placeholders for server-side types that can't be ported to client-side (e.g. ORM dictionary types)
|
|
57
|
-
|
|
58
|
-
The generator also allows you to (re-)generate a full SDK in seconds with a single command. And in case something goes wrong, you'll be able to pinpoint exactly what the problem is thanks to the colorful (can be disabled) verbose output. A log file can also be used to store all output informations in one place.
|
|
59
|
-
|
|
60
|
-
## Quick usage example
|
|
61
|
-
|
|
62
|
-
The SDK generator only requires you to create a small JSON configuration file to indicate where you API is located at, and to run a simple `npx nest-sdk-generator <path to the configuration file>` command. This will generate a client-side SDK hierarchised in modules, controllers and methods that will allow you to call your API's routes without manually writing the URIs yourself. The query parameters and body content is also strictly typed and automatically converted to be sent to your API.
|
|
63
|
-
|
|
64
|
-
Calling methods will look like this:
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
import { userController } from "../sdk/userModule"
|
|
68
|
-
import { articleController } from "../sdk/articleModule"
|
|
69
|
-
|
|
70
|
-
const user = await userController.profile({ username: "jack" })
|
|
71
|
-
|
|
72
|
-
const userArticles = await articleController.getAll({ author: user.id })
|
|
73
|
-
|
|
74
|
-
const user = await articleController.publish({}, {
|
|
75
|
-
author: user.id,
|
|
76
|
-
content: `${user.pseudo} has already published ${userArticles.length} articles!`
|
|
77
|
-
})
|
|
29
|
+
renameUser({ id, name: 'Jane' }) // PATCH /users/:id with body { name }
|
|
30
|
+
renameUser({ id }) // Type error: `name` is missing
|
|
78
31
|
```
|
|
79
32
|
|
|
80
|
-
|
|
33
|
+
**Contents**
|
|
34
|
+
|
|
35
|
+
- [Requirements](#requirements)
|
|
36
|
+
- [Getting started](#getting-started)
|
|
37
|
+
- [How controllers become endpoints](#how-controllers-become-endpoints)
|
|
38
|
+
- [Generated files](#generated-files)
|
|
39
|
+
- [Configuration](#configuration)
|
|
40
|
+
- [The request function](#the-request-function)
|
|
41
|
+
- [Adding caching (tags)](#adding-caching-tags)
|
|
42
|
+
- [Command line](#command-line)
|
|
43
|
+
- [Analysis engines](#analysis-engines)
|
|
44
|
+
- [Recommended workflow](#recommended-workflow)
|
|
45
|
+
- [Limitations](#limitations)
|
|
46
|
+
- [Troubleshooting](#troubleshooting)
|
|
47
|
+
- [Plain SDK mode (`--generate`)](#plain-sdk-mode---generate)
|
|
48
|
+
- [Upgrading from 2.x](#upgrading-from-2x)
|
|
49
|
+
- [Development](#development)
|
|
50
|
+
- [Credits and license](#credits-and-license)
|
|
81
51
|
|
|
82
|
-
##
|
|
52
|
+
## Requirements
|
|
83
53
|
|
|
84
|
-
|
|
54
|
+
- **A NestJS API written in TypeScript** with a `tsconfig.json`
|
|
55
|
+
- **A client that can see the API's source code** (typically a monorepo): generated files import your DTOs and entities directly from the API's source with `import type`, so the client's TypeScript project must be able to type-check those files (see [Client TypeScript setup](#client-typescript-setup))
|
|
56
|
+
- **Redux Toolkit 2.x** with React (`@reduxjs/toolkit/query/react`) in the client
|
|
57
|
+
- **Node.js 20.19+ or 22.12+** to run the generator (older versions fall back to a slower [engine](#analysis-engines))
|
|
85
58
|
|
|
86
|
-
|
|
59
|
+
## Getting started
|
|
87
60
|
|
|
88
|
-
|
|
61
|
+
The examples assume a monorepo with the API in `api/` and a React app in `web/`.
|
|
89
62
|
|
|
90
|
-
|
|
91
|
-
- Recursive extraction of types controllers depend on, including types located in `node_modules`
|
|
92
|
-
- Can extract classes, interfaces, enumerations and type aliases
|
|
93
|
-
- Fully compatible with WSL, even if packages are installed using symbolic links from Windows
|
|
94
|
-
- Compatible with alternative package managers like PNPM
|
|
95
|
-
- Extremely detailed output by default for easier debugging in case of errors
|
|
96
|
-
- Tree-shaking so the compiled code will only contain the methods you use from the generated SDK, no matter its size
|
|
63
|
+
### 1. Install
|
|
97
64
|
|
|
98
|
-
|
|
65
|
+
```sh
|
|
66
|
+
cd web
|
|
67
|
+
npm install --save-dev @buildvision/nest-sdk-generator
|
|
68
|
+
```
|
|
99
69
|
|
|
100
|
-
|
|
70
|
+
### 2. Create a configuration file
|
|
101
71
|
|
|
102
|
-
|
|
72
|
+
`web/api-gen.config.json` (paths are relative to this file):
|
|
103
73
|
|
|
104
|
-
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"apiInputPath": "../api",
|
|
77
|
+
"sdkOutputPath": "./src/api/endpoints",
|
|
78
|
+
"sdkInterfacePath": "./src/api/BaseRequest.ts",
|
|
79
|
+
"generateTimestamps": false,
|
|
80
|
+
"controllerOutput": {
|
|
81
|
+
"fileName": { "removeSuffix": ".controller", "addSuffix": ".endpoints" },
|
|
82
|
+
"exportName": { "removeSuffix": "Controller", "addSuffix": "Endpoints" },
|
|
83
|
+
"endpointGroupName": { "removeSuffix": "Controller" }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
105
87
|
|
|
106
|
-
|
|
88
|
+
All options are described in [Configuration](#configuration).
|
|
107
89
|
|
|
108
|
-
|
|
90
|
+
### 3. Write the request function
|
|
109
91
|
|
|
110
|
-
|
|
92
|
+
Every endpoint calls a single function you own, which performs the HTTP request: this is where the base URL, authentication and error handling live. Create the file `sdkInterfacePath` points to (see [The request function](#the-request-function) for a complete example):
|
|
111
93
|
|
|
112
|
-
|
|
94
|
+
```ts
|
|
95
|
+
// web/src/api/BaseRequest.ts
|
|
96
|
+
import type { BaseQueryFn } from '@reduxjs/toolkit/query'
|
|
97
|
+
|
|
98
|
+
export type BaseRequestArgs = {
|
|
99
|
+
body: unknown
|
|
100
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
|
|
101
|
+
query: Record<string, any>
|
|
102
|
+
route: string
|
|
103
|
+
}
|
|
113
104
|
|
|
114
|
-
|
|
115
|
-
- `_types`, containing the types used by the controllers, with the same directory structure than in your original project
|
|
105
|
+
export type BaseRequestFn = BaseQueryFn<BaseRequestArgs, unknown, { status: number; message: string }>
|
|
116
106
|
|
|
117
|
-
|
|
107
|
+
export const BaseRequest: BaseRequestFn = async ({ body, method, query, route }) => {
|
|
108
|
+
// ...perform the request, return { data } or { error }
|
|
109
|
+
}
|
|
110
|
+
```
|
|
118
111
|
|
|
119
|
-
|
|
112
|
+
If the file doesn't exist, the generator creates a starter implementation for you (see [`generateDefaultSdkInterface`](#configuration)).
|
|
120
113
|
|
|
121
|
-
|
|
122
|
-
- If the requests fail or cannot be decoded correctly, the promise fails
|
|
114
|
+
### 4. Generate the client
|
|
123
115
|
|
|
124
|
-
|
|
116
|
+
```sh
|
|
117
|
+
npx nest-sdk-generator api-gen.config.json --generate-rtk
|
|
118
|
+
```
|
|
125
119
|
|
|
126
|
-
|
|
120
|
+
### 5. Create the RTK Query API
|
|
127
121
|
|
|
128
|
-
|
|
122
|
+
```ts
|
|
123
|
+
// web/src/api/index.ts
|
|
124
|
+
import { configureStore } from '@reduxjs/toolkit'
|
|
125
|
+
import { createApi } from '@reduxjs/toolkit/query/react'
|
|
129
126
|
|
|
130
|
-
|
|
127
|
+
import { BaseRequest } from './BaseRequest'
|
|
128
|
+
import endpoints from './endpoints'
|
|
131
129
|
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
export const rtkApi = createApi({
|
|
131
|
+
reducerPath: 'api',
|
|
132
|
+
baseQuery: BaseRequest,
|
|
133
|
+
endpoints: endpoints.buildRTKEndpoints,
|
|
134
|
+
})
|
|
134
135
|
|
|
135
|
-
|
|
136
|
+
// Endpoints grouped by controller: API.Users.getUser, API.Comments.addComment...
|
|
137
|
+
export const API = endpoints.groupRTKEndpoints(rtkApi)
|
|
136
138
|
|
|
137
|
-
|
|
138
|
-
.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
| └── src
|
|
142
|
-
| └── index.ts
|
|
143
|
-
└── front
|
|
144
|
-
└── src
|
|
145
|
-
└── index.ts
|
|
139
|
+
export const store = configureStore({
|
|
140
|
+
reducer: { [rtkApi.reducerPath]: rtkApi.reducer },
|
|
141
|
+
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(rtkApi.middleware),
|
|
142
|
+
})
|
|
146
143
|
```
|
|
147
144
|
|
|
148
|
-
|
|
145
|
+
Wrap your app in react-redux's `<Provider store={store}>` as usual.
|
|
149
146
|
|
|
150
|
-
|
|
147
|
+
### 6. Use the endpoints
|
|
151
148
|
|
|
152
|
-
```
|
|
153
|
-
{
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
149
|
+
```tsx
|
|
150
|
+
import { API } from './api'
|
|
151
|
+
|
|
152
|
+
export function Comments({ articleId }: { articleId: string }) {
|
|
153
|
+
const { data: comments, isLoading } = API.Comments.listComments.useQuery({ articleId })
|
|
154
|
+
const [addComment, { isLoading: isSaving }] = API.Comments.addComment.useMutation()
|
|
155
|
+
|
|
156
|
+
if (isLoading) return <p>Loading...</p>
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<>
|
|
160
|
+
<ul>
|
|
161
|
+
{comments?.map((comment) => (
|
|
162
|
+
<li key={comment.id}>{comment.text}</li>
|
|
163
|
+
))}
|
|
164
|
+
</ul>
|
|
165
|
+
<button disabled={isSaving} onClick={() => addComment({ articleId, text: 'Nice!' })}>
|
|
166
|
+
Comment
|
|
167
|
+
</button>
|
|
168
|
+
</>
|
|
169
|
+
)
|
|
157
170
|
}
|
|
158
171
|
```
|
|
159
172
|
|
|
160
|
-
|
|
173
|
+
Each grouped endpoint is a regular RTK Query endpoint, so everything RTK Query offers works: `useQuery`, `useLazyQuery`, `useMutation`, `initiate`, `select`, `rtkApi.util`...
|
|
161
174
|
|
|
162
|
-
|
|
163
|
-
sdk-generator apps/front/sdk-generator.json
|
|
164
|
-
```
|
|
175
|
+
### Client TypeScript setup
|
|
165
176
|
|
|
166
|
-
|
|
177
|
+
Generated files contain `import type` statements pointing at your API's source files, and your client's compiler type-checks those files **with the client's compiler options**. In practice the client's `tsconfig.json` needs:
|
|
167
178
|
|
|
168
|
-
|
|
179
|
+
- `"experimentalDecorators": true`, as Nest's parameter decorators (`@Param()`, `@Body()`...) are only valid with legacy decorators
|
|
180
|
+
- The API's options that affect type-checking of DTOs and entities, most commonly `"strictPropertyInitialization": false`
|
|
181
|
+
- The API's path aliases (`paths`), if its files import each other through aliases like `@api/*`
|
|
182
|
+
- Access to the packages the API's types depend on (e.g. `class-validator`, your ORM): they are resolved from the API's directory, so they are found as long as the API's dependencies are installed
|
|
169
183
|
|
|
170
|
-
|
|
184
|
+
No API code ends up in your client bundle: only types are imported.
|
|
171
185
|
|
|
172
|
-
|
|
186
|
+
## How controllers become endpoints
|
|
173
187
|
|
|
174
|
-
|
|
188
|
+
**Controllers.** Every `*.controller.ts` file of the API's TypeScript project (as defined by its tsconfig) is analyzed. The first class of the file must have a `@Controller()` decorator called with no argument or a string literal (`@Controller('users')`); other files are skipped. Each controller belongs to the module declared in the nearest `*.module.ts` file, in the controller's directory or a parent directory.
|
|
175
189
|
|
|
176
|
-
|
|
190
|
+
**Routes.** Methods with exactly one of `@Get()`, `@Post()`, `@Put()`, `@Patch()` or `@Delete()`, called with no argument or a string literal, become endpoints. The route is the controller's prefix followed by the method's path: `@Controller('users')` + `@Get(':id/avatar')` becomes `/users/:id/avatar`. Methods without one of these decorators are ignored.
|
|
177
191
|
|
|
178
|
-
|
|
192
|
+
**Arguments.** The endpoint takes a single object merging all of the route's inputs:
|
|
179
193
|
|
|
180
|
-
|
|
181
|
-
|
|
194
|
+
| Controller argument | Endpoint argument | Sent as |
|
|
195
|
+
| --- | --- | --- |
|
|
196
|
+
| `@Param('id') id: string` | `{ id: string }` | Interpolated into the route |
|
|
197
|
+
| `@Query('page') page: number` | `{ page: number }` | Query string (GET only) |
|
|
198
|
+
| `@Query() query: ListQuery` | `ListQuery` | Query string (GET only) |
|
|
199
|
+
| `@Body('name') name: string` | `{ name: string }` | JSON body (non-GET only) |
|
|
200
|
+
| `@Body() dto: CreateUserDto` | `CreateUserDto` | JSON body (non-GET only) |
|
|
201
|
+
| `@Req()`, `@Headers()`, custom decorators... | - | Ignored |
|
|
182
202
|
|
|
183
|
-
|
|
184
|
-
// typeof user == UserDTO
|
|
185
|
-
```
|
|
203
|
+
For example, `update(@Param('id') id: string, @Body() dto: UpdateUserDto)` produces an endpoint taking `{ id: string } & UpdateUserDto`. Routes without inputs take `void`.
|
|
186
204
|
|
|
187
|
-
|
|
205
|
+
**Results.** The result type is the method's return type with `Promise<>` unwrapped, exactly as TypeScript sees it, whether declared or inferred. A method returning `this.usersService.findOne(id)` gets the service's return type.
|
|
188
206
|
|
|
189
|
-
|
|
207
|
+
**RTK Query.** `GET` routes become queries (`builder.query`), every other method becomes a mutation (`builder.mutation`). The endpoint's name is the controller method's name, so **method names must be unique across all controllers**: RTK Query keeps all endpoints of an API in a single namespace.
|
|
190
208
|
|
|
191
|
-
|
|
209
|
+
**Validation.** Generation fails with an explicit error when a route can't be represented reliably, for instance a `GET` route with a `@Body()`, a non-`GET` route with a `@Query()`, a route parameter missing from the URL, a parameter used twice, or a wildcard route.
|
|
192
210
|
|
|
193
|
-
|
|
211
|
+
## Generated files
|
|
194
212
|
|
|
195
|
-
|
|
213
|
+
With the configuration above, the output directory contains:
|
|
196
214
|
|
|
197
|
-
```
|
|
198
|
-
|
|
215
|
+
```
|
|
216
|
+
endpoints/
|
|
217
|
+
├── index.ts # Combines all controllers
|
|
218
|
+
├── baseRequest.ts # Re-exports the types of your request function
|
|
219
|
+
├── users.endpoints.ts # One file per controller
|
|
220
|
+
└── comments.endpoints.ts
|
|
199
221
|
```
|
|
200
222
|
|
|
201
|
-
|
|
223
|
+
Each controller file exports a class with:
|
|
202
224
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
225
|
+
- `queries` and `mutations`: functions turning an endpoint's arguments into a request for your request function (`{ body, method, query, route }`)
|
|
226
|
+
- `buildRTKEndpoints(builder)`: the controller's RTK Query endpoint definitions
|
|
227
|
+
- `groupRTKEndpoints(api)`: picks the controller's endpoints from a created API
|
|
206
228
|
|
|
207
|
-
|
|
229
|
+
`index.ts` default-exports every controller class plus:
|
|
230
|
+
|
|
231
|
+
- `buildRTKEndpoints`: all endpoint definitions, to pass to `createApi({ endpoints })`
|
|
232
|
+
- `groupRTKEndpoints(api)`: the created API's endpoints grouped by controller (`API.Users.getUser`)
|
|
233
|
+
|
|
234
|
+
It also exports the `GeneratedAPIType` type, the type of the created API.
|
|
235
|
+
|
|
236
|
+
Every endpoint is documented with its HTTP method, route and source controller method, so hovering an endpoint in your editor shows where it comes from:
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
/**
|
|
240
|
+
* GET /articles/:articleId/comments <=> CommentsController.listComments
|
|
241
|
+
*
|
|
242
|
+
* file:///./../../../api/src/articles/comments/comments.controller.ts (cmd + click)
|
|
243
|
+
*/
|
|
244
|
+
listComments: builder.query<Comment[], { articleId: string }>({
|
|
245
|
+
query: this.queries.listComments,
|
|
246
|
+
}),
|
|
208
247
|
```
|
|
209
248
|
|
|
210
|
-
|
|
249
|
+
Generated files are overwritten on every run: don't edit them.
|
|
211
250
|
|
|
212
|
-
|
|
251
|
+
## Configuration
|
|
213
252
|
|
|
214
|
-
|
|
253
|
+
The configuration file is a JSON file. Relative paths are resolved from the configuration file's directory.
|
|
215
254
|
|
|
216
|
-
|
|
255
|
+
### Paths (required)
|
|
217
256
|
|
|
218
|
-
|
|
257
|
+
| Option | Description |
|
|
258
|
+
| --- | --- |
|
|
259
|
+
| `apiInputPath` | Directory of the NestJS API, containing its `tsconfig.json` |
|
|
260
|
+
| `sdkOutputPath` | Directory to generate the client in. It is **deleted and recreated** on each run (see `overwriteOldOutputDir`), so don't point it at a directory containing other files. Its parent directory must exist. |
|
|
261
|
+
| `sdkInterfacePath` | Your [request function](#the-request-function)'s module. It must export the `BaseRequestArgs` and `BaseRequestFn` types; the generated `baseRequest.ts` re-exports them. |
|
|
219
262
|
|
|
220
|
-
|
|
221
|
-
| ----------------------------- | ------- | ------------- | ------------------------------------------------------------------------------------- |
|
|
222
|
-
| `apiInputPath` | - | `string` | Path to your API's source folder (e.g. `apps/api`) |
|
|
223
|
-
| `sdkOutputPath` | - | `string` | Path to generate the SDK at (e.g. `apps/front/src/sdk`) |
|
|
224
|
-
| `sdkInterfacePath` | - | `string` | Path to the SDK interface (e.g. `apps/front/src/sdk-interface.ts`) |
|
|
225
|
-
| `magicTypes` | `[]` | `MagicType[]` | Magic types (see below) |
|
|
226
|
-
| `jsonOutput` | `null` | `string` | Write the analyzer's output to a file |
|
|
227
|
-
| `jsonPrettyOutput` | `null` | `boolean` | Prettify the output JSON if the option above is enabled |
|
|
228
|
-
| `prettierConfig` | `null` | `string` | Use a specific Prettier config (otherwise will try to find one in a parent directory) |
|
|
229
|
-
| `prettify` | `true` | `boolean` | Set to `false` to disable SDK files prettifying |
|
|
230
|
-
| `tsconfigFile` | `tsconfig.json` | `string` | Use a custom tsconfig file |
|
|
231
|
-
| `overwriteOldOutputDir` | `true` | `boolean` | Set to `false` to not remove the old SDK directory when re-generating it |
|
|
232
|
-
| `generateDefaultSdkInterface` | `true` | `boolean` | Set to `false` to not generate a default SDK interface when it does not exist yet |
|
|
233
|
-
| `generateTimestamps` | `true` | `boolean` | Set to `false` to not put the timestamp in the generated SDK files |
|
|
234
|
-
| `verbose` | `false` | `boolean` | Display verbose informations |
|
|
235
|
-
| `noColor` | `false` | `boolean` | Disable colored output |
|
|
263
|
+
### Naming
|
|
236
264
|
|
|
237
|
-
|
|
265
|
+
By default, generated names follow your controllers: `users.controller.ts` produces `users.controller.ts` exporting `UsersController`, grouped as `API.UsersController`. `controllerOutput` adjusts these names, each with an optional `removeSuffix` (removed from the end of the name, if present) and `addSuffix` (appended):
|
|
238
266
|
|
|
239
|
-
|
|
267
|
+
| Option | Applies to | Example: `UsersController` in `users.controller.ts` |
|
|
268
|
+
| --- | --- | --- |
|
|
269
|
+
| `controllerOutput.fileName` | The generated file's name, from the controller's file name without extension | `{ "removeSuffix": ".controller", "addSuffix": ".endpoints" }` gives `users.endpoints.ts` |
|
|
270
|
+
| `controllerOutput.exportName` | The generated class's name | `{ "removeSuffix": "Controller", "addSuffix": "Endpoints" }` gives `UsersEndpoints` |
|
|
271
|
+
| `controllerOutput.endpointGroupName` | The key in `groupRTKEndpoints(api)` | `{ "removeSuffix": "Controller" }` gives `API.Users` |
|
|
240
272
|
|
|
241
|
-
|
|
242
|
-
|
|
273
|
+
`removeSuffix` is interpreted as a regular expression: escape special characters if you need an exact match (in `.controller`, the `.` matches any character, which is harmless in practice).
|
|
274
|
+
|
|
275
|
+
### Output
|
|
276
|
+
|
|
277
|
+
| Option | Default | Description |
|
|
278
|
+
| --- | --- | --- |
|
|
279
|
+
| `prettify` | `true` | Format generated files with Prettier |
|
|
280
|
+
| `prettierConfig` | - | Path to a Prettier configuration file (JSON). By default, the nearest `.prettierrc` file above `sdkOutputPath` is used, if any; only JSON `.prettierrc` files are supported. |
|
|
281
|
+
| `generateTimestamps` | `true` | Add the generation date to each file. Set to `false` to get identical output when nothing changed (recommended). |
|
|
282
|
+
| `overwriteOldOutputDir` | `true` | Replace `sdkOutputPath` if it exists. When `false`, generation fails if it exists. |
|
|
283
|
+
| `generateDefaultSdkInterface` | `true` | Create a starter request function at `sdkInterfacePath` if the file doesn't exist. An existing file is never overwritten. |
|
|
284
|
+
|
|
285
|
+
### Analysis
|
|
286
|
+
|
|
287
|
+
| Option | Default | Description |
|
|
288
|
+
| --- | --- | --- |
|
|
289
|
+
| `tsconfigFile` | `tsconfig.json` | Name of the API's tsconfig file, in `apiInputPath` (a file name, not a path) |
|
|
290
|
+
| `engine` | `tsgo` | [Analysis engine](#analysis-engines): `tsgo`, `typescript` or `ts-morph` |
|
|
291
|
+
| `typescriptPath` | - | TypeScript package used by the `typescript` engine. By default, the `typescript` package installed for the API (resolved from `apiInputPath`) is used. |
|
|
292
|
+
| `magicTypes` | `[]` | Replacements for types that can't be extracted, for the [plain SDK mode](#plain-sdk-mode---generate) |
|
|
243
293
|
|
|
244
|
-
|
|
245
|
-
export class Author {
|
|
246
|
-
// ...
|
|
294
|
+
### Debugging
|
|
247
295
|
|
|
248
|
-
|
|
249
|
-
|
|
296
|
+
| Option | Default | Description |
|
|
297
|
+
| --- | --- | --- |
|
|
298
|
+
| `verbose` | `false` | Log every analyzed controller, method, argument and type |
|
|
299
|
+
| `jsonOutput` | - | Write the analysis result (modules, controllers, routes and types) to this JSON file |
|
|
300
|
+
| `jsonPrettyOutput` | `false` | Indent the JSON output |
|
|
301
|
+
| `noColor` | `false` | Currently ineffective: set the `FORCE_COLOR=0` environment variable to disable colors |
|
|
250
302
|
|
|
251
|
-
|
|
303
|
+
## The request function
|
|
304
|
+
|
|
305
|
+
The generated endpoints don't perform requests themselves: each one turns its arguments into a `BaseRequestArgs` object and hands it to RTK Query's base query, which is your request function. You decide how requests are made: base URL, headers, authentication, serialization, error format, retries, logging...
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
// web/src/api/BaseRequest.ts
|
|
309
|
+
import type { BaseQueryFn } from '@reduxjs/toolkit/query'
|
|
310
|
+
|
|
311
|
+
/** What each generated endpoint asks the request function to do */
|
|
312
|
+
export type BaseRequestArgs = {
|
|
313
|
+
body: unknown
|
|
314
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
|
|
315
|
+
query: Record<string, any>
|
|
316
|
+
route: string // e.g. "/users/42", with route parameters already interpolated
|
|
252
317
|
}
|
|
253
318
|
|
|
254
|
-
|
|
319
|
+
export type RequestError = { status: number; message: string }
|
|
255
320
|
|
|
256
|
-
|
|
321
|
+
export type BaseRequestFn = BaseQueryFn<BaseRequestArgs, unknown, RequestError>
|
|
257
322
|
|
|
258
|
-
|
|
323
|
+
// Include your API's global prefix and version, if any (e.g. app.setGlobalPrefix('api'))
|
|
324
|
+
const BASE_URL = 'https://api.example.com'
|
|
259
325
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
326
|
+
export const BaseRequest: BaseRequestFn = async ({ body, method, query, route }) => {
|
|
327
|
+
const url = new URL(BASE_URL + route)
|
|
328
|
+
|
|
329
|
+
for (const [key, value] of Object.entries(query)) {
|
|
330
|
+
if (value === undefined) continue
|
|
331
|
+
url.searchParams.append(key, typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value))
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const response = await fetch(url, {
|
|
335
|
+
method,
|
|
336
|
+
headers: { 'Content-Type': 'application/json' /* , Authorization: `Bearer ${token}` */ },
|
|
337
|
+
body: method === 'GET' ? undefined : JSON.stringify(body ?? {}),
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
const text = await response.text()
|
|
341
|
+
|
|
342
|
+
if (!response.ok) {
|
|
343
|
+
return { error: { status: response.status, message: text } }
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return { data: text ? JSON.parse(text) : undefined }
|
|
270
347
|
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
The function receives RTK Query's usual extra arguments (`api`, with `getState`, `dispatch` and `signal`, and `extraOptions`), so you can read a token from the store or support request cancellation. To handle errors globally (e.g. show a toast), wrap the function and pass the wrapper to `createApi`.
|
|
351
|
+
|
|
352
|
+
The `error` returned by the hooks is either the error type you declare in `BaseRequestFn`, or RTK Query's `SerializedError` (for unexpected exceptions).
|
|
353
|
+
|
|
354
|
+
**Starter implementation.** When `sdkInterfacePath` doesn't exist, the generator writes a more complete starter (query serialization, a `RequestError` class with HTTP metadata, user-facing messages from Nest's error responses) that you can adapt. It assumes the API is served from `/api` on the current host.
|
|
355
|
+
|
|
356
|
+
## Adding caching (tags)
|
|
357
|
+
|
|
358
|
+
Generated endpoints don't declare cache tags. Add them with RTK Query's [`enhanceEndpoints`](https://redux-toolkit.js.org/rtk-query/api/created-api/code-splitting#enhanceendpoints), which keeps everything typed:
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
export const rtkApi = createApi({
|
|
362
|
+
reducerPath: 'api',
|
|
363
|
+
baseQuery: BaseRequest,
|
|
364
|
+
endpoints: endpoints.buildRTKEndpoints,
|
|
365
|
+
}).enhanceEndpoints({
|
|
366
|
+
addTagTypes: ['Comment'],
|
|
367
|
+
endpoints: {
|
|
368
|
+
listComments: { providesTags: ['Comment'] },
|
|
369
|
+
addComment: { invalidatesTags: ['Comment'] },
|
|
370
|
+
removeComment: { invalidatesTags: ['Comment'] },
|
|
371
|
+
},
|
|
372
|
+
})
|
|
271
373
|
|
|
374
|
+
export const API = endpoints.groupRTKEndpoints(rtkApi)
|
|
272
375
|
```
|
|
273
376
|
|
|
274
|
-
|
|
377
|
+
The same applies to other per-endpoint options like `keepUnusedDataFor` or `transformResponse`.
|
|
275
378
|
|
|
276
|
-
|
|
379
|
+
## Command line
|
|
277
380
|
|
|
278
|
-
|
|
381
|
+
```sh
|
|
382
|
+
nest-sdk-generator <config file> [--generate-rtk | --generate] [--engine <tsgo|typescript|ts-morph>]
|
|
383
|
+
```
|
|
279
384
|
|
|
280
|
-
|
|
385
|
+
| Argument | Description |
|
|
386
|
+
| --- | --- |
|
|
387
|
+
| `<config file>` | Path to the configuration file (required) |
|
|
388
|
+
| `--generate-rtk` | Generate the RTK Query client |
|
|
389
|
+
| `--generate` | Generate the [plain SDK](#plain-sdk-mode---generate) |
|
|
390
|
+
| `--engine <name>`, `--engine=<name>` | Analysis engine, overriding the configuration file |
|
|
281
391
|
|
|
282
|
-
|
|
392
|
+
Without `--generate-rtk` or `--generate`, the API is only analyzed: useful to validate it, or to produce a `jsonOutput`. The process exits with a non-zero code on errors (`1` for analysis and generation errors, `2` to `4` when the configuration file is missing or invalid).
|
|
283
393
|
|
|
284
|
-
|
|
394
|
+
## Analysis engines
|
|
285
395
|
|
|
286
|
-
|
|
396
|
+
Most of the generation time is spent by the TypeScript compiler analyzing the API. Three engines are available:
|
|
287
397
|
|
|
288
|
-
|
|
398
|
+
| Engine | Compiler | Notes |
|
|
399
|
+
| --- | --- | --- |
|
|
400
|
+
| `tsgo` (default) | TypeScript 7's native compiler, bundled | Fastest |
|
|
401
|
+
| `typescript` | Your API's own TypeScript compiler (6.x), in-process | Same output as `tsgo`*. Used automatically if `tsgo` can't start. |
|
|
402
|
+
| `ts-morph` | TypeScript 4.8, bundled with ts-morph | Same output as version 2.x of this package |
|
|
289
403
|
|
|
290
|
-
|
|
404
|
+
On an API with 66 controllers, 687 routes and ~2,500 source files (plus large dependencies like `googleapis`), generating the RTK Query client takes:
|
|
291
405
|
|
|
292
|
-
|
|
406
|
+
| Engine | Duration |
|
|
407
|
+
| --- | --- |
|
|
408
|
+
| `tsgo` | 2.4s |
|
|
409
|
+
| `typescript` | 8.8s |
|
|
410
|
+
| `ts-morph` | 17.7s |
|
|
293
411
|
|
|
294
|
-
|
|
412
|
+
The `tsgo` engine requires Node.js 20.19+ or 22.12+, and Node.js itself rather than Bun's runtime: `bun x` and `npx` run the generator with Node.js, but `bun --bun` doesn't. When `tsgo` can't be started (unsupported runtime or platform), the generator falls back to the `typescript` engine and says so. The `typescript` engine needs a TypeScript 6 (or older) package with the JavaScript compiler API: the API's own, or the one set with `typescriptPath`.
|
|
295
413
|
|
|
296
|
-
|
|
414
|
+
Newer TypeScript versions infer some types more precisely than TypeScript 4.8 (e.g. `(User | undefined)[]` becomes `User[]` after a `.filter()` with a type guard), order union members deterministically, and omit default type arguments. The `tsgo` and `typescript` engines may therefore generate slightly different, more precise types than the `ts-morph` engine.
|
|
297
415
|
|
|
298
|
-
|
|
416
|
+
\* TypeScript 6 and 7 print a few rare types differently (e.g. classes declared inside generic functions, used as base classes). This doesn't affect the RTK Query client, but can change the types extracted for the plain SDK mode and `jsonOutput`.
|
|
299
417
|
|
|
300
|
-
|
|
418
|
+
## Recommended workflow
|
|
301
419
|
|
|
302
|
-
|
|
303
|
-
// ...
|
|
304
|
-
/// Parent module: articleModule
|
|
305
|
-
/// Controller: "articleController" registered as "article" (5 routes)
|
|
420
|
+
- **Don't commit the generated client.** Add `sdkOutputPath` to your `.gitignore` and generate it before anything that needs it:
|
|
306
421
|
|
|
307
|
-
|
|
422
|
+
```json
|
|
423
|
+
{
|
|
424
|
+
"scripts": {
|
|
425
|
+
"build:api": "nest-sdk-generator api-gen.config.json --generate-rtk",
|
|
426
|
+
"dev": "npm run build:api && next dev",
|
|
427
|
+
"build": "npm run build:api && next build",
|
|
428
|
+
"typecheck": "npm run build:api && tsc --noEmit",
|
|
429
|
+
"test": "npm run build:api && vitest run"
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
```
|
|
308
433
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
return request('GET', `/article`, body, query)
|
|
313
|
-
},
|
|
434
|
+
- **Regenerate while developing.** The generator doesn't watch files; pair it with a file watcher, e.g. `nodemon --watch ../api/src -e ts --exec "npm run build:api"`.
|
|
435
|
+
- **Set `generateTimestamps: false`** so unchanged APIs produce identical files, and tools watching the output don't rebuild for nothing.
|
|
436
|
+
- **Type-check the client in CI.** As the client is generated from the API, a breaking API change surfaces as a type error in the client code that uses it.
|
|
314
437
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
438
|
+
## Limitations
|
|
439
|
+
|
|
440
|
+
- **Static analysis only.** Routes are discovered from the decorators listed in [How controllers become endpoints](#how-controllers-become-endpoints): routes registered dynamically, `@All()`, `@Head()`, `@Options()`, `@Controller()` options objects, versioning and global prefixes aren't taken into account (put global prefixes in your request function's base URL).
|
|
441
|
+
- **No wildcard routes** (`*`, `+`, `?` in paths), and route parameter names may only contain letters, digits and `_`.
|
|
442
|
+
- **Generic `@Param()` objects aren't supported**: use one `@Param('name')` per route parameter.
|
|
443
|
+
- **Optional keyed parameters are required keys.** `@Query('page') page?: number` produces `{ page: number | undefined }`: pass `page: undefined` explicitly, or use a `@Query()` DTO with optional properties.
|
|
444
|
+
- **Controller paths without a prefix.** With `@Controller()` (no prefix), the method's path is used as is: write `@Get('/articles')` rather than `@Get('articles')` to get a route starting with `/`.
|
|
445
|
+
- **One module file per directory.** A controller's module is found by looking for a `*.module.ts` file in its directory, then its parents.
|
|
446
|
+
- **Unique names.** Controller class names, and method names across controllers, must be unique.
|
|
447
|
+
- **Types must be declared at the top level of a file.** Types used by routes are also extracted during analysis (for the plain SDK mode and `jsonOutput`), and types declared inside namespaces can't be, which fails the generation.
|
|
448
|
+
- **The API isn't type-checked.** Generation doesn't report compilation errors in the API; types the compiler can't resolve become `any`.
|
|
449
|
+
|
|
450
|
+
## Troubleshooting
|
|
323
451
|
|
|
324
|
-
|
|
452
|
+
| Error | Cause and fix |
|
|
453
|
+
| --- | --- |
|
|
454
|
+
| `Duplicate method name found: X in A and B` | Two controllers have a method named `X`, which would produce two RTK Query endpoints with the same name. Rename one of them. |
|
|
455
|
+
| `GET request should not have @Body params` / `POST request should not have @Query params` | Move the inputs to `@Query()` (GET) or `@Body()` (other methods). |
|
|
456
|
+
| `Route param X does not appear in route URL` | A `@Param('X')` has no matching `:X` in the route, or a generic `@Param()` object is used. |
|
|
457
|
+
| `No module file was found for controller at path ...` | Add a `*.module.ts` file in the controller's directory or a parent directory. |
|
|
458
|
+
| `Type X was not found in file ...` | `X` is not declared at the top level of that file (e.g. it is in a namespace). |
|
|
459
|
+
| Type errors in API files when type-checking the client | Align the client's compiler options with the API's (see [Client TypeScript setup](#client-typescript-setup)). |
|
|
460
|
+
| Generation is slow | Check that the `tsgo` engine is used (`"verbose": true` logs the engine), and that you aren't running the generator with `bun --bun`. |
|
|
461
|
+
|
|
462
|
+
For anything else, `"verbose": true` logs every step of the analysis, and `jsonOutput` shows what the analyzer understood of your API.
|
|
463
|
+
|
|
464
|
+
## Plain SDK mode (`--generate`)
|
|
465
|
+
|
|
466
|
+
`--generate` produces a framework-agnostic SDK instead of RTK Query endpoints: one file per controller with `queries` and `mutations` objects whose methods call a `request(method, uri, body, query)` function exported by `sdkInterfacePath`. In this mode, the types used by the routes are copied into an `_types` directory (types from outside the API's directory go to `_types/_externalN/...`), so the client doesn't need access to the API's source.
|
|
467
|
+
|
|
468
|
+
Types that can't be copied, like ORM types with circular references or many dependencies, can be replaced with placeholders using `magicTypes`:
|
|
469
|
+
|
|
470
|
+
```json
|
|
471
|
+
{
|
|
472
|
+
"magicTypes": [
|
|
473
|
+
{
|
|
474
|
+
"nodeModuleFilePath": "@mikro-orm/core/entity/Collection.d.ts",
|
|
475
|
+
"typeName": "Collection",
|
|
476
|
+
"placeholderContent": "export type Collection<T, _> = Array<T>;"
|
|
477
|
+
}
|
|
478
|
+
]
|
|
325
479
|
}
|
|
326
480
|
```
|
|
327
481
|
|
|
328
|
-
|
|
482
|
+
When a type named `typeName` is imported from `node_modules/<nodeModuleFilePath>`, `placeholderContent` is used instead. Mikro-ORM's `Collection` is replaced by default.
|
|
329
483
|
|
|
330
|
-
|
|
484
|
+
This mode is maintained for compatibility: the RTK Query client is the primary use case of this package. Known issue: classes with an index signature (`[key: string]: ...`) produce type files Prettier rejects.
|
|
331
485
|
|
|
332
|
-
|
|
486
|
+
## Upgrading from 2.x
|
|
333
487
|
|
|
334
|
-
|
|
335
|
-
the requests thanks to the provided URI and query/body parameters, which means you can write the requests and responses to the local storage, send them to a log server, or anything else.
|
|
488
|
+
Version 3.0 adds the [analysis engines](#analysis-engines); configurations and generated file structures are unchanged.
|
|
336
489
|
|
|
337
|
-
|
|
490
|
+
- Generation is much faster, but the default `tsgo` engine requires Node.js 20.19+ / 22.12+.
|
|
491
|
+
- Some generated types may change, as newer TypeScript versions infer types more precisely. If this breaks your build, fix the client code (the new types reflect what your API's compiler sees), or temporarily use `--engine=ts-morph` to get exactly the 2.x output.
|
|
492
|
+
- `jsonOutput` works again (it crashed in 2.x).
|
|
338
493
|
|
|
339
|
-
|
|
494
|
+
## Development
|
|
340
495
|
|
|
341
|
-
|
|
496
|
+
```sh
|
|
497
|
+
npm install
|
|
498
|
+
npm run build # Build the generator into build/
|
|
499
|
+
npm run build:baseline # Build version 2.3.1 into .parity/baseline, used as a reference by the tests
|
|
500
|
+
npm test # Run the tests
|
|
501
|
+
npm run lint
|
|
502
|
+
```
|
|
342
503
|
|
|
343
|
-
|
|
504
|
+
The tests generate clients for the fixture API in `test/fixtures` (and ~30 error cases) and compare the results of:
|
|
344
505
|
|
|
345
|
-
|
|
506
|
+
- the 2.3.1 baseline and the `ts-morph` engine, which must produce exactly the same output
|
|
507
|
+
- the analyzer behind the `tsgo` and `typescript` engines, running on ts-morph's TypeScript 4.8 program, which must also produce exactly the same output (this shows it is equivalent to the original analyzer)
|
|
508
|
+
- the `tsgo` and `typescript` engines, which must produce exactly the same output
|
|
346
509
|
|
|
347
|
-
|
|
510
|
+
To also run them on a real project, set `PARITY_REAL_API_CONFIG` to the path of its generator configuration file; generated files are written to a temporary directory.
|
|
348
511
|
|
|
349
|
-
|
|
512
|
+
## Credits and license
|
|
350
513
|
|
|
351
|
-
|
|
514
|
+
This package is maintained by [BuildVision](https://buildvision.io). It started as a fork of [nest-sdk-generator](https://github.com/lonestone/nest-sdk-generator) by [Lonestone](https://lonestone.io/), which generated plain SDKs; this fork adds the RTK Query client and the analysis engines.
|
|
352
515
|
|
|
353
|
-
|
|
516
|
+
Published under the [MIT License](LICENSE.md).
|