@lenne.tech/nest-server 11.42.8 → 11.42.10

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 (68) hide show
  1. package/.claude/rules/better-auth.md +24 -0
  2. package/.claude/rules/configurable-features.md +5 -3
  3. package/.claude/rules/testing.md +4 -4
  4. package/FRAMEWORK-API.md +3 -2
  5. package/dist/core/common/decorators/api-methods.decorator.d.ts +2 -0
  6. package/dist/core/common/decorators/api-methods.decorator.js +8 -0
  7. package/dist/core/common/decorators/api-methods.decorator.js.map +1 -0
  8. package/dist/core/common/helpers/swagger.helper.js +312 -1
  9. package/dist/core/common/helpers/swagger.helper.js.map +1 -1
  10. package/dist/core/common/interfaces/server-options.interface.d.ts +1 -0
  11. package/dist/core/modules/ai/core-ai-mcp.controller.js +15 -1
  12. package/dist/core/modules/ai/core-ai-mcp.controller.js.map +1 -1
  13. package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.js +8 -1
  14. package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.js.map +1 -1
  15. package/dist/core/modules/ai/helpers/ai-mcp-oauth.registry.d.ts +7 -0
  16. package/dist/core/modules/ai/helpers/ai-mcp-oauth.registry.js +12 -0
  17. package/dist/core/modules/ai/helpers/ai-mcp-oauth.registry.js.map +1 -0
  18. package/dist/core/modules/better-auth/better-auth.config.js +12 -0
  19. package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
  20. package/dist/core/modules/better-auth/core-better-auth-api.middleware.js +2 -2
  21. package/dist/core/modules/better-auth/core-better-auth-api.middleware.js.map +1 -1
  22. package/dist/core/modules/better-auth/core-better-auth-openapi.helper.d.ts +62 -0
  23. package/dist/core/modules/better-auth/core-better-auth-openapi.helper.js +95 -0
  24. package/dist/core/modules/better-auth/core-better-auth-openapi.helper.js.map +1 -0
  25. package/dist/core/modules/better-auth/core-better-auth.service.d.ts +10 -2
  26. package/dist/core/modules/better-auth/core-better-auth.service.js +89 -0
  27. package/dist/core/modules/better-auth/core-better-auth.service.js.map +1 -1
  28. package/dist/core/modules/tus/core-tus.controller.js +3 -0
  29. package/dist/core/modules/tus/core-tus.controller.js.map +1 -1
  30. package/dist/core/modules/tus/core-tus.service.d.ts +3 -0
  31. package/dist/core/modules/tus/core-tus.service.js +58 -25
  32. package/dist/core/modules/tus/core-tus.service.js.map +1 -1
  33. package/dist/core/modules/tus/interfaces/tus-config.interface.js +1 -0
  34. package/dist/core/modules/tus/interfaces/tus-config.interface.js.map +1 -1
  35. package/dist/core/modules/tus/tus.module.d.ts +2 -0
  36. package/dist/core/modules/tus/tus.module.js +23 -1
  37. package/dist/core/modules/tus/tus.module.js.map +1 -1
  38. package/dist/core.module.js +1 -1
  39. package/dist/core.module.js.map +1 -1
  40. package/dist/index.d.ts +1 -0
  41. package/dist/index.js +1 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/tsconfig.build.tsbuildinfo +1 -1
  44. package/docs/REQUEST-LIFECYCLE.md +9 -1
  45. package/docs/security-overrides.md +6 -5
  46. package/migration-guides/11.42.8-to-11.42.9.md +186 -0
  47. package/migration-guides/11.42.9-to-11.42.10.md +111 -0
  48. package/package.json +7 -8
  49. package/src/core/common/decorators/api-methods.decorator.ts +25 -0
  50. package/src/core/common/helpers/swagger.helper.ts +454 -1
  51. package/src/core/common/interfaces/server-options.interface.ts +46 -8
  52. package/src/core/common/interfaces/swagger-setup-options.interface.ts +5 -2
  53. package/src/core/modules/ai/README.md +7 -0
  54. package/src/core/modules/ai/core-ai-mcp.controller.ts +19 -2
  55. package/src/core/modules/ai/helpers/ai-mcp-oauth.helper.ts +12 -2
  56. package/src/core/modules/ai/helpers/ai-mcp-oauth.registry.ts +29 -0
  57. package/src/core/modules/better-auth/README.md +34 -0
  58. package/src/core/modules/better-auth/better-auth.config.ts +27 -1
  59. package/src/core/modules/better-auth/core-better-auth-api.middleware.ts +4 -17
  60. package/src/core/modules/better-auth/core-better-auth-openapi.helper.ts +219 -0
  61. package/src/core/modules/better-auth/core-better-auth.service.ts +166 -2
  62. package/src/core/modules/tus/README.md +50 -14
  63. package/src/core/modules/tus/core-tus.controller.ts +6 -0
  64. package/src/core/modules/tus/core-tus.service.ts +97 -26
  65. package/src/core/modules/tus/interfaces/tus-config.interface.ts +1 -0
  66. package/src/core/modules/tus/tus.module.ts +43 -1
  67. package/src/core.module.ts +2 -1
  68. package/src/index.ts +1 -0
@@ -157,6 +157,7 @@ JWT-based authentication for existing projects:
157
157
  | `@CommonError(code)` | Error code registration |
158
158
  | `@SkipTenantCheck()` | Opt out of CoreTenantGuard validation on a method (not for tenant-restricted API tokens — they stay bound) |
159
159
  | `@ApiTokenScopes(...scopes)` | Open a route/class to API tokens holding one of the scopes; tokens are denied everywhere else |
160
+ | `@ApiMethods(...methods)` | Swagger only (11.42.9): the HTTP methods an `@All()` handler actually serves — `setupSwagger()` documents those instead of all eight. No runtime effect |
160
161
 
161
162
  ### File Handling
162
163
 
@@ -166,7 +167,7 @@ JWT-based authentication for existing projects:
166
167
  | **REST Endpoints** | `GET /files/id/:id`, `GET /files/:filename` (core, gated by `file.downloadRoles`, default ADMIN); `POST /files/upload`, `DELETE /files/:id` (project-specific) |
167
168
  | **GraphQL Endpoints** | `getFileInfo` (`file.downloadRoles`), `uploadFile` / `uploadFiles` (`file.uploadRoles`), `deleteFile` (`file.deleteRoles`) — all default ADMIN |
168
169
  | **File access control** | Roles are the coarse filter; per-file rules go in `CoreFileService.checkRights()` using metadata written at upload time. Cover BOTH the `id` and the `filename` branch — the filename route authorizes on the by-name lookup alone when presigned S3 downloads are on, and `deleteFileByName()` always does. Working reference: `src/server/modules/file/file.service.ts` (with `file.downloadRoles: [S_USER]` in `src/config.env.ts`, so the rule is actually reached). Both file classes carry `@SkipTenantCheck()` — GridFS is not tenant-scoped |
169
- | **TUS Module** | Resumable uploads via tus.io protocol (creation, termination, expiration), gated by `tus.roles` (default `S_USER`); `OPTIONS` stays public for the CORS preflight. Records owner and, with `multiTenancy` only, the validated tenant at upload creation; a custom service plugs in via `TusModule.forRoot({ service })` |
170
+ | **TUS Module** | Resumable uploads via tus.io protocol (creation, termination, expiration), gated by `tus.roles` (default `S_USER`); `OPTIONS` stays public for the CORS preflight. Records owner and, with `multiTenancy` only, the validated tenant at upload creation; a custom service plugs in via `TusModule.forRoot({ service })`. Configured by `TusModule.forRoot({ config })`, else the server config's `tus` key (11.42.9+). Switched-off `creation` / `termination` answer 501 (enforced by `CoreTusService`, not `@tus/server`); upload URLs follow the path the upload was created at |
170
171
  | **GridFS Migration** | Completed TUS uploads auto-migrate to GridFS |
171
172
  | **CORS Support** | Automatic CORS headers for browser uploads |
172
173
 
@@ -636,6 +637,13 @@ If authentication succeeds, `req.user` is set with the authenticated user (inclu
636
637
  > raw-forwarded routes. A forged `POST /iam/update-user {"roles":["admin"]}` is rejected with
637
638
  > `FIELD_NOT_ALLOWED` (HTTP 400) at the input-parse stage, before any persistence.
638
639
 
640
+ > **Never routed: `/iam/open-api/*` (11.42.9).** nest-server registers Better-Auth's `openAPI()` plugin
641
+ > only so `setupSwagger()` can document Better-Auth's own routes, and marks its endpoints `SERVER_ONLY`:
642
+ > better-call leaves them out of the router, so a forwarded request answers 404 however its path is
643
+ > spelled, while the generator still runs in-process. Deliberately not a path check in the middleware —
644
+ > it would see the path as sent, Better-Auth the one `new URL()` produced (`/iam/./open-api/…`). Only a
645
+ > project that registered `openAPI()` itself in `betterAuth.plugins` gets the routes.
646
+
639
647
  > **"Raw" has one exception since 11.38.0: the password-setting reset routes.**
640
648
  > `CoreBetterAuthApiMiddleware.normalizeResetPassword()` rewrites the password field of
641
649
  > `/reset-password`, `/email-otp/reset-password` and `/phone-number/reset-password` to its
@@ -18,7 +18,7 @@ used to **exact-pin**, to a vulnerable version:
18
18
  |---------|----------|------------------------------------------|
19
19
  | `ws` | [GHSA-96hv-2xvq-fx4p](https://github.com/advisories/GHSA-96hv-2xvq-fx4p) — high: memory-exhaustion DoS + uninitialized memory disclosure. Patched `>=8.21.0` | Older `@nestjs/graphql` releases declare `"ws": "8.20.1"` — an **exact pin**, not a caret. 13.4.5, the version this framework declares, pins `8.21.3` |
20
20
  | `js-yaml` | [GHSA-pm4m-ph32-ghv5](https://github.com/advisories/GHSA-pm4m-ph32-ghv5) — high: exponential parsing time in flow collections (DoS), patched `>=5.2.2`; [GHSA-r3ph-w7gj-g6xm](https://github.com/advisories/GHSA-r3ph-w7gj-g6xm) — moderate: `maxTotalMergeKeys` does not bound CPU use for empty merge sources, covers `<=5.4.0`, patched `>=5.4.1` (published 2026-09-29) | `@nestjs/swagger` declares js-yaml as an **exact pin**, the same shape as the `ws` case: older releases `5.2.1`, 11.4.7 — the version this framework declares, and the newest 11.x — `5.3.0`. No swagger update reaches the fix, so **this entry is load-bearing again** |
21
- | `@graphql-tools/utils` | [GHSA-7mx3-vvmw-hjmv](https://github.com/advisories/GHSA-7mx3-vvmw-hjmv) — high: prototype pollution in `mergeDeep`, which runs while the GraphQL schema is assembled. Covers `<=12.0.0`, patched `>=12.0.1` (added 2026-10-06) | `@nestjs/graphql` 13.4.5 — the version this framework declares, and the newest 13.x — declares `"@graphql-tools/utils": "12.0.0"`, an **exact pin**. Only `@nestjs/graphql` 14.x moves it, so **this entry is load-bearing**. `@apollo/server` reaches it too, through `@graphql-tools/schema` / `merge` — and in a lockfile that still holds schema 10.0.x (resolved before 2026-08-12), as **utils 11.x**: vulnerable too, with no 11.x fix, and outside the override key. Lift it with `pnpm update --depth Infinity @graphql-tools/schema @graphql-tools/merge`; the current releases request utils `^12.0.3`, which needs Node >=22.15 |
21
+ | `@graphql-tools/utils` | [GHSA-7mx3-vvmw-hjmv](https://github.com/advisories/GHSA-7mx3-vvmw-hjmv) — high: prototype pollution in `mergeDeep`, which runs while the GraphQL schema is assembled. Covers `<=12.0.0`, patched `>=12.0.1` (added 2026-10-06) | `@nestjs/graphql` 13.4.5 — the version this framework declares, and the newest 13.x — declares `"@graphql-tools/utils": "12.0.0"`, an **exact pin**. Only `@nestjs/graphql` 14.x moves it, so **this entry is load-bearing**. `@apollo/server` reaches it too, through `@graphql-tools/schema` / `merge` — and in a lockfile that still holds schema 10.0.x (resolved before 2026-08-12), as **utils 11.x**: vulnerable too, with no 11.x fix, and outside the override key. Lift it with `pnpm update --depth Infinity @graphql-tools/schema @graphql-tools/merge`; the current releases request utils `^12.0.3`, which needs Node >=22.15 — the framework's own floor since 11.42.10, which is why the snippet below targets 12.0.3 |
22
22
  | `multer` | [GHSA-wc9g-mqfw-jrwm](https://github.com/advisories/GHSA-wc9g-mqfw-jrwm), [GHSA-535w-7cp7-47q4](https://github.com/advisories/GHSA-535w-7cp7-47q4), [GHSA-qfvm-cv95-jqjf](https://github.com/advisories/GHSA-qfvm-cv95-jqjf) — high: DoS via crafted multipart input; [GHSA-qvfw-j98x-7q72](https://github.com/advisories/GHSA-qvfw-j98x-7q72) — low: file size limit bypass. Patched `>=2.3.0` | `@nestjs/platform-express` up to 11.2.5 declares `"multer": "2.2.0"` — an **exact pin**. A direct `multer` dependency does not move it: you get both copies, and FileInterceptor uses the vulnerable one. 11.2.6, which this framework declared from 11.41.4, pins `2.4.0`, and so does 11.2.7, declared since 11.41.7 |
23
23
 
24
24
  **Status since 11.42.4:** `@graphql-tools/utils` joins `js-yaml` as load-bearing — no `@nestjs/graphql`
@@ -64,12 +64,13 @@ overrides:
64
64
  'multer@>=2.0.0 <2.4.0': '2.4.0'
65
65
 
66
66
  # @nestjs/graphql 13.4.5 exact-pins @graphql-tools/utils@12.0.0 — GHSA-7mx3-vvmw-hjmv (high, patched >=12.0.1).
67
- # Target 12.0.1 rather than the newest 12.x: 12.0.2+ requires Node >=22.15 via @whatwg-node/promise-helpers@2.
68
- # If your engines floor is already >=22.15, prefer the newest 12.x, so the caret requesters are not held back.
67
+ # Target the newest 12.x, so the caret requesters (@graphql-tools/schema / merge) are not held back.
68
+ # 12.0.2+ requires Node >=22.15 via @whatwg-node/promise-helpers@2 — the floor this framework declares
69
+ # since 11.42.10. On an older Node, take 12.0.1 instead.
69
70
  # Does NOT reach a utils 11.x pulled in by an older @graphql-tools/schema 10.0.x — refresh that one instead:
70
- # pnpm update --depth Infinity @graphql-tools/schema @graphql-tools/merge (current releases need Node >=22.15)
71
+ # pnpm update --depth Infinity @graphql-tools/schema @graphql-tools/merge
71
72
  # Remove once @nestjs/graphql pins >=12.0.1 in the major you use.
72
- '@graphql-tools/utils@>=12.0.0 <12.0.1': '12.0.1'
73
+ '@graphql-tools/utils@>=12.0.0 <12.0.3': '12.0.3'
73
74
  ```
74
75
 
75
76
  ### Retired: `@hono/node-server` (removed 2026-08-22, nest-server 11.36.1)
@@ -0,0 +1,186 @@
1
+ # Migration Guide: 11.42.8 → 11.42.9
2
+
3
+ ## Overview
4
+
5
+ | Category | Details |
6
+ | -------------------- | --------------------------------------------------------------------------------------------------------------- |
7
+ | **New Feature** | `setupSwagger()` documents every HTTP interface the server makes usable — Better-Auth's own routes, `/ai/mcp`, the MCP OAuth endpoints (§1) |
8
+ | **Bugfix** | …and only those: routes the running configuration makes fail for every caller leave the document (§2) |
9
+ | **New Feature** | `@ApiMethods()` — the HTTP methods an `@All()` handler actually serves (§3) |
10
+ | **Bugfix** | The `tus` key in the server configuration takes effect — it was never read (§5). **Check your `tus` block** |
11
+ | **Bugfix** | TUS: upload URLs work below a global prefix and a project route, a `path` naming a new route moves the endpoint, `termination: false` / `creation: false` are enforced, new `relativeLocation` (§5) |
12
+ | **Bugfix** | `healthCheck.enabled: false` leaves the health check endpoints out (§6) |
13
+ | **Breaking Changes** | None in the API. A `tus` block in `config.env.ts` that did nothing before now applies (§5) |
14
+ | **Migration Effort** | Review a `tus` block in `config.env.ts` if you have one (§5.1); a proxy that renames the tus route or relative upload URLs need a look (§5.2). Regenerate a client built from `/api-docs-json` if you keep one (§4) |
15
+
16
+ ## Quick Migration
17
+
18
+ ```bash
19
+ pnpm update @lenne.tech/nest-server
20
+ ```
21
+
22
+ Vendor-mode projects pick it up with the core update.
23
+
24
+ ## 1. Everything usable is documented
25
+
26
+ The document is built from the running application, so it follows the server configuration and every
27
+ project module automatically: a module that is registered appears, one that is not stays out. Three
28
+ kinds of interface used to be invisible to `@nestjs/swagger` because they have no Nest handler:
29
+
30
+ | Interface | Documented since 11.42.9 |
31
+ | --- | --- |
32
+ | Routes Better-Auth serves itself under `/iam` — whatever the configured plugins add (two-factor, passkey, sessions, password change, …), including plugins a project adds via `betterAuth.plugins` | from Better-Auth's own OpenAPI generator, under your Better-Auth controller (§1.1) |
33
+ | `/ai/mcp` (MCP server, `ai.mcp`) | POST/GET/DELETE, with the bearer requirement the endpoint enforces itself |
34
+ | MCP OAuth endpoints (`mountAiMcpOAuth()` in `main.ts`) | exactly the endpoints the call mounted (`/.well-known/…`, `/authorize`, `/token`, `/register`) |
35
+
36
+ ### 1.1 Better-Auth routes
37
+
38
+ - Documented under your Better-Auth controller: same tags, and `operationTags` receives them with
39
+ `controller` set to that controller's class name (`handler` is Better-Auth's operation id).
40
+ - Operation ids: `<Controller>_betterAuth_<betterAuthOperationId>`, e.g.
41
+ `BetterAuthController_betterAuth_changePassword`. Unique and stable across builds.
42
+ - Schemas Better-Auth refers to are added as `BetterAuthUser`, `BetterAuthSession`, … so they cannot
43
+ collide with your own `User` model; Better-Auth's OpenAPI 3.1 constructs are converted to 3.0.
44
+ - Security: what Better-Auth enforces. A route behind one of its session middlewares (or one of the
45
+ social-account routes that check the session in their handler) carries the global requirement —
46
+ bearer or the session cookie; every other route is public (`security: []`): sign-in, password reset,
47
+ get-session, the two-factor verification steps of a sign-in. Better-Auth's own generator marks every
48
+ route bearer-only, so this is read from the live instance, not from the generator. Routes of plugins
49
+ you add yourself (`admin()`, `organization()`, …) always carry the global requirement.
50
+ - With API tokens configured they carry "API tokens: not allowed on this route" and never appear in the
51
+ API-token view.
52
+ - Under a global prefix or URI versioning Better-Auth's own routes are not reachable (the API
53
+ middleware does not forward them); they are then not documented, and a warning says why.
54
+
55
+ **Behind it:** Better-Auth exposes its generator only through its `openAPI()` plugin, so nest-server
56
+ registers that plugin itself (`disableDefaultReference: true`). Its own route,
57
+ `/iam/open-api/generate-schema`, is not part of your API: its endpoints are marked server-only, so
58
+ Better-Auth never routes them and they answer 404, as before this release. If you registered `openAPI()`
59
+ in `betterAuth.plugins` yourself, nothing changes — your registration replaces the internal one.
60
+
61
+ ## 2. Only what can succeed is documented
62
+
63
+ A route that answers an error to every caller under the running configuration is left out. Each rule
64
+ reads the switch the handler itself reads:
65
+
66
+ | Configuration | Left out |
67
+ | --- | --- |
68
+ | `auth.legacyEndpoints` keeps REST closed (the default since 11.38.0) | legacy `/auth/signin`, `/auth/signup`, `/auth/logout`, `/auth/refresh-token` (they answer 410) |
69
+ | `betterAuth.emailAndPassword.enabled: false` | `/iam/sign-in/email`, `/iam/sign-up/email` |
70
+ | `betterAuth.emailAndPassword.disableSignUp: true` | `/iam/sign-up/email` |
71
+ | `betterAuth.emailAndPassword.passwordReset: false` | `/iam/request-password-reset`, `/iam/reset-password`, `/iam/reset-password/{token}` |
72
+ | Better-Auth's own options, read from the live instance — so `betterAuth.options` counts too | e.g. POST `/iam/get-session` without `session.deferSessionRefresh`, social routes without a provider, two-factor OTP routes without `otpOptions.sendOTP`, `/iam/delete-user` without `user.deleteUser.enabled`, `/iam/change-email` without `user.changeEmail.enabled`, `/iam/update-session` without session fields that accept input |
73
+ | A Hub panel switched off (`hub.collectors.*`, `hub.mailbox`, `hub.emailPreview`, `hub.db`, `hub.migrations`) | its `*.json` endpoint and actions, which would only answer `{ available: false }` or 400 |
74
+
75
+ A project controller extending a core controller is covered too — the rules go by the handler, not by
76
+ the class name.
77
+
78
+ ## 3. `@ApiMethods()` for `@All()` handlers
79
+
80
+ `@nestjs/swagger` documents an `@All()` handler as eight operations (GET … SEARCH), whatever it serves.
81
+ The TUS handlers now declare theirs, so `/tus` shows POST and `/tus/{id}` HEAD/PATCH/DELETE. Use the
82
+ decorator on your own catch-all handlers:
83
+
84
+ ```typescript
85
+ import { ApiMethods } from '@lenne.tech/nest-server';
86
+
87
+ @All(':id')
88
+ @ApiMethods('get', 'delete')
89
+ async handle(@Req() req: Request, @Res() res: Response) { … }
90
+ ```
91
+
92
+ It affects the document only, never routing.
93
+
94
+ ## 4. Generated clients
95
+
96
+ A client generated from `/api-docs-json` (hey-api, openapi-generator, …) gains functions for the newly
97
+ documented routes and loses those for routes your configuration switches off (and the eight
98
+ `/iam/{path}` catch-all functions since 11.42.8). Regenerate and let the compiler point at anything that
99
+ used a removed function.
100
+
101
+ ## 5. TUS: the configuration takes effect
102
+
103
+ ### 5.1 The server configuration's `tus` key is read
104
+
105
+ `TusModule.forRoot()` without a `config` of its own now reads the `tus` key of the server configuration
106
+ (`config.env.ts`, `NSC__TUS__*`) — the zero-config rule `BetterAuthModule.forRoot()` already follows. An
107
+ explicit `TusModule.forRoot({ config })` still wins. **Before this release that key was never read**:
108
+ TUS ran with the defaults whatever it said.
109
+
110
+ What that means for you, if `config.env.ts` has a `tus` block:
111
+
112
+ | You set | Before | Now |
113
+ | --- | --- | --- |
114
+ | `tus: false` | TUS enabled anyway | `/tus` is not registered |
115
+ | `allowedTypes` | every type accepted | other types answer 415 at creation |
116
+ | `maxSize` | 50 GB | your limit (413 above it) |
117
+ | `roles` | `[S_USER]` | your roles |
118
+ | `termination`, `creation`, `path`, `expiration`, … | defaults | your values (see 5.2 / 5.3) |
119
+
120
+ These are the values the block was written to enforce, so this is the behaviour you asked for — but it
121
+ starts on this upgrade. Check that the allowed types cover what your clients upload before you deploy.
122
+
123
+ ### 5.2 `path` and upload URLs
124
+
125
+ Before this release `path` only changed the URL handed to a client for a new upload (`Location`); the
126
+ endpoint itself always stayed where the controller declared it. Every setup that works today keeps
127
+ working:
128
+
129
+ | `path` | Since 11.42.9 |
130
+ | --- | --- |
131
+ | unset | The upload URL is built from the path the upload was created at — so resuming now also works below a global prefix, a URI version or a project controller's own route, where it answered 404 before |
132
+ | ends with the controller's route — `'/api/tus'` (global prefix) or `'/files/tus'` (a proxy that strips `/files`) | Unchanged: the controller stays, upload URLs use this path. This was the workaround for the old behaviour and needs no change |
133
+ | a new route — `'/uploads'` | The controller is now mounted there (a project controller too, as `roles` already did). Before, the endpoint stayed at `/tus` while clients were sent to `/uploads/…`, so no upload could ever be resumed |
134
+
135
+ Like any controller route, a new route sits below the global prefix and the URI version.
136
+
137
+ Two cases need a look:
138
+
139
+ - **A proxy that renames the route** (public `/files/upload` forwarded as `/tus`): `path: '/files/upload'`
140
+ now moves the endpoint. Remove `path` and override `uploadCollectionPath(req)` in your
141
+ `CoreTusService` subclass to return the public path.
142
+ - **Relative upload URLs.** The framework now builds the upload URL itself, which takes precedence over
143
+ `@tus/server`'s `relativeLocation`. Use `tus.relativeLocation: true` (new); a `relativeLocation` you
144
+ pass to the tus server yourself — in a vendored core, for example — is honoured as well.
145
+
146
+ ### 5.3 Switched-off extensions are enforced
147
+
148
+ `@tus/server` itself ignores the extension flags. Since this release:
149
+
150
+ | Setting | Effect |
151
+ | --- | --- |
152
+ | `termination: false` | DELETE answers 501, OPTIONS no longer advertises `termination` |
153
+ | `creation: false` | POST answers 501, OPTIONS no longer advertises the creation extensions |
154
+ | `creationWithUpload: false` | a POST carrying data answers 501 |
155
+
156
+ `checksum` and `concatenation` have no effect: neither built-in store supports them. The boot log now
157
+ lists the extensions the server actually offers.
158
+
159
+ ## 6. `healthCheck.enabled: false`
160
+
161
+ The flag was declared and ignored: any `healthCheck` object registered `GET /health-check` and the
162
+ `healthCheck` query. `enabled: false` now leaves both out (and out of the Swagger document).
163
+
164
+ ## Compatibility Notes
165
+
166
+ - The Swagger work changes nothing at runtime. Better-Auth's `openAPI()` plugin is now registered, but
167
+ kept off the router, so `/iam/open-api/*` answers 404 exactly as before. The runtime changes of this
168
+ release are the ones in §5 and §6.
169
+ - A `CoreTusService` subclass that overrides `onUploadCreate()` keeps the extension checks of §5.3: they
170
+ run before the hook, whether or not the override calls `super`.
171
+ - The description of Better-Auth's routes is prepared once at application bootstrap
172
+ (`CoreBetterAuthService.onApplicationBootstrap()`). A project service extending
173
+ `CoreBetterAuthService` that overrides `onApplicationBootstrap()` must call `super` to keep it.
174
+ - An explicit `@ApiBearerAuth()` / `@ApiSecurity()` on a route the guards admit to everybody now keeps
175
+ its security requirement instead of being documented as public.
176
+
177
+ ## References
178
+
179
+ - Swagger helper: `src/core/common/helpers/swagger.helper.ts`
180
+ - Better-Auth rules: `src/core/modules/better-auth/core-better-auth-openapi.helper.ts`
181
+ - Decorator: `src/core/common/decorators/api-methods.decorator.ts`
182
+ - TUS: `src/core/modules/tus/tus.module.ts`, `src/core/modules/tus/core-tus.service.ts`, `src/core/modules/tus/README.md`
183
+ - Tests: `tests/unit/swagger-helper.spec.ts`, `tests/unit/swagger-availability.spec.ts`,
184
+ `tests/unit/better-auth-openapi-availability.spec.ts`, `tests/better-auth-openapi.e2e-spec.ts`,
185
+ `tests/unit/tus-module-config.spec.ts`, `tests/tus-protocol-config.e2e-spec.ts`,
186
+ `tests/unit/core-module-signatures.spec.ts`
@@ -0,0 +1,111 @@
1
+ # Migration Guide: 11.42.9 → 11.42.10
2
+
3
+ ## Overview
4
+
5
+ | Category | Details |
6
+ | -------------------- | --------------------------------------------------------------------------------------------------------- |
7
+ | **Requirement** | `engines.node` is now `>= 22.15` (was `>= 22.12`). In practice the floor already applied — see §1 |
8
+ | **Security** | Recommended `@graphql-tools/utils` override target moves from 12.0.1 to 12.0.3 (§2) |
9
+ | **Dependencies** | `nodemailer` 10.0.16 (bug fixes only). Dev tooling: transitive `handlebars` 4.7.10 (§3) |
10
+ | **Breaking Changes** | None in the API |
11
+ | **Migration Effort** | None for projects on Node ≥ 22.15. Projects that carry their own `@graphql-tools/utils` override should raise it (§2) |
12
+
13
+ ## Quick Migration
14
+
15
+ ```bash
16
+ pnpm update @lenne.tech/nest-server
17
+ ```
18
+
19
+ Vendor-mode projects pick it up with the core update. No code or configuration change is required.
20
+
21
+ ## 1. Node ≥ 22.15
22
+
23
+ The declared minimum Node version rises from 22.12 to 22.15. This changes the declaration, not the reality:
24
+ `@apollo/server` resolves `@graphql-tools/schema` 10.1.3, which requests `@graphql-tools/utils ^12.0.3`, and
25
+ that in turn requires `@whatwg-node/promise-helpers` 2 — whose own `engines` field demands Node `>=22.15.0`.
26
+ Every fresh install of a GraphQL-enabled project has carried that requirement since those releases came out.
27
+ The framework's `engines` field now says so instead of promising a floor its own dependency tree does not
28
+ honour.
29
+
30
+ Check the Node version wherever the server runs:
31
+
32
+ ```bash
33
+ node --version # must print v22.15.0 or newer (any Node 24 qualifies)
34
+ ```
35
+
36
+ The places to look: the Docker base image (`FROM node:…`), the CI runner's Node version, and the version a
37
+ host or PaaS pins. The starter's Dockerfile and the framework's own CI already run Node 24.
38
+
39
+ ## 2. `@graphql-tools/utils` override: target 12.0.3
40
+
41
+ Overrides in this package's `pnpm-workspace.yaml` do not reach your project (see
42
+ [security-overrides.md](../docs/security-overrides.md)), so a project that copied the
43
+ `@graphql-tools/utils` entry for GHSA-7mx3-vvmw-hjmv holds its own. Until now the recommended target was
44
+ 12.0.1, because 12.0.2+ needs Node ≥ 22.15. With the framework's floor at 22.15, take the newest 12.x:
45
+
46
+ ```yaml
47
+ # Before
48
+ overrides:
49
+ '@graphql-tools/utils@>=12.0.0 <12.0.1': '12.0.1'
50
+
51
+ # After
52
+ overrides:
53
+ '@graphql-tools/utils@>=12.0.0 <12.0.3': '12.0.3'
54
+ ```
55
+
56
+ ```bash
57
+ pnpm install
58
+ pnpm update --depth Infinity @graphql-tools/schema @graphql-tools/merge
59
+ ```
60
+
61
+ Why it matters beyond tidiness: the key intersects the `^12.0.0` range that `@graphql-tools/schema` and
62
+ `@graphql-tools/merge` request, so a target behind the newest 12.x holds them back too. Commit
63
+ `pnpm-workspace.yaml` and `pnpm-lock.yaml` together — a workspace file without the re-installed lockfile fails
64
+ every `pnpm install --frozen-lockfile`, typically in the Docker build.
65
+
66
+ Afterwards `@nestjs/graphql` 13.4.5 still brings its own exact-pinned `@graphql-tools/schema` 10.1.0 /
67
+ `merge` 9.2.3 next to the 10.1.3 / 9.2.6 that `@apollo/server` uses. That is expected: both copies share the
68
+ single overridden `@graphql-tools/utils` 12.0.3 and the single `graphql`.
69
+
70
+ Projects still on Node < 22.15 keep the 12.0.1 target until they upgrade Node.
71
+
72
+ ## 3. Dependency updates
73
+
74
+ - **`nodemailer` 10.0.15 → 10.0.16** — bug fixes in the address parser and cookie handling, no API change.
75
+ A project that carries a lock-step `nodemailer` override (nest-server-starter does) should raise it to
76
+ `'nodemailer@<10.0.16': '10.0.16'`.
77
+ - **`handlebars` 4.7.10 (dev tooling only)** — three advisories (GHSA-8r5x-fm3f-whwj, GHSA-p8wg-vrv2-v86f,
78
+ GHSA-xw65-4hp5-5hc7) reach projects through `@compodoc/compodoc`, which already allows the fixed version.
79
+ If `pnpm audit` reports them in your project, refresh the stale lockfile entry instead of adding an override:
80
+
81
+ ```bash
82
+ pnpm update --depth Infinity handlebars
83
+ ```
84
+
85
+ ## Compatibility Notes
86
+
87
+ | Pattern | Status |
88
+ | ------- | ------ |
89
+ | Projects on Node 24 (starter Dockerfile, CI) | Unaffected |
90
+ | Projects on Node 22.12 – 22.14 | Already outside what `@graphql-tools/utils` 12.0.3 declares; upgrade Node |
91
+ | Vendor-mode projects | The core carries no `engines` field — check the Node version of your own runtime (§1) |
92
+ | `engine-strict=true` in `.npmrc` | Installs now fail on Node < 22.15 instead of warning |
93
+
94
+ ## Troubleshooting
95
+
96
+ | Symptom | Cause | Fix |
97
+ | ------- | ----- | --- |
98
+ | `pnpm install` warns `Unsupported engine` for `@whatwg-node/promise-helpers` | Node older than 22.15 | Upgrade Node (§1) |
99
+ | `ERR_PNPM_UNSUPPORTED_ENGINE` for `@lenne.tech/nest-server` | `engine-strict` with Node < 22.15 | Upgrade Node (§1) |
100
+ | `pnpm audit` reports `@graphql-tools/utils` after updating | Your own override still targets an older version, or an old `@graphql-tools/schema` 10.0.x pulls utils 11.x | Raise the override and refresh schema / merge (§2) |
101
+ | `pnpm audit` reports `handlebars` via `@compodoc/compodoc` | Stale lockfile entry | `pnpm update --depth Infinity handlebars` (§3) |
102
+
103
+ ## Module Documentation
104
+
105
+ - [Security overrides](../docs/security-overrides.md) — which overrides are load-bearing and how to write your own
106
+ - `.claude/rules/package-management.md` — override and suppression rules
107
+
108
+ ## References
109
+
110
+ - [GHSA-7mx3-vvmw-hjmv](https://github.com/advisories/GHSA-7mx3-vvmw-hjmv) — `@graphql-tools/utils` prototype pollution
111
+ - [nest-server-starter](https://github.com/lenneTech/nest-server-starter) — reference project
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.42.8",
3
+ "version": "11.42.10",
4
4
  "description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
5
5
  "keywords": [
6
6
  "node",
@@ -84,10 +84,10 @@
84
84
  "url": "https://github.com/lenneTech/nest-server/issues"
85
85
  },
86
86
  "engines": {
87
- "node": ">= 22.12",
87
+ "node": ">= 22.15",
88
88
  "pnpm": "^11.0.0"
89
89
  },
90
- "packageManager": "pnpm@11.13.1+sha512.b2fc7683b8a6525414e7d13e1ba28caaddde96bf66ec540bfaeb7e702b81f3e0be4d1f295edf7f9fe0396740a8dce4509c582ddf79891f4543fea32d37645f25",
90
+ "packageManager": "pnpm@11.28.5+sha512.484563d30b4c2ad27cb48aa94ff2b302dbcebc8ff6bf28816c2589886e146ce7f76f354f3bc1a438e6f5e9768c302aa0a6f7add8232903cd4cf81426cbc14d9e",
91
91
  "dependencies": {
92
92
  "@apollo/server": "5.5.1",
93
93
  "@as-integrations/express5": "1.1.2",
@@ -129,7 +129,7 @@
129
129
  "mongoose": "9.10.4",
130
130
  "multer": "2.4.0",
131
131
  "node-mailjet": "6.0.11",
132
- "nodemailer": "10.0.15",
132
+ "nodemailer": "10.0.16",
133
133
  "passport": "0.7.0",
134
134
  "passport-jwt": "4.0.1",
135
135
  "reflect-metadata": "0.2.2",
@@ -182,9 +182,9 @@
182
182
  }
183
183
  },
184
184
  "devDependencies": {
185
- "@aws-sdk/client-s3": "3.1146.0",
186
- "@aws-sdk/lib-storage": "3.1146.0",
187
- "@aws-sdk/s3-request-presigner": "3.1146.0",
185
+ "@aws-sdk/client-s3": "3.1147.0",
186
+ "@aws-sdk/lib-storage": "3.1147.0",
187
+ "@aws-sdk/s3-request-presigner": "3.1147.0",
188
188
  "@better-auth/core": "1.7.7",
189
189
  "@better-auth/passkey": "1.7.7",
190
190
  "@compodoc/compodoc": "2.0.0",
@@ -203,7 +203,6 @@
203
203
  "@types/node": "26.6.4",
204
204
  "@types/passport": "1.0.17",
205
205
  "@vitest/coverage-v8": "4.1.11",
206
- "ansi-colors": "4.1.3",
207
206
  "better-auth": "1.7.7",
208
207
  "bullmq": "6.3.11",
209
208
  "cross-env": "10.1.0",
@@ -0,0 +1,25 @@
1
+ import { SetMetadata } from '@nestjs/common';
2
+
3
+ /** Metadata key of {@link ApiMethods} */
4
+ export const API_METHODS_KEY = 'lt:apiMethods';
5
+
6
+ /**
7
+ * Lists the HTTP methods an `@All()` handler actually serves.
8
+ *
9
+ * `@nestjs/swagger` documents an `@All()` handler as eight operations (GET, POST, PUT, DELETE, PATCH,
10
+ * OPTIONS, HEAD, SEARCH), whatever the handler does with them. `setupSwagger()` keeps only the methods
11
+ * named here, so a catch-all that serves three of them is documented with three.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * @All(':id')
16
+ * @ApiMethods('head', 'patch', 'delete')
17
+ * async handleUpload(@Req() req: Request, @Res() res: Response) { … }
18
+ * ```
19
+ * @since 11.42.9
20
+ */
21
+ export const ApiMethods = (...methods: string[]) =>
22
+ SetMetadata(
23
+ API_METHODS_KEY,
24
+ methods.map((method) => method.toLowerCase()),
25
+ );