@tsed/platform-multer 8.7.0
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/.barrelsby.json +5 -0
- package/lib/esm/adapters/express/MulterModule.js +30 -0
- package/lib/esm/adapters/express/MulterModule.js.map +1 -0
- package/lib/esm/adapters/koa/MulterModule.js +57 -0
- package/lib/esm/adapters/koa/MulterModule.js.map +1 -0
- package/lib/esm/common/constants/constants.js +3 -0
- package/lib/esm/common/constants/constants.js.map +1 -0
- package/lib/esm/common/decorators/multerFileSize.js +32 -0
- package/lib/esm/common/decorators/multerFileSize.js.map +1 -0
- package/lib/esm/common/decorators/multerOptions.js +50 -0
- package/lib/esm/common/decorators/multerOptions.js.map +1 -0
- package/lib/esm/common/decorators/multipartFile.js +77 -0
- package/lib/esm/common/decorators/multipartFile.js.map +1 -0
- package/lib/esm/common/errors/MulterException.js +17 -0
- package/lib/esm/common/errors/MulterException.js.map +1 -0
- package/lib/esm/common/index.js +11 -0
- package/lib/esm/common/index.js.map +1 -0
- package/lib/esm/common/interfaces/PlatformMulterSettings.js +2 -0
- package/lib/esm/common/interfaces/PlatformMulterSettings.js.map +1 -0
- package/lib/esm/common/middlewares/PlatformMulterMiddleware.js +40 -0
- package/lib/esm/common/middlewares/PlatformMulterMiddleware.js.map +1 -0
- package/lib/esm/index.js +2 -0
- package/lib/esm/index.js.map +1 -0
- package/lib/types/adapters/express/MulterModule.d.ts +1 -0
- package/lib/types/adapters/koa/MulterModule.d.ts +1 -0
- package/lib/types/common/constants/constants.d.ts +7 -0
- package/lib/types/common/decorators/multerFileSize.d.ts +28 -0
- package/lib/types/common/decorators/multerOptions.d.ts +43 -0
- package/lib/types/common/decorators/multipartFile.d.ts +48 -0
- package/lib/types/common/errors/MulterException.d.ts +14 -0
- package/lib/types/common/index.d.ts +10 -0
- package/lib/types/common/interfaces/PlatformMulterSettings.d.ts +44 -0
- package/lib/types/common/middlewares/PlatformMulterMiddleware.d.ts +11 -0
- package/lib/types/index.d.ts +1 -0
- package/package.json +102 -0
- package/src/adapters/express/MulterModule.ts +40 -0
- package/src/adapters/koa/MulterModule.ts +73 -0
- package/src/common/constants/constants.ts +10 -0
- package/src/common/decorators/multerFileSize.spec.ts +22 -0
- package/src/common/decorators/multerFileSize.ts +32 -0
- package/src/common/decorators/multerOptions.spec.ts +21 -0
- package/src/common/decorators/multerOptions.ts +53 -0
- package/src/common/decorators/multipartFile.spec.ts +260 -0
- package/src/common/decorators/multipartFile.ts +91 -0
- package/src/common/errors/MulterException.ts +20 -0
- package/src/common/index.ts +10 -0
- package/src/common/interfaces/PlatformMulterSettings.ts +45 -0
- package/src/common/middlewares/PlatformMulterMiddleware.spec.ts +98 -0
- package/src/common/middlewares/PlatformMulterMiddleware.ts +46 -0
- package/src/index.ts +1 -0
- package/vitest.config.mts +21 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {DecoratorParameters, Metadata, StoreMerge, useDecorators, useMethodDecorators} from "@tsed/core";
|
|
2
|
+
import {UseBefore} from "@tsed/platform-middlewares";
|
|
3
|
+
import {ParamTypes, UseParam} from "@tsed/platform-params";
|
|
4
|
+
import {InFile} from "@tsed/schema";
|
|
5
|
+
import type {Field} from "multer";
|
|
6
|
+
|
|
7
|
+
import {PLATFORM_MULTER_OPTIONS} from "../constants/constants.js";
|
|
8
|
+
import type {PlatformMulterFile} from "../interfaces/PlatformMulterSettings.js";
|
|
9
|
+
import {PlatformMulterMiddleware} from "../middlewares/PlatformMulterMiddleware.js";
|
|
10
|
+
|
|
11
|
+
function mapOptions(name: string, maxCount: number | undefined): {fields: Field[]} {
|
|
12
|
+
return {
|
|
13
|
+
fields: [
|
|
14
|
+
{
|
|
15
|
+
name,
|
|
16
|
+
maxCount
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Define a parameter as Multipart file.
|
|
24
|
+
*
|
|
25
|
+
* ```typescript
|
|
26
|
+
* import {Post} from "@tsed/schema";
|
|
27
|
+
* import {Controller, MulterOptions, MultipartFile} from "@tsed/platform-multer";
|
|
28
|
+
* import {Controller} from "@tsed/di";
|
|
29
|
+
* import {Multer} from "@types/multer";
|
|
30
|
+
*
|
|
31
|
+
* type MulterFile = Express.Multer.File;
|
|
32
|
+
*
|
|
33
|
+
* @Controller('/')
|
|
34
|
+
* class MyCtrl {
|
|
35
|
+
* @Post('/file')
|
|
36
|
+
* private uploadFile(@MultipartFile("file1") file: MulterFile) {
|
|
37
|
+
*
|
|
38
|
+
* }
|
|
39
|
+
*
|
|
40
|
+
* @Post('/file')
|
|
41
|
+
* @MulterOptions({dest: "/other-dir"})
|
|
42
|
+
* private uploadFile(@MultipartFile("file1") file: MulterFile) {
|
|
43
|
+
*
|
|
44
|
+
* }
|
|
45
|
+
*
|
|
46
|
+
* @Post('/file2')
|
|
47
|
+
* @MulterOptions({dest: "/other-dir"})
|
|
48
|
+
* private uploadFile(@MultipartFile("file1") file: MulterFile, @MultipartFile("file2") file2: MulterFile) {
|
|
49
|
+
*
|
|
50
|
+
* }
|
|
51
|
+
*
|
|
52
|
+
* @Post('/files')
|
|
53
|
+
* private uploadFile(@MultipartFile("file1") files: MulterFile[]) {
|
|
54
|
+
*
|
|
55
|
+
* }
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* > See the tutorial on the [multer configuration](/docs/upload-files.md).
|
|
60
|
+
*
|
|
61
|
+
* @param name
|
|
62
|
+
* @param maxCount
|
|
63
|
+
* @returns Function
|
|
64
|
+
* @decorator
|
|
65
|
+
* @input
|
|
66
|
+
*/
|
|
67
|
+
export function MultipartFile(name: string, maxCount?: number): ParameterDecorator {
|
|
68
|
+
return (...args: DecoratorParameters): void => {
|
|
69
|
+
const [target, propertyKey, index] = args;
|
|
70
|
+
const multiple = Metadata.getParamTypes(target, propertyKey)[index as number] === Array;
|
|
71
|
+
|
|
72
|
+
name = (typeof name === "object" ? undefined : name)!;
|
|
73
|
+
|
|
74
|
+
const expression = [name, !multiple && "0"].filter(Boolean).join(".");
|
|
75
|
+
|
|
76
|
+
const decorators = useDecorators(
|
|
77
|
+
InFile(name),
|
|
78
|
+
useMethodDecorators(StoreMerge(PLATFORM_MULTER_OPTIONS, mapOptions(name, maxCount)), UseBefore(PlatformMulterMiddleware)),
|
|
79
|
+
UseParam({
|
|
80
|
+
paramType: ParamTypes.FILES,
|
|
81
|
+
dataPath: "$ctx.request.files",
|
|
82
|
+
expression,
|
|
83
|
+
useValidation: true
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
decorators(...args);
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type MultipartFile = PlatformMulterFile;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {BadRequest} from "@tsed/exceptions";
|
|
2
|
+
import type {MulterError} from "multer";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Exception thrown when a Multer error occurs during file upload operations.
|
|
6
|
+
* Extends the BadRequest exception with Multer-specific properties.
|
|
7
|
+
*/
|
|
8
|
+
export class MulterException extends BadRequest {
|
|
9
|
+
origin: MulterError;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new MulterException
|
|
13
|
+
* @param er - The original Multer error
|
|
14
|
+
*/
|
|
15
|
+
constructor(er: MulterError) {
|
|
16
|
+
super(er.message);
|
|
17
|
+
this.origin = er;
|
|
18
|
+
this.name = er.code;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Automatically generated by @tsed/barrels.
|
|
3
|
+
*/
|
|
4
|
+
export * from "./constants/constants.js";
|
|
5
|
+
export * from "./decorators/multerFileSize.js";
|
|
6
|
+
export * from "./decorators/multerOptions.js";
|
|
7
|
+
export * from "./decorators/multipartFile.js";
|
|
8
|
+
export * from "./errors/MulterException.js";
|
|
9
|
+
export * from "./interfaces/PlatformMulterSettings.js";
|
|
10
|
+
export * from "./middlewares/PlatformMulterMiddleware.js";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type {Readable} from "node:stream";
|
|
2
|
+
|
|
3
|
+
import type {Field, Multer, Options} from "multer";
|
|
4
|
+
|
|
5
|
+
declare global {
|
|
6
|
+
namespace TsED {
|
|
7
|
+
export interface Configuration {
|
|
8
|
+
multer?: Options;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type PlatformMulter = Multer;
|
|
14
|
+
export type PlatformMulterSettings = Options;
|
|
15
|
+
export interface PlatformMulterFile {
|
|
16
|
+
/** Name of the form field associated with this file. */
|
|
17
|
+
fieldname: string;
|
|
18
|
+
/** Name of the file on the uploader's computer. */
|
|
19
|
+
originalname: string;
|
|
20
|
+
/**
|
|
21
|
+
* Value of the `Content-Transfer-Encoding` header for this file.
|
|
22
|
+
* @deprecated since July 2015
|
|
23
|
+
* @see RFC 7578, Section 4.7
|
|
24
|
+
*/
|
|
25
|
+
encoding: string;
|
|
26
|
+
/** Value of the `Content-Type` header for this file. */
|
|
27
|
+
mimetype: string;
|
|
28
|
+
/** Size of the file in bytes. */
|
|
29
|
+
size: number;
|
|
30
|
+
/**
|
|
31
|
+
* A readable stream of this file. Only available to the `_handleFile`
|
|
32
|
+
* callback for custom `StorageEngine`s.
|
|
33
|
+
*/
|
|
34
|
+
stream: Readable;
|
|
35
|
+
/** `DiskStorage` only: Directory to which this file has been uploaded. */
|
|
36
|
+
destination: string;
|
|
37
|
+
/** `DiskStorage` only: Name of this file within `destination`. */
|
|
38
|
+
filename: string;
|
|
39
|
+
/** `DiskStorage` only: Full path to the uploaded file. */
|
|
40
|
+
path: string;
|
|
41
|
+
/** `MemoryStorage` only: A Buffer containing the entire file. */
|
|
42
|
+
buffer: Buffer;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type PlatformMulterField = Field;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {catchAsyncError} from "@tsed/core";
|
|
2
|
+
import {inject, injectable} from "@tsed/di";
|
|
3
|
+
import {Exception} from "@tsed/exceptions";
|
|
4
|
+
import {EndpointMetadata} from "@tsed/platform-http";
|
|
5
|
+
import {PlatformTest} from "@tsed/platform-http/testing";
|
|
6
|
+
import {MulterError} from "multer";
|
|
7
|
+
|
|
8
|
+
import {MULTER_MODULE} from "../constants/constants.js";
|
|
9
|
+
import {MulterOptions} from "../decorators/multerOptions.js";
|
|
10
|
+
import {MultipartFile} from "../decorators/multipartFile.js";
|
|
11
|
+
import {PlatformMulterMiddleware} from "./PlatformMulterMiddleware.js";
|
|
12
|
+
|
|
13
|
+
async function getFixture(options = {}) {
|
|
14
|
+
class Test {
|
|
15
|
+
@MulterOptions(options)
|
|
16
|
+
upload(@MultipartFile("file1") file1: any) {}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const multerMiddleware = vi.fn();
|
|
20
|
+
const multer = {
|
|
21
|
+
fields: vi.fn().mockReturnValue(multerMiddleware)
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
injectable(MULTER_MODULE).factory(() => {
|
|
25
|
+
return {
|
|
26
|
+
get: vi.fn().mockReturnValue(multer)
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const middleware = await inject<PlatformMulterMiddleware>(PlatformMulterMiddleware);
|
|
31
|
+
const ctx: any = PlatformTest.createRequestContext();
|
|
32
|
+
ctx.endpoint = EndpointMetadata.get(Test, "upload");
|
|
33
|
+
|
|
34
|
+
const multerModuleFactory = inject<{get: any}>(MULTER_MODULE);
|
|
35
|
+
|
|
36
|
+
return {middleware, ctx, multer, multerModuleFactory, multerMiddleware};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("PlatformMulterMiddleware", () => {
|
|
40
|
+
beforeEach(() =>
|
|
41
|
+
PlatformTest.create({
|
|
42
|
+
multer: {
|
|
43
|
+
dest: "/dest"
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
afterEach(() => PlatformTest.reset());
|
|
48
|
+
it("should create middleware", async () => {
|
|
49
|
+
const {middleware, ctx, multer, multerModuleFactory, multerMiddleware} = await getFixture({});
|
|
50
|
+
|
|
51
|
+
await middleware.use(ctx);
|
|
52
|
+
|
|
53
|
+
expect(multerModuleFactory.get).toHaveBeenCalledWith({
|
|
54
|
+
dest: "/dest"
|
|
55
|
+
});
|
|
56
|
+
expect(multer.fields).toHaveBeenCalledWith([{maxCount: undefined, name: "file1"}]);
|
|
57
|
+
expect(multerMiddleware).toHaveBeenCalledWith(ctx.request.raw, ctx.response.raw);
|
|
58
|
+
});
|
|
59
|
+
it("should create middleware with storage", async () => {
|
|
60
|
+
const {middleware, ctx, multer, multerModuleFactory, multerMiddleware} = await getFixture({
|
|
61
|
+
storage: "storage"
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
await middleware.use(ctx);
|
|
65
|
+
|
|
66
|
+
expect(multerModuleFactory.get).toHaveBeenCalledWith({
|
|
67
|
+
storage: "storage"
|
|
68
|
+
});
|
|
69
|
+
expect(multer.fields).toHaveBeenCalledWith([{maxCount: undefined, name: "file1"}]);
|
|
70
|
+
expect(multerMiddleware).toHaveBeenCalledWith(ctx.request.raw, ctx.response.raw);
|
|
71
|
+
});
|
|
72
|
+
it("should catch error with code", async () => {
|
|
73
|
+
const {middleware, ctx, multerMiddleware} = await getFixture();
|
|
74
|
+
const error = new MulterError("LIMIT_FILE_SIZE", "field");
|
|
75
|
+
|
|
76
|
+
multerMiddleware.mockRejectedValue(error);
|
|
77
|
+
|
|
78
|
+
const actualError: any | undefined = await catchAsyncError(() => middleware.use(ctx));
|
|
79
|
+
|
|
80
|
+
expect(actualError).toBeInstanceOf(Exception);
|
|
81
|
+
expect(actualError?.message).toEqual("File too large");
|
|
82
|
+
expect(actualError?.status).toEqual(400);
|
|
83
|
+
});
|
|
84
|
+
it("should throw error without code", async () => {
|
|
85
|
+
const {middleware, ctx, multerMiddleware} = await getFixture();
|
|
86
|
+
|
|
87
|
+
multerMiddleware.mockRejectedValue(new Error("test"));
|
|
88
|
+
|
|
89
|
+
let actualError: any;
|
|
90
|
+
try {
|
|
91
|
+
await middleware.use(ctx);
|
|
92
|
+
} catch (er) {
|
|
93
|
+
actualError = er;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
expect(actualError.message).toEqual("test");
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {constant, DIContext, inject, injectable, ProviderType} from "@tsed/di";
|
|
2
|
+
import {MiddlewareMethods} from "@tsed/platform-middlewares";
|
|
3
|
+
import {Context} from "@tsed/platform-params";
|
|
4
|
+
import {JsonMethodStore} from "@tsed/schema";
|
|
5
|
+
import type {Field, Options} from "multer";
|
|
6
|
+
|
|
7
|
+
import {MULTER_MODULE, PLATFORM_MULTER_OPTIONS} from "../constants/constants.js";
|
|
8
|
+
import {MulterException} from "../errors/MulterException.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @middleware
|
|
12
|
+
*/
|
|
13
|
+
export class PlatformMulterMiddleware implements MiddlewareMethods {
|
|
14
|
+
async use(@Context() ctx: DIContext & {endpoint: JsonMethodStore}) {
|
|
15
|
+
try {
|
|
16
|
+
const {fields, options = {}} = ctx.endpoint.get<{
|
|
17
|
+
fields: Field[];
|
|
18
|
+
options: Options;
|
|
19
|
+
}>(PLATFORM_MULTER_OPTIONS);
|
|
20
|
+
|
|
21
|
+
const settings: Options = {
|
|
22
|
+
...constant<Options>("multer", {}),
|
|
23
|
+
...options
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/* istanbul ignore next */
|
|
27
|
+
if (settings.storage) {
|
|
28
|
+
settings.dest = undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const {get} = await inject<Promise<MULTER_MODULE>>(MULTER_MODULE);
|
|
32
|
+
|
|
33
|
+
const middleware: any = get(settings).fields(fields);
|
|
34
|
+
|
|
35
|
+
return await middleware(ctx.getRequest(), ctx.getResponse());
|
|
36
|
+
} catch (er) {
|
|
37
|
+
if (er.code) {
|
|
38
|
+
throw new MulterException(er);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
throw er;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
injectable(PlatformMulterMiddleware).type(ProviderType.MIDDLEWARE).priority(-10);
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./common/index.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
import {presets} from "@tsed/vitest/presets";
|
|
3
|
+
import {defineConfig} from "vitest/config";
|
|
4
|
+
|
|
5
|
+
export default defineConfig(
|
|
6
|
+
{
|
|
7
|
+
...presets,
|
|
8
|
+
test: {
|
|
9
|
+
...presets.test,
|
|
10
|
+
coverage: {
|
|
11
|
+
...presets.test.coverage,
|
|
12
|
+
thresholds: {
|
|
13
|
+
statements: 53.17,
|
|
14
|
+
branches: 80,
|
|
15
|
+
functions: 75,
|
|
16
|
+
lines: 53.17
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
);
|