@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.
- package/adapters/fastify-adapter.d.ts +50 -7
- package/adapters/fastify-adapter.js +149 -15
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/interfaces/external/fastify-multipart-options.interface.d.ts +53 -0
- package/interfaces/external/fastify-multipart-options.interface.js +1 -0
- package/interfaces/external/index.d.ts +1 -0
- package/interfaces/external/index.js +1 -0
- package/multipart/files.constants.d.ts +1 -0
- package/multipart/files.constants.js +1 -0
- package/multipart/index.d.ts +4 -0
- package/multipart/index.js +4 -0
- package/multipart/interceptors/any-files.interceptor.d.ts +12 -0
- package/multipart/interceptors/any-files.interceptor.js +13 -0
- package/multipart/interceptors/file-fields.interceptor.d.ts +13 -0
- package/multipart/interceptors/file-fields.interceptor.js +14 -0
- package/multipart/interceptors/file-stream.interceptor.d.ts +31 -0
- package/multipart/interceptors/file-stream.interceptor.js +32 -0
- package/multipart/interceptors/file.interceptor.d.ts +17 -0
- package/multipart/interceptors/file.interceptor.js +18 -0
- package/multipart/interceptors/files.interceptor.d.ts +14 -0
- package/multipart/interceptors/files.interceptor.js +15 -0
- package/multipart/interceptors/index.d.ts +6 -0
- package/multipart/interceptors/index.js +6 -0
- package/multipart/interceptors/no-files.interceptor.d.ts +12 -0
- package/multipart/interceptors/no-files.interceptor.js +13 -0
- package/multipart/interfaces/files-upload-module.interface.d.ts +18 -0
- package/multipart/interfaces/files-upload-module.interface.js +1 -0
- package/multipart/interfaces/index.d.ts +3 -0
- package/multipart/interfaces/index.js +3 -0
- package/multipart/interfaces/multipart-file.interface.d.ts +70 -0
- package/multipart/interfaces/multipart-file.interface.js +1 -0
- package/multipart/interfaces/multipart-options.interface.d.ts +77 -0
- package/multipart/interfaces/multipart-options.interface.js +1 -0
- package/multipart/multipart/append-field.util.d.ts +6 -0
- package/multipart/multipart/append-field.util.js +112 -0
- package/multipart/multipart/multipart-interceptor.factory.d.ts +13 -0
- package/multipart/multipart/multipart-interceptor.factory.js +130 -0
- package/multipart/multipart/multipart.constants.d.ts +55 -0
- package/multipart/multipart/multipart.constants.js +74 -0
- package/multipart/multipart/multipart.error.d.ts +10 -0
- package/multipart/multipart/multipart.error.js +15 -0
- package/multipart/multipart/multipart.parser.d.ts +39 -0
- package/multipart/multipart/multipart.parser.js +369 -0
- package/multipart/multipart/multipart.utils.d.ts +28 -0
- package/multipart/multipart/multipart.utils.js +54 -0
- package/multipart/multipart.constants.d.ts +1 -0
- package/multipart/multipart.constants.js +1 -0
- package/multipart/multipart.module.d.ts +18 -0
- package/multipart/multipart.module.js +75 -0
- package/multipart/storage/disk.storage.d.ts +24 -0
- package/multipart/storage/disk.storage.js +58 -0
- package/multipart/storage/index.d.ts +2 -0
- package/multipart/storage/index.js +2 -0
- package/multipart/storage/memory.storage.d.ts +8 -0
- package/multipart/storage/memory.storage.js +23 -0
- 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<
|
|
95
|
-
initHttpServer(): void;
|
|
96
|
-
useStaticAssets(options: FastifyStaticOptions):
|
|
97
|
-
setViewEngine(options: FastifyViewOptions | string):
|
|
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
|
-
|
|
258
|
-
|
|
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
|
-
|
|
379
|
+
this.closeOpenConnections();
|
|
303
380
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
316
|
-
|
|
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
|
-
|
|
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(
|
|
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
package/index.js
CHANGED
|
@@ -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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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,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
|
+
}
|