fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add research and implementation plan documents for NestJS plugin.

+660
+400
docs/implementation-plan-nestjs-plugin.md
··· 1 + # NestJS Plugin Implementation Plan 2 + 3 + > Consensus output from 4-agent architecture debate. Synthesized from competing proposals by Architecture Lead (minimal MVP), Feature Architect (fuller implementation), DX Engineer (developer experience), and Devil's Advocate (stress-testing). 4 + > 5 + > **Status: Implemented.** This document reflects the final decisions and actual implementation. 6 + 7 + ## Executive Summary 8 + 9 + Generate type-safe controller method types from OpenAPI specs for NestJS applications. The plugin generates **per-tag `ControllerMethods` type aliases** mapping operation IDs to typed method signatures. NestJS developers add `implements Pick<PetsControllerMethods, ...>` to their controller classes for compile-time contract enforcement. 10 + 11 + **Why per-tag grouping (not flat)?** Tag-based grouping was originally deferred to v1.1, but was implemented directly because it maps naturally to NestJS's one-controller-per-resource pattern. Operations are grouped by their first OpenAPI tag into `{PascalTag}ControllerMethods` types. Untagged operations go to `DefaultControllerMethods`. 12 + 13 + --- 14 + 15 + ## Debate Resolution Summary 16 + 17 + | Decision | Resolution | Winning Argument | 18 + | ---------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | 19 + | Output format | Per-tag `{Tag}ControllerMethods` type aliases | Tag grouping maps 1:1 to NestJS controllers; implemented directly instead of deferring | 20 + | `type` vs `interface` | `type` alias | DSL naturally produces `type` aliases; works with `implements` in TypeScript just like `interface` | 21 + | Parameters | Individual params: `path`, `query`, `body`, `headers` | All agreed: maps 1:1 to NestJS `@Param()`, `@Query()`, `@Body()`, `@Headers()` | 22 + | Parameter ordering | Required before optional | Prevents TS errors from optional params preceding required ones | 23 + | Return type | `Promise<OperationResponse>` (union of success responses) | DX Engineer: controllers return values, not status-code maps | 24 + | Tag grouping | Implemented in MVP | Natural fit for NestJS controller-per-resource pattern | 25 + | Decorator generation | Deferred to v2 | All agreed: not MVP scope | 26 + | Config options | None for MVP | Arch Lead: zero config = fastest path to value | 27 + | `implements` viability | Works with documented constraint | Devil's Advocate confirmed: TS decorators don't affect signature matching, but users must use whole-object param style | 28 + 29 + --- 30 + 31 + ## Critical Design Decision: The `implements` Constraint 32 + 33 + The Devil's Advocate identified the most important technical finding: 34 + 35 + **TypeScript `implements` works with NestJS decorated parameters**, but only if the user adopts whole-object parameter style: 36 + 37 + ```typescript 38 + // WORKS with implements - whole-object style (recommended) 39 + @Get(':petId') 40 + async showPetById(@Param() path: ShowPetByIdData['path']): Promise<ShowPetByIdResponse> { ... } 41 + 42 + // DOES NOT WORK with implements - decomposed style 43 + @Get(':petId') 44 + async showPetById(@Param('petId') petId: string): Promise<ShowPetByIdResponse> { ... } 45 + ``` 46 + 47 + The whole-object style is a recognized NestJS pattern and arguably cleaner. This constraint is prominently documented. 48 + 49 + **Known limitations (documented):** 50 + 51 + - Methods using `@Res()` for raw response access are incompatible (extra parameter breaks assignability) 52 + - File upload operations using `@UploadedFile()` may need to bypass `implements` for specific methods 53 + - SSE endpoints returning `Observable<MessageEvent>` don't match `Promise<T>` return type 54 + 55 + --- 56 + 57 + ## Actual Output Format 58 + 59 + ### Generated `nestjs.gen.ts` 60 + 61 + For the Petstore spec (with CRUD operations): 62 + 63 + ```typescript 64 + // This file is auto-generated by @hey-api/openapi-ts 65 + 66 + import type { 67 + CreatePetData, 68 + CreatePetResponse, 69 + DeletePetData, 70 + DeletePetResponse, 71 + GetInventoryResponse, 72 + ListPetsData, 73 + ListPetsResponse, 74 + ShowPetByIdData, 75 + ShowPetByIdResponse, 76 + UpdatePetData, 77 + UpdatePetResponse, 78 + } from './types.gen'; 79 + 80 + export type PetsControllerMethods = { 81 + createPet: (body: CreatePetData['body']) => Promise<CreatePetResponse>; 82 + deletePet: (path: DeletePetData['path']) => Promise<DeletePetResponse>; 83 + listPets: (query?: ListPetsData['query']) => Promise<ListPetsResponse>; 84 + showPetById: (path: ShowPetByIdData['path']) => Promise<ShowPetByIdResponse>; 85 + updatePet: ( 86 + path: UpdatePetData['path'], 87 + body: UpdatePetData['body'], 88 + ) => Promise<UpdatePetResponse>; 89 + }; 90 + 91 + export type StoreControllerMethods = { 92 + getInventory: () => Promise<GetInventoryResponse>; 93 + }; 94 + ``` 95 + 96 + ### Design Decisions in Output 97 + 98 + 1. **`type` alias (not `interface`)** -- DSL naturally produces `type` aliases; both work with `implements` in TypeScript 99 + 2. **Per-tag grouping** -- operations grouped by first OpenAPI tag into `{PascalTag}ControllerMethods` 100 + 3. **Individual method parameters** -- `path`, `query`, `body`, `headers` matching NestJS decorator extraction 101 + 4. **Required-before-optional sorting** -- params sorted so required come first, preventing TS signature errors 102 + 5. **Optional params use `?`** -- uses `hasParameterGroupObjectRequired()` to determine optionality 103 + 6. **Return type is `Promise<OperationResponse>`** -- union of success response body types, not status-code-indexed map 104 + 7. **No external imports** -- unlike Fastify which imports `RouteHandler` from the `fastify` package, NestJS output is pure TypeScript. Zero runtime dependencies 105 + 8. **Untagged operations** -- grouped under `DefaultControllerMethods` 106 + 107 + ### Parameter Mapping 108 + 109 + ``` 110 + OpenAPI → Method Parameter 111 + ───────────────────────────────────────────── 112 + operation.parameters.path → path: OperationData['path'] 113 + operation.parameters.query → query?: OperationData['query'] 114 + operation.body → body: OperationData['body'] 115 + operation.parameters.header→ headers?: OperationData['headers'] 116 + ``` 117 + 118 + Parameters are collected then sorted: required before optional. This prevents TypeScript errors where an optional parameter would precede a required one (e.g., `(query?: T, body: U)` is invalid). 119 + 120 + ### Response Type Mapping 121 + 122 + Uses `operationResponsesMap()` to check for responses, then queries the `role: 'response'` symbol (the union of success response bodies), NOT the `role: 'responses'` status-code-indexed type. Wraps in `Promise<T>`, falls back to `Promise<void>` when no response symbol exists. 123 + 124 + --- 125 + 126 + ## Usage Patterns 127 + 128 + ### Basic Usage 129 + 130 + ```typescript 131 + import { Controller, Get, Post, Param, Query, Body } from '@nestjs/common'; 132 + import type { PetsControllerMethods } from '../client/nestjs.gen'; 133 + import type { ListPetsData, ShowPetByIdData, CreatePetData } from '../client/types.gen'; 134 + 135 + @Controller('pets') 136 + export class PetsController implements Pick< 137 + PetsControllerMethods, 138 + 'listPets' | 'createPet' | 'showPetById' 139 + > { 140 + @Get() 141 + async listPets(@Query() query?: ListPetsData['query']) { 142 + return []; 143 + } 144 + 145 + @Post() 146 + async createPet(@Body() body: CreatePetData['body']) { 147 + return { id: 1, name: body.name }; 148 + } 149 + 150 + @Get(':petId') 151 + async showPetById(@Param() path: ShowPetByIdData['path']) { 152 + return { id: Number(path.petId), name: 'Kitty' }; 153 + } 154 + } 155 + ``` 156 + 157 + ### Incremental Adoption (DX Engineer's Key Insight) 158 + 159 + Unlike Fastify where you must provide ALL handlers at once, NestJS developers can adopt incrementally: 160 + 161 + ```typescript 162 + // Step 1: Add implements to ONE controller 163 + @Controller('pets') 164 + export class PetsController implements Pick<PetsControllerMethods, 'listPets'> { 165 + @Get() 166 + async listPets(@Query() query?: ListPetsData['query']) { ... } 167 + 168 + // Other methods remain untyped for now 169 + @Post() 170 + async createPet(@Body() body: any) { ... } 171 + } 172 + 173 + // Step 2: Gradually add more methods to the Pick union 174 + // TypeScript catches any signature mismatches immediately 175 + ``` 176 + 177 + ### Migration from Existing App 178 + 179 + ```typescript 180 + // BEFORE: existing controller, no generated types 181 + @Controller('pets') 182 + export class PetsController { 183 + @Get() 184 + async findAll(@Query('limit') limit?: number) { ... } 185 + } 186 + 187 + // AFTER: add implements, rename method to match operationId, use whole-object params 188 + @Controller('pets') 189 + export class PetsController implements Pick<PetsControllerMethods, 'listPets'> { 190 + @Get() 191 + async listPets(@Query() query?: ListPetsData['query']) { ... } 192 + } 193 + ``` 194 + 195 + --- 196 + 197 + ## Plugin Implementation 198 + 199 + ### File Structure 200 + 201 + ``` 202 + packages/openapi-ts/src/plugins/nestjs/ 203 + ├── plugin.ts # Core logic (143 lines) 204 + ├── types.ts # Plugin type definitions (5 lines) 205 + ├── config.ts # Plugin registration (18 lines) 206 + └── index.ts # Re-exports (2 lines) 207 + ``` 208 + 209 + ### `types.ts` 210 + 211 + ```typescript 212 + import type { DefinePlugin, Plugin } from '@hey-api/shared'; 213 + 214 + export type UserConfig = Plugin.Name<'nestjs'> & Plugin.Hooks & Plugin.UserExports; 215 + 216 + export type NestJSPlugin = DefinePlugin<UserConfig, UserConfig>; 217 + ``` 218 + 219 + ### `config.ts` 220 + 221 + ```typescript 222 + import { definePluginConfig } from '@hey-api/shared'; 223 + 224 + import { handler } from './plugin'; 225 + import type { NestJSPlugin } from './types'; 226 + 227 + export const defaultConfig: NestJSPlugin['Config'] = { 228 + config: { 229 + includeInEntry: false, 230 + }, 231 + dependencies: ['@hey-api/typescript'], 232 + handler, 233 + name: 'nestjs', 234 + }; 235 + 236 + export const defineConfig = definePluginConfig(defaultConfig); 237 + ``` 238 + 239 + ### `index.ts` 240 + 241 + ```typescript 242 + export { defaultConfig, defineConfig } from './config'; 243 + export type { NestJSPlugin } from './types'; 244 + ``` 245 + 246 + ### `plugin.ts` (Core Logic) 247 + 248 + The handler groups operations by first tag, builds method signatures per operation, and emits one type alias per tag: 249 + 250 + ```typescript 251 + export const handler: NestJSPlugin['Handler'] = ({ plugin }) => { 252 + const operationsByTag = new Map<string, Array<{ name: string; type: ... }>>(); 253 + 254 + plugin.forEach('operation', ({ operation, tags }) => { 255 + const tag = tags?.[0] ?? 'default'; 256 + // ... collect methods per tag 257 + }, { order: 'declarations' }); 258 + 259 + for (const [tag, methods] of operationsByTag) { 260 + const pascalTag = toCase(tag, 'PascalCase'); 261 + // emit: export type {PascalTag}ControllerMethods = { ... } 262 + } 263 + }; 264 + ``` 265 + 266 + Key implementation details: 267 + 268 + - `operationToMethod()` builds each method's function type 269 + - Parameters collected into array, then sorted (required before optional) 270 + - Uses `plugin.querySymbol({ role: 'data' })` for param types 271 + - Uses `plugin.querySymbol({ role: 'response' })` for return types 272 + - Uses `operationResponsesMap()` to verify responses exist before referencing response symbol 273 + 274 + ### Registration 275 + 276 + **`packages/openapi-ts/src/plugins/config.ts`:** 277 + 278 + ```typescript 279 + import { defaultConfig as nestjs } from './nestjs/config'; 280 + // added to defaultPluginConfigs map 281 + ``` 282 + 283 + **`packages/openapi-ts/src/index.ts`:** 284 + 285 + ```typescript 286 + import type { NestJSPlugin } from './plugins/nestjs/types'; 287 + // added to PluginConfigMap: nestjs: NestJSPlugin['Types'] 288 + ``` 289 + 290 + --- 291 + 292 + ## Example Project 293 + 294 + ### Structure 295 + 296 + ``` 297 + examples/openapi-ts-nestjs/ 298 + ├── openapi-ts.config.ts # plugins: ['nestjs', '@hey-api/sdk', ...] 299 + ├── openapi.json # Extended Petstore spec (CRUD + store) 300 + ├── eslint.config.js # NestJS-specific linting 301 + ├── package.json 302 + ├── tsconfig.json 303 + ├── vite.config.ts # For Vitest 304 + ├── src/ 305 + │ ├── client/ # Generated output 306 + │ │ ├── nestjs.gen.ts # Generated controller method types 307 + │ │ ├── types.gen.ts # Generated types 308 + │ │ ├── sdk.gen.ts # Client SDK 309 + │ │ └── index.ts 310 + │ ├── pets/ 311 + │ │ ├── pets.controller.ts # Implements PetsControllerMethods 312 + │ │ ├── dto/ # class-validator DTOs 313 + │ │ └── pets.module.ts 314 + │ ├── store/ 315 + │ │ ├── store.controller.ts # Implements StoreControllerMethods 316 + │ │ └── store.module.ts 317 + │ ├── common/ 318 + │ │ └── filters/ 319 + │ │ └── http-exception.filter.ts 320 + │ ├── app.module.ts # Root module 321 + │ └── main.ts # Bootstrap with ValidationPipe 322 + └── test/ 323 + └── pets.test.ts # Integration tests 324 + ``` 325 + 326 + ### Key Decisions 327 + 328 + - **Express adapter** (not Fastify) -- Express is the default NestJS adapter and avoids confusion with the existing Fastify plugin example 329 + - **@nestjs/swagger** -- OpenAPI documentation integration 330 + - **class-validator DTOs** -- request validation with `ValidationPipe` 331 + - **Exception filters** -- custom HTTP exception handling 332 + - **NestJS-specific ESLint** -- `@darraghor/eslint-plugin-nestjs-typed` 333 + - **Two controllers** -- pets and store, demonstrating per-tag grouping 334 + - **Integration tests** -- using `@nestjs/testing` + supertest 335 + 336 + --- 337 + 338 + ## Documentation 339 + 340 + Located at `docs/openapi-ts/plugins/nest.md` (~155 lines): 341 + 342 + 1. **Title + Beta Warning** 343 + 2. **About** -- "NestJS is a progressive Node.js framework..." 344 + 3. **Features** -- type-safe controller methods, tag-based grouping, incremental adoption, zero runtime coupling 345 + 4. **Installation** -- config snippet with `'nestjs'` plugin 346 + 5. **Output** -- generated per-tag types with code example 347 + 6. **Usage** -- `implements Pick<ControllerMethods, ...>` pattern 348 + 7. **Production Example** -- link to example app 349 + 8. **Constraints** -- whole-object parameter style, `@Res()` incompatibility 350 + 9. **API** -- link to UserConfig type 351 + 352 + --- 353 + 354 + ## Roadmap 355 + 356 + ### MVP (this PR) -- Done 357 + 358 + - [x] Plugin files: `plugin.ts`, `types.ts`, `config.ts`, `index.ts` 359 + - [x] Plugin registration in `config.ts` and `index.ts` 360 + - [x] Per-tag `{Tag}ControllerMethods` type alias generation 361 + - [x] Method signatures with typed path, query, body, headers params 362 + - [x] Required-before-optional parameter sorting 363 + - [x] `Promise<Response>` return types (with `Promise<void>` fallback) 364 + - [x] Example project at `examples/openapi-ts-nestjs/` 365 + - [x] Documentation at `docs/openapi-ts/plugins/nest.md` 366 + - [x] Test snapshots for OpenAPI 2.0, 3.0, and 3.1 specs 367 + 368 + ### v2 -- Decorator & Validation Support 369 + 370 + - [ ] `@ApiResponse()` / `@ApiOperation()` decorator generation 371 + - [ ] DTO class generation with `class-validator` decorators 372 + - [ ] Service interface generation 373 + - [ ] Module scaffolding option 374 + 375 + --- 376 + 377 + ## Edge Cases Handled 378 + 379 + | Edge Case | Handling | Source | 380 + | --------------------------- | ------------------------------------------------------------------ | ------------------------------------ | 381 + | No operationId | System auto-generates from method+path (e.g., `getPetsByPetId`) | Already handled by `operationToId()` | 382 + | Duplicate operation IDs | Numeric suffix appended (`result2`, `result3`) | Already handled by `operationToId()` | 383 + | Invalid TS identifier chars | Sanitized by `sanitizeNamespaceIdentifier()` | Already handled | 384 + | No parameters | Method has no params: `getInventory: () => Promise<...>` | Natural | 385 + | No response body (204) | Returns `Promise<void>` | Handled via fallback | 386 + | No tags | Grouped under `DefaultControllerMethods` | Handled | 387 + | Multiple tags | First tag wins | Handled | 388 + | File uploads (multipart) | Body type is generated but may not match `@UploadedFile()` pattern | Document as limitation | 389 + | SSE/streaming | Return type won't match `Observable<MessageEvent>` | Document as limitation | 390 + | `@Res()` usage | Extra param breaks `implements` assignability | Document as limitation | 391 + 392 + --- 393 + 394 + ## Resolved Open Questions 395 + 396 + 1. **`interface` vs `type` in DSL** -- `$.type.alias()` produces a `type` alias. Both `type` and `interface` work with `implements` in TypeScript, so this is fine. 397 + 398 + 2. **Response type symbol** -- `role: 'response'` (singular) returns the flattened union of success response bodies. Combined with `operationResponsesMap()` check to verify responses exist before referencing the symbol. 399 + 400 + 3. **`$.type.func()` parameter syntax** -- Uses `funcType.param(key, (p) => p.required(...).type(...))` pattern. Verified by examining DSL usage in other plugins.
+260
docs/openapi-ts/plugins/nestjs-research.md
··· 1 + # NestJS Plugin Research & Requirements 2 + 3 + ## Context 4 + 5 + Investigate NestJS plugin following Fastify plugin pattern. Generate type-safe controller method types from OpenAPI specs for NestJS applications. 6 + 7 + ## Fastify Baseline 8 + 9 + **Current implementation:** 10 + 11 + - Location: `packages/openapi-ts/src/plugins/fastify/` 12 + - Output: `RouteHandlers` interface mapping operation IDs to typed handlers 13 + - Pattern: `RouteHandler<{ Body?, Headers?, Params?, Querystring?, Reply }>` 14 + - Size: ~155 lines core logic 15 + - Deps: `@hey-api/typescript` plugin 16 + - Runtime: Uses `fastify-openapi-glue` library for route registration 17 + - Example: `examples/openapi-ts-fastify/` 18 + - Docs: `docs/openapi-ts/plugins/fastify.md` (~100 lines) 19 + 20 + ## NestJS vs Fastify 21 + 22 + | Aspect | Fastify | NestJS | 23 + | ----------------- | ---------------------- | ------------------------------------------------ | 24 + | **Style** | Functional handlers | Class-based controllers | 25 + | **Typing** | Type generics | DTOs + decorators | 26 + | **DI** | Manual | Built-in container | 27 + | **Runtime lib** | `fastify-openapi-glue` | `@nestjs/swagger` | 28 + | **Module system** | None (flat object) | `@Module` required | 29 + | **Parameters** | Generic types | Decorator metadata (`@Param`, `@Query`, `@Body`) | 30 + 31 + ## Research Questions 32 + 33 + ### High Priority (MVP) -- Resolved 34 + 35 + **1. Output Format** 36 + 37 + - **Decided**: Per-tag `type` aliases (e.g., `PetsControllerMethods`, `StoreControllerMethods`) 38 + - Rationale: Tag-based grouping maps naturally to NestJS's one-controller-per-resource pattern. `type` aliases work with `implements` in TypeScript just like `interface`. 39 + 40 + **2. Method Mapping** 41 + 42 + - Map operation IDs to camelCase method names (matches SDK functions) 43 + - Example: `GET /pets` with operationId `listPets` → `listPets()` method 44 + 45 + **3. Type Structure** 46 + Request parameters: 47 + 48 + - Path params: `operation.parameters.path` → method param type 49 + - Query params: `operation.parameters.query` → method param type 50 + - Request body: `operation.body` → method param type 51 + - Headers: `operation.parameters.header` → method param type 52 + - **Parameter ordering**: Required params sorted before optional params 53 + 54 + Response: 55 + 56 + - Success responses: `operation.responses` → `Promise<OperationResponse>` return type (union of success bodies) 57 + - Error responses: Excluded — NestJS handles errors via exception filters 58 + - No response body (204): Returns `Promise<void>` 59 + 60 + **4. Platform Support** 61 + 62 + - NestJS supports both Express and Fastify adapters 63 + - Generated types are adapter-agnostic 64 + - Runtime decorators handle platform differences 65 + 66 + ### Medium Priority (Future) 67 + 68 + **5. @nestjs/swagger Integration** 69 + 70 + - Current: Generate standalone types 71 + - Future: Optionally generate `@ApiResponse()`, `@ApiOperation()` decorators 72 + - Requires config flag: `generateDecorators?: boolean` 73 + 74 + **6. Validation** 75 + 76 + - Current: Type-only validation via TypeScript 77 + - Future: Generate `class-validator` decorators in DTOs 78 + - Requires config flag: `generateValidation?: boolean` 79 + 80 + **7. Module Generation** 81 + 82 + - Current: Generate controller method types only 83 + - Future: Generate full `@Module()` setup 84 + - Requires config option: `moduleGeneration?: 'type' | 'class' | 'module'` 85 + 86 + **8. DI Layer** 87 + 88 + - Current: Controller method types only 89 + - Future: Generate service interfaces for business logic separation 90 + - Note: `ServiceMethods` was prototyped and removed — added complexity without clear value for MVP 91 + 92 + ### Low Priority (v2+) 93 + 94 + 9. Guards/interceptors/pipes type generation 95 + 10. Exception filters for typed error responses 96 + 11. SwaggerModule auto-configuration 97 + 12. Validation pipe integration 98 + 99 + ## Implemented Output 100 + 101 + ### Generated Type Aliases 102 + 103 + ```typescript 104 + // client/nestjs.gen.ts 105 + import type { 106 + CreatePetData, 107 + CreatePetResponse, 108 + DeletePetData, 109 + DeletePetResponse, 110 + ListPetsData, 111 + ListPetsResponse, 112 + ShowPetByIdData, 113 + ShowPetByIdResponse, 114 + UpdatePetData, 115 + UpdatePetResponse, 116 + } from './types.gen'; 117 + 118 + export type PetsControllerMethods = { 119 + createPet: (body: CreatePetData['body']) => Promise<CreatePetResponse>; 120 + deletePet: (path: DeletePetData['path']) => Promise<DeletePetResponse>; 121 + listPets: (query?: ListPetsData['query']) => Promise<ListPetsResponse>; 122 + showPetById: (path: ShowPetByIdData['path']) => Promise<ShowPetByIdResponse>; 123 + updatePet: ( 124 + path: UpdatePetData['path'], 125 + body: UpdatePetData['body'], 126 + ) => Promise<UpdatePetResponse>; 127 + }; 128 + 129 + export type StoreControllerMethods = { 130 + getInventory: () => Promise<GetInventoryResponse>; 131 + }; 132 + ``` 133 + 134 + ### Usage Pattern 135 + 136 + ```typescript 137 + // controllers/pets.controller.ts 138 + import { Controller, Get, Post, Param, Query, Body } from '@nestjs/common'; 139 + import type { PetsControllerMethods } from '../client/nestjs.gen'; 140 + 141 + @Controller('pets') 142 + export class PetsController implements Pick< 143 + PetsControllerMethods, 144 + 'listPets' | 'createPet' | 'showPetById' 145 + > { 146 + @Get() 147 + async listPets(@Query() query?: ListPetsData['query']) { 148 + return []; 149 + } 150 + 151 + @Post() 152 + async createPet(@Body() body: CreatePetDto) { 153 + return { id: 1 }; 154 + } 155 + 156 + @Get(':petId') 157 + async showPetById(@Param() path: ShowPetByIdData['path']) { 158 + return { id: Number(path.petId), name: 'Kitty' }; 159 + } 160 + } 161 + ``` 162 + 163 + ## Example Structure 164 + 165 + ``` 166 + examples/openapi-ts-nestjs/ 167 + ├── openapi-ts.config.ts # plugins: ['nestjs', '@hey-api/sdk'] 168 + ├── openapi.json # Petstore spec (extended with update/delete) 169 + ├── eslint.config.js # NestJS-specific linting 170 + ├── package.json # @nestjs/core, @nestjs/common, @nestjs/platform-express 171 + ├── src/ 172 + │ ├── client/ 173 + │ │ ├── nestjs.gen.ts # Generated controller method types 174 + │ │ ├── types.gen.ts # Shared types (from TypeScript plugin) 175 + │ │ ├── sdk.gen.ts # Client SDK (from SDK plugin) 176 + │ │ └── index.ts 177 + │ ├── pets/ 178 + │ │ ├── pets.controller.ts # Implements PetsControllerMethods 179 + │ │ └── pets.module.ts 180 + │ ├── store/ 181 + │ │ ├── store.controller.ts # Implements StoreControllerMethods 182 + │ │ └── store.module.ts 183 + │ ├── common/ 184 + │ │ └── filters/ 185 + │ │ └── http-exception.filter.ts 186 + │ ├── app.module.ts # NestJS module setup 187 + │ └── main.ts # Bootstrap app 188 + └── test/ 189 + └── pets.test.ts # Integration tests 190 + ``` 191 + 192 + ## Dependencies 193 + 194 + ### Plugin (dev) 195 + 196 + - `@hey-api/typescript` - Base type generation (required dependency) 197 + 198 + ### Example (runtime) 199 + 200 + - `@nestjs/core` (^10.0.0) 201 + - `@nestjs/common` (^10.0.0) 202 + - `@nestjs/platform-express` (^10.0.0) or `@nestjs/platform-fastify` 203 + - `@nestjs/swagger` - OpenAPI documentation 204 + - `class-validator` - DTO request validation 205 + - `class-transformer` - DTO transformation 206 + 207 + ## Implementation Approach 208 + 209 + 1. **Plugin structure** at `packages/openapi-ts/src/plugins/nestjs/`: 210 + - `plugin.ts` (~143 lines) - Core logic 211 + - `types.ts` (5 lines) - Plugin type definitions 212 + - `config.ts` (18 lines) - Plugin registration 213 + - `index.ts` (2 lines) - Re-exports 214 + 215 + 2. **Type generation** using per-tag grouping: 216 + - Iterate operations via `plugin.forEach('operation')` 217 + - Group by first OpenAPI tag (default tag: `'default'`) 218 + - For each operation: extract params, build method signature 219 + - Emit per-tag `type` alias: `{PascalTag}ControllerMethods` 220 + 221 + 3. **Parameter mapping** with required-before-optional sorting: 222 + - Collect all params (path, query, body, headers) 223 + - Sort: required params first, optional params last 224 + - Emit as individual function parameters 225 + 226 + 4. **Response type mapping**: 227 + - Use `operationResponsesMap()` to get response info 228 + - Query `role: 'response'` symbol (union of success bodies, not status-code map) 229 + - Wrap in `Promise<T>`, fallback to `Promise<void>` 230 + 231 + 5. **Registration**: 232 + - Added to `packages/openapi-ts/src/plugins/config.ts` 233 + - Added to `packages/openapi-ts/src/index.ts` PluginConfigMap 234 + 235 + ## Deferred Features (v2+) 236 + 237 + - Full `@ApiResponse()`, `@ApiOperation()` decorator generation 238 + - `class-validator` decorators in generated DTOs 239 + - Full module generation with `@Module()` decorator 240 + - Service interface generation for DI layer (prototyped and removed) 241 + - Guard/interceptor/pipe type generation 242 + - Exception filter types 243 + - SwaggerModule auto-configuration 244 + 245 + ## Critical Files Reference 246 + 247 + **Plugin implementation:** 248 + 249 + - `packages/openapi-ts/src/plugins/nestjs/plugin.ts` - Core logic (143 lines) 250 + - `packages/openapi-ts/src/plugins/nestjs/types.ts` - UserConfig 251 + - `packages/openapi-ts/src/plugins/nestjs/config.ts` - Plugin registration 252 + - `packages/openapi-ts/src/plugins/nestjs/index.ts` - Re-exports 253 + 254 + **Studied for implementation:** 255 + 256 + - `packages/openapi-ts/src/plugins/fastify/plugin.ts` - Core logic template 257 + - `packages/openapi-ts/src/plugins/fastify/types.ts` - UserConfig pattern 258 + - `packages/openapi-ts/src/plugins/fastify/config.ts` - Plugin registration 259 + - `examples/openapi-ts-fastify/src/handlers.ts` - Handler pattern 260 + - `docs/openapi-ts/plugins/fastify.md` - Docs template