@nestjs/core 12.0.1 → 12.0.2

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.
@@ -2,83 +2,450 @@ import type { HttpServer, RequestMethod, VersioningOptions } from '@nestjs/commo
2
2
  import type { RequestHandler, VersionValue } from '@nestjs/common/internal';
3
3
  import type { NestApplicationOptions } from '@nestjs/common';
4
4
  /**
5
+ * Base class for HTTP platform adapters (see `ExpressAdapter` and
6
+ * `FastifyAdapter` for reference implementations).
7
+ *
8
+ * It implements the {@link HttpServer} contract that the Nest core relies on,
9
+ * and that interface is where each method's calling conventions are
10
+ * documented: when the core invokes it, with which arguments, and what it
11
+ * expects back. This class only adds:
12
+ *
13
+ * - default implementations that delegate to the wrapped framework
14
+ * `instance` (`use()`, the HTTP-verb methods, `listen()`) or are inert
15
+ * (`init()`, `normalizePath()`, `mapException()`, `beforeClose()`, the
16
+ * `setOn*Hook()` setters);
17
+ * - storage for the native server (`httpServer`) and the framework instance
18
+ * (`instance`), with their accessors;
19
+ * - the introspection hooks used by instrumentation tooling.
20
+ *
21
+ * Every remaining {@link HttpServer} member is declared abstract here, even
22
+ * the ones the interface marks optional, so subclasses cannot forget them.
23
+ * `setBaseViewsDir()`, `useBodyParser()` and `isRouteOrderSensitive()` are
24
+ * not declared on this class; implement them when the platform supports
25
+ * them (see {@link HttpServer} for what the core does when they are absent).
26
+ *
27
+ * Keep in mind that the core also reads and writes properties of the request
28
+ * object (`body`, `params`, `query`, `headers`, ...), that Server-Sent Events
29
+ * write directly to the Node.js response, and that `app.listen()` and the
30
+ * WebSocket adapters use the value returned by
31
+ * {@link AbstractHttpAdapter.getHttpServer} as a Node.js `net.Server`; see the
32
+ * {@link HttpServer} documentation for details.
33
+ *
34
+ * @typeParam TServer - Type of the native HTTP server stored in `httpServer`
35
+ * (e.g. `http.Server | https.Server`).
36
+ * @typeParam TRequest - Type of the framework request object.
37
+ * @typeParam TResponse - Type of the framework response object.
38
+ *
39
+ * @see [HTTP adapter](https://docs.nestjs.com/faq/http-adapter)
40
+ *
5
41
  * @publicApi
6
42
  */
7
43
  export declare abstract class AbstractHttpAdapter<TServer = any, TRequest = any, TResponse = any> implements HttpServer<TRequest, TResponse> {
8
44
  protected instance?: any | undefined;
45
+ /**
46
+ * Native HTTP server created by {@link AbstractHttpAdapter.initHttpServer}
47
+ * and returned by {@link AbstractHttpAdapter.getHttpServer}.
48
+ */
9
49
  protected httpServer: TServer;
50
+ /**
51
+ * Callback registered through
52
+ * {@link AbstractHttpAdapter.setOnRouteTriggered}, if any.
53
+ */
10
54
  protected onRouteTriggered: ((requestMethod: RequestMethod, path: string) => void) | undefined;
55
+ /**
56
+ * @param instance The framework application instance to delegate to (e.g.
57
+ * an Express `Application`). Subclasses typically create a default one
58
+ * when none is given.
59
+ */
11
60
  constructor(instance?: any | undefined);
61
+ /**
62
+ * Asynchronous setup hook, awaited by `NestFactory.create()` and again by
63
+ * `app.init()`, so overrides must be idempotent. No-op by default.
64
+ *
65
+ * @see {@link HttpServer.init}
66
+ */
12
67
  init(): Promise<void>;
68
+ /**
69
+ * Registers a global middleware by delegating to `instance.use(...args)`.
70
+ *
71
+ * @see {@link HttpServer.use}
72
+ */
13
73
  use(...args: any[]): any;
74
+ /**
75
+ * Registers a `GET` route by delegating to `instance.get(...args)`.
76
+ *
77
+ * @see {@link HttpServer.get}
78
+ */
14
79
  get(handler: RequestHandler): any;
15
80
  get(path: any, handler: RequestHandler): any;
81
+ /**
82
+ * Registers a `POST` route by delegating to `instance.post(...args)`.
83
+ *
84
+ * @see {@link HttpServer.post}
85
+ */
16
86
  post(handler: RequestHandler): any;
17
87
  post(path: any, handler: RequestHandler): any;
88
+ /**
89
+ * Registers a `HEAD` route by delegating to `instance.head(...args)`.
90
+ *
91
+ * @see {@link HttpServer.head}
92
+ */
18
93
  head(handler: RequestHandler): any;
19
94
  head(path: any, handler: RequestHandler): any;
95
+ /**
96
+ * Registers a `DELETE` route by delegating to `instance.delete(...args)`.
97
+ *
98
+ * @see {@link HttpServer.delete}
99
+ */
20
100
  delete(handler: RequestHandler): any;
21
101
  delete(path: any, handler: RequestHandler): any;
102
+ /**
103
+ * Registers a `PUT` route by delegating to `instance.put(...args)`.
104
+ *
105
+ * @see {@link HttpServer.put}
106
+ */
22
107
  put(handler: RequestHandler): any;
23
108
  put(path: any, handler: RequestHandler): any;
109
+ /**
110
+ * Registers a `PATCH` route by delegating to `instance.patch(...args)`.
111
+ *
112
+ * @see {@link HttpServer.patch}
113
+ */
24
114
  patch(handler: RequestHandler): any;
25
115
  patch(path: any, handler: RequestHandler): any;
116
+ /**
117
+ * Registers a WebDAV `PROPFIND` route by delegating to
118
+ * `instance.propfind(...args)`. Override when the framework exposes the
119
+ * verb under a different API.
120
+ *
121
+ * @see {@link HttpServer.propfind}
122
+ */
26
123
  propfind(handler: RequestHandler): any;
27
124
  propfind(path: any, handler: RequestHandler): any;
125
+ /**
126
+ * Registers a WebDAV `PROPPATCH` route by delegating to
127
+ * `instance.proppatch(...args)`. Override when the framework exposes the
128
+ * verb under a different API.
129
+ *
130
+ * @see {@link HttpServer.proppatch}
131
+ */
28
132
  proppatch(handler: RequestHandler): any;
29
133
  proppatch(path: any, handler: RequestHandler): any;
134
+ /**
135
+ * Registers a WebDAV `MKCOL` route by delegating to
136
+ * `instance.mkcol(...args)`. Override when the framework exposes the verb
137
+ * under a different API.
138
+ *
139
+ * @see {@link HttpServer.mkcol}
140
+ */
30
141
  mkcol(handler: RequestHandler): any;
31
142
  mkcol(path: any, handler: RequestHandler): any;
143
+ /**
144
+ * Registers a WebDAV `COPY` route by delegating to
145
+ * `instance.copy(...args)`. Override when the framework exposes the verb
146
+ * under a different API.
147
+ *
148
+ * @see {@link HttpServer.copy}
149
+ */
32
150
  copy(handler: RequestHandler): any;
33
151
  copy(path: any, handler: RequestHandler): any;
152
+ /**
153
+ * Registers a WebDAV `MOVE` route by delegating to
154
+ * `instance.move(...args)`. Override when the framework exposes the verb
155
+ * under a different API.
156
+ *
157
+ * @see {@link HttpServer.move}
158
+ */
34
159
  move(handler: RequestHandler): any;
35
160
  move(path: any, handler: RequestHandler): any;
161
+ /**
162
+ * Registers a WebDAV `LOCK` route by delegating to
163
+ * `instance.lock(...args)`. Override when the framework exposes the verb
164
+ * under a different API.
165
+ *
166
+ * @see {@link HttpServer.lock}
167
+ */
36
168
  lock(handler: RequestHandler): any;
37
169
  lock(path: any, handler: RequestHandler): any;
170
+ /**
171
+ * Registers a WebDAV `UNLOCK` route by delegating to
172
+ * `instance.unlock(...args)`. Override when the framework exposes the verb
173
+ * under a different API.
174
+ *
175
+ * @see {@link HttpServer.unlock}
176
+ */
38
177
  unlock(handler: RequestHandler): any;
39
178
  unlock(path: any, handler: RequestHandler): any;
179
+ /**
180
+ * Registers a route for every HTTP method by delegating to
181
+ * `instance.all(...args)`.
182
+ *
183
+ * @see {@link HttpServer.all}
184
+ */
40
185
  all(handler: RequestHandler): any;
41
186
  all(path: any, handler: RequestHandler): any;
187
+ /**
188
+ * Registers a `SEARCH` route by delegating to `instance.search(...args)`.
189
+ * Override when the framework exposes the verb under a different API.
190
+ *
191
+ * @see {@link HttpServer.search}
192
+ */
42
193
  search(handler: RequestHandler): any;
43
194
  search(path: any, handler: RequestHandler): any;
195
+ /**
196
+ * Registers a `QUERY` route by delegating to `instance.query(...args)`.
197
+ * Override when the framework exposes the verb under a different API.
198
+ *
199
+ * @see {@link HttpServer.query}
200
+ */
44
201
  query(handler: RequestHandler): any;
45
202
  query(path: any, handler: RequestHandler): any;
203
+ /**
204
+ * Registers an `OPTIONS` route by delegating to
205
+ * `instance.options(...args)`.
206
+ *
207
+ * @see {@link HttpServer.options}
208
+ */
46
209
  options(handler: RequestHandler): any;
47
210
  options(path: any, handler: RequestHandler): any;
211
+ /**
212
+ * Starts listening by delegating to
213
+ * `instance.listen(port, hostname, callback)`. The core always passes its
214
+ * own callback as the last argument and expects it to be invoked once the
215
+ * server is bound, or with an `Error` on failure. Override when the
216
+ * framework instance does not expose a Node-style `listen()`, e.g. to call
217
+ * `httpServer.listen()` instead.
218
+ *
219
+ * @see {@link HttpServer.listen}
220
+ */
48
221
  listen(port: string | number, callback?: () => void): any;
49
222
  listen(port: string | number, hostname: string, callback?: () => void): any;
223
+ /**
224
+ * Returns the native HTTP server stored by
225
+ * {@link AbstractHttpAdapter.initHttpServer} (or
226
+ * {@link AbstractHttpAdapter.setHttpServer}).
227
+ *
228
+ * @see {@link HttpServer.getHttpServer}
229
+ */
50
230
  getHttpServer(): TServer;
231
+ /**
232
+ * Replaces the native HTTP server returned by
233
+ * {@link AbstractHttpAdapter.getHttpServer}. Mostly useful for tests and
234
+ * for adapters that obtain the server from elsewhere.
235
+ */
51
236
  setHttpServer(httpServer: TServer): void;
237
+ /**
238
+ * Replaces the framework application instance that the default method
239
+ * implementations delegate to.
240
+ */
52
241
  setInstance<T = any>(instance: T): void;
242
+ /**
243
+ * Returns the framework application instance passed to the constructor
244
+ * (or set through {@link AbstractHttpAdapter.setInstance}).
245
+ *
246
+ * @see {@link HttpServer.getInstance}
247
+ */
53
248
  getInstance<T = any>(): T;
249
+ /**
250
+ * Converts and validates a route path before registration. Returns the
251
+ * path unchanged by default; override to translate Nest's path syntax to
252
+ * the router's and to throw on invalid paths.
253
+ *
254
+ * @see {@link HttpServer.normalizePath}
255
+ */
54
256
  normalizePath(path: string): string;
257
+ /**
258
+ * Registers a callback that the router invokes right before each route
259
+ * handler runs, with the resolved `RequestMethod` and the route path as
260
+ * declared (before {@link AbstractHttpAdapter.normalizePath}). The router
261
+ * wraps every handler at registration time, so the callback must be set
262
+ * before `app.init()` to take effect. Intended for instrumentation and
263
+ * devtools; there is no need to override it.
264
+ */
55
265
  setOnRouteTriggered(onRouteTriggered: (requestMethod: RequestMethod, path: string) => void): void;
266
+ /**
267
+ * Returns the callback registered through
268
+ * {@link AbstractHttpAdapter.setOnRouteTriggered}, if any. Read by the
269
+ * router when registering routes.
270
+ */
56
271
  getOnRouteTriggered(): ((requestMethod: RequestMethod, path: string) => void) | undefined;
272
+ /**
273
+ * Registers a hook to run at the start of every request. No-op by default;
274
+ * the built-in adapters accept a `(req, res, done) => void | Promise<void>`
275
+ * function and call `done()` to continue processing. Intended for
276
+ * instrumentation and devtools.
277
+ */
57
278
  setOnRequestHook(onRequestHook: Function): void;
279
+ /**
280
+ * Registers a hook to run once a response has been sent. No-op by default;
281
+ * the built-in adapters accept a `(req, res) => void | Promise<void>`
282
+ * function. Intended for instrumentation and devtools.
283
+ */
58
284
  setOnResponseHook(onResponseHook: Function): void;
285
+ /**
286
+ * Called by `app.close()` before the shutdown hooks run. No-op by default;
287
+ * override to enter a "shutting down" state.
288
+ *
289
+ * @see {@link HttpServer.beforeClose}
290
+ */
59
291
  beforeClose(): void;
292
+ /**
293
+ * Translates a framework-native error into something the exception filters
294
+ * understand. Invoked by the global exception layer with every error it
295
+ * receives, before it reaches the filters; the returned value is what the
296
+ * filters see. Returns the error unchanged by default. The built-in
297
+ * adapters map, for instance, body-parser `SyntaxError`s to
298
+ * `BadRequestException`.
299
+ */
60
300
  mapException(error: unknown): unknown;
301
+ /**
302
+ * Stops the server; called by `app.close()`. May return a promise.
303
+ *
304
+ * @see {@link HttpServer.close}
305
+ */
61
306
  abstract close(): any;
307
+ /**
308
+ * Creates the native server and stores it in `httpServer`, honoring the
309
+ * `httpsOptions`, `forceCloseConnections` and `return503OnClosing`
310
+ * application options. Called once when the application is constructed.
311
+ *
312
+ * @see {@link HttpServer.initHttpServer}
313
+ */
62
314
  abstract initHttpServer(options: NestApplicationOptions): any;
315
+ /**
316
+ * Serves static files; pass-through for `app.useStaticAssets()`.
317
+ *
318
+ * @see {@link HttpServer.useStaticAssets}
319
+ */
63
320
  abstract useStaticAssets(...args: any[]): any;
321
+ /**
322
+ * Configures the template engine used by
323
+ * {@link AbstractHttpAdapter.render}; pass-through for
324
+ * `app.setViewEngine()`.
325
+ *
326
+ * @see {@link HttpServer.setViewEngine}
327
+ */
64
328
  abstract setViewEngine(engine: string): any;
329
+ /**
330
+ * Returns the request host name, used for `@Controller({ host })`.
331
+ *
332
+ * @see {@link HttpServer.getRequestHostname}
333
+ */
65
334
  abstract getRequestHostname(request: any): any;
335
+ /**
336
+ * Returns the upper-case request method (`'GET'`, `'POST'`, ...).
337
+ *
338
+ * @see {@link HttpServer.getRequestMethod}
339
+ */
66
340
  abstract getRequestMethod(request: any): any;
341
+ /**
342
+ * Returns the original request URL, including the query string.
343
+ *
344
+ * @see {@link HttpServer.getRequestUrl}
345
+ */
67
346
  abstract getRequestUrl(request: any): any;
347
+ /**
348
+ * Sets the status code without sending the response.
349
+ *
350
+ * @see {@link HttpServer.status}
351
+ */
68
352
  abstract status(response: any, statusCode: number): any;
353
+ /**
354
+ * Sends the response body, handling empty bodies, `StreamableFile`,
355
+ * objects (as JSON) and primitives.
356
+ *
357
+ * @see {@link HttpServer.reply}
358
+ */
69
359
  abstract reply(response: any, body: any, statusCode?: number): any;
360
+ /**
361
+ * Terminates the response, optionally writing `message` first.
362
+ *
363
+ * @see {@link HttpServer.end}
364
+ */
70
365
  abstract end(response: any, message?: string): any;
366
+ /**
367
+ * Renders a view template (`@Render()`).
368
+ *
369
+ * @see {@link HttpServer.render}
370
+ */
71
371
  abstract render(response: any, view: string, options: any): any;
372
+ /**
373
+ * Issues a redirect (`@Redirect()`).
374
+ *
375
+ * @see {@link HttpServer.redirect}
376
+ */
72
377
  abstract redirect(response: any, statusCode: number, url: string): any;
378
+ /**
379
+ * Installs the global exception layer.
380
+ *
381
+ * @see {@link HttpServer.setErrorHandler}
382
+ */
73
383
  abstract setErrorHandler(handler: Function, prefix?: string): any;
384
+ /**
385
+ * Installs the catch-all handler for unmatched requests.
386
+ *
387
+ * @see {@link HttpServer.setNotFoundHandler}
388
+ */
74
389
  abstract setNotFoundHandler(handler: Function, prefix?: string): any;
390
+ /**
391
+ * Reports whether response headers have already been flushed. Must return
392
+ * synchronously.
393
+ *
394
+ * @see {@link HttpServer.isHeadersSent}
395
+ */
75
396
  abstract isHeadersSent(response: any): any;
397
+ /**
398
+ * Reads a response header that was set earlier. Not used by the core
399
+ * router; part of the base class so ecosystem packages can read headers
400
+ * through the adapter regardless of the platform.
401
+ */
76
402
  abstract getHeader(response: any, name: string): any;
403
+ /**
404
+ * Sets (replaces) a response header (`@Header()`).
405
+ *
406
+ * @see {@link HttpServer.setHeader}
407
+ */
77
408
  abstract setHeader(response: any, name: string, value: string): any;
409
+ /**
410
+ * Appends a value to a response header, turning it into a multi-value
411
+ * header (e.g. several `Set-Cookie` entries) instead of replacing it. Not
412
+ * used by the core router; part of the base class so ecosystem packages
413
+ * can append headers through the adapter regardless of the platform.
414
+ */
78
415
  abstract appendHeader(response: any, name: string, value: string): any;
416
+ /**
417
+ * Registers the default JSON and URL-encoded body parsers, exposing
418
+ * `req.rawBody` when `rawBody` is `true`.
419
+ *
420
+ * @see {@link HttpServer.registerParserMiddleware}
421
+ */
79
422
  abstract registerParserMiddleware(prefix?: string, rawBody?: boolean): any;
423
+ /**
424
+ * Enables CORS. The core currently calls this with `options` only; the
425
+ * `prefix` parameter is reserved.
426
+ *
427
+ * @see {@link HttpServer.enableCors}
428
+ */
80
429
  abstract enableCors(options?: any, prefix?: string): any;
430
+ /**
431
+ * Returns the function used to mount Nest middleware for one HTTP method.
432
+ * May be asynchronous.
433
+ *
434
+ * @see {@link HttpServer.createMiddlewareFactory}
435
+ */
81
436
  abstract createMiddlewareFactory(requestMethod: RequestMethod): ((path: string, callback: Function) => any) | Promise<(path: string, callback: Function) => any>;
437
+ /**
438
+ * Returns the platform identifier (`'express'`, `'fastify'`) that
439
+ * ecosystem packages branch on.
440
+ *
441
+ * @see {@link HttpServer.getType}
442
+ */
82
443
  abstract getType(): string;
444
+ /**
445
+ * Guards a route handler by request version for header, media-type and
446
+ * custom versioning.
447
+ *
448
+ * @see {@link HttpServer.applyVersionFilter}
449
+ */
83
450
  abstract applyVersionFilter(handler: Function, version: VersionValue, versioningOptions: VersioningOptions): (req: TRequest, res: TResponse, next: () => void) => Function;
84
451
  }
@@ -1,14 +1,74 @@
1
1
  /**
2
+ * Base class for HTTP platform adapters (see `ExpressAdapter` and
3
+ * `FastifyAdapter` for reference implementations).
4
+ *
5
+ * It implements the {@link HttpServer} contract that the Nest core relies on,
6
+ * and that interface is where each method's calling conventions are
7
+ * documented: when the core invokes it, with which arguments, and what it
8
+ * expects back. This class only adds:
9
+ *
10
+ * - default implementations that delegate to the wrapped framework
11
+ * `instance` (`use()`, the HTTP-verb methods, `listen()`) or are inert
12
+ * (`init()`, `normalizePath()`, `mapException()`, `beforeClose()`, the
13
+ * `setOn*Hook()` setters);
14
+ * - storage for the native server (`httpServer`) and the framework instance
15
+ * (`instance`), with their accessors;
16
+ * - the introspection hooks used by instrumentation tooling.
17
+ *
18
+ * Every remaining {@link HttpServer} member is declared abstract here, even
19
+ * the ones the interface marks optional, so subclasses cannot forget them.
20
+ * `setBaseViewsDir()`, `useBodyParser()` and `isRouteOrderSensitive()` are
21
+ * not declared on this class; implement them when the platform supports
22
+ * them (see {@link HttpServer} for what the core does when they are absent).
23
+ *
24
+ * Keep in mind that the core also reads and writes properties of the request
25
+ * object (`body`, `params`, `query`, `headers`, ...), that Server-Sent Events
26
+ * write directly to the Node.js response, and that `app.listen()` and the
27
+ * WebSocket adapters use the value returned by
28
+ * {@link AbstractHttpAdapter.getHttpServer} as a Node.js `net.Server`; see the
29
+ * {@link HttpServer} documentation for details.
30
+ *
31
+ * @typeParam TServer - Type of the native HTTP server stored in `httpServer`
32
+ * (e.g. `http.Server | https.Server`).
33
+ * @typeParam TRequest - Type of the framework request object.
34
+ * @typeParam TResponse - Type of the framework response object.
35
+ *
36
+ * @see [HTTP adapter](https://docs.nestjs.com/faq/http-adapter)
37
+ *
2
38
  * @publicApi
3
39
  */
4
40
  export class AbstractHttpAdapter {
5
41
  instance;
42
+ /**
43
+ * Native HTTP server created by {@link AbstractHttpAdapter.initHttpServer}
44
+ * and returned by {@link AbstractHttpAdapter.getHttpServer}.
45
+ */
6
46
  httpServer;
47
+ /**
48
+ * Callback registered through
49
+ * {@link AbstractHttpAdapter.setOnRouteTriggered}, if any.
50
+ */
7
51
  onRouteTriggered;
52
+ /**
53
+ * @param instance The framework application instance to delegate to (e.g.
54
+ * an Express `Application`). Subclasses typically create a default one
55
+ * when none is given.
56
+ */
8
57
  constructor(instance) {
9
58
  this.instance = instance;
10
59
  }
60
+ /**
61
+ * Asynchronous setup hook, awaited by `NestFactory.create()` and again by
62
+ * `app.init()`, so overrides must be idempotent. No-op by default.
63
+ *
64
+ * @see {@link HttpServer.init}
65
+ */
11
66
  async init() { }
67
+ /**
68
+ * Registers a global middleware by delegating to `instance.use(...args)`.
69
+ *
70
+ * @see {@link HttpServer.use}
71
+ */
12
72
  use(...args) {
13
73
  return this.instance.use(...args);
14
74
  }
@@ -66,30 +126,97 @@ export class AbstractHttpAdapter {
66
126
  listen(port, hostname, callback) {
67
127
  return this.instance.listen(port, hostname, callback);
68
128
  }
129
+ /**
130
+ * Returns the native HTTP server stored by
131
+ * {@link AbstractHttpAdapter.initHttpServer} (or
132
+ * {@link AbstractHttpAdapter.setHttpServer}).
133
+ *
134
+ * @see {@link HttpServer.getHttpServer}
135
+ */
69
136
  getHttpServer() {
70
137
  return this.httpServer;
71
138
  }
139
+ /**
140
+ * Replaces the native HTTP server returned by
141
+ * {@link AbstractHttpAdapter.getHttpServer}. Mostly useful for tests and
142
+ * for adapters that obtain the server from elsewhere.
143
+ */
72
144
  setHttpServer(httpServer) {
73
145
  this.httpServer = httpServer;
74
146
  }
147
+ /**
148
+ * Replaces the framework application instance that the default method
149
+ * implementations delegate to.
150
+ */
75
151
  setInstance(instance) {
76
152
  this.instance = instance;
77
153
  }
154
+ /**
155
+ * Returns the framework application instance passed to the constructor
156
+ * (or set through {@link AbstractHttpAdapter.setInstance}).
157
+ *
158
+ * @see {@link HttpServer.getInstance}
159
+ */
78
160
  getInstance() {
79
161
  return this.instance;
80
162
  }
163
+ /**
164
+ * Converts and validates a route path before registration. Returns the
165
+ * path unchanged by default; override to translate Nest's path syntax to
166
+ * the router's and to throw on invalid paths.
167
+ *
168
+ * @see {@link HttpServer.normalizePath}
169
+ */
81
170
  normalizePath(path) {
82
171
  return path;
83
172
  }
173
+ /**
174
+ * Registers a callback that the router invokes right before each route
175
+ * handler runs, with the resolved `RequestMethod` and the route path as
176
+ * declared (before {@link AbstractHttpAdapter.normalizePath}). The router
177
+ * wraps every handler at registration time, so the callback must be set
178
+ * before `app.init()` to take effect. Intended for instrumentation and
179
+ * devtools; there is no need to override it.
180
+ */
84
181
  setOnRouteTriggered(onRouteTriggered) {
85
182
  this.onRouteTriggered = onRouteTriggered;
86
183
  }
184
+ /**
185
+ * Returns the callback registered through
186
+ * {@link AbstractHttpAdapter.setOnRouteTriggered}, if any. Read by the
187
+ * router when registering routes.
188
+ */
87
189
  getOnRouteTriggered() {
88
190
  return this.onRouteTriggered;
89
191
  }
192
+ /**
193
+ * Registers a hook to run at the start of every request. No-op by default;
194
+ * the built-in adapters accept a `(req, res, done) => void | Promise<void>`
195
+ * function and call `done()` to continue processing. Intended for
196
+ * instrumentation and devtools.
197
+ */
90
198
  setOnRequestHook(onRequestHook) { }
199
+ /**
200
+ * Registers a hook to run once a response has been sent. No-op by default;
201
+ * the built-in adapters accept a `(req, res) => void | Promise<void>`
202
+ * function. Intended for instrumentation and devtools.
203
+ */
91
204
  setOnResponseHook(onResponseHook) { }
205
+ /**
206
+ * Called by `app.close()` before the shutdown hooks run. No-op by default;
207
+ * override to enter a "shutting down" state.
208
+ *
209
+ * @see {@link HttpServer.beforeClose}
210
+ */
92
211
  beforeClose() { }
212
+ /**
213
+ * Translates a framework-native error into something the exception filters
214
+ * understand. Invoked by the global exception layer with every error it
215
+ * receives, before it reaches the filters; the returned value is what the
216
+ * filters see. Returns the error unchanged by default. The built-in
217
+ * adapters map, for instance, body-parser `SyntaxError`s to
218
+ * `BadRequestException`.
219
+ */
93
220
  mapException(error) {
94
221
  return error;
95
222
  }
@@ -44,6 +44,15 @@ export class DiscoverableMetaHostCollection {
44
44
  static insertByMetaKey(metaKey, instanceWrapper, collection) {
45
45
  if (collection.has(metaKey)) {
46
46
  const wrappers = collection.get(metaKey);
47
+ // provider wrappers are re-created between registration and instantiation
48
+ // (the prototype phase replaces the map entry with a copy), so entries
49
+ // are keyed by the wrapper instance id to keep one entry per provider
50
+ for (const existing of wrappers) {
51
+ if (existing.id === instanceWrapper.id) {
52
+ wrappers.delete(existing);
53
+ break;
54
+ }
55
+ }
47
56
  wrappers.add(instanceWrapper);
48
57
  }
49
58
  else {
@@ -87,7 +96,7 @@ export class DiscoverableMetaHostCollection {
87
96
  // of `wrapper.metatype` to resolve processor's class properly.
88
97
  // But since calling `wrapper.instance` could degrade overall performance
89
98
  // we must defer it as much we can.
90
- instanceWrapper.metatype || instanceWrapper.inject
99
+ !instanceWrapper.metatype || instanceWrapper.inject
91
100
  ? (instanceWrapper.instance?.constructor ?? instanceWrapper.metatype)
92
101
  : instanceWrapper.metatype);
93
102
  }
@@ -22,6 +22,9 @@ export class BaseExceptionFilter {
22
22
  : {
23
23
  statusCode: exception.getStatus(),
24
24
  message: res,
25
+ ...(exception.errorCode !== undefined && {
26
+ errorCode: exception.errorCode,
27
+ }),
25
28
  };
26
29
  const response = host.getArgByIndex(1);
27
30
  if (!applicationRef.isHeadersSent(response)) {
@@ -75,9 +78,14 @@ export class BaseExceptionFilter {
75
78
  err instanceof Error) {
76
79
  return true;
77
80
  }
78
- // Plain "http error"-shaped values (e.g. objects thrown by third-party
79
- // middleware) that carry a status code and a message
80
- return !!(err.statusCode && err.message);
81
+ // Plain "http error"-shaped values (e.g. non-Error objects thrown by third-party
82
+ // middleware) that carry a valid HTTP status code and a message
83
+ return (!(err instanceof Error) &&
84
+ Number.isInteger(err.statusCode) &&
85
+ err.statusCode >= 400 &&
86
+ err.statusCode < 600 &&
87
+ typeof err.message === 'string' &&
88
+ err.message !== '');
81
89
  }
82
90
  }
83
91
  __decorate([
@@ -0,0 +1 @@
1
+ export declare function isDebugMode(): boolean;
@@ -0,0 +1,3 @@
1
+ export function isDebugMode() {
2
+ return !!process.env.NEST_DEBUG;
3
+ }
@@ -116,7 +116,6 @@ export declare class Injector {
116
116
  private printResolvingDependenciesLog;
117
117
  private printLookingForProviderLog;
118
118
  private printFoundInModuleLog;
119
- private isDebugMode;
120
119
  private getContextId;
121
120
  private getNowTimestamp;
122
121
  }
@@ -9,6 +9,7 @@ import { UnknownDependenciesException } from '../errors/exceptions/unknown-depen
9
9
  import { Barrier } from '../helpers/barrier.js';
10
10
  import { makeSafeInstanceDecorator } from '../helpers/safe-instance-decorator.js';
11
11
  import { STATIC_CONTEXT } from './constants.js';
12
+ import { isDebugMode } from './helpers/is-debug-mode.util.js';
12
13
  import { INQUIRER } from './inquirer/index.js';
13
14
  import { InstanceWrapper, } from './instance-wrapper.js';
14
15
  import { SettlementSignal } from './settlement-signal.js';
@@ -195,11 +196,24 @@ export class Injector {
195
196
  * We are duplicating it here because that one is not supposed to be exported.
196
197
  */
197
198
  function isOptionalFactoryDependency(value) {
198
- return (!isUndefined(value.token) &&
199
- !isUndefined(value.optional) &&
199
+ const token = value?.token;
200
+ if (isNil(token)) {
201
+ return false;
202
+ }
203
+ return (!isUndefined(value.optional) &&
200
204
  !value.prototype);
201
205
  }
202
206
  const mapFactoryProviderInjectArray = (item, index) => {
207
+ const isWrappedDependency = !isNil(item) && typeof item === 'object' && 'token' in item;
208
+ const token = isWrappedDependency
209
+ ? item.token
210
+ : item;
211
+ if (isNil(token)) {
212
+ throw new UndefinedDependencyException(wrapper.name, {
213
+ index,
214
+ dependencies: (wrapper.inject ?? undefined),
215
+ });
216
+ }
203
217
  if (typeof item !== 'object') {
204
218
  return item;
205
219
  }
@@ -270,7 +284,7 @@ export class Injector {
270
284
  */
271
285
  if (instanceHost.donePromise) {
272
286
  void instanceHost.donePromise
273
- .then(() => this.loadProvider(instanceWrapper, moduleRef, resolutionContext))
287
+ .then(() => this.loadProvider(instanceWrapper, instanceWrapper.host ?? moduleRef, resolutionContext))
274
288
  .catch(err => {
275
289
  instanceWrapper.settlementSignal?.error(err);
276
290
  });
@@ -610,7 +624,7 @@ export class Injector {
610
624
  return isFunction(token) ? token.name : token.toString();
611
625
  }
612
626
  printResolvingDependenciesLog(token, inquirer) {
613
- if (!this.isDebugMode()) {
627
+ if (!isDebugMode()) {
614
628
  return;
615
629
  }
616
630
  const tokenName = this.getTokenName(token);
@@ -620,7 +634,7 @@ export class Injector {
620
634
  this.logger.log(messageToPrint);
621
635
  }
622
636
  printLookingForProviderLog(token, moduleRef) {
623
- if (!this.isDebugMode()) {
637
+ if (!isDebugMode()) {
624
638
  return;
625
639
  }
626
640
  const tokenName = this.getTokenName(token);
@@ -628,16 +642,13 @@ export class Injector {
628
642
  this.logger.log(`Looking for ${clc.cyanBright(tokenName)}${clc.green(' in ')}${clc.magentaBright(moduleRefName)}`);
629
643
  }
630
644
  printFoundInModuleLog(token, moduleRef) {
631
- if (!this.isDebugMode()) {
645
+ if (!isDebugMode()) {
632
646
  return;
633
647
  }
634
648
  const tokenName = this.getTokenName(token);
635
649
  const moduleRefName = moduleRef?.metatype?.name ?? 'unknown';
636
650
  this.logger.log(`Found ${clc.cyanBright(tokenName)}${clc.green(' in ')}${clc.magentaBright(moduleRefName)}`);
637
651
  }
638
- isDebugMode() {
639
- return !!process.env.NEST_DEBUG;
640
- }
641
652
  getContextId(contextId, instanceWrapper) {
642
653
  return contextId.getParent
643
654
  ? contextId.getParent({
@@ -1,4 +1,5 @@
1
1
  import { Logger } from '@nestjs/common';
2
+ import { DiscoverableMetaHostCollection } from '../discovery/discoverable-meta-host-collection.js';
2
3
  import { MODULE_INIT_MESSAGE } from '../helpers/messages.js';
3
4
  import { InternalCoreModule } from './internal-core-module/internal-core-module.js';
4
5
  export class InstanceLoader {
@@ -56,6 +57,11 @@ export class InstanceLoader {
56
57
  await Promise.all(wrappers.map(async (item) => {
57
58
  await this.injector.loadProvider(item, moduleRef);
58
59
  this.graphInspector.inspectInstanceWrapper(item, moduleRef);
60
+ if (!item.isAlias) {
61
+ // an alias wrapper is factory-shaped, so inspecting it would
62
+ // discover the aliased provider twice
63
+ DiscoverableMetaHostCollection.inspectProvider(this.container.getModules(), item);
64
+ }
59
65
  }));
60
66
  }
61
67
  createPrototypesOfControllers(moduleRef) {
@@ -99,6 +99,5 @@ export declare class InstanceWrapper<T = any> {
99
99
  private initialize;
100
100
  private printIntrospectedAsRequestScoped;
101
101
  private printIntrospectedAsDurable;
102
- private isDebugMode;
103
102
  private generateUuid;
104
103
  }
@@ -3,6 +3,7 @@ import { clc, isNil, isString, isUndefined, randomStringGenerator, } from '@nest
3
3
  import { iterate } from 'iterare';
4
4
  import { UuidFactory } from '../inspector/uuid-factory.js';
5
5
  import { STATIC_CONTEXT } from './constants.js';
6
+ import { isDebugMode } from './helpers/is-debug-mode.util.js';
6
7
  import { isClassProvider, isFactoryProvider, isValueProvider, } from './helpers/provider-classifier.js';
7
8
  export const INSTANCE_METADATA_SYMBOL = Symbol.for('instance_metadata:cache');
8
9
  export const INSTANCE_ID_SYMBOL = Symbol.for('instance_metadata:id');
@@ -368,7 +369,7 @@ export class InstanceWrapper {
368
369
  this.scope === Scope.TRANSIENT && (this.transientMap = new Map());
369
370
  }
370
371
  printIntrospectedAsRequestScoped() {
371
- if (!this.isDebugMode() || this.name === 'REQUEST') {
372
+ if (!isDebugMode() || this.name === 'REQUEST') {
372
373
  return;
373
374
  }
374
375
  if (isString(this.name)) {
@@ -376,16 +377,13 @@ export class InstanceWrapper {
376
377
  }
377
378
  }
378
379
  printIntrospectedAsDurable() {
379
- if (!this.isDebugMode()) {
380
+ if (!isDebugMode()) {
380
381
  return;
381
382
  }
382
383
  if (isString(this.name)) {
383
384
  InstanceWrapper.logger.log(`${clc.cyanBright(this.name)}${clc.green(' introspected as ')}${clc.magentaBright('durable')}`);
384
385
  }
385
386
  }
386
- isDebugMode() {
387
- return !!process.env.NEST_DEBUG;
388
- }
389
387
  generateUuid() {
390
388
  let key = this.name?.toString() ?? this.token?.toString();
391
389
  key += this.host?.name ?? '';
@@ -14,25 +14,33 @@ export class LazyModuleLoader {
14
14
  this.moduleOverrides = moduleOverrides;
15
15
  }
16
16
  async load(loaderFn, loadOpts) {
17
- this.registerLoggerConfiguration(loadOpts);
18
- const moduleClassOrDynamicDefinition = await loaderFn();
19
- const moduleInstances = await this.dependenciesScanner.scanForModules({
20
- moduleDefinition: moduleClassOrDynamicDefinition,
21
- overrides: this.moduleOverrides,
22
- lazy: true,
23
- });
24
- if (moduleInstances.length === 0) {
25
- // The module has been loaded already. In this case, we must
26
- // retrieve a module reference from the existing container.
27
- const { token } = await this.moduleCompiler.compile(moduleClassOrDynamicDefinition);
28
- const moduleInstance = this.modulesContainer.get(token);
29
- return moduleInstance && this.getTargetModuleRef(moduleInstance);
17
+ const originalLogger = this.instanceLoader.logger;
18
+ try {
19
+ this.registerLoggerConfiguration(loadOpts);
20
+ const moduleClassOrDynamicDefinition = await loaderFn();
21
+ const moduleInstances = await this.dependenciesScanner.scanForModules({
22
+ moduleDefinition: moduleClassOrDynamicDefinition,
23
+ overrides: this.moduleOverrides,
24
+ lazy: true,
25
+ });
26
+ if (moduleInstances.length === 0) {
27
+ // The module has been loaded already. In this case, we must
28
+ // retrieve a module reference from the existing container.
29
+ const { token } = await this.moduleCompiler.compile(moduleClassOrDynamicDefinition);
30
+ const moduleInstance = this.modulesContainer.get(token);
31
+ return moduleInstance && this.getTargetModuleRef(moduleInstance);
32
+ }
33
+ const lazyModulesContainer = this.createLazyModulesContainer(moduleInstances);
34
+ await this.dependenciesScanner.scanModulesForDependencies(lazyModulesContainer);
35
+ await this.instanceLoader.createInstancesOfDependencies(lazyModulesContainer);
36
+ const [targetModule] = moduleInstances;
37
+ return this.getTargetModuleRef(targetModule);
38
+ }
39
+ finally {
40
+ if (loadOpts?.logger === false) {
41
+ this.instanceLoader.setLogger(originalLogger);
42
+ }
30
43
  }
31
- const lazyModulesContainer = this.createLazyModulesContainer(moduleInstances);
32
- await this.dependenciesScanner.scanModulesForDependencies(lazyModulesContainer);
33
- await this.instanceLoader.createInstancesOfDependencies(lazyModulesContainer);
34
- const [targetModule] = moduleInstances;
35
- return this.getTargetModuleRef(targetModule);
36
44
  }
37
45
  registerLoggerConfiguration(loadOpts) {
38
46
  if (loadOpts?.logger === false) {
@@ -208,17 +208,17 @@ export class Module {
208
208
  return provider.provide;
209
209
  }
210
210
  isCustomClass(provider) {
211
- return !isUndefined(provider.useClass);
211
+ return !isNil(provider.useClass);
212
212
  }
213
213
  isCustomValue(provider) {
214
214
  return (isObject(provider) &&
215
- Object.prototype.hasOwnProperty.call(provider, 'useValue'));
215
+ Object.hasOwn(provider, 'useValue'));
216
216
  }
217
217
  isCustomFactory(provider) {
218
- return !isUndefined(provider.useFactory);
218
+ return !isNil(provider.useFactory);
219
219
  }
220
220
  isCustomUseExisting(provider) {
221
- return !isUndefined(provider.useExisting);
221
+ return !isNil(provider.useExisting);
222
222
  }
223
223
  isDynamicModule(exported) {
224
224
  return exported && exported.module;
@@ -19,9 +19,9 @@ export declare class NestApplicationContext<TOptions extends NestApplicationCont
19
19
  protected injector: Injector;
20
20
  protected readonly logger: Logger;
21
21
  private shouldFlushLogsOnOverride;
22
- private readonly activeShutdownSignals;
22
+ private readonly shutdownCleanupRefs;
23
23
  private readonly moduleCompiler;
24
- private shutdownCleanupRef?;
24
+ private shutdownPromise?;
25
25
  private _instanceLinksHost;
26
26
  private _moduleRefsForHooksByDistance?;
27
27
  private initializationPromise?;
@@ -103,6 +103,17 @@ export declare class NestApplicationContext<TOptions extends NestApplicationCont
103
103
  * @returns {Promise<void>}
104
104
  */
105
105
  close(signal?: string): Promise<void>;
106
+ /**
107
+ * Runs the shutdown sequence, at most once per cycle. Callers that arrive
108
+ * while a shutdown is already in flight - a process signal delivered during
109
+ * an explicit `close()`, or the other way round - await the very same
110
+ * promise instead of starting a second, concurrent teardown.
111
+ *
112
+ * @param {string} [signal] The system signal that triggered the shutdown
113
+ * @returns {Promise<void>}
114
+ */
115
+ protected shutdown(signal?: string): Promise<void>;
116
+ private runShutdownSequence;
106
117
  /**
107
118
  * Sets custom logger service.
108
119
  * Flushes buffered logs if auto flush is on.
@@ -123,6 +134,9 @@ export declare class NestApplicationContext<TOptions extends NestApplicationCont
123
134
  * `onApplicationShutdown` function of a provider if the
124
135
  * process receives a shutdown signal.
125
136
  *
137
+ * Repeated calls are idempotent per signal. Shutdown hooks can be
138
+ * re-enabled after the application context has been closed.
139
+ *
126
140
  * @param {ShutdownSignal[]} [signals=[]] The system signals it should listen to
127
141
  * @param {ShutdownHooksOptions} [options={}] Options for configuring shutdown hooks behavior
128
142
  *
@@ -1,5 +1,4 @@
1
1
  import { Logger, ShutdownSignal, } from '@nestjs/common';
2
- import { iterate } from 'iterare';
3
2
  import { MESSAGES } from './constants.js';
4
3
  import { UnknownModuleException } from './errors/exceptions/index.js';
5
4
  import { createContextId } from './helpers/context-id-factory.js';
@@ -22,9 +21,9 @@ export class NestApplicationContext extends AbstractInstanceResolver {
22
21
  timestamp: true,
23
22
  });
24
23
  shouldFlushLogsOnOverride = false;
25
- activeShutdownSignals = new Array();
24
+ shutdownCleanupRefs = new Map();
26
25
  moduleCompiler;
27
- shutdownCleanupRef;
26
+ shutdownPromise;
28
27
  _instanceLinksHost;
29
28
  _moduleRefsForHooksByDistance;
30
29
  initializationPromise;
@@ -121,6 +120,26 @@ export class NestApplicationContext extends AbstractInstanceResolver {
121
120
  * @returns {Promise<void>}
122
121
  */
123
122
  async close(signal) {
123
+ await this.shutdown(signal);
124
+ }
125
+ /**
126
+ * Runs the shutdown sequence, at most once per cycle. Callers that arrive
127
+ * while a shutdown is already in flight - a process signal delivered during
128
+ * an explicit `close()`, or the other way round - await the very same
129
+ * promise instead of starting a second, concurrent teardown.
130
+ *
131
+ * @param {string} [signal] The system signal that triggered the shutdown
132
+ * @returns {Promise<void>}
133
+ */
134
+ shutdown(signal) {
135
+ this.shutdownPromise ??= this.runShutdownSequence(signal).finally(() => {
136
+ // Let the context be shut down again once this cycle has settled,
137
+ // successfully or not.
138
+ this.shutdownPromise = undefined;
139
+ });
140
+ return this.shutdownPromise;
141
+ }
142
+ async runShutdownSequence(signal) {
124
143
  await this.initializationPromise;
125
144
  await this.prepareClose();
126
145
  await this.callDestroyHook();
@@ -158,6 +177,9 @@ export class NestApplicationContext extends AbstractInstanceResolver {
158
177
  * `onApplicationShutdown` function of a provider if the
159
178
  * process receives a shutdown signal.
160
179
  *
180
+ * Repeated calls are idempotent per signal. Shutdown hooks can be
181
+ * re-enabled after the application context has been closed.
182
+ *
161
183
  * @param {ShutdownSignal[]} [signals=[]] The system signals it should listen to
162
184
  * @param {ShutdownHooksOptions} [options={}] Options for configuring shutdown hooks behavior
163
185
  *
@@ -167,16 +189,7 @@ export class NestApplicationContext extends AbstractInstanceResolver {
167
189
  if (!signals || isEmptyArray(signals)) {
168
190
  signals = Object.values(ShutdownSignal);
169
191
  }
170
- else {
171
- // given signals array should be unique because
172
- // process shouldn't listen to the same signal more than once.
173
- signals = Array.from(new Set(signals));
174
- }
175
- signals = iterate(signals)
176
- .map((signal) => signal.toString().toUpperCase().trim())
177
- // filter out the signals which is already listening to
178
- .filter(signal => !this.activeShutdownSignals.includes(signal))
179
- .toArray();
192
+ signals = Array.from(new Set(signals.map((signal) => signal.toString().toUpperCase().trim()))).filter(signal => !this.shutdownCleanupRefs.has(signal));
180
193
  this.listenToShutdownSignals(signals, options);
181
194
  return this;
182
195
  }
@@ -198,22 +211,14 @@ export class NestApplicationContext extends AbstractInstanceResolver {
198
211
  * @param {ShutdownHooksOptions} options Options for configuring shutdown hooks behavior
199
212
  */
200
213
  listenToShutdownSignals(signals, options = {}) {
201
- let receivedSignal = false;
202
214
  const cleanup = async (signal) => {
203
215
  try {
204
- if (receivedSignal) {
205
- // If we receive another signal while we're waiting
206
- // for the server to stop, just ignore it.
216
+ if (this.shutdownPromise) {
217
+ // If a shutdown is already under way - because of another signal or
218
+ // an explicit `close()` call - just ignore this one.
207
219
  return;
208
220
  }
209
- receivedSignal = true;
210
- await this.initializationPromise;
211
- await this.prepareClose();
212
- await this.callDestroyHook();
213
- await this.callBeforeShutdownHook(signal);
214
- await this.dispose();
215
- await this.callShutdownHook(signal);
216
- signals.forEach(sig => process.removeListener(sig, cleanup));
221
+ await this.shutdown(signal);
217
222
  if (options.useProcessExit) {
218
223
  // Use process.exit() to ensure the 'exit' event is properly triggered.
219
224
  // This is required for async loggers (like Pino with transports)
@@ -229,9 +234,8 @@ export class NestApplicationContext extends AbstractInstanceResolver {
229
234
  process.exit(1);
230
235
  }
231
236
  };
232
- this.shutdownCleanupRef = cleanup;
233
237
  signals.forEach((signal) => {
234
- this.activeShutdownSignals.push(signal);
238
+ this.shutdownCleanupRefs.set(signal, cleanup);
235
239
  process.on(signal, cleanup);
236
240
  });
237
241
  }
@@ -239,12 +243,10 @@ export class NestApplicationContext extends AbstractInstanceResolver {
239
243
  * Unsubscribes from shutdown signals (process events)
240
244
  */
241
245
  unsubscribeFromProcessSignals() {
242
- if (!this.shutdownCleanupRef) {
243
- return;
244
- }
245
- this.activeShutdownSignals.forEach(signal => {
246
- process.removeListener(signal, this.shutdownCleanupRef);
246
+ this.shutdownCleanupRefs.forEach((cleanup, signal) => {
247
+ process.removeListener(signal, cleanup);
247
248
  });
249
+ this.shutdownCleanupRefs.clear();
248
250
  }
249
251
  /**
250
252
  * Calls the `onModuleInit` function on the registered
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nestjs/core",
3
- "version": "12.0.1",
3
+ "version": "12.0.2",
4
4
  "description": "Nest - modern, fast, powerful node.js web framework (@core)",
5
5
  "author": "Kamil Mysliwiec",
6
6
  "license": "MIT",
@@ -36,7 +36,7 @@
36
36
  "uid": "2.0.2"
37
37
  },
38
38
  "devDependencies": {
39
- "@nestjs/common": "12.0.1"
39
+ "@nestjs/common": "12.0.2"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@nestjs/common": "^12.0.0",
@@ -57,5 +57,5 @@
57
57
  "optional": true
58
58
  }
59
59
  },
60
- "gitHead": "4c751c503bc753095f4b4f052e106f95218cc33f"
60
+ "gitHead": "ee168a569d30e600d301ed38abb14d2171a6f315"
61
61
  }
@@ -33,6 +33,7 @@ export declare class SseStream extends Transform {
33
33
  private _destination;
34
34
  private _statusCode;
35
35
  private _additionalHeaders;
36
+ private readonly _isHttp2;
36
37
  constructor(req?: IncomingMessage);
37
38
  get headersCommitted(): boolean;
38
39
  pipe<T extends WritableHeaderStream>(destination: T, options?: {
@@ -41,9 +41,15 @@ export class SseStream extends Transform {
41
41
  _destination = null;
42
42
  _statusCode = 200;
43
43
  _additionalHeaders;
44
+ _isHttp2;
44
45
  constructor(req) {
45
46
  super({ objectMode: true });
46
- if (req && req.socket) {
47
+ this._isHttp2 = (req?.httpVersionMajor ?? 1) > 1;
48
+ // Under HTTP/2 these calls are not request-scoped: `setTimeout` is routed
49
+ // to the shared `Http2Session` and the rest to the shared TCP socket, so
50
+ // tuning one SSE request would disable the idle timeout for every stream
51
+ // on that connection. See https://nodejs.org/api/http2.html#requestsocket
52
+ if (req && req.socket && !this._isHttp2) {
47
53
  req.socket.setKeepAlive(true);
48
54
  req.socket.setNoDelay(true);
49
55
  req.socket.setTimeout(0);
@@ -79,7 +85,9 @@ export class SseStream extends Transform {
79
85
  ...additionalHeaders,
80
86
  // See https://github.com/dunglas/mercure/blob/main/subscribe.go#L347-L362
81
87
  'Content-Type': 'text/event-stream',
82
- Connection: 'keep-alive',
88
+ // Hop-by-hop header, forbidden in HTTP/2
89
+ // https://www.rfc-editor.org/rfc/rfc9113#section-8.2.2
90
+ ...(!this._isHttp2 && { Connection: 'keep-alive' }),
83
91
  // Disable cache, even for old browsers and proxies
84
92
  'Cache-Control': 'private, no-cache, no-store, must-revalidate, max-age=0, no-transform',
85
93
  Pragma: 'no-cache',