@lenne.tech/nest-server 11.42.5 → 11.42.7

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 (29) hide show
  1. package/.claude/rules/configurable-features.md +2 -1
  2. package/.claude/rules/testing.md +3 -3
  3. package/FRAMEWORK-API.md +1 -1
  4. package/dist/core/common/helpers/swagger.helper.d.ts +11 -0
  5. package/dist/core/common/helpers/swagger.helper.js +362 -0
  6. package/dist/core/common/helpers/swagger.helper.js.map +1 -0
  7. package/dist/core/common/interfaces/swagger-setup-options.interface.d.ts +33 -0
  8. package/dist/core/common/interfaces/swagger-setup-options.interface.js +3 -0
  9. package/dist/core/common/interfaces/swagger-setup-options.interface.js.map +1 -0
  10. package/dist/core/common/services/core-body-parser.initializer.d.ts +2 -0
  11. package/dist/core/common/services/core-body-parser.initializer.js +23 -3
  12. package/dist/core/common/services/core-body-parser.initializer.js.map +1 -1
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +2 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/main.js +8 -14
  17. package/dist/main.js.map +1 -1
  18. package/dist/tsconfig.build.tsbuildinfo +1 -1
  19. package/docs/REQUEST-LIFECYCLE.md +1 -1
  20. package/migration-guides/11.42.3-to-11.42.4.md +10 -1
  21. package/migration-guides/11.42.5-to-11.42.6.md +113 -0
  22. package/migration-guides/11.42.6-to-11.42.7.md +115 -0
  23. package/package.json +4 -4
  24. package/src/core/common/helpers/swagger.helper.ts +529 -0
  25. package/src/core/common/interfaces/server-options.interface.ts +5 -0
  26. package/src/core/common/interfaces/swagger-setup-options.interface.ts +131 -0
  27. package/src/core/common/services/core-body-parser.initializer.ts +47 -5
  28. package/src/index.ts +2 -0
  29. package/src/main.ts +9 -15
@@ -569,7 +569,7 @@ Both use body-parser's default limit of **100 kB** unless `bodyParser` says othe
569
569
  bodyParser: { json: { limit: '2mb' } },
570
570
  ```
571
571
 
572
- `CoreBodyParserInitializer` applies it during module init by replacing the handle of the global parser layers NestJS created — in place, because NestJS registers them before any init hook runs and a parser appended later would sit behind the routes. Running during module init is what makes the limit hold under `Test.createTestingModule()`, which never runs `main.ts`. A path-scoped parser (`server.use('/upload', json({ limit }))` in `main.ts`) runs before the global one and is left untouched — that remains the way to give one path a different limit.
572
+ `CoreBodyParserInitializer` applies it during module init by replacing the handle of the global parser layers NestJS created — in place, because NestJS registers them before any init hook runs and a parser appended later would sit behind the routes. Running during module init is what makes the limit hold under `Test.createTestingModule()`, which never runs `main.ts`. A path-scoped parser (`server.use('/upload', json({ limit }))` in `main.ts`) runs before the global one and is left untouched — that remains the way to give one path a different limit. Keep `server.useBodyParser('json')` (or `'urlencoded'`) in `main.ts` next to it: NestJS registers its global parser only when no layer with the same function name exists, so a path-scoped one registered before `app.init()` suppresses it, and every other path receives an empty body. Since 11.42.6 `CoreBodyParserInitializer` warns at boot in that case.
573
573
 
574
574
  The parser runs **before authentication and routing**. Over the limit, the whole request is refused:
575
575
 
@@ -63,6 +63,14 @@ server.useBodyParser('json', { limit: '2mb' });
63
63
  bodyParser: { json: { limit: '2mb' } },
64
64
  ```
65
65
 
66
+ **Exception: keep `useBodyParser()` when `main.ts` also registers a path-scoped parser** such as
67
+ `server.use('/upload', json({ limit: '20mb' }))`. NestJS registers its own global parser only if no
68
+ layer with the same function name exists yet, and a path-scoped `json()` registered before
69
+ `app.init()` counts as one. Removing `useBodyParser()` then leaves the app without a global JSON
70
+ parser: every request outside that path reaches its handler with an empty body, and a sign-in fails
71
+ with a validation error. Keep `server.useBodyParser('json')` (the configured limit is applied to it)
72
+ and set the limit in the config as above. Since 11.42.6 the server warns at boot when it finds this.
73
+
66
74
  **Starter-based projects:** `config.env.ts` builds `local` / `e2e` / `ci` from `localConfig()` and the
67
75
  deployed environments from `deployedConfig()`. Put the limit where BOTH see it — the starter ships a
68
76
  shared `PROJECT_BODY_PARSER` constant for this since its 11.42.4 update — or set
@@ -75,7 +83,8 @@ The config is applied during module init, so it holds in every test that builds
75
83
  global parser your `main.ts` registered; keeping both is harmless, but the config wins.
76
84
 
77
85
  A **path-scoped** parser is left alone: `server.use('/upload', json({ limit: '20mb' }))` in `main.ts`
78
- still gives that one path its own limit. The parser runs before routing, so a per-route limit can only
86
+ still gives that one path its own limit, as long as the global parser stays registered next to it
87
+ (see the exception above). The parser runs before routing, so a per-route limit can only
79
88
  key on the path — and all GraphQL operations share one.
80
89
 
81
90
  **`rawBody: true`:** `req.rawBody` is kept for apps created with `NestFactory.create(…, { rawBody:
@@ -0,0 +1,113 @@
1
+ # Migration Guide: 11.42.5 → 11.42.6
2
+
3
+ ## Overview
4
+
5
+ | Category | Details |
6
+ | -------------------- | ------------------------------------------------------------------------------------------------------------ |
7
+ | **Correction** | The 11.42.3 → 11.42.4 guide told every project to drop `useBodyParser()` from `main.ts`. With a path-scoped parser in `main.ts` that removes JSON parsing everywhere else (§1) |
8
+ | **Improvement** | The server warns at boot when NestJS skipped its global body parser because of such a path-scoped one (§1) |
9
+ | **Tooling** | `check:overrides` no longer reports a failed audit as clean, and its probe follows `pnpm_config_registry` (§2) |
10
+ | **Breaking Changes** | None |
11
+ | **Migration Effort** | None for most projects. Projects that removed `useBodyParser()` while registering a path-scoped parser in `main.ts` must add it back (§1) |
12
+
13
+ ## Quick Migration
14
+
15
+ ```bash
16
+ pnpm update @lenne.tech/nest-server
17
+ pnpm run update # starter-based projects: raises every exact pin you share with the framework
18
+ ```
19
+
20
+ Then check `main.ts` as described in §1. Vendor-mode projects pick up the core update and need the
21
+ same check.
22
+
23
+ **Exact pins below the framework's own split packages in two.** A project that pins `@nestjs/core`,
24
+ `@nestjs/common`, `mongoose` or another package the framework also declares, at a LOWER exact version,
25
+ gets two copies of it. The install only prints a peer warning. The failure appears in the tests, as a
26
+ dependency-injection error that does not look like a version problem (see Troubleshooting).
27
+ `pnpm run update` (`extras/sync-packages.mjs` in starter-based projects) raises those pins. Without
28
+ that script, list the deviations and raise each one:
29
+
30
+ ```bash
31
+ node -e "const f=require('@lenne.tech/nest-server/package.json'),p=require('./package.json');const want={...f.dependencies,...f.peerDependencies};for(const s of ['dependencies','devDependencies'])for(const[k,v]of Object.entries(p[s]||{}))if(want[k]&&/^\d/.test(want[k])&&want[k]!==v)console.log(s,k,v,'->',want[k])"
32
+ ```
33
+
34
+ An `overrides:` entry in `pnpm-workspace.yaml` must not pin such a package below the framework's
35
+ version either: it forces the framework onto the older version without anybody having tested it.
36
+
37
+ ## 1. Keep `useBodyParser()` next to a path-scoped parser
38
+
39
+ §1 of the [11.42.3 → 11.42.4 guide](./11.42.3-to-11.42.4.md) recommended moving the body limit from
40
+ `main.ts` into `bodyParser.json.limit` and deleting the `useBodyParser()` call. That is right, with one
41
+ exception the guide did not name.
42
+
43
+ NestJS registers its own global JSON and URL-encoded parsers inside `app.init()`, but only when no
44
+ layer with the same function name exists yet. It compares the NAME and nothing else. A path-scoped
45
+ parser registered in `main.ts` before `listen()` carries that name:
46
+
47
+ ```typescript
48
+ // main.ts
49
+ server.use('/diagnostics', json({ limit: '256kb' })); // a layer named `jsonParser`
50
+ ```
51
+
52
+ With `useBodyParser()` removed, NestJS treats the JSON parser as already applied and registers none.
53
+ Every request outside `/diagnostics` then reaches its handler with an empty body. A sign-in fails with
54
+ a validation error such as "Missing input", and before this release nothing was logged.
55
+
56
+ **Check `main.ts`.** If it registers `json(...)` or `urlencoded(...)` on a path, keep or restore the
57
+ global parser next to it:
58
+
59
+ ```typescript
60
+ // main.ts
61
+ server.use('/diagnostics', json({ limit: '256kb' }));
62
+ server.useBodyParser('json'); // the configured bodyParser.json.limit is applied to it
63
+ ```
64
+
65
+ Keep the limit itself in the config (`bodyParser.json.limit`), where it also holds under
66
+ `Test.createTestingModule()`. The e2e suite cannot catch the missing parser: the testing module never
67
+ runs `main.ts`, so it never registers the path-scoped parser either.
68
+
69
+ Since 11.42.6 the server logs a warning at boot in exactly this situation:
70
+
71
+ ```
72
+ No global json body parser is registered: NestJS skips its own when a layer named `jsonParser`
73
+ already exists, and a path-scoped `json()` parser registered before `app.init()` counts as one. …
74
+ ```
75
+
76
+ The framework only reports it. It does not register the missing parser itself: NestJS would have put
77
+ it after everything `main.ts` registered, and that position is no longer known during module init. A
78
+ raw-body webhook or a proxy registered after the path-scoped parser must keep receiving the unread
79
+ request stream.
80
+
81
+ ## 2. `check:overrides`: a failed audit is no longer reported as clean
82
+
83
+ Two fixes in `scripts/check-overrides.mjs` (and the shared registry block in `scripts/check.mjs`):
84
+
85
+ - When pnpm 11 cannot reach the registry, `pnpm audit --json` prints
86
+ `{"error": {"message": "fetch failed"}}` instead of a report. The guard read that as "0 advisories"
87
+ and printed `ok — N override(s) checked … none is failing`. It now prints
88
+ `could not obtain an audit report` (exit 0) on a live run, and fails (exit 1) when the failed report
89
+ was passed in with `--audit-file`, which is the CI path. The suppression checks still run in that
90
+ case, because they ask the GitHub Advisory API rather than the audit: a suppressed advisory that
91
+ gained a fix still fails with `FIX AVAILABLE`.
92
+ - The clean-tree probe reads `pnpm_config_registry`, the variable pnpm 11 audits against. It used to
93
+ read `npm_config_registry`, which pnpm 11 ignores, and could then ask a registry the audit never used.
94
+
95
+ `scripts/` is not part of the npm package. Projects created from nest-server-starter or lt-monorepo
96
+ receive these fixes with their template update.
97
+
98
+ ## Troubleshooting
99
+
100
+ | Symptom | Cause | Fix |
101
+ | ------- | ----- | --- |
102
+ | Boot warning "No global json body parser is registered" | `main.ts` registers a path-scoped `json()` and no global parser | Add `server.useBodyParser('json')` to `main.ts` (§1) |
103
+ | Requests arrive with an empty body outside one path; sign-in reports "Missing input" | Same cause, on a version before 11.42.6 | Same fix (§1) |
104
+ | Tests fail with `Nest can't resolve dependencies of the MongooseCoreModule (MongooseConnectionName, ?)` (often hidden behind `Cannot read properties of undefined` from a `beforeAll` that swallows it) | Two copies of `@nestjs/core`: the project pins it below the framework's version | Raise the exact pins (Quick Migration), then check that `pnpm-lock.yaml` holds one copy of each |
105
+ | `check:overrides` prints `could not obtain an audit report (fetch failed)` | The npm registry was not reachable during the audit | Re-run with network access, or pass a captured report with `--audit-file` |
106
+ | `check:overrides` fails with `records a failed audit, not a result` | The report passed with `--audit-file` is a pnpm error, not an audit | Capture the report again once the registry answers |
107
+
108
+ ## Module Documentation
109
+
110
+ - [REQUEST-LIFECYCLE.md — 0d. Body parsers](../docs/REQUEST-LIFECYCLE.md)
111
+ - [11.42.3 → 11.42.4 guide §1](./11.42.3-to-11.42.4.md)
112
+ - `.claude/rules/configurable-features.md` — Body Parser Limits row
113
+ - `.claude/rules/package-management.md` — the `check:overrides` guard
@@ -0,0 +1,115 @@
1
+ # Migration Guide: 11.42.6 → 11.42.7
2
+
3
+ ## Overview
4
+
5
+ | Category | Details |
6
+ | -------------------- | --------------------------------------------------------------------------------------------------------------- |
7
+ | **New Feature** | `setupSwagger()` — a Swagger document that says how each route is reached: security, API-token scopes, tenant header (§1) |
8
+ | **New Feature** | Optional second document with only the routes an API token may call (§2) |
9
+ | **Dependencies** | `nodemailer` 10.0.15, `@modelcontextprotocol/sdk` 1.32.1, dev: `oxlint` 1.87.0 (§3) |
10
+ | **Breaking Changes** | None |
11
+ | **Migration Effort** | Optional. Switching `main.ts` to the helper replaces the hand-built `DocumentBuilder` block with one call |
12
+
13
+ ## Quick Migration
14
+
15
+ ```bash
16
+ pnpm update @lenne.tech/nest-server
17
+ ```
18
+
19
+ Nothing changes until `main.ts` calls the new helper. Vendor-mode projects pick it up with the core
20
+ update.
21
+
22
+ ## 1. `setupSwagger()` instead of a hand-built `DocumentBuilder`
23
+
24
+ The starters built the document like this:
25
+
26
+ ```typescript
27
+ const config = new DocumentBuilder().setTitle('…').addBearerAuth().build();
28
+ SwaggerModule.setup('swagger', server, () => SwaggerModule.createDocument(server, config, { … }), {
29
+ jsonDocumentUrl: '/api-docs-json',
30
+ });
31
+ ```
32
+
33
+ `addBearerAuth()` registers a scheme but attaches it to no route. A token entered under "Authorize" is
34
+ therefore sent with almost no request: in one project 10 of 158 operations carried a security entry.
35
+ And what a caller needs to know — is the route public, which API-token scope does it release, does it
36
+ read the tenant header — lived only in decorators.
37
+
38
+ ```typescript
39
+ import { setupSwagger } from '@lenne.tech/nest-server';
40
+
41
+ setupSwagger(server, {
42
+ description: 'API of my project',
43
+ documentOptions: {
44
+ extraModels: [CoreUserModel, CoreAuthModel, User, PersistenceModel, CorePersistenceModel, FilterArgs],
45
+ },
46
+ title: 'My Project API',
47
+ version: packageJson.version,
48
+ });
49
+ ```
50
+
51
+ Paths stay as before (`/swagger`, `/api-docs-json`) unless `path` / `jsonDocumentUrl` say otherwise.
52
+ What the document now contains, read from the same metadata the guards read:
53
+
54
+ | Feature in the config | In the document |
55
+ | --- | --- |
56
+ | always | `bearer` as a global security requirement — "Authorize" applies to every route. Public routes (no `@Roles`, or `S_EVERYONE`) carry `security: []` |
57
+ | `betterAuth` + `cookies` on | the session cookie (`iam.session_token`, or the name `COOKIE_PREFIX` / `basePath` produce) as a second, alternative requirement |
58
+ | `apiTokens` on | per operation `x-api-token-scopes` and a description line: "allowed with scope `read`" or "not allowed on this route". `@ApiTokenScopes()` on the method replaces the class declaration, exactly as in `assertApiTokenScopes()` |
59
+ | `multiTenancy` on | a reusable `TenantHeader` parameter (`X-Tenant-Id`, or `multiTenancy.headerName`) on every operation except public ones and `@SkipTenantCheck()`; its description says which callers need it (sessions and personal tokens) and which may omit it (company tokens) |
60
+
61
+ Optional:
62
+
63
+ - `operationTags: (operation) => string[] | undefined` regroups operations by use case instead of by
64
+ controller; `tags: [{ name, description }]` orders and describes them.
65
+ - `configureBuilder: (builder) => builder` adds servers, contact or further schemes.
66
+ - `customOptions` is passed to `SwaggerModule.setup()` for both documents.
67
+
68
+ Each operation is mapped to its handler through the default operation id (`<Controller>_<method>`, with
69
+ `[<n>]` for a handler serving several paths). An own `operationIdFactory` is therefore refused. Two different controller classes with the same name
70
+ cannot be told apart; their operations are left as Swagger built them, with a warning.
71
+
72
+ ## 2. A document for API-token callers
73
+
74
+ ```typescript
75
+ setupSwagger(server, { …, apiTokenView: true });
76
+ // or: apiTokenView: { path: 'swagger-partner', title: 'Partner API' }
77
+ ```
78
+
79
+ Serves a second document (default: UI `swagger-api-tokens`, JSON `/api-docs-api-tokens-json`) with only
80
+ the operations that release a scope, the bearer scheme as the only requirement and without the schemas
81
+ nothing refers to any more. Keep its path outside the path of the full UI, which serves its assets below
82
+ its own path.
83
+
84
+ The tenant header stays in this view while personal (user) tokens are enabled (`apiTokens.userTokens`,
85
+ the default): such a token without a company restriction chooses the company like a session does, and
86
+ without the header it is checked against its global roles and refused on company routes. With company
87
+ tokens only (`userTokens: false`) the header is dropped — a company token carries its company.
88
+
89
+ ## 3. Dependency updates
90
+
91
+ Patch-level maintenance, no code changes required:
92
+
93
+ | Package | Old | New | Section |
94
+ | --- | --- | --- | --- |
95
+ | `nodemailer` | 10.0.14 | 10.0.15 | dependencies |
96
+ | `@modelcontextprotocol/sdk` | 1.32.0 | 1.32.1 | dependencies |
97
+ | `oxlint` | 1.86.0 | 1.87.0 | devDependencies |
98
+
99
+ A project that pins `oxlint` itself should raise it to 1.87.0 alongside the framework —
100
+ `pnpm run update` in starter-based projects does this. A project that carries a `nodemailer`
101
+ override matching the framework's version (the starter does) should raise it to `10.0.15`.
102
+
103
+ ## Compatibility Notes
104
+
105
+ - The helper is additive. `DocumentBuilder` + `SwaggerModule.setup()` keep working unchanged.
106
+ - The document is built on the first request, as before.
107
+ - `buildSwaggerDocument()` and `buildApiTokenSwaggerDocument()` are exported for tests: a project can
108
+ assert in its e2e suite that a route documents the scope it enforces.
109
+
110
+ ## References
111
+
112
+ - Helper: `src/core/common/helpers/swagger.helper.ts`
113
+ - Options: `src/core/common/interfaces/swagger-setup-options.interface.ts`
114
+ - Tests: `tests/unit/swagger-helper.spec.ts`
115
+ - [nest-server-starter](https://github.com/lenneTech/nest-server-starter) `src/main.ts`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.42.5",
3
+ "version": "11.42.7",
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",
@@ -92,7 +92,7 @@
92
92
  "@apollo/server": "5.5.1",
93
93
  "@as-integrations/express5": "1.1.2",
94
94
  "@getbrevo/brevo": "6.0.3",
95
- "@modelcontextprotocol/sdk": "1.32.0",
95
+ "@modelcontextprotocol/sdk": "1.32.1",
96
96
  "@nestjs/apollo": "13.4.5",
97
97
  "@nestjs/common": "11.2.7",
98
98
  "@nestjs/core": "11.2.7",
@@ -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.14",
132
+ "nodemailer": "10.0.15",
133
133
  "passport": "0.7.0",
134
134
  "passport-jwt": "4.0.1",
135
135
  "reflect-metadata": "0.2.2",
@@ -212,7 +212,7 @@
212
212
  "npm-watch": "0.13.0",
213
213
  "otpauth": "9.5.2",
214
214
  "oxfmt": "0.71.0",
215
- "oxlint": "1.86.0",
215
+ "oxlint": "1.87.0",
216
216
  "rimraf": "6.1.3",
217
217
  "smtp-server": "3.19.17",
218
218
  "ts-node": "10.9.2",