1
- import type { Multipart } from "@fastify/multipart" ;
2
1
import {
3
2
BadRequestException ,
4
3
ExecutionContext ,
5
- InternalServerErrorException ,
6
4
createParamDecorator ,
7
5
} from "@nestjs/common" ;
8
6
import type { HttpArgumentsHost } from "@nestjs/common/interfaces" ;
9
7
import type express from "express" ;
10
- import type { FastifyReply , FastifyRequest } from "fastify" ;
11
- import multer from "multer" ;
8
+ import type ExpressMulter from "multer" ;
12
9
13
10
import type { IRequestFormDataProps } from "../options/IRequestFormDataProps" ;
14
11
import { Singleton } from "../utils/Singleton" ;
@@ -35,13 +32,13 @@ import { validate_request_form_data } from "./internal/validate_request_form_dat
35
32
* 3. Only `boolean`, `bigint`, `number`, `string`, `Blob`, `File` or their array types are allowed
36
33
* 4. By the way, union type never be not allowed
37
34
*
38
- * By the way, if you're using `fastify`, you have to setup `@ fastify/multipart `
35
+ * By the way, if you're using `fastify`, you have to setup `fastify-multer `
39
36
* and configure like below when composing the NestJS application. If you don't do
40
37
* that, `@TypedFormData.Body()` will not work properly, and throw 500 internal
41
38
* server error when `Blob` or `File` type being utilized.
42
39
*
43
40
* ```typescript
44
- * import multipart from "fastify-multipart ";
41
+ * import fastifyMulter from "fastify-multer ";
45
42
* import { NestFactory } from "@nestjs/core";
46
43
* import {
47
44
* FastifyAdapter,
@@ -53,7 +50,7 @@ import { validate_request_form_data } from "./internal/validate_request_form_dat
53
50
* AppModule,
54
51
* new FastifyAdapter(),
55
52
* );
56
- * app.register(multipart );
53
+ * app.register(fastifyMulter.contentParser );
57
54
* await app.listen(3000);
58
55
* }
59
56
* ```
@@ -69,20 +66,29 @@ export namespace TypedFormData {
69
66
*
70
67
* Much easier and type safer than `@nest.UploadFile()` decorator.
71
68
*
72
- * @param props Automatically filled by transformer
69
+ * @param factory Factory function ncreating the `multer` or `fastify-multer`
70
+ * instance. In the factory function, you also can specify the
71
+ * multer composition options like `storage` engine.
72
+ */
73
+ export function Body < Multer extends IMulterBase > (
74
+ factory : ( ) => Multer | Promise < Multer > ,
75
+ ) : ParameterDecorator ;
76
+
77
+ /**
78
+ * @internal
73
79
*/
74
80
export function Body < T extends object > (
75
- props ?: IRequestFormDataProps < T > ,
81
+ factory : ( ) => Promise < IMulterBase > ,
82
+ props ?: IRequestFormDataProps < T > | undefined ,
76
83
) : ParameterDecorator {
77
84
if ( typeof File === "undefined" )
78
85
throw new Error (
79
86
"Error on TypedFormData.Body(): 'File' class is not supported in the older version of NodeJS. Upgrade the NodeJS to the modern." ,
80
87
) ;
81
88
const checker = validate_request_form_data ( props ) ;
82
- const predicator = ( type : "express" | "fastify" ) =>
83
- new Singleton ( ( ) =>
84
- type === "express" ? decodeExpress ( props ! ) : decodeFastify ( props ! ) ,
85
- ) ;
89
+ const uploader = new Singleton ( async ( ) =>
90
+ decode ( ( await factory ( ) ) as ExpressMulter . Multer , props ! ) ,
91
+ ) ;
86
92
return createParamDecorator ( async function TypedFormDataBody (
87
93
_unknown : any ,
88
94
context : ExecutionContext ,
@@ -93,11 +99,9 @@ export namespace TypedFormData {
93
99
throw new BadRequestException (
94
100
`Request body type is not "multipart/form-data".` ,
95
101
) ;
96
-
97
- const decoder = isExpressRequest ( request )
98
- ? predicator ( "express" ) . get ( )
99
- : predicator ( "fastify" ) . get ( ) ;
100
- const data : FormData = await decoder ( {
102
+ const data : FormData = await (
103
+ await uploader . get ( )
104
+ ) ( {
101
105
request : request as any ,
102
106
response : http . getResponse ( ) ,
103
107
} ) ;
@@ -106,13 +110,27 @@ export namespace TypedFormData {
106
110
return output ;
107
111
} ) ( ) ;
108
112
}
113
+
114
+ /**
115
+ * Base type of the `multer` or `fastify-multer`.
116
+ */
117
+ export interface IMulterBase {
118
+ single ( fieldName : string ) : any ;
119
+ array ( fieldName : string , maxCount ?: number ) : any ;
120
+ fields ( fields : readonly object [ ] ) : any ;
121
+ any ( ) : any ;
122
+ none ( ) : any ;
123
+ }
109
124
}
110
125
111
126
/**
112
127
* @internal
113
128
*/
114
- const decodeExpress = < T > ( props : IRequestFormDataProps < T > ) => {
115
- const upload = multerApplication . get ( ) . fields (
129
+ const decode = < T > (
130
+ multer : ExpressMulter . Multer ,
131
+ props : IRequestFormDataProps < T > ,
132
+ ) => {
133
+ const upload = multer . fields (
116
134
props ! . files . map ( ( file ) => ( {
117
135
name : file . name ,
118
136
...( file . limit === 1 ? { maxCount : 1 } : { } ) ,
@@ -141,40 +159,6 @@ const decodeExpress = <T>(props: IRequestFormDataProps<T>) => {
141
159
} ;
142
160
} ;
143
161
144
- /**
145
- * @internal
146
- */
147
- const decodeFastify =
148
- < T > ( _props : IRequestFormDataProps < T > ) =>
149
- async ( socket : {
150
- request : FastifyRequest & {
151
- parts ?( ) : AsyncIterableIterator < Multipart > ;
152
- } ;
153
- response : FastifyReply ;
154
- } ) : Promise < FormData > => {
155
- if (
156
- socket . request . files === undefined ||
157
- typeof socket . request . files !== "function"
158
- )
159
- throw new InternalServerErrorException (
160
- "Have not configured the `fastify-multipart` plugin yet. Inquiry to the backend developer." ,
161
- ) ;
162
- const data : FormData = new FormData ( ) ;
163
- for await ( const part of socket . request . parts ( ) )
164
- if ( part . type === "file" )
165
- data . append (
166
- part . fieldname ,
167
- new File ( [ await part . toBuffer ( ) ] , part . filename , {
168
- type : part . mimetype ,
169
- } ) ,
170
- ) ;
171
- else if ( Array . isArray ( part . value ) )
172
- for ( const elem of part . value )
173
- data . append ( part . fieldname , String ( elem ) ) ;
174
- else data . append ( part . fieldname , String ( part . value ) ) ;
175
- return data ;
176
- } ;
177
-
178
162
/**
179
163
* @internal
180
164
*/
@@ -209,15 +193,3 @@ const isMultipartFormData = (text?: string): boolean =>
209
193
. split ( ";" )
210
194
. map ( ( str ) => str . trim ( ) )
211
195
. some ( ( str ) => str === "multipart/form-data" ) ;
212
-
213
- /**
214
- * @internal
215
- */
216
- const isExpressRequest = (
217
- request : express . Request | FastifyRequest ,
218
- ) : request is express . Request => ( request as express . Request ) . app !== undefined ;
219
-
220
- /**
221
- * @internal
222
- */
223
- const multerApplication = new Singleton ( ( ) => multer ( ) ) ;
0 commit comments