@nestjs/platform-fastify 12.0.4 → 12.1.1

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 (57) hide show
  1. package/adapters/fastify-adapter.d.ts +50 -7
  2. package/adapters/fastify-adapter.js +149 -15
  3. package/index.d.ts +1 -0
  4. package/index.js +1 -0
  5. package/interfaces/external/fastify-multipart-options.interface.d.ts +53 -0
  6. package/interfaces/external/fastify-multipart-options.interface.js +1 -0
  7. package/interfaces/external/index.d.ts +1 -0
  8. package/interfaces/external/index.js +1 -0
  9. package/multipart/files.constants.d.ts +1 -0
  10. package/multipart/files.constants.js +1 -0
  11. package/multipart/index.d.ts +4 -0
  12. package/multipart/index.js +4 -0
  13. package/multipart/interceptors/any-files.interceptor.d.ts +12 -0
  14. package/multipart/interceptors/any-files.interceptor.js +13 -0
  15. package/multipart/interceptors/file-fields.interceptor.d.ts +13 -0
  16. package/multipart/interceptors/file-fields.interceptor.js +14 -0
  17. package/multipart/interceptors/file-stream.interceptor.d.ts +31 -0
  18. package/multipart/interceptors/file-stream.interceptor.js +32 -0
  19. package/multipart/interceptors/file.interceptor.d.ts +17 -0
  20. package/multipart/interceptors/file.interceptor.js +18 -0
  21. package/multipart/interceptors/files.interceptor.d.ts +14 -0
  22. package/multipart/interceptors/files.interceptor.js +15 -0
  23. package/multipart/interceptors/index.d.ts +6 -0
  24. package/multipart/interceptors/index.js +6 -0
  25. package/multipart/interceptors/no-files.interceptor.d.ts +12 -0
  26. package/multipart/interceptors/no-files.interceptor.js +13 -0
  27. package/multipart/interfaces/files-upload-module.interface.d.ts +18 -0
  28. package/multipart/interfaces/files-upload-module.interface.js +1 -0
  29. package/multipart/interfaces/index.d.ts +3 -0
  30. package/multipart/interfaces/index.js +3 -0
  31. package/multipart/interfaces/multipart-file.interface.d.ts +70 -0
  32. package/multipart/interfaces/multipart-file.interface.js +1 -0
  33. package/multipart/interfaces/multipart-options.interface.d.ts +77 -0
  34. package/multipart/interfaces/multipart-options.interface.js +1 -0
  35. package/multipart/multipart/append-field.util.d.ts +6 -0
  36. package/multipart/multipart/append-field.util.js +112 -0
  37. package/multipart/multipart/multipart-interceptor.factory.d.ts +13 -0
  38. package/multipart/multipart/multipart-interceptor.factory.js +130 -0
  39. package/multipart/multipart/multipart.constants.d.ts +55 -0
  40. package/multipart/multipart/multipart.constants.js +74 -0
  41. package/multipart/multipart/multipart.error.d.ts +10 -0
  42. package/multipart/multipart/multipart.error.js +15 -0
  43. package/multipart/multipart/multipart.parser.d.ts +39 -0
  44. package/multipart/multipart/multipart.parser.js +369 -0
  45. package/multipart/multipart/multipart.utils.d.ts +28 -0
  46. package/multipart/multipart/multipart.utils.js +54 -0
  47. package/multipart/multipart.constants.d.ts +1 -0
  48. package/multipart/multipart.constants.js +1 -0
  49. package/multipart/multipart.module.d.ts +18 -0
  50. package/multipart/multipart.module.js +75 -0
  51. package/multipart/storage/disk.storage.d.ts +24 -0
  52. package/multipart/storage/disk.storage.js +58 -0
  53. package/multipart/storage/index.d.ts +2 -0
  54. package/multipart/storage/index.js +2 -0
  55. package/multipart/storage/memory.storage.d.ts +8 -0
  56. package/multipart/storage/memory.storage.js +23 -0
  57. package/package.json +7 -2
@@ -1,16 +1,29 @@
1
1
  import { FastifyCorsOptions } from '@fastify/cors';
2
- import { Logger, type RequestMethod, type VersioningOptions } from '@nestjs/common';
2
+ import { Logger, type NestApplicationOptions, type RequestMethod, type VersioningOptions } from '@nestjs/common';
3
3
  import { FastifyBaseLogger, FastifyBodyParser, FastifyInstance, FastifyPluginAsync, FastifyPluginCallback, FastifyRegister, FastifyReply, FastifyRequest, FastifyServerOptions, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, RequestGenericInterface, RouteGenericInterface } from 'fastify';
4
4
  import * as http from 'http';
5
5
  import * as http2 from 'http2';
6
6
  import * as https from 'https';
7
7
  import { InjectOptions, Chain as LightMyRequestChain, Response as LightMyRequestResponse } from 'light-my-request';
8
- import { type VersionValue } from '@nestjs/common/internal';
8
+ import { type SecurityRequestHook, type VersionValue } from '@nestjs/common/internal';
9
9
  import { AbstractHttpAdapter } from '@nestjs/core';
10
- import { FastifyStaticOptions, FastifyViewOptions } from '../interfaces/external/index.js';
10
+ import { FastifyMultipartOptions, FastifyStaticOptions, FastifyViewOptions } from '../interfaces/external/index.js';
11
11
  import { NestFastifyBodyParserOptions } from '../interfaces/index.js';
12
12
  type FastifyAdapterBaseOptions<Server extends RawServerBase = RawServerDefault, Logger extends FastifyBaseLogger = FastifyBaseLogger> = FastifyServerOptions<Server, Logger> & {
13
13
  skipMiddie?: boolean;
14
+ /**
15
+ * Controls the "@fastify/multipart" plugin (an optional peer dependency),
16
+ * which the upload interceptors from "@nestjs/platform-fastify/multipart"
17
+ * require.
18
+ *
19
+ * - unset (default): the adapter registers the plugin when an upload
20
+ * interceptor is used, unless it is already registered.
21
+ * - an object: plugin options (e.g. `limits`); the plugin is registered
22
+ * when the application initializes.
23
+ * - `true`: the same, with the plugin defaults.
24
+ * - `false`: the adapter never registers the plugin.
25
+ */
26
+ multipart?: boolean | FastifyMultipartOptions;
14
27
  };
15
28
  type FastifyHttp2SecureOptions<Server extends http2.Http2SecureServer, Logger extends FastifyBaseLogger = FastifyBaseLogger> = FastifyAdapterBaseOptions<Server, Logger> & {
16
29
  http2: true;
@@ -46,10 +59,15 @@ export declare class FastifyAdapter<TServer extends RawServerBase = RawServerDef
46
59
  protected readonly logger: Logger;
47
60
  protected readonly instance: TInstance;
48
61
  protected _pathPrefix?: string;
62
+ private readonly openConnections;
63
+ private isClosing;
49
64
  private _isParserRegistered;
50
65
  private onRequestHook?;
51
66
  private onResponseHook?;
52
67
  private isMiddieRegistered;
68
+ private multipartMode;
69
+ private multipartOptions;
70
+ private isMultipartRequested;
53
71
  private pendingMiddlewares;
54
72
  private versioningOptions?;
55
73
  private readonly versionConstraint;
@@ -89,12 +107,23 @@ export declare class FastifyAdapter<TServer extends RawServerBase = RawServerDef
89
107
  getHttpServer<T = TServer>(): T;
90
108
  getInstance<T = TInstance>(): T;
91
109
  register<TRegister extends Parameters<FastifyRegister<FastifyInstance<TServer, TRawRequest, TRawResponse>>>>(plugin: TRegister['0'], opts?: TRegister['1']): any;
110
+ /**
111
+ * Registers the "@fastify/multipart" plugin, unless the adapter's
112
+ * `multipart` option is `false`, or the plugin has been registered
113
+ * already by the time it loads. Idempotent. Called by the upload
114
+ * interceptors from "@nestjs/platform-fastify/multipart".
115
+ *
116
+ * The plugin is loaded lazily, and a missing package fails the
117
+ * application's startup (`ready()`) with an error naming the package,
118
+ * rather than exiting the process.
119
+ */
120
+ useMultipart(): void;
92
121
  inject(): LightMyRequestChain;
93
122
  inject(opts: InjectOptions | string): Promise<LightMyRequestResponse>;
94
- close(): Promise<undefined>;
95
- initHttpServer(): void;
96
- useStaticAssets(options: FastifyStaticOptions): Promise<any>;
97
- setViewEngine(options: FastifyViewOptions | string): Promise<any>;
123
+ close(): Promise<void>;
124
+ initHttpServer(options?: NestApplicationOptions): void;
125
+ useStaticAssets(options: FastifyStaticOptions): any;
126
+ setViewEngine(options: FastifyViewOptions | string): any;
98
127
  isHeadersSent(response: TReply): boolean;
99
128
  getHeader(response: any, name: string): any;
100
129
  setHeader(response: TReply, name: string, value: string): FastifyReply<RouteGenericInterface, TServer, TRawRequest, TRawResponse, unknown, import("fastify").FastifySchema, import("fastify").FastifyTypeProviderDefault, unknown>;
@@ -104,6 +133,18 @@ export declare class FastifyAdapter<TServer extends RawServerBase = RawServerDef
104
133
  getRequestUrl(request: TRequest): string;
105
134
  getRequestUrl(request: TRawRequest): string;
106
135
  enableCors(options?: FastifyCorsOptions): void;
136
+ /**
137
+ * Runs the request hook of the built-in HTTP security features in an
138
+ * `onRequest` hook: before middie (Nest middleware), content-type parsing,
139
+ * guards and handlers, and also for unmatched routes. Headers set by the
140
+ * hook go to the Node.js response (`reply.raw`): Fastify merges them into
141
+ * every response it sends, errors and `404`s included, while
142
+ * `reply.header()` / `@Header()` values take precedence, and responses
143
+ * written to `reply.raw` directly (e.g. `@Sse()`) carry them too. A
144
+ * rejection goes to `done(error)`, i.e. to the Nest exception layer
145
+ * installed with `setErrorHandler()`.
146
+ */
147
+ registerSecurityHook(hook: SecurityRequestHook<TRequest>): void;
107
148
  registerParserMiddleware(prefix?: string, rawBody?: boolean): void;
108
149
  useBodyParser(type: string | string[] | RegExp, rawBody: boolean, options?: NestFastifyBodyParserOptions, parser?: FastifyBodyParser<Buffer, TServer>): void;
109
150
  createMiddlewareFactory(requestMethod: RequestMethod): Promise<(path: string, callback: Function) => any>;
@@ -144,5 +185,7 @@ export declare class FastifyAdapter<TServer extends RawServerBase = RawServerDef
144
185
  * and leaves any other request target untouched.
145
186
  */
146
187
  private getPathFromRequestTarget;
188
+ private trackOpenConnections;
189
+ private closeOpenConnections;
147
190
  }
148
191
  export {};
@@ -2,9 +2,10 @@ import { HttpException, HttpStatus, Logger, StreamableFile, VERSION_NEUTRAL, Ver
2
2
  import { fastify, } from 'fastify';
3
3
  import Reply from 'fastify/lib/reply.js';
4
4
  import fastifySymbols from 'fastify/lib/symbols.js';
5
+ import * as net from 'net';
5
6
  import { pathToRegexp } from 'path-to-regexp';
6
7
  import middie from '@fastify/middie';
7
- import { loadPackage, isNil, isString, isUndefined, } from '@nestjs/common/internal';
8
+ import { loadPackage, tryLoadPackage, isNil, isString, isUndefined, } from '@nestjs/common/internal';
8
9
  import { AbstractHttpAdapter } from '@nestjs/core';
9
10
  import { LegacyRouteConverter } from '@nestjs/core/internal';
10
11
  const { kRouteContext } = fastifySymbols;
@@ -13,16 +14,29 @@ import { parse as querystringParse } from 'fast-querystring';
13
14
  import urlSanitizer from 'find-my-way/lib/url-sanitizer.js';
14
15
  import { FASTIFY_ROUTE_CONFIG_METADATA, FASTIFY_ROUTE_CONSTRAINTS_METADATA, FASTIFY_ROUTE_SCHEMA_METADATA, } from '../constants.js';
15
16
  const { safeDecodeURI } = urlSanitizer;
17
+ const MISSING_MULTIPART_PACKAGE_MESSAGE = 'The "@fastify/multipart" package is missing. Please, make sure to install it (npm i @fastify/multipart) ' +
18
+ 'to use the file upload interceptors from "@nestjs/platform-fastify/multipart".';
19
+ function isFastifyMultipartPlugin(plugin) {
20
+ const fn = plugin?.default ?? plugin;
21
+ return (typeof fn === 'function' &&
22
+ (fn[Symbol.for('plugin-meta')]?.name === '@fastify/multipart' ||
23
+ fn.name === 'fastifyMultipart'));
24
+ }
16
25
  /**
17
26
  * @publicApi
18
27
  */
19
28
  export class FastifyAdapter extends AbstractHttpAdapter {
20
29
  logger = new Logger(FastifyAdapter.name);
21
30
  _pathPrefix;
31
+ openConnections = new Set();
32
+ isClosing = false;
22
33
  _isParserRegistered;
23
34
  onRequestHook;
24
35
  onResponseHook;
25
36
  isMiddieRegistered;
37
+ multipartMode = 'auto';
38
+ multipartOptions = {};
39
+ isMultipartRequested = false;
26
40
  pendingMiddlewares = [];
27
41
  versioningOptions;
28
42
  versionConstraint = {
@@ -110,6 +124,15 @@ export class FastifyAdapter extends AbstractHttpAdapter {
110
124
  if (instanceOrOptions?.skipMiddie) {
111
125
  this.isMiddieRegistered = true;
112
126
  }
127
+ const multipart = instanceOrOptions
128
+ ?.multipart;
129
+ if (multipart === false) {
130
+ this.multipartMode = 'off';
131
+ }
132
+ else if (multipart) {
133
+ this.multipartMode = 'eager';
134
+ this.multipartOptions = multipart === true ? {} : { ...multipart };
135
+ }
113
136
  this.instance.addHook('onRequest', (request, reply, done) => {
114
137
  if (this.onRequestHook) {
115
138
  this.onRequestHook(request, reply, done);
@@ -134,6 +157,9 @@ export class FastifyAdapter extends AbstractHttpAdapter {
134
157
  this.onResponseHook = hook;
135
158
  }
136
159
  async init() {
160
+ if (this.multipartMode === 'eager') {
161
+ this.useMultipart();
162
+ }
137
163
  if (this.isMiddieRegistered) {
138
164
  return;
139
165
  }
@@ -254,8 +280,9 @@ export class FastifyAdapter extends AbstractHttpAdapter {
254
280
  }
255
281
  body = body.getStream();
256
282
  }
257
- if (fastifyReply.getHeader('Content-Type') !== undefined &&
258
- fastifyReply.getHeader('Content-Type') !== 'application/json' &&
283
+ const responseContentType = fastifyReply.getHeader('Content-Type');
284
+ if (typeof responseContentType === 'string' &&
285
+ !responseContentType.startsWith('application/json') &&
259
286
  body?.statusCode >= HttpStatus.BAD_REQUEST) {
260
287
  Logger.warn("Content-Type doesn't match Reply body, you might need a custom ExceptionFilter for non-JSON responses", FastifyAdapter.name);
261
288
  fastifyReply.header('Content-Type', 'application/json');
@@ -292,35 +319,94 @@ export class FastifyAdapter extends AbstractHttpAdapter {
292
319
  return this.instance;
293
320
  }
294
321
  register(plugin, opts) {
322
+ if (this.multipartMode !== 'off' &&
323
+ isFastifyMultipartPlugin(plugin) &&
324
+ !this.instance.hasRequestDecorator('isMultipart')) {
325
+ // The adapter owns the "@fastify/multipart" registration, so that the
326
+ // plugin is registered once whether or not the user registers it too.
327
+ // The user's options apply over the adapter's.
328
+ this.multipartOptions = { ...this.multipartOptions, ...opts };
329
+ this.useMultipart();
330
+ return this.instance;
331
+ }
295
332
  return this.instance.register(plugin, opts);
296
333
  }
334
+ /**
335
+ * Registers the "@fastify/multipart" plugin, unless the adapter's
336
+ * `multipart` option is `false`, or the plugin has been registered
337
+ * already by the time it loads. Idempotent. Called by the upload
338
+ * interceptors from "@nestjs/platform-fastify/multipart".
339
+ *
340
+ * The plugin is loaded lazily, and a missing package fails the
341
+ * application's startup (`ready()`) with an error naming the package,
342
+ * rather than exiting the process.
343
+ */
344
+ useMultipart() {
345
+ if (this.multipartMode === 'off' || this.isMultipartRequested) {
346
+ return;
347
+ }
348
+ this.isMultipartRequested = true;
349
+ const registerMultipart = async (instance) => {
350
+ // Plugins load in registration order, so one the user registered
351
+ // before this point has loaded by now.
352
+ if (instance.hasRequestDecorator('isMultipart')) {
353
+ return;
354
+ }
355
+ const multipart = await tryLoadPackage('@fastify/multipart', () => import('@fastify/multipart'));
356
+ if (!multipart) {
357
+ throw new Error(MISSING_MULTIPART_PACKAGE_MESSAGE);
358
+ }
359
+ // Copied: the plugin writes its defaults into the options it receives.
360
+ const { limits, ...options } = this.multipartOptions;
361
+ await instance.register(multipart, {
362
+ ...options,
363
+ ...(limits && { limits: { ...limits } }),
364
+ });
365
+ };
366
+ // Like `fastify-plugin`: register on the root context, not a child one.
367
+ Object.assign(registerMultipart, {
368
+ [Symbol.for('skip-override')]: true,
369
+ [Symbol.for('fastify.display-name')]: 'nestjs-multipart',
370
+ });
371
+ this.instance.register(registerMultipart);
372
+ }
297
373
  inject(opts) {
298
374
  return this.instance.inject(opts);
299
375
  }
300
376
  async close() {
377
+ this.isClosing = true;
301
378
  try {
302
- return await this.instance.close();
379
+ this.closeOpenConnections();
303
380
  }
304
- catch (err) {
305
- // Check if server is still running
306
- if (err.code !== 'ERR_SERVER_NOT_RUNNING') {
307
- throw err;
308
- }
309
- return;
381
+ finally {
382
+ await this.instance.close().catch(err => {
383
+ // Check if server is still running
384
+ if (err.code !== 'ERR_SERVER_NOT_RUNNING') {
385
+ throw err;
386
+ }
387
+ });
310
388
  }
311
389
  }
312
- initHttpServer() {
390
+ initHttpServer(options = {}) {
313
391
  this.httpServer = this.instance.server;
392
+ if (options?.forceCloseConnections) {
393
+ this.trackOpenConnections();
394
+ }
314
395
  }
315
- async useStaticAssets(options) {
316
- return this.register(await loadPackage('@fastify/static', 'FastifyAdapter.useStaticAssets()', () => import('@fastify/static')), options);
396
+ // `register()` accepts a promise of a plugin, so the import is handed over
397
+ // unawaited on purpose. `NestApplication.useStaticAssets()` discards what
398
+ // this returns, so awaiting here would enqueue the plugin after the caller
399
+ // has already moved on to `listen()`.
400
+ useStaticAssets(options) {
401
+ return this.register(loadPackage('@fastify/static', 'FastifyAdapter.useStaticAssets()', () => import('@fastify/static')), options);
317
402
  }
318
- async setViewEngine(options) {
403
+ // Handed over unawaited for the same reason as `useStaticAssets()` above.
404
+ setViewEngine(options) {
319
405
  if (isString(options)) {
320
406
  new Logger('FastifyAdapter').error("setViewEngine() doesn't support a string argument.");
321
407
  process.exit(1);
322
408
  }
323
- return this.register(await loadPackage('@fastify/view', 'FastifyAdapter.setViewEngine()', () => import('@fastify/view')), options);
409
+ return this.register(loadPackage('@fastify/view', 'FastifyAdapter.setViewEngine()', () => import('@fastify/view')), options);
324
410
  }
325
411
  isHeadersSent(response) {
326
412
  return response.sent;
@@ -356,6 +442,22 @@ export class FastifyAdapter extends AbstractHttpAdapter {
356
442
  enableCors(options) {
357
443
  this.register(import('@fastify/cors'), options);
358
444
  }
445
+ /**
446
+ * Runs the request hook of the built-in HTTP security features in an
447
+ * `onRequest` hook: before middie (Nest middleware), content-type parsing,
448
+ * guards and handlers, and also for unmatched routes. Headers set by the
449
+ * hook go to the Node.js response (`reply.raw`): Fastify merges them into
450
+ * every response it sends, errors and `404`s included, while
451
+ * `reply.header()` / `@Header()` values take precedence, and responses
452
+ * written to `reply.raw` directly (e.g. `@Sse()`) carry them too. A
453
+ * rejection goes to `done(error)`, i.e. to the Nest exception layer
454
+ * installed with `setErrorHandler()`.
455
+ */
456
+ registerSecurityHook(hook) {
457
+ this.instance.addHook('onRequest', (request, reply, done) => {
458
+ done(hook(request, reply.raw));
459
+ });
460
+ }
359
461
  registerParserMiddleware(prefix, rawBody) {
360
462
  if (this._isParserRegistered) {
361
463
  return;
@@ -626,4 +728,36 @@ export class FastifyAdapter extends AbstractHttpAdapter {
626
728
  }
627
729
  return pathStart === -1 ? '/' : url.slice(pathStart);
628
730
  }
731
+ trackOpenConnections() {
732
+ const track = (socket) => {
733
+ if (this.isClosing) {
734
+ // Fastify runs its `preClose` hooks before it stops accepting
735
+ // connections, so destroy anything that arrives in the meantime
736
+ socket.destroy();
737
+ return;
738
+ }
739
+ if (this.openConnections.has(socket)) {
740
+ return;
741
+ }
742
+ this.openConnections.add(socket);
743
+ socket.on('close', () => this.openConnections.delete(socket));
744
+ };
745
+ this.httpServer.on('connection', track);
746
+ // Sockets accepted by the secondary servers Fastify binds for every
747
+ // address `listen()` resolves to are only reachable through requests.
748
+ // `inject()` requests carry a mock socket, which must not be tracked.
749
+ this.instance.addHook('onRequest', (request, _reply, done) => {
750
+ const socket = request.raw.socket;
751
+ if (socket instanceof net.Socket) {
752
+ track(socket);
753
+ }
754
+ done();
755
+ });
756
+ }
757
+ closeOpenConnections() {
758
+ for (const socket of this.openConnections) {
759
+ socket.destroy();
760
+ this.openConnections.delete(socket);
761
+ }
762
+ }
629
763
  }
package/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './adapters/index.js';
2
2
  export * from './interfaces/index.js';
3
3
  export * from './decorators/index.js';
4
+ export * from './multipart/index.js';
package/index.js CHANGED
@@ -7,3 +7,4 @@
7
7
  export * from './adapters/index.js';
8
8
  export * from './interfaces/index.js';
9
9
  export * from './decorators/index.js';
10
+ export * from './multipart/index.js';
@@ -0,0 +1,53 @@
1
+ /**
2
+ * "@fastify/multipart" plugin options, as accepted by the FastifyAdapter's
3
+ * `multipart` option. Declared locally so the adapter's typings do not
4
+ * require the (optional) plugin to be installed.
5
+ *
6
+ * `attachFieldsToBody` is deliberately left out: it consumes the body before
7
+ * the upload interceptors from "@nestjs/platform-fastify/multipart" run.
8
+ *
9
+ * @see https://github.com/fastify/fastify-multipart/blob/main/types/index.d.ts
10
+ * @publicApi
11
+ */
12
+ export interface FastifyMultipartOptions {
13
+ /**
14
+ * Limits applied to every multipart request. The upload interceptors merge
15
+ * their own `limits` over these.
16
+ */
17
+ limits?: {
18
+ /**
19
+ * Max field name size in bytes. Not enforced by the plugin for multipart
20
+ * bodies; the upload interceptors enforce their own `limits.fieldNameSize`.
21
+ */
22
+ fieldNameSize?: number;
23
+ /** Max field value size in bytes (Default: 1 MiB) */
24
+ fieldSize?: number;
25
+ /** Max number of non-file fields (Default: Infinity) */
26
+ fields?: number;
27
+ /** Max file size in bytes (Default: the Fastify `bodyLimit`, 1 MiB) */
28
+ fileSize?: number;
29
+ /** Max number of file fields (Default: Infinity) */
30
+ files?: number;
31
+ /** Max number of header key => value pairs (Default: 2000) */
32
+ headerPairs?: number;
33
+ /** Max number of parts, fields and files together (Default: 1000) */
34
+ parts?: number;
35
+ };
36
+ /**
37
+ * Throw when a file exceeds `limits.fileSize` while it is read through
38
+ * `req.file()` / `req.files()`. The upload interceptors always reject such
39
+ * a file, whatever this is set to.
40
+ */
41
+ throwFileSizeLimit?: boolean;
42
+ /** Keep the full path of files instead of just the base name (Default: false) */
43
+ preservePath?: boolean;
44
+ /** Default character set for part header values (Default: 'latin1') */
45
+ defParamCharset?: string;
46
+ /** Default character set when one isn't defined (Default: 'utf8') */
47
+ defCharset?: string;
48
+ /**
49
+ * Detect if a part is a file. By default a part is a file if its content
50
+ * type is `application/octet-stream` or it has a file name.
51
+ */
52
+ isPartAFile?: (fieldName: string | undefined, contentType: string | undefined, fileName: string | undefined) => boolean;
53
+ }
@@ -1,2 +1,3 @@
1
1
  export * from './fastify-static-options.interface.js';
2
2
  export * from './fastify-view-options.interface.js';
3
+ export * from './fastify-multipart-options.interface.js';
@@ -1,2 +1,3 @@
1
1
  export * from './fastify-static-options.interface.js';
2
2
  export * from './fastify-view-options.interface.js';
3
+ export * from './fastify-multipart-options.interface.js';
@@ -0,0 +1 @@
1
+ export declare const MULTIPART_MODULE_OPTIONS = "MULTIPART_MODULE_OPTIONS";
@@ -0,0 +1 @@
1
+ export const MULTIPART_MODULE_OPTIONS = 'MULTIPART_MODULE_OPTIONS';
@@ -0,0 +1,4 @@
1
+ export * from './interceptors/index.js';
2
+ export * from './interfaces/index.js';
3
+ export * from './multipart.module.js';
4
+ export * from './storage/index.js';
@@ -0,0 +1,4 @@
1
+ export * from './interceptors/index.js';
2
+ export * from './interfaces/index.js';
3
+ export * from './multipart.module.js';
4
+ export * from './storage/index.js';
@@ -0,0 +1,12 @@
1
+ import type { NestInterceptor, Type } from '@nestjs/common';
2
+ import type { MultipartOptions } from '../interfaces/index.js';
3
+ /**
4
+ * Accepts every file, in any field, and exposes them as an array in
5
+ * `req.files` (`@UploadedFiles()`). The Fastify counterpart of
6
+ * platform-express's `AnyFilesInterceptor`.
7
+ *
8
+ * @param localOptions options merged over the `MultipartModule` defaults
9
+ *
10
+ * @publicApi
11
+ */
12
+ export declare function AnyFilesInterceptor(localOptions?: MultipartOptions): Type<NestInterceptor>;
@@ -0,0 +1,13 @@
1
+ import { createMultipartInterceptor } from '../multipart/multipart-interceptor.factory.js';
2
+ /**
3
+ * Accepts every file, in any field, and exposes them as an array in
4
+ * `req.files` (`@UploadedFiles()`). The Fastify counterpart of
5
+ * platform-express's `AnyFilesInterceptor`.
6
+ *
7
+ * @param localOptions options merged over the `MultipartModule` defaults
8
+ *
9
+ * @publicApi
10
+ */
11
+ export function AnyFilesInterceptor(localOptions) {
12
+ return createMultipartInterceptor({ fields: 'ANY', strategy: 'ARRAY' }, localOptions);
13
+ }
@@ -0,0 +1,13 @@
1
+ import type { NestInterceptor, Type } from '@nestjs/common';
2
+ import type { MultipartField, MultipartOptions } from '../interfaces/index.js';
3
+ /**
4
+ * Accepts files in each of `uploadFields` and exposes them grouped by field
5
+ * name in `req.files` (`@UploadedFiles()`). The Fastify counterpart of
6
+ * platform-express's `FileFieldsInterceptor`.
7
+ *
8
+ * @param uploadFields accepted fields, each with an optional `maxCount`
9
+ * @param localOptions options merged over the `MultipartModule` defaults
10
+ *
11
+ * @publicApi
12
+ */
13
+ export declare function FileFieldsInterceptor(uploadFields: MultipartField[], localOptions?: MultipartOptions): Type<NestInterceptor>;
@@ -0,0 +1,14 @@
1
+ import { createMultipartInterceptor } from '../multipart/multipart-interceptor.factory.js';
2
+ /**
3
+ * Accepts files in each of `uploadFields` and exposes them grouped by field
4
+ * name in `req.files` (`@UploadedFiles()`). The Fastify counterpart of
5
+ * platform-express's `FileFieldsInterceptor`.
6
+ *
7
+ * @param uploadFields accepted fields, each with an optional `maxCount`
8
+ * @param localOptions options merged over the `MultipartModule` defaults
9
+ *
10
+ * @publicApi
11
+ */
12
+ export function FileFieldsInterceptor(uploadFields, localOptions) {
13
+ return createMultipartInterceptor({ fields: uploadFields, strategy: 'OBJECT' }, localOptions);
14
+ }
@@ -0,0 +1,31 @@
1
+ import type { NestInterceptor, Type } from '@nestjs/common';
2
+ import type { MultipartOptions } from '../interfaces/index.js';
3
+ /**
4
+ * Hands the route handler a single file in `fieldName` as a stream
5
+ * (`req.file.stream`, see `MultipartFileStream`), without buffering it or
6
+ * writing it to disk. Fastify only; there is no platform-express
7
+ * counterpart.
8
+ *
9
+ * - Text fields must precede the file in the form: parts that follow it are
10
+ * not parsed.
11
+ * - Whatever the handler leaves unread is discarded before the response is
12
+ * sent, so that the client can finish uploading. When the handler returns
13
+ * a `StreamableFile` (which may be the upload itself), it is discarded
14
+ * once the response has been sent instead. So a handler that responds
15
+ * with a `StreamableFile` of something other than the upload should read
16
+ * the upload first: otherwise, when the connection is not kept alive
17
+ * (`Connection: close`), Node.js closes it right after the response, and
18
+ * a client still uploading fails with EPIPE / ECONNRESET.
19
+ * - A file in any other field fails the request with a 400.
20
+ * - The file size is unknown up front, so `MaxFileSizeValidator` does not
21
+ * apply, and neither does `FileTypeValidator`'s magic-number check.
22
+ * - Once `limits.fileSize` is exceeded, the stream errors with a
23
+ * `PayloadTooLargeException`.
24
+ *
25
+ * @param fieldName name of the form field that carries the file
26
+ * @param localOptions options merged over the `MultipartModule` defaults
27
+ * (`dest` and `storage` do not apply)
28
+ *
29
+ * @publicApi
30
+ */
31
+ export declare function FileStreamInterceptor(fieldName: string, localOptions?: Omit<MultipartOptions, 'dest' | 'storage'>): Type<NestInterceptor>;
@@ -0,0 +1,32 @@
1
+ import { createMultipartStreamInterceptor } from '../multipart/multipart-interceptor.factory.js';
2
+ /**
3
+ * Hands the route handler a single file in `fieldName` as a stream
4
+ * (`req.file.stream`, see `MultipartFileStream`), without buffering it or
5
+ * writing it to disk. Fastify only; there is no platform-express
6
+ * counterpart.
7
+ *
8
+ * - Text fields must precede the file in the form: parts that follow it are
9
+ * not parsed.
10
+ * - Whatever the handler leaves unread is discarded before the response is
11
+ * sent, so that the client can finish uploading. When the handler returns
12
+ * a `StreamableFile` (which may be the upload itself), it is discarded
13
+ * once the response has been sent instead. So a handler that responds
14
+ * with a `StreamableFile` of something other than the upload should read
15
+ * the upload first: otherwise, when the connection is not kept alive
16
+ * (`Connection: close`), Node.js closes it right after the response, and
17
+ * a client still uploading fails with EPIPE / ECONNRESET.
18
+ * - A file in any other field fails the request with a 400.
19
+ * - The file size is unknown up front, so `MaxFileSizeValidator` does not
20
+ * apply, and neither does `FileTypeValidator`'s magic-number check.
21
+ * - Once `limits.fileSize` is exceeded, the stream errors with a
22
+ * `PayloadTooLargeException`.
23
+ *
24
+ * @param fieldName name of the form field that carries the file
25
+ * @param localOptions options merged over the `MultipartModule` defaults
26
+ * (`dest` and `storage` do not apply)
27
+ *
28
+ * @publicApi
29
+ */
30
+ export function FileStreamInterceptor(fieldName, localOptions) {
31
+ return createMultipartStreamInterceptor(fieldName, localOptions);
32
+ }
@@ -0,0 +1,17 @@
1
+ import type { NestInterceptor, Type } from '@nestjs/common';
2
+ import type { MultipartOptions } from '../interfaces/index.js';
3
+ /**
4
+ * Accepts a single file in `fieldName` and exposes it as `req.file`
5
+ * (`@UploadedFile()`), with the text fields in `req.body`. The Fastify
6
+ * counterpart of platform-express's `FileInterceptor`.
7
+ *
8
+ * Requires the `@fastify/multipart` package, which the FastifyAdapter
9
+ * registers as a plugin when an upload interceptor is used (see its
10
+ * `multipart` option).
11
+ *
12
+ * @param fieldName name of the form field that carries the file
13
+ * @param localOptions options merged over the `MultipartModule` defaults
14
+ *
15
+ * @publicApi
16
+ */
17
+ export declare function FileInterceptor(fieldName: string, localOptions?: MultipartOptions): Type<NestInterceptor>;
@@ -0,0 +1,18 @@
1
+ import { createMultipartInterceptor } from '../multipart/multipart-interceptor.factory.js';
2
+ /**
3
+ * Accepts a single file in `fieldName` and exposes it as `req.file`
4
+ * (`@UploadedFile()`), with the text fields in `req.body`. The Fastify
5
+ * counterpart of platform-express's `FileInterceptor`.
6
+ *
7
+ * Requires the `@fastify/multipart` package, which the FastifyAdapter
8
+ * registers as a plugin when an upload interceptor is used (see its
9
+ * `multipart` option).
10
+ *
11
+ * @param fieldName name of the form field that carries the file
12
+ * @param localOptions options merged over the `MultipartModule` defaults
13
+ *
14
+ * @publicApi
15
+ */
16
+ export function FileInterceptor(fieldName, localOptions) {
17
+ return createMultipartInterceptor({ fields: [{ name: fieldName, maxCount: 1 }], strategy: 'VALUE' }, localOptions);
18
+ }
@@ -0,0 +1,14 @@
1
+ import type { NestInterceptor, Type } from '@nestjs/common';
2
+ import type { MultipartOptions } from '../interfaces/index.js';
3
+ /**
4
+ * Accepts up to `maxCount` files in `fieldName` and exposes them as an array
5
+ * in `req.files` (`@UploadedFiles()`). The Fastify counterpart of
6
+ * platform-express's `FilesInterceptor`.
7
+ *
8
+ * @param fieldName name of the form field that carries the files
9
+ * @param maxCount maximum number of files to accept
10
+ * @param localOptions options merged over the `MultipartModule` defaults
11
+ *
12
+ * @publicApi
13
+ */
14
+ export declare function FilesInterceptor(fieldName: string, maxCount?: number, localOptions?: MultipartOptions): Type<NestInterceptor>;
@@ -0,0 +1,15 @@
1
+ import { createMultipartInterceptor } from '../multipart/multipart-interceptor.factory.js';
2
+ /**
3
+ * Accepts up to `maxCount` files in `fieldName` and exposes them as an array
4
+ * in `req.files` (`@UploadedFiles()`). The Fastify counterpart of
5
+ * platform-express's `FilesInterceptor`.
6
+ *
7
+ * @param fieldName name of the form field that carries the files
8
+ * @param maxCount maximum number of files to accept
9
+ * @param localOptions options merged over the `MultipartModule` defaults
10
+ *
11
+ * @publicApi
12
+ */
13
+ export function FilesInterceptor(fieldName, maxCount, localOptions) {
14
+ return createMultipartInterceptor({ fields: [{ name: fieldName, maxCount }], strategy: 'ARRAY' }, localOptions);
15
+ }
@@ -0,0 +1,6 @@
1
+ export * from './any-files.interceptor.js';
2
+ export * from './file-fields.interceptor.js';
3
+ export * from './file-stream.interceptor.js';
4
+ export * from './file.interceptor.js';
5
+ export * from './files.interceptor.js';
6
+ export * from './no-files.interceptor.js';