@buildvision/nest-sdk-generator 2.3.1 → 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.
Files changed (93) hide show
  1. package/.eslintrc.js +1 -1
  2. package/.github/workflows/test.yml +35 -0
  3. package/README.md +402 -239
  4. package/build/analyzer/classdeps.js +36 -4
  5. package/build/analyzer/controller.d.ts +2 -14
  6. package/build/analyzer/controller.js +38 -18
  7. package/build/analyzer/controllers.d.ts +2 -2
  8. package/build/analyzer/controllers.js +37 -5
  9. package/build/analyzer/extractor.d.ts +2 -41
  10. package/build/analyzer/extractor.js +63 -66
  11. package/build/analyzer/index.d.ts +2 -6
  12. package/build/analyzer/index.js +107 -86
  13. package/build/analyzer/methods.d.ts +2 -30
  14. package/build/analyzer/methods.js +7 -16
  15. package/build/analyzer/module.js +36 -4
  16. package/build/analyzer/params.d.ts +3 -39
  17. package/build/analyzer/params.js +17 -37
  18. package/build/analyzer/route.js +8 -9
  19. package/build/analyzer/sdk.d.ts +146 -0
  20. package/build/analyzer/sdk.js +120 -0
  21. package/build/analyzer/typedeps.d.ts +15 -0
  22. package/build/analyzer/typedeps.js +62 -10
  23. package/build/bin.js +72 -26
  24. package/build/config.d.ts +13 -0
  25. package/build/config.js +36 -3
  26. package/build/engine/analyze.d.ts +16 -0
  27. package/build/engine/analyze.js +786 -0
  28. package/build/engine/ast.d.ts +24 -0
  29. package/build/engine/ast.js +100 -0
  30. package/build/engine/file-order.d.ts +17 -0
  31. package/build/engine/file-order.js +97 -0
  32. package/build/engine/host.d.ts +63 -0
  33. package/build/engine/host.js +16 -0
  34. package/build/engine/index.d.ts +15 -0
  35. package/build/engine/index.js +93 -0
  36. package/build/engine/ts-morph-compat-host.d.ts +8 -0
  37. package/build/engine/ts-morph-compat-host.js +18 -0
  38. package/build/engine/tsgo-host.d.ts +9 -0
  39. package/build/engine/tsgo-host.js +136 -0
  40. package/build/engine/typescript-host.d.ts +26 -0
  41. package/build/engine/typescript-host.js +142 -0
  42. package/build/generator/genmodules.d.ts +1 -4
  43. package/build/generator/genmodules.js +23 -27
  44. package/build/generator/gentypes.d.ts +1 -1
  45. package/build/generator/gentypes.js +36 -4
  46. package/build/generator/index.d.ts +1 -1
  47. package/build/generator/index.js +91 -69
  48. package/build/generator/prettier.js +43 -9
  49. package/build/generator/sdk-interface.d.ts +1 -1
  50. package/build/generator/sdk-interface.js +5 -5
  51. package/build/logging.d.ts +2 -0
  52. package/build/logging.js +14 -11
  53. package/build/rtk-query-generator/genmodules.d.ts +1 -4
  54. package/build/rtk-query-generator/genmodules.js +27 -31
  55. package/build/rtk-query-generator/index.d.ts +1 -1
  56. package/build/rtk-query-generator/index.js +87 -65
  57. package/build/rtk-query-generator/prettier.js +43 -9
  58. package/build/rtk-query-generator/sdk-interface.js +3 -4
  59. package/build/utils.js +38 -6
  60. package/package.json +29 -11
  61. package/src/analyzer/controller.ts +3 -31
  62. package/src/analyzer/controllers.ts +3 -2
  63. package/src/analyzer/extractor.ts +17 -86
  64. package/src/analyzer/index.ts +5 -11
  65. package/src/analyzer/methods.ts +5 -36
  66. package/src/analyzer/params.ts +13 -55
  67. package/src/analyzer/sdk.ts +247 -0
  68. package/src/analyzer/typedeps.ts +22 -4
  69. package/src/bin.ts +29 -5
  70. package/src/config.ts +15 -0
  71. package/src/engine/analyze.ts +1110 -0
  72. package/src/engine/ast.ts +105 -0
  73. package/src/engine/file-order.ts +104 -0
  74. package/src/engine/host.ts +79 -0
  75. package/src/engine/index.ts +79 -0
  76. package/src/engine/ts-morph-compat-host.ts +20 -0
  77. package/src/engine/tsgo-host.ts +119 -0
  78. package/src/engine/typescript-host.ts +159 -0
  79. package/src/generator/genmodules.ts +9 -4
  80. package/src/generator/gentypes.ts +1 -1
  81. package/src/generator/index.ts +1 -1
  82. package/src/logging.ts +6 -1
  83. package/src/rtk-query-generator/genmodules.ts +9 -4
  84. package/src/rtk-query-generator/index.ts +1 -2
  85. package/src/rtk-query-generator/sdk-interface.ts +2 -3
  86. package/tsconfig.json +7 -6
  87. package/build/analyzer/decorator.d.ts +0 -10
  88. package/build/analyzer/decorator.js +0 -29
  89. package/build/rtk-query-generator/gentypes.d.ts +0 -10
  90. package/build/rtk-query-generator/gentypes.js +0 -62
  91. package/build/tsconfig.tsbuildinfo +0 -1
  92. package/bun.lockb +0 -0
  93. package/tsconfig.tsbuildinfo +0 -1
package/README.md CHANGED
@@ -1,353 +1,516 @@
1
- # nest-sdk-generator by [![](https://user-images.githubusercontent.com/73881870/130991041-a4a1f0f4-21f5-4a54-a085-974f80f56ed2.png)](https://lonestone.io/)
1
+ # @buildvision/nest-sdk-generator
2
2
 
3
- [![](https://img.shields.io/npm/v/nest-sdk-generator)](https://www.npmjs.com/package/nest-sdk-generator)
3
+ [![npm](https://img.shields.io/npm/v/@buildvision/nest-sdk-generator)](https://www.npmjs.com/package/@buildvision/nest-sdk-generator)
4
+ [![Test](https://github.com/build-vision/nest-sdk-generator/actions/workflows/test.yml/badge.svg)](https://github.com/build-vision/nest-sdk-generator/actions/workflows/test.yml)
4
5
 
5
- `nest-sdk-generator` is a tool that allows you to build automatically and in seconds a SDK for client applications to consume a NestJS server's API.
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 project is split in two parts:
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
- - The **analyzer** looks for all modules, controllers, methods and types in the NestJS server, and builds a JSON schema containing all informations
10
- - The **generator** takes this JSON schema and generates a directory containing the SDK
11
-
12
- The project has been created and is currently maintained by our developers at [Lonestone](https://lonestone.io/).
13
-
14
- For a quick glance at performances, check [here](#how-fast-is-it).
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
- **Table of contents:**
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
- - [What is nest-sdk-generator and why should I use it?](#what-is-nest-sdk-generator-and-why-should-i-use-it)
19
- - [Quick usage example](#quick-usage-example)
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
- You can find a complete demonstration API in the [`demo/server`](demo/server) directory, as well as a frontend using a SDK based on this API in [`demo/front`](demo/front). The SDK configuration is located in [`demo/front/sdk-generator.json`](demo/front/sdk-generator.json).
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
- ## How fast is it?
52
+ ## Requirements
83
53
 
84
- On our internal test project, with 20 controllers and 80 routes, the full process takes 3 seconds (from a cold start) on a Core i7-8750H (6-core laptop CPU from 2018) to generate 84 file with 2246 lines of code.
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
- The duration should be roughly proportional to the quantity of routes and DTOs you use in your application, although on very small applications in usually won't go below 2 seconds on such a CPU.
59
+ ## Getting started
87
60
 
88
- ## Features
61
+ The examples assume a monorepo with the API in `api/` and a React app in `web/`.
89
62
 
90
- - Full support for idiomatic NestJS modules and controllers
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
- ## Limitations
65
+ ```sh
66
+ cd web
67
+ npm install --save-dev @buildvision/nest-sdk-generator
68
+ ```
99
69
 
100
- nest-sdk-generator comes with a set of limitations which can find below:
70
+ ### 2. Create a configuration file
101
71
 
102
- 1. nest-sdk-generator does not check if the source files compile correctly. Therefore, if you try to use a type that doesn't exist, the generation may still succeed although compiling the code would fail. In such case, the resulting output type is `any`.
72
+ `web/api-gen.config.json` (paths are relative to this file):
103
73
 
104
- 2. A current limitation of nest-sdk-generator is that it finds a controller's module by looking for a `.module.ts` file in the current directory, and parent directories if none is found in the controller's one. This means controller files must be put under a module's directory, and two module files cannot be put in the same directory.
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
- 3. Types belonging to namespaces are currently not supported and will result in error in the generated code
88
+ All options are described in [Configuration](#configuration).
107
89
 
108
- 4. Due to limitations of the TypeScript compiler, importing from the project's root (e.g. `import { SomeType } from "src/file.ts"` instead of `import { SomeType } from "../file.ts"`) will result in an `any` type at generation, because such types are not recognized when manipulating types. If you want to avoid this behaviour in Visual Studio Code, you can add `"typescript.preferences.importModuleSpecifier": "relative"` to your settings.
90
+ ### 3. Write the request function
109
91
 
110
- ## Using the SDK
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
- The SDK exposes several directories:
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
- - One directory for each module in your NestJS application, with inside one file for each controller belonging to this module
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
- Each controller file exposes a simple record object with keys being your controller's methods' name. Each method takes the route's arguments (e.g. the identifier in `/users/get/:id`), the request's BODY (JSON) as well as the query parameters (e.g. `?something=...`).
107
+ export const BaseRequest: BaseRequestFn = async ({ body, method, query, route }) => {
108
+ // ...perform the request, return { data } or { error }
109
+ }
110
+ ```
118
111
 
119
- Methods return a typed `Promise<>` with the original method's return type.
112
+ If the file doesn't exist, the generator creates a starter implementation for you (see [`generateDefaultSdkInterface`](#configuration)).
120
113
 
121
- - If the requests succeeds and returns a valid JSON string, it is parsed and returned by the method
122
- - If the requests fail or cannot be decoded correctly, the promise fails
114
+ ### 4. Generate the client
123
115
 
124
- ## Architecture
116
+ ```sh
117
+ npx nest-sdk-generator api-gen.config.json --generate-rtk
118
+ ```
125
119
 
126
- nest-sdk-generator analyzes your NestJS API using a provided configuration file, and produces a client-side SDK. This SDK is made of modules containing route methods that all call a central handler with the corresponding URI and parameters. The handler makes the request to the server and transmits the response. This is where you can customize the data to send to the server, if they need normalization, encryption, hashing, parsing, authentication, or anything else.
120
+ ### 5. Create the RTK Query API
127
121
 
128
- ## Step-by-step generation tutorial
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
- Generating a SDK is made in two steps:
127
+ import { BaseRequest } from './BaseRequest'
128
+ import endpoints from './endpoints'
131
129
 
132
- - Creating a configuration file for the generator
133
- - Performing the generation through `sdk-generator generate`
130
+ export const rtkApi = createApi({
131
+ reducerPath: 'api',
132
+ baseQuery: BaseRequest,
133
+ endpoints: endpoints.buildRTKEndpoints,
134
+ })
134
135
 
135
- Let's suppose we have a monorepo, with our server being in `apps/api` and running at `http://localhost:3000`, while our frontend is located in `apps/front`. We have the following structure:
136
+ // Endpoints grouped by controller: API.Users.getUser, API.Comments.addComment...
137
+ export const API = endpoints.groupRTKEndpoints(rtkApi)
136
138
 
137
- ```
138
- .
139
- └── apps
140
- ├── api
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
- We want the SDK to be located in `apps/front/sdk`.
145
+ Wrap your app in react-redux's `<Provider store={store}>` as usual.
149
146
 
150
- First, we must create a configuration file. Let's put it in `front/sdk-generator.json`:
147
+ ### 6. Use the endpoints
151
148
 
152
- ```json
153
- {
154
- "apiInputPath": "../server",
155
- "sdkOutputPath": "sdk",
156
- "sdkInterfacePath": "sdk-interface.ts"
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
- Let's now generate the SDK:
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
- ```shell
163
- sdk-generator apps/front/sdk-generator.json
164
- ```
175
+ ### Client TypeScript setup
165
176
 
166
- We now have a `apps/front/sdk` directory with our SDK inside, and a default interface located in `apps/front/sdk-interface.ts`, which will handle the requests. It will only be generated if it doesn't exist, so you can edit it and commit in your repository without worrying about it being overwritten.
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
- **NOTE:** You can disable this generation by setting the `generateDefaultSdkInterface` setting to `false` in the `sdk-generator.json` file.
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
- #### Recommandations
184
+ No API code ends up in your client bundle: only types are imported.
171
185
 
172
- In most cases, you should add the SDK's output path to your `.gitignore` and perform the generation automatically in your CI.
186
+ ## How controllers become endpoints
173
187
 
174
- If you want to save time in the CI, you can save the generated SDK as an artifact and put it in the registry with the key being the hash of the server's source directory. This way, the SDK will only be rebuilt when the source directory changes.
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
- ## SDK usage
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
- Let's suppose the NestJS server has an `UserModule` module, containing an `UserController` controller with a `getOne(id: string): Promise<UserDTO>` method. We can use it from the SDK:
192
+ **Arguments.** The endpoint takes a single object merging all of the route's inputs:
179
193
 
180
- ```typescript
181
- import { userController } from '<sdk path>/userModule/userController'
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
- const user = await userController.getOne({ id: 'some_id' })
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
- Each method takes three arguments: the parameters (`:xxx` in the original method's route), the body's content, and the query (`?xxx=yyy`). The query is always optional, while the body is only optional if nothing or an empty object is expected. The parameters are only optional if no parameter, nor body, is expected.
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
- #### Importing API types
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
- It's also possible to manually import types the API depends on.
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
- For instance, let's suppose `UserDTO` comes from a shared DTO package which is installed in our NestJS API's directory under `node_modules/@project/dtos/user.d.ts`.
211
+ ## Generated files
194
212
 
195
- We can import it from the SDK like this:
213
+ With the configuration above, the output directory contains:
196
214
 
197
- ```typescript
198
- import { UserDTO } from '<sdk path>/_types/node_modules/@project/dtos/user.d'
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
- It can then be used normally:
223
+ Each controller file exports a class with:
202
224
 
203
- ```typescript
204
- import { userController } from '<sdk path>/userModule/userController'
205
- import { UserDTO } from '<sdk path>/_types/node_modules/@project/dtos/user.d'
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
- const user: UserDTO = await userController.getOne({ id: 'some_id' })
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
- ### External files
249
+ Generated files are overwritten on every run: don't edit them.
211
250
 
212
- In some situations, some of the types you are importing may belong to an external directory than the provided API source directory. For instance, if you use a mono-repository, you may have your API in `apps/api` but your DTOs built as a package in `node_modules/@apps/dtos`. In such case, the files will be placed in a special directory in `_types` called `_externalX` where `X` is the depth level (the farest the exernal file is, the larger this number will be).
251
+ ## Configuration
213
252
 
214
- This will make no difference when using these types, but this is a special case where the types hierarchy isn't perfectly respected.
253
+ The configuration file is a JSON file. Relative paths are resolved from the configuration file's directory.
215
254
 
216
- ## Configuration options
255
+ ### Paths (required)
217
256
 
218
- Here is the list of the configuration options you can use in the JSON file:
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
- | Option name | Default | Value type | Description |
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
- ## Magic types
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
- In some cases, there are types that may not be exportable using the generator. For instance:
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
- ```tsx
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
- @Entity()
245
- export class Author {
246
- // ...
294
+ ### Debugging
247
295
 
248
- @OneToMany(() => Article, (article) => article.author)
249
- articles = new Collection<Article>(this)
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
- Here, the `articles` field uses the `Collection<T>` type of Mikro-ORM, which is a circular type, meaning we can't export it. Plus, it contains lots of methods and depends on multiple Mikro-ORM types, which we don't want in our SDK.
321
+ export type BaseRequestFn = BaseQueryFn<BaseRequestArgs, unknown, RequestError>
257
322
 
258
- To solve this problem, we can add a `magicTypes` entry in our configuration file:
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
- ```json
261
- {
262
- // ...
263
- "magicTypes": [
264
- {
265
- "nodeModuleFilePath": "@mikro-orm/core/entity/Collection.d.ts",
266
- "typeName": "Collection",
267
- "placeholderContent": "export type Collection<T, _> = Array<T>;"
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
- Now, when the SDK generator encounters the `Collection` type when it's imported from `@mikro-orm/core/entity/Collection.d.ts`, it will link to the `placeholderContent` we provided instead.
377
+ The same applies to other per-endpoint options like `keepUnusedDataFor` or `transformResponse`.
275
378
 
276
- Note that some magic types are already [built-in](src/analyzer/builtin.ts), such as the one we just saw. If you use a library very frequently and think others may benefit from a new magic type, feel free to [open a PR](https://github.com/lonestone/nest-sdk-generator/pulls)!
379
+ ## Command line
277
380
 
278
- ## Frequently-asked questions
381
+ ```sh
382
+ nest-sdk-generator <config file> [--generate-rtk | --generate] [--engine <tsgo|typescript|ts-morph>]
383
+ ```
279
384
 
280
- ### Does this replace Swagger?
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
- No, nest-sdk-generator only generates a client-side SDK to make requests more easily to the server ; it doesn't generate a documentation by itself. Although all routes are organized with their original name and split across controllers and modules the same way they were in the API, that doesn't make a full documentation in itself.
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
- ### I have a GraphQL API, what can this project do for me?
394
+ ## Analysis engines
285
395
 
286
- Unfortunately, nest-sdk-generator isn't compatible with GraphQL API, and for a good reason: the whole point of this project is to bring structural typing to an API, but GraphQL already provides that. So there would be no point in this tool being compatible with GraphQL projects.
396
+ Most of the generation time is spent by the TypeScript compiler analyzing the API. Three engines are available:
287
397
 
288
- ### Does the SDK has any performance overhead?
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
- Absolutely not. The generated SDK is only made of simple objets that will call the handler you provided through the interface script, and the said script will be in charge of making the request (with `fetch` by default). This means that no kind of data transformation/conversion happens behind-the-scenes.
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
- ### How do I update the SDK once I change the API's source code?
406
+ | Engine | Duration |
407
+ | --- | --- |
408
+ | `tsgo` | 2.4s |
409
+ | `typescript` | 8.8s |
410
+ | `ts-morph` | 17.7s |
293
411
 
294
- You simply run the same shell command you used to generate the source code originally. There isn't a special subcommand for it, as it will simply delete the old SDK and replace it with the new one.
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
- ### Is the SDK documented?
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
- Each file in the SDK uses a generic documentation, including the exact route model for route methods. This means you will be able to check what route is called by which method each time.
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
- Here is a quick glance at a generated file sample:
418
+ ## Recommended workflow
301
419
 
302
- ```ts
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
- export default {
310
- // GET @ /article
311
- getAll(params: {} = {}, body: {} = {}, query: {} = {}): Promise<Article[]> {
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
- // GET @ /article/:slug
316
- getOne(
317
- params: { slug: string },
318
- body: {} = {},
319
- query: {} = {},
320
- ): Promise<Article> {
321
- return request('GET', `/article/${params.slug}`, body, query)
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
- ### Can I add header or other data on-the-fly when making requests?
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
- All of the SDK's methods use a central handler which calls a function you provided, where you can absolutely everything you can. You are in charge of making the requests thanks to the provided URI and query/body parameters, which means you can add, edit or remove whatever data you want.
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
- ### Is there a way to log the requests or responses somewhere?
486
+ ## Upgrading from 2.x
333
487
 
334
- All of the SDK's methods use a central handler which calls a function you provided, where you can do absolutely everything you can. You are in charge of making
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
- ### Does the API server needs to be running to generate a SDK?
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
- No, the SDK is generated by analyzing the source code of your API, which means you don't need a server running for this.
494
+ ## Development
340
495
 
341
- ### Can I fork this project to make my own version of it?
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
- Absolutely, that's what the MIT license is for. Although, if you could get in touch with us to indicate what changes you made to the generator it would be hugely helpful to improve it on our side ;)
504
+ The tests generate clients for the fixture API in `test/fixtures` (and ~30 error cases) and compare the results of:
344
505
 
345
- ### Can I use this project in commercial software?
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
- Absolutely, the MIT license allows you to do that without any royalties or particular constraint.
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
- If you use this project to produce any kind of program, be it paid or not, we'd love to hear about it! Feel free to get in touch with us at [contact@lonestone.studio](mailto:contact@lonestone.studio) to tell us about your projects, so we can improve our tool even more with additional real-use cases.
512
+ ## Credits and license
350
513
 
351
- ## License
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
- This project is published under the terms of the [MIT License](LICENSE.md).
516
+ Published under the [MIT License](LICENSE.md).