@lenne.tech/nest-server 11.42.7 → 11.42.8

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.
@@ -0,0 +1,74 @@
1
+ # Migration Guide: 11.42.7 → 11.42.8
2
+
3
+ ## Overview
4
+
5
+ | Category | Details |
6
+ | -------------------- | ---------------------------------------------------------------------------------------------------- |
7
+ | **Bugfix** | `setupSwagger()` enriches every operation an `@All()` route produces, including `SEARCH` (§1) |
8
+ | **Bugfix** | The Better-Auth pass-through route no longer appears as `/iam/{path}` in the Swagger document (§2) |
9
+ | **Dependencies** | dev: `vite` 8.3.3; override `js-yaml` 5.4.3 (§3) |
10
+ | **Breaking Changes** | None |
11
+ | **Migration Effort** | None for most projects. Regenerate a client built from `/api-docs-json` if you keep one (§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.
20
+
21
+ ## 1. `SEARCH` operations of `@All()` routes are enriched
22
+
23
+ For an `@All()` route, `@nestjs/swagger` documents one operation per HTTP method — including
24
+ `SEARCH`, which no OpenAPI version lists as a path-item method. 11.42.7 walked a fixed list of
25
+ methods and skipped it, so that one operation stayed as Swagger built it: no API-token scope line,
26
+ no `security: []` on a public route, no tenant header, and `operationTags` did not reach it — it
27
+ landed in its own tag group next to the regrouped siblings.
28
+
29
+ The helper now reads the operations from the path item itself, so `SEARCH` (and any other method
30
+ `@nestjs/swagger` emits) is treated like every other operation — in the full document and in the
31
+ API-token view.
32
+
33
+ Nothing to change in your code. If you assert on the document in tests, a `search` operation of an
34
+ `@All()` route now carries the same `x-api-token-scopes`, description line and tags as its siblings.
35
+
36
+ ## 2. `/iam/{path}` is gone from the document
37
+
38
+ `CoreBetterAuthController.handlePluginRoutes` (`@All('*path')`) forwards every other `/iam/*` request
39
+ to Better-Auth's own handler. It appeared in Swagger as eight `/iam/{path}` operations without
40
+ parameters or description. It is now `@ApiExcludeEndpoint()`; the route itself is unchanged.
41
+
42
+ - **Generated clients:** a client generated from `/api-docs-json` (hey-api, openapi-generator, …)
43
+ loses its `coreBetterAuthControllerHandlePluginRoutes*` functions on the next generation. They
44
+ could not express any real Better-Auth call, so nothing should depend on them — but regenerate and
45
+ let the compiler confirm it.
46
+ - **Own controller:** a project controller that **overrides** `handlePluginRoutes` (re-declaring
47
+ `@All('*path')`) must also re-declare `@ApiExcludeEndpoint()` to keep it out of the document.
48
+ Decorator metadata belongs to the method, so an override starts without it. A controller that
49
+ only inherits the method is covered automatically.
50
+
51
+ ## 3. Dependency updates
52
+
53
+ Maintenance only, nothing to change in your project:
54
+
55
+ | Package | Old | New | Where |
56
+ | --- | --- | --- | --- |
57
+ | `vite` | 8.3.2 | 8.3.3 | devDependencies (test tooling) |
58
+ | `js-yaml` 5.x | 5.4.2 | 5.4.3 | override target in `pnpm-workspace.yaml` (parser bug fix) |
59
+
60
+ `mongoose` 9.11.0 / `mongodb` 7.7.0 are deliberately not part of this release: the framework and the
61
+ starter have to raise `mongodb` together, because two driver copies make Better-Auth's Mongo adapter
62
+ reject each other's ids.
63
+
64
+ ## Compatibility Notes
65
+
66
+ - Both changes only affect the generated Swagger/OpenAPI document. Routing, guards and API-token
67
+ enforcement are unchanged.
68
+ - The document gets smaller: the framework's own test server goes from 142 to 134 operations.
69
+
70
+ ## References
71
+
72
+ - Helper: `src/core/common/helpers/swagger.helper.ts` (`operationsOf()`)
73
+ - Controller: `src/core/modules/better-auth/core-better-auth.controller.ts` (`handlePluginRoutes`)
74
+ - Tests: `tests/unit/swagger-helper.spec.ts`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.42.7",
3
+ "version": "11.42.8",
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",
@@ -221,7 +221,7 @@
221
221
  "tus-js-client": "4.3.1",
222
222
  "typescript": "5.9.3",
223
223
  "unplugin-swc": "2.0.0",
224
- "vite": "8.3.2",
224
+ "vite": "8.3.3",
225
225
  "vite-plugin-node": "8.0.0",
226
226
  "vitest": "4.1.11"
227
227
  },
@@ -55,9 +55,12 @@ export const SWAGGER_TENANT_HEADER_PARAMETER = 'TenantHeader';
55
55
  /** Vendor extension listing the scopes an API token needs for an operation */
56
56
  export const SWAGGER_API_TOKEN_SCOPES_EXTENSION = 'x-api-token-scopes';
57
57
 
58
- const HTTP_METHODS = ['delete', 'get', 'head', 'options', 'patch', 'post', 'put', 'trace'] as const;
59
-
60
- type HttpMethod = (typeof HTTP_METHODS)[number];
58
+ /**
59
+ * Fields of an OpenAPI path item that are not operations. Every other key is an operation: they are read
60
+ * from the path item rather than from a list of HTTP methods, because `@All()` makes @nestjs/swagger emit
61
+ * a `search` operation too, which no OpenAPI version lists — a fixed list left it unenriched.
62
+ */
63
+ const PATH_ITEM_FIELDS = new Set(['$ref', 'description', 'parameters', 'servers', 'summary']);
61
64
 
62
65
  /** An operation with the vendor extension this helper writes */
63
66
  type EnrichedOperation = OperationObject & { [SWAGGER_API_TOKEN_SCOPES_EXTENSION]?: string[] };
@@ -243,10 +246,9 @@ export function buildApiTokenSwaggerDocument(
243
246
 
244
247
  const paths: OpenAPIObject['paths'] = {};
245
248
  for (const [path, item] of Object.entries(view.paths)) {
246
- const kept: PathItemObject = {};
247
- for (const method of HTTP_METHODS) {
248
- const operation = item[method] as EnrichedOperation | undefined;
249
- if (!operation?.[SWAGGER_API_TOKEN_SCOPES_EXTENSION]?.length) {
249
+ const kept: Record<string, EnrichedOperation> = {};
250
+ for (const [method, operation] of operationsOf(item)) {
251
+ if (!operation[SWAGGER_API_TOKEN_SCOPES_EXTENSION]?.length) {
250
252
  continue;
251
253
  }
252
254
  operation.security = [{ [SWAGGER_BEARER_SCHEME]: [] }];
@@ -440,25 +442,30 @@ function isRef(value: unknown): value is ReferenceObject {
440
442
 
441
443
  function forEachOperation(
442
444
  document: OpenAPIObject,
443
- visit: (path: string, method: HttpMethod, operation: EnrichedOperation) => void,
445
+ visit: (path: string, method: string, operation: EnrichedOperation) => void,
444
446
  ): void {
445
447
  for (const [path, item] of Object.entries(document.paths ?? {})) {
446
- for (const method of HTTP_METHODS) {
447
- const operation = item[method];
448
- if (operation) {
449
- visit(path, method, operation);
450
- }
448
+ for (const [method, operation] of operationsOf(item)) {
449
+ visit(path, method, operation);
451
450
  }
452
451
  }
453
452
  }
454
453
 
454
+ /** A path-item key that is not an operation: a field OpenAPI defines there, or a vendor extension. */
455
+ function isPathItemField(key: string): boolean {
456
+ return PATH_ITEM_FIELDS.has(key) || key.startsWith('x-');
457
+ }
458
+
459
+ /** The operations of a path item, keyed by their lower-case HTTP method (`get`, ..., `search`). */
460
+ function operationsOf(item: PathItemObject): [string, EnrichedOperation][] {
461
+ return Object.entries(item).filter(
462
+ ([key, value]) => !isPathItemField(key) && typeof value === 'object' && value !== null && !Array.isArray(value),
463
+ ) as [string, EnrichedOperation][];
464
+ }
465
+
455
466
  /** Fields of a path item that are not operations (summary, description, servers, shared parameters). */
456
467
  function pathLevelFields(item: PathItemObject): PathItemObject {
457
- const fields: PathItemObject = { ...item };
458
- for (const method of HTTP_METHODS) {
459
- delete fields[method];
460
- }
461
- return fields;
468
+ return Object.fromEntries(Object.entries(item).filter(([key]) => isPathItemField(key))) as PathItemObject;
462
469
  }
463
470
 
464
471
  /**
@@ -753,6 +753,7 @@ export class CoreBetterAuthController {
753
753
  * @see README.md "Architecture: Why Custom Controllers?"
754
754
  */
755
755
  @All('*path')
756
+ @ApiExcludeEndpoint() // A pass-through to Better-Auth: one `/iam/{path}` per HTTP method, with nothing to document
756
757
  @Roles(RoleEnum.S_EVERYONE)
757
758
  async handlePluginRoutes(@Req() req: Request, @Res() res: Response): Promise<void> {
758
759
  return this.handleBetterAuthPlugins(req, res);