Skip to content

Commit f33a5c0

Browse files
authored
Merge pull request #1116 from samchon/feat/multer
Complement #941: finalize `TypedFormData()` to support `fastify-multer`.
2 parents 53a5b14 + cc9cfad commit f33a5c0

File tree

106 files changed

+376
-331
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+376
-331
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"private": true,
33
"name": "@nestia/station",
4-
"version": "4.0.0-dev.20241027",
4+
"version": "4.0.0-dev.20241027-4",
55
"description": "Nestia station",
66
"scripts": {
77
"build": "node build/index.js",

packages/benchmark/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nestia/benchmark",
3-
"version": "0.2.3",
3+
"version": "0.3.0-dev.20241127-2",
44
"description": "NestJS Performance Benchmark Program",
55
"main": "lib/index.js",
66
"typings": "lib/index.d.ts",
@@ -22,7 +22,7 @@
2222
"author": "Jeongho Nam",
2323
"license": "MIT",
2424
"devDependencies": {
25-
"@nestia/core": "^3.8.1",
25+
"@nestia/core": "^4.0.0-dev.20241027-4",
2626
"@nestia/e2e": "^0.7.0",
2727
"@nestia/sdk": "^3.8.1",
2828
"@nestjs/common": "^10.3.10",
@@ -38,7 +38,7 @@
3838
"uuid": "^10.0.0"
3939
},
4040
"dependencies": {
41-
"@nestia/fetcher": "^3.8.1",
41+
"@nestia/fetcher": "^4.0.0-dev.20241027-4",
4242
"tgrid": "^1.0.3",
4343
"tstl": "^3.0.0"
4444
},

packages/benchmark/test/api/Primitive.ts

-1
This file was deleted.

packages/core/package.json

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nestia/core",
3-
"version": "4.0.0-dev.20241027",
3+
"version": "4.0.0-dev.20241027-4",
44
"description": "Super-fast validation decorators of NestJS",
55
"main": "lib/index.js",
66
"typings": "lib/index.d.ts",
@@ -36,46 +36,44 @@
3636
},
3737
"homepage": "https://nestia.io",
3838
"dependencies": {
39-
"@nestia/fetcher": "../fetcher/nestia-fetcher-4.0.0-dev.20241027.tgz",
39+
"@nestia/fetcher": "^4.0.0-dev.20241027-4",
4040
"@nestjs/common": ">=7.0.1",
4141
"@nestjs/core": ">=7.0.1",
4242
"@samchon/openapi": "^2.0.0-dev.20241127-2",
4343
"detect-ts-node": "^1.0.5",
4444
"get-function-location": "^2.0.0",
4545
"glob": "^7.2.0",
46-
"multer": "1.4.5-lts.1",
4746
"path-parser": "^6.1.0",
4847
"raw-body": "^2.0.0",
4948
"reflect-metadata": ">=0.1.12",
5049
"rxjs": ">=6.0.3",
5150
"tgrid": "^1.0.0",
52-
"typia": "^7.0.0-dev.20241027-2",
51+
"typia": ">=7.0.0-dev.20241027-2 <8.0.0",
5352
"ws": "^7.5.3"
5453
},
5554
"peerDependencies": {
56-
"@nestia/fetcher": ">=4.0.0-dev.20241027",
55+
"@nestia/fetcher": ">=4.0.0-dev.20241027-4",
5756
"@nestjs/common": ">=7.0.1",
5857
"@nestjs/core": ">=7.0.1",
5958
"reflect-metadata": ">=0.1.12",
6059
"rxjs": ">=6.0.3",
6160
"typia": ">=7.0.0-dev.20241027-2 <8.0.0"
6261
},
6362
"devDependencies": {
64-
"@fastify/multipart": "^8.1.0",
6563
"@nestjs/common": "^10.3.3",
6664
"@nestjs/core": "^10.3.3",
6765
"@types/express": "^4.17.15",
6866
"@types/glob": "^7.2.0",
6967
"@types/inquirer": "^9.0.3",
70-
"@types/multer": "^1.4.11",
68+
"@types/multer": "^1.4.12",
7169
"@types/ts-expose-internals": "npm:[email protected]",
7270
"@types/ws": "^8.5.10",
7371
"@typescript-eslint/eslint-plugin": "^5.46.1",
7472
"@typescript-eslint/parser": "^5.46.1",
7573
"commander": "^10.0.0",
7674
"comment-json": "^4.2.3",
7775
"eslint-plugin-deprecation": "^1.4.1",
78-
"fastify": "^4.25.2",
76+
"fastify": "^4.28.1",
7977
"git-last-commit": "^1.0.1",
8078
"inquirer": "^8.2.5",
8179
"rimraf": "^3.0.2",

packages/core/src/decorators/TypedFormData.ts

+38-66
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
import type { Multipart } from "@fastify/multipart";
21
import {
32
BadRequestException,
43
ExecutionContext,
5-
InternalServerErrorException,
64
createParamDecorator,
75
} from "@nestjs/common";
86
import type { HttpArgumentsHost } from "@nestjs/common/interfaces";
97
import type express from "express";
10-
import type { FastifyReply, FastifyRequest } from "fastify";
11-
import multer from "multer";
8+
import type ExpressMulter from "multer";
129

1310
import type { IRequestFormDataProps } from "../options/IRequestFormDataProps";
1411
import { Singleton } from "../utils/Singleton";
@@ -35,13 +32,13 @@ import { validate_request_form_data } from "./internal/validate_request_form_dat
3532
* 3. Only `boolean`, `bigint`, `number`, `string`, `Blob`, `File` or their array types are allowed
3633
* 4. By the way, union type never be not allowed
3734
*
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`
3936
* and configure like below when composing the NestJS application. If you don't do
4037
* that, `@TypedFormData.Body()` will not work properly, and throw 500 internal
4138
* server error when `Blob` or `File` type being utilized.
4239
*
4340
* ```typescript
44-
* import multipart from "fastify-multipart";
41+
* import fastifyMulter from "fastify-multer";
4542
* import { NestFactory } from "@nestjs/core";
4643
* import {
4744
* FastifyAdapter,
@@ -53,7 +50,7 @@ import { validate_request_form_data } from "./internal/validate_request_form_dat
5350
* AppModule,
5451
* new FastifyAdapter(),
5552
* );
56-
* app.register(multipart);
53+
* app.register(fastifyMulter.contentParser);
5754
* await app.listen(3000);
5855
* }
5956
* ```
@@ -69,20 +66,29 @@ export namespace TypedFormData {
6966
*
7067
* Much easier and type safer than `@nest.UploadFile()` decorator.
7168
*
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
7379
*/
7480
export function Body<T extends object>(
75-
props?: IRequestFormDataProps<T>,
81+
factory: () => Promise<IMulterBase>,
82+
props?: IRequestFormDataProps<T> | undefined,
7683
): ParameterDecorator {
7784
if (typeof File === "undefined")
7885
throw new Error(
7986
"Error on TypedFormData.Body(): 'File' class is not supported in the older version of NodeJS. Upgrade the NodeJS to the modern.",
8087
);
8188
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+
);
8692
return createParamDecorator(async function TypedFormDataBody(
8793
_unknown: any,
8894
context: ExecutionContext,
@@ -93,11 +99,9 @@ export namespace TypedFormData {
9399
throw new BadRequestException(
94100
`Request body type is not "multipart/form-data".`,
95101
);
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+
)({
101105
request: request as any,
102106
response: http.getResponse(),
103107
});
@@ -106,13 +110,27 @@ export namespace TypedFormData {
106110
return output;
107111
})();
108112
}
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+
}
109124
}
110125

111126
/**
112127
* @internal
113128
*/
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(
116134
props!.files.map((file) => ({
117135
name: file.name,
118136
...(file.limit === 1 ? { maxCount: 1 } : {}),
@@ -141,40 +159,6 @@ const decodeExpress = <T>(props: IRequestFormDataProps<T>) => {
141159
};
142160
};
143161

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-
178162
/**
179163
* @internal
180164
*/
@@ -209,15 +193,3 @@ const isMultipartFormData = (text?: string): boolean =>
209193
.split(";")
210194
.map((str) => str.trim())
211195
.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());

packages/core/src/transformers/ParameterDecoratorTransformer.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,14 @@ const FUNCTORS: Record<string, Programmer> = {
100100
? props.arguments
101101
: [TypedQueryBodyProgrammer.generate(props)],
102102
"TypedFormData.Body": (props) =>
103-
props.arguments.length
104-
? props.arguments
105-
: [TypedFormDataBodyProgrammer.generate(props)],
103+
props.arguments.length === 0
104+
? [
105+
ts.factory.createIdentifier("undefined"),
106+
TypedFormDataBodyProgrammer.generate(props),
107+
]
108+
: props.arguments.length === 1
109+
? [props.arguments[0], TypedFormDataBodyProgrammer.generate(props)]
110+
: props.arguments,
106111
PlainBody: (props) =>
107112
props.arguments.length
108113
? props.arguments

packages/fetcher/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nestia/fetcher",
3-
"version": "4.0.0-dev.20241027",
3+
"version": "4.0.0-dev.20241027-4",
44
"description": "Fetcher library of Nestia SDK",
55
"main": "lib/index.js",
66
"typings": "lib/index.d.ts",

packages/sdk/package.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nestia/sdk",
3-
"version": "4.0.0-dev.20241027",
3+
"version": "4.0.0-dev.20241027-4",
44
"description": "Nestia SDK and Swagger generator",
55
"main": "lib/index.js",
66
"typings": "lib/index.d.ts",
@@ -32,8 +32,8 @@
3232
},
3333
"homepage": "https://nestia.io",
3434
"dependencies": {
35-
"@nestia/core": "../core/nestia-core-4.0.0-dev.20241027.tgz",
36-
"@nestia/fetcher": "../fetcher/nestia-fetcher-4.0.0-dev.20241027.tgz",
35+
"@nestia/core": "^4.0.0-dev.20241027-4",
36+
"@nestia/fetcher": "^4.0.0-dev.20241027-4",
3737
"@samchon/openapi": "^2.0.0-dev.20241127-2",
3838
"cli": "^1.0.1",
3939
"get-function-location": "^2.0.0",
@@ -47,8 +47,8 @@
4747
"typia": "^7.0.0-dev.20241027-2"
4848
},
4949
"peerDependencies": {
50-
"@nestia/core": ">=4.0.0-dev.20241027",
51-
"@nestia/fetcher": ">=4.0.0-dev.20241027",
50+
"@nestia/core": ">=4.0.0-dev.20241027-4",
51+
"@nestia/fetcher": ">=4.0.0-dev.20241027-4",
5252
"@nestjs/common": ">=7.0.1",
5353
"@nestjs/core": ">=7.0.1",
5454
"reflect-metadata": ">=0.1.12",

test/features/all/swagger.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
}
88
],
99
"info": {
10-
"version": "4.0.0-dev.20241027",
10+
"version": "4.0.0-dev.20241027-2",
1111
"title": "@samchon/nestia-test",
1212
"description": "Test program of Nestia",
1313
"license": {

test/features/app-globalPrefix-versionUri-routerModule/swagger.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
}
88
],
99
"info": {
10-
"version": "4.0.0-dev.20241027",
10+
"version": "4.0.0-dev.20241027-2",
1111
"title": "@samchon/nestia-test",
1212
"description": "Test program of Nestia",
1313
"license": {

test/features/app-globalPrefix-versionUri/swagger.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
}
88
],
99
"info": {
10-
"version": "4.0.0-dev.20241027",
10+
"version": "4.0.0-dev.20241027-2",
1111
"title": "@samchon/nestia-test",
1212
"description": "Test program of Nestia",
1313
"license": {

test/features/app-globalPrefix/swagger.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
}
88
],
99
"info": {
10-
"version": "4.0.0-dev.20241027",
10+
"version": "4.0.0-dev.20241027-2",
1111
"title": "@samchon/nestia-test",
1212
"description": "Test program of Nestia",
1313
"license": {

test/features/app-routerModule/swagger.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
}
88
],
99
"info": {
10-
"version": "4.0.0-dev.20241027",
10+
"version": "4.0.0-dev.20241027-2",
1111
"title": "@samchon/nestia-test",
1212
"description": "Test program of Nestia",
1313
"license": {

test/features/app-versionHeader/swagger.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
}
88
],
99
"info": {
10-
"version": "4.0.0-dev.20241027",
10+
"version": "4.0.0-dev.20241027-2",
1111
"title": "@samchon/nestia-test",
1212
"description": "Test program of Nestia",
1313
"license": {

test/features/app-versionUri/swagger.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
}
88
],
99
"info": {
10-
"version": "4.0.0-dev.20241027",
10+
"version": "4.0.0-dev.20241027-2",
1111
"title": "@samchon/nestia-test",
1212
"description": "Test program of Nestia",
1313
"license": {

0 commit comments

Comments
 (0)