@nestjs/platform-express 12.1.2 → 12.1.3

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.
@@ -5,7 +5,7 @@ import * as http from 'http';
5
5
  import * as https from 'https';
6
6
  import { NestExpressBodyParserOptionsFor, NestExpressBodyParserType } from '../interfaces/nest-express-body-parser.interface.js';
7
7
  import { ServeStaticOptions } from '../interfaces/serve-static-options.interface.js';
8
- import { type CorsOptions, type CorsOptionsDelegate, type VersionValue } from '@nestjs/common/internal';
8
+ import { type CorsOptions, type CorsOptionsDelegate, type RouteInfo, type VersionValue } from '@nestjs/common/internal';
9
9
  import type { NestApplicationOptions } from '@nestjs/common';
10
10
  import { AbstractHttpAdapter } from '@nestjs/core';
11
11
  type VersionedRoute = <TRequest extends Record<string, any> = any, TResponse = any>(req: TRequest, res: TResponse, next: () => void) => any;
@@ -21,7 +21,18 @@ export declare class ExpressAdapter extends AbstractHttpAdapter<http.Server | ht
21
21
  private onRequestHook?;
22
22
  private onResponseHook?;
23
23
  constructor(instance?: any);
24
- setOnRequestHook(onRequestHook: (req: express.Request, res: express.Response, done: () => void) => Promise<void> | void): void;
24
+ private runRequestHook;
25
+ /**
26
+ * Registers a request hook. Call `done()` to continue processing the request;
27
+ * resolving the returned promise does not continue it. Only the first
28
+ * completion is forwarded to Express. Errors after `done()` are logged
29
+ * without advancing the request again.
30
+ */
31
+ setOnRequestHook(onRequestHook: (req: express.Request, res: express.Response, done: (err?: Error) => void) => Promise<void> | void): void;
32
+ /**
33
+ * Registers a hook to run after the response has been sent.
34
+ * Thrown errors and rejected promises are logged.
35
+ */
25
36
  setOnResponseHook(onResponseHook: (req: express.Request, res: express.Response) => Promise<void> | void): void;
26
37
  reply(response: any, body: any, statusCode?: number): any;
27
38
  status(response: any, statusCode: number): any;
@@ -29,7 +40,7 @@ export declare class ExpressAdapter extends AbstractHttpAdapter<http.Server | ht
29
40
  render(response: any, view: string, options: any): any;
30
41
  redirect(response: any, statusCode: number, url: string): any;
31
42
  setErrorHandler(handler: Function, prefix?: string): any;
32
- setNotFoundHandler(handler: Function, prefix?: string): any;
43
+ setNotFoundHandler(handler: Function, prefix?: string, excludedRoutes?: RouteInfo[]): any;
33
44
  isHeadersSent(response: any): boolean;
34
45
  getHeader(response: any, name: string): any;
35
46
  setHeader(response: any, name: string, value: string): any;
@@ -4,6 +4,7 @@ import express from 'express';
4
4
  import * as http from 'http';
5
5
  import * as https from 'https';
6
6
  import { pathToRegexp } from 'path-to-regexp';
7
+ import { finished } from 'stream';
7
8
  import { getBodyParserOptions } from './utils/get-body-parser-options.util.js';
8
9
  import { getMediaTypeVersion } from './utils/get-media-type-version.util.js';
9
10
  import { addLeadingSlash, isFunction, isNil, isObject, isString, isUndefined, stripEndSlash, } from '@nestjs/common/internal';
@@ -30,21 +31,59 @@ export class ExpressAdapter extends AbstractHttpAdapter {
30
31
  super(instance || express());
31
32
  this.instance.use((req, res, next) => {
32
33
  if (this.onResponseHook) {
33
- res.on('finish', () => {
34
- void this.onResponseHook.apply(this, [req, res]);
34
+ res.on('finish', async () => {
35
+ try {
36
+ await this.onResponseHook.apply(this, [req, res]);
37
+ }
38
+ catch (err) {
39
+ this.logger.error(err);
40
+ }
35
41
  });
36
42
  }
37
43
  if (this.onRequestHook) {
38
- void this.onRequestHook.apply(this, [req, res, next]);
44
+ this.runRequestHook(req, res, next);
39
45
  }
40
46
  else {
41
47
  next();
42
48
  }
43
49
  });
44
50
  }
51
+ runRequestHook(req, res, next) {
52
+ let completed = false;
53
+ const doneOnce = (err) => {
54
+ if (completed) {
55
+ if (err) {
56
+ this.logger.error(err);
57
+ }
58
+ return;
59
+ }
60
+ completed = true;
61
+ next(err);
62
+ };
63
+ try {
64
+ // Not returned to Express: rejections share the once-guard with done()
65
+ const result = this.onRequestHook.call(this, req, res, doneOnce);
66
+ if (result && typeof result.then === 'function') {
67
+ result.then(undefined, err => doneOnce(err || new Error('Request hook failed')));
68
+ }
69
+ }
70
+ catch (err) {
71
+ doneOnce(err || new Error('Request hook failed'));
72
+ }
73
+ }
74
+ /**
75
+ * Registers a request hook. Call `done()` to continue processing the request;
76
+ * resolving the returned promise does not continue it. Only the first
77
+ * completion is forwarded to Express. Errors after `done()` are logged
78
+ * without advancing the request again.
79
+ */
45
80
  setOnRequestHook(onRequestHook) {
46
81
  this.onRequestHook = onRequestHook;
47
82
  }
83
+ /**
84
+ * Registers a hook to run after the response has been sent.
85
+ * Thrown errors and rejected promises are logged.
86
+ */
48
87
  setOnResponseHook(onResponseHook) {
49
88
  this.onResponseHook = onResponseHook;
50
89
  }
@@ -61,6 +100,13 @@ export class ExpressAdapter extends AbstractHttpAdapter {
61
100
  stream.once('error', err => {
62
101
  body.errorHandler(err, response);
63
102
  });
103
+ // pipe() leaves the source open when the client disconnects early,
104
+ // also before reply() runs ("close" has already fired by then)
105
+ finished(response, () => {
106
+ if (!stream.readableEnded) {
107
+ stream.destroy();
108
+ }
109
+ });
64
110
  return stream
65
111
  .pipe(response)
66
112
  .on('error', (err) => body.errorLogger(err));
@@ -98,13 +144,23 @@ export class ExpressAdapter extends AbstractHttpAdapter {
98
144
  // root-mounted routes) still pass through the exception layer.
99
145
  return this.use(handler);
100
146
  }
101
- setNotFoundHandler(handler, prefix) {
147
+ setNotFoundHandler(handler, prefix, excludedRoutes) {
102
148
  const normalizedPrefix = this.normalizePrefix(prefix);
103
149
  if (normalizedPrefix) {
104
150
  this.registeredPrefixes.add(normalizedPrefix);
105
151
  const router = express.Router();
106
152
  router.all('*path', handler);
107
- return this.use(normalizedPrefix, router);
153
+ this.use(normalizedPrefix, router);
154
+ // Excluded routes live at the root, out of the prefix router's reach.
155
+ // Mount the handler on each exact path, for every method, so a method
156
+ // miss there (`POST /hello` with only `GET /hello`) still reaches the
157
+ // exception layer. Not `use(path, router)`: a subtree mount would claim
158
+ // a raw `/hello/live` added after `init()`; a wildcard exclusion covers
159
+ // its subtree through its own pattern.
160
+ for (const route of excludedRoutes ?? []) {
161
+ this.instance.all(addLeadingSlash(route.path), handler);
162
+ }
163
+ return;
108
164
  }
109
165
  return this.use((req, res, next) => {
110
166
  // When multiple apps share this adapter, a non-prefixed app's 404
@@ -202,10 +258,22 @@ export class ExpressAdapter extends AbstractHttpAdapter {
202
258
  createMiddlewareFactory(requestMethod) {
203
259
  return (path, callback) => {
204
260
  try {
205
- const convertedPath = LegacyRouteConverter.tryConvert(path);
206
- return this.routerMethodFactory
207
- .get(this.instance, requestMethod)
208
- .call(this.instance, convertedPath, callback);
261
+ // The core marks an exact-match path with a trailing "$" (e.g. "/api$").
262
+ // Express 5 reads "$" literally and "use()" matches by prefix, so strip
263
+ // the marker and register the path as an exact "all()" route instead.
264
+ const isExactPath = path.endsWith('$');
265
+ const convertedPath = LegacyRouteConverter.tryConvert(isExactPath ? path.slice(0, -1) : path);
266
+ let router = this.routerMethodFactory.get(this.instance, requestMethod);
267
+ if (isExactPath && router === this.instance.use) {
268
+ router = this.instance.all;
269
+ }
270
+ // Express routes are not strict, so "/api" also matches "/api/". That
271
+ // path is left to the wildcard entry registered next to it (an optional
272
+ // "{*path}" matches it), so the middleware doesn't run twice.
273
+ const handler = isExactPath
274
+ ? (req, res, next) => req.path.endsWith('/') ? next() : callback(req, res, next)
275
+ : callback;
276
+ return router.call(this.instance, convertedPath, handler);
209
277
  }
210
278
  catch (e) {
211
279
  if (e instanceof TypeError) {
@@ -16,6 +16,10 @@ export interface MulterLimits {
16
16
  parts?: number;
17
17
  /** For multipart forms, the max number of header key=> value pairs to parse Default: 2000(same as node's http). */
18
18
  headerPairs?: number;
19
+ /** Max nesting depth of field names; `a[b][c]` has 2 levels (Default: Infinity) */
20
+ fieldNestingDepth?: number;
21
+ /** Max numeric array index accepted in field names; `a[3]` uses index 3 (Default: Infinity) */
22
+ fieldArrayIndexLimit?: number;
19
23
  }
20
24
  /**
21
25
  * @see https://github.com/expressjs/multer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nestjs/platform-express",
3
- "version": "12.1.2",
3
+ "version": "12.1.3",
4
4
  "description": "Nest - modern, fast, powerful node.js web framework (@platform-express)",
5
5
  "author": "Kamil Mysliwiec",
6
6
  "license": "MIT",
@@ -32,12 +32,12 @@
32
32
  "tslib": "2.8.1"
33
33
  },
34
34
  "devDependencies": {
35
- "@nestjs/common": "12.1.2",
36
- "@nestjs/core": "12.1.2"
35
+ "@nestjs/common": "12.1.3",
36
+ "@nestjs/core": "12.1.3"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@nestjs/common": "^12.0.0",
40
40
  "@nestjs/core": "^12.0.0"
41
41
  },
42
- "gitHead": "8ab60aa1b2927fbe0e23135ffc65d610c623236a"
42
+ "gitHead": "4514623279f6087808e3c8f08fbf9f37d3c17482"
43
43
  }