@globalart/nestjs-fastify 1.0.13 → 2.0.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/dist/index.cjs ADDED
@@ -0,0 +1,144 @@
1
+ let _nestjs_common = require("@nestjs/common");
2
+ let _nestjs_swagger = require("@nestjs/swagger");
3
+ let _nestjs_common_constants = require("@nestjs/common/constants");
4
+ let _nestjs_common_enums_route_paramtypes_enum = require("@nestjs/common/enums/route-paramtypes.enum");
5
+
6
+ //#region src/decorators/index.ts
7
+ const API_PARAMETERS = "swagger/apiParameters";
8
+ const IGNORED_TYPES = new Set([
9
+ "Object",
10
+ "String",
11
+ "Number",
12
+ "Boolean",
13
+ "Array",
14
+ "Promise",
15
+ "FastifyRequest",
16
+ "IncomingMessage",
17
+ "FastifyReply",
18
+ "ServerResponse",
19
+ "LoggerService",
20
+ "MultiPartFile"
21
+ ]);
22
+ function getBodyType(target, key, idx) {
23
+ const routeArgs = Reflect.getMetadata(_nestjs_common_constants.ROUTE_ARGS_METADATA, target.constructor, key) || {};
24
+ const types = Reflect.getMetadata("design:paramtypes", target, key) || [];
25
+ const bodyKey = Object.keys(routeArgs).find((k) => k.includes(`:${_nestjs_common_enums_route_paramtypes_enum.RouteParamtypes.BODY}`) || routeArgs[k].index === _nestjs_common_enums_route_paramtypes_enum.RouteParamtypes.BODY);
26
+ if (bodyKey) return types[routeArgs[bodyKey].index];
27
+ return types.find((t, i) => i !== idx && t && !IGNORED_TYPES.has(t.name));
28
+ }
29
+ function updateSwagger(target, key, idx, field, isArray) {
30
+ const method = target[key];
31
+ if (!method) return;
32
+ const params = Reflect.getMetadata(API_PARAMETERS, method) || [];
33
+ let bodyParam = params.find((p) => p.in === "body");
34
+ const fileProp = {
35
+ type: isArray ? "array" : "string",
36
+ format: "binary",
37
+ ...isArray && { items: {
38
+ type: "string",
39
+ format: "binary"
40
+ } }
41
+ };
42
+ if (bodyParam?.schema) {
43
+ bodyParam.schema.properties = {
44
+ ...bodyParam.schema.properties,
45
+ [field]: fileProp
46
+ };
47
+ Reflect.defineMetadata(API_PARAMETERS, params, method);
48
+ return;
49
+ }
50
+ let BodyClass = bodyParam?.type || getBodyType(target, key, idx);
51
+ if (!BodyClass?.name?.endsWith("_CombinedDto")) {
52
+ const Parent = BodyClass || class {};
53
+ BodyClass = class extends Parent {};
54
+ Object.defineProperty(BodyClass, "name", { value: `${target.constructor.name}_${key}_CombinedDto` });
55
+ const newParam = {
56
+ in: "body",
57
+ type: BodyClass,
58
+ required: true
59
+ };
60
+ const pIdx = params.findIndex((p) => p.in === "body");
61
+ if (pIdx > -1) params[pIdx] = newParam;
62
+ else params.push(newParam);
63
+ Reflect.defineMetadata(API_PARAMETERS, params, method);
64
+ }
65
+ (0, _nestjs_swagger.ApiProperty)(fileProp)(BodyClass.prototype, field);
66
+ }
67
+ const createUploader = (isArray, extract) => (field = isArray ? "files" : "file") => (target, key, idx) => {
68
+ updateSwagger(target, key, idx, field, isArray);
69
+ (0, _nestjs_common.createParamDecorator)((data, ctx) => extract(ctx.switchToHttp().getRequest(), data))(field)(target, key, idx);
70
+ };
71
+ const UploadedFiles = createUploader(true, (req) => {
72
+ const files = [];
73
+ if (req.storedFiles) Object.values(req.storedFiles).forEach((f) => files.push(...f));
74
+ return files;
75
+ });
76
+ const UploadedFile = createUploader(false, (req, field) => req.storedFiles?.[field]?.[0] || null);
77
+
78
+ //#endregion
79
+ //#region src/utils/index.ts
80
+ const getFileFromPart = async (part) => {
81
+ const buffer = await part.toBuffer();
82
+ return {
83
+ buffer,
84
+ size: buffer.byteLength,
85
+ filename: part.filename,
86
+ mimetype: part.mimetype,
87
+ fieldname: part.fieldname
88
+ };
89
+ };
90
+ const validateFile = (file, options) => {
91
+ const validators = [];
92
+ if (options.maxFileSize) validators.push(new _nestjs_common.MaxFileSizeValidator({ maxSize: options.maxFileSize }));
93
+ if (options.fileType) validators.push(new _nestjs_common.FileTypeValidator({ fileType: options.fileType }));
94
+ for (const validator of validators) {
95
+ if (validator.isValid(file)) continue;
96
+ return validator.buildErrorMessage(file);
97
+ }
98
+ };
99
+
100
+ //#endregion
101
+ //#region src/interceptors/index.ts
102
+ function MultipartInterceptor(options = {}) {
103
+ class MixinInterceptor {
104
+ async intercept(context, next) {
105
+ const req = context.switchToHttp().getRequest();
106
+ if (!req.isMultipart()) throw new _nestjs_common.HttpException("The request should be a form-data", _nestjs_common.HttpStatus.BAD_REQUEST);
107
+ const files = {};
108
+ const body = {};
109
+ for await (const part of req.parts()) {
110
+ if (part.type !== "file") {
111
+ body[part.fieldname] = part.value;
112
+ continue;
113
+ }
114
+ const file = await getFileFromPart(part);
115
+ const validationResult = validateFile(file, options);
116
+ if (validationResult) throw new _nestjs_common.HttpException(validationResult, _nestjs_common.HttpStatus.UNPROCESSABLE_ENTITY);
117
+ files[part.fieldname] = files[part.fieldname] || [];
118
+ files[part.fieldname].push(file);
119
+ }
120
+ req.storedFiles = files;
121
+ req.body = body;
122
+ return next.handle();
123
+ }
124
+ }
125
+ return (0, _nestjs_common.mixin)(MixinInterceptor);
126
+ }
127
+
128
+ //#endregion
129
+ //#region src/model/index.ts
130
+ var MultipartOptions = class {
131
+ constructor(maxFileSize, fileType) {
132
+ this.maxFileSize = maxFileSize;
133
+ this.fileType = fileType;
134
+ }
135
+ };
136
+
137
+ //#endregion
138
+ exports.MultipartInterceptor = MultipartInterceptor;
139
+ exports.MultipartOptions = MultipartOptions;
140
+ exports.UploadedFile = UploadedFile;
141
+ exports.UploadedFiles = UploadedFiles;
142
+ exports.getFileFromPart = getFileFromPart;
143
+ exports.validateFile = validateFile;
144
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["ROUTE_ARGS_METADATA","RouteParamtypes","fileProp: ApiPropertyOptions","files: any[]","validators: FileValidator[]","MaxFileSizeValidator","FileTypeValidator","HttpException","HttpStatus","files: Record<string, any[]>","body: Record<string, any>","maxFileSize?: number","fileType?: string | RegExp"],"sources":["../src/decorators/index.ts","../src/utils/index.ts","../src/interceptors/index.ts","../src/model/index.ts"],"sourcesContent":["import { createParamDecorator, ExecutionContext } from \"@nestjs/common\";\nimport * as fastify from \"fastify\";\nimport { ApiProperty, ApiPropertyOptions } from \"@nestjs/swagger\";\nimport { ROUTE_ARGS_METADATA } from \"@nestjs/common/constants\";\nimport { RouteParamtypes } from \"@nestjs/common/enums/route-paramtypes.enum\";\n\nconst API_PARAMETERS = \"swagger/apiParameters\";\nconst IGNORED_TYPES = new Set([\n \"Object\", \"String\", \"Number\", \"Boolean\", \"Array\", \"Promise\",\n \"FastifyRequest\", \"IncomingMessage\", \"FastifyReply\", \"ServerResponse\", \"LoggerService\", \"MultiPartFile\"\n]);\n\nfunction getBodyType(target: any, key: string, idx: number): any {\n const routeArgs = Reflect.getMetadata(ROUTE_ARGS_METADATA, target.constructor, key) || {};\n const types = Reflect.getMetadata(\"design:paramtypes\", target, key) || [];\n\n const bodyKey = Object.keys(routeArgs).find(k =>\n k.includes(`:${RouteParamtypes.BODY}`) || routeArgs[k].index === RouteParamtypes.BODY\n );\n if (bodyKey) return types[routeArgs[bodyKey].index];\n\n return types.find((t: any, i: number) => i !== idx && t && !IGNORED_TYPES.has(t.name));\n}\n\nfunction updateSwagger(target: any, key: string, idx: number, field: string, isArray: boolean) {\n const method = target[key];\n if (!method) return;\n\n const params = Reflect.getMetadata(API_PARAMETERS, method) || [];\n let bodyParam = params.find((p: any) => p.in === \"body\");\n const fileProp: ApiPropertyOptions = {\n type: isArray ? \"array\" : \"string\",\n format: \"binary\",\n ...(isArray && { items: { type: \"string\", format: \"binary\" } })\n };\n\n if (bodyParam?.schema) {\n bodyParam.schema.properties = { ...bodyParam.schema.properties, [field]: fileProp };\n Reflect.defineMetadata(API_PARAMETERS, params, method);\n return;\n }\n\n let BodyClass = bodyParam?.type || getBodyType(target, key, idx);\n if (!BodyClass?.name?.endsWith(\"_CombinedDto\")) {\n const Parent = BodyClass || class { };\n BodyClass = class extends Parent { };\n Object.defineProperty(BodyClass, \"name\", { value: `${target.constructor.name}_${key}_CombinedDto` });\n\n const newParam = { in: \"body\", type: BodyClass, required: true };\n const pIdx = params.findIndex((p: any) => p.in === \"body\");\n if (pIdx > -1) params[pIdx] = newParam; else params.push(newParam);\n Reflect.defineMetadata(API_PARAMETERS, params, method);\n }\n\n ApiProperty(fileProp)(BodyClass.prototype, field);\n}\n\nconst createUploader = (isArray: boolean, extract: (req: fastify.FastifyRequest, f: string) => any) =>\n (field: string = isArray ? \"files\" : \"file\") =>\n (target: any, key: string, idx: number) => {\n updateSwagger(target, key, idx, field, isArray);\n createParamDecorator((data: string, ctx: ExecutionContext) =>\n extract(ctx.switchToHttp().getRequest(), data)\n )(field)(target, key, idx);\n };\n\nexport const UploadedFiles = createUploader(true, (req) => {\n const files: any[] = [];\n if (req.storedFiles) Object.values(req.storedFiles).forEach(f => files.push(...f));\n return files;\n});\n\nexport const UploadedFile = createUploader(false, (req, field) => req.storedFiles?.[field]?.[0] || null);\n","import { MultipartFile } from \"@fastify/multipart\";\nimport {\n FileTypeValidator,\n FileValidator,\n MaxFileSizeValidator,\n} from \"@nestjs/common\";\nimport { MultipartOptions } from \"../model\";\n\nexport const getFileFromPart = async (\n part: MultipartFile,\n): Promise<Storage.MultipartFile> => {\n const buffer = await part.toBuffer();\n return {\n buffer,\n size: buffer.byteLength,\n filename: part.filename,\n mimetype: part.mimetype,\n fieldname: part.fieldname,\n };\n};\n\nexport const validateFile = (\n file: Storage.MultipartFile,\n options: MultipartOptions,\n): string | void => {\n const validators: FileValidator[] = [];\n\n if (options.maxFileSize)\n validators.push(new MaxFileSizeValidator({ maxSize: options.maxFileSize }));\n if (options.fileType)\n validators.push(new FileTypeValidator({ fileType: options.fileType }));\n\n for (const validator of validators) {\n if (validator.isValid(file)) continue;\n\n return validator.buildErrorMessage(file);\n }\n};\n","import { Observable } from \"rxjs\";\nimport {\n CallHandler,\n ExecutionContext,\n HttpException,\n HttpStatus,\n mixin,\n NestInterceptor,\n Type,\n} from \"@nestjs/common\";\nimport * as fastify from \"fastify\";\nimport { MultipartValue } from \"@fastify/multipart\";\nimport { MultipartOptions } from \"../model\";\nimport { getFileFromPart, validateFile } from \"../utils\";\n\nexport function MultipartInterceptor(\n options: MultipartOptions = {},\n): Type<NestInterceptor> {\n class MixinInterceptor implements NestInterceptor {\n async intercept(\n context: ExecutionContext,\n next: CallHandler,\n ): Promise<Observable<any>> {\n const req = context.switchToHttp().getRequest() as fastify.FastifyRequest;\n\n if (!req.isMultipart())\n throw new HttpException(\n \"The request should be a form-data\",\n HttpStatus.BAD_REQUEST,\n );\n\n const files: Record<string, any[]> = {};\n const body: Record<string, any> = {};\n for await (const part of req.parts()) {\n if (part.type !== \"file\") {\n body[part.fieldname] = (part as MultipartValue).value;\n continue;\n }\n const file = await getFileFromPart(part);\n const validationResult = validateFile(file, options);\n\n if (validationResult)\n throw new HttpException(\n validationResult,\n HttpStatus.UNPROCESSABLE_ENTITY,\n );\n\n files[part.fieldname] = files[part.fieldname] || [];\n files[part.fieldname].push(file);\n }\n req.storedFiles = files;\n req.body = body;\n\n return next.handle();\n }\n }\n\n return mixin(MixinInterceptor);\n}\n","export class MultipartOptions {\n constructor(\n public maxFileSize?: number,\n public fileType?: string | RegExp,\n ) {}\n}\n"],"mappings":"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"}
@@ -2,8 +2,8 @@ import { NestInterceptor, Type } from "@nestjs/common";
2
2
  import { MultipartFile } from "@fastify/multipart";
3
3
 
4
4
  //#region src/decorators/index.d.ts
5
- declare const UploadedFiles: (...dataOrPipes: unknown[]) => ParameterDecorator;
6
- declare const UploadedFile: (...dataOrPipes: unknown[]) => ParameterDecorator;
5
+ declare const UploadedFiles: (field?: string) => (target: any, key: string, idx: number) => void;
6
+ declare const UploadedFile: (field?: string) => (target: any, key: string, idx: number) => void;
7
7
  //#endregion
8
8
  //#region src/types/index.d.ts
9
9
  type MultiPartFile = Storage.MultipartFile;
@@ -24,4 +24,4 @@ declare const getFileFromPart: (part: MultipartFile) => Promise<Storage.Multipar
24
24
  declare const validateFile: (file: Storage.MultipartFile, options: MultipartOptions) => string | void;
25
25
  //#endregion
26
26
  export { MultiPartFile, MultiPartFiles, MultipartInterceptor, MultipartOptions, UploadedFile, UploadedFiles, getFileFromPart, validateFile };
27
- //# sourceMappingURL=index.d.ts.map
27
+ //# sourceMappingURL=index.d.cts.map
package/dist/index.d.mts CHANGED
@@ -2,8 +2,8 @@ import { NestInterceptor, Type } from "@nestjs/common";
2
2
  import { MultipartFile } from "@fastify/multipart";
3
3
 
4
4
  //#region src/decorators/index.d.ts
5
- declare const UploadedFiles: (...dataOrPipes: unknown[]) => ParameterDecorator;
6
- declare const UploadedFile: (...dataOrPipes: unknown[]) => ParameterDecorator;
5
+ declare const UploadedFiles: (field?: string) => (target: any, key: string, idx: number) => void;
6
+ declare const UploadedFile: (field?: string) => (target: any, key: string, idx: number) => void;
7
7
  //#endregion
8
8
  //#region src/types/index.d.ts
9
9
  type MultiPartFile = Storage.MultipartFile;
package/dist/index.mjs CHANGED
@@ -1,22 +1,79 @@
1
1
  import { FileTypeValidator, HttpException, HttpStatus, MaxFileSizeValidator, createParamDecorator, mixin } from "@nestjs/common";
2
+ import { ApiProperty } from "@nestjs/swagger";
3
+ import { ROUTE_ARGS_METADATA } from "@nestjs/common/constants";
4
+ import { RouteParamtypes } from "@nestjs/common/enums/route-paramtypes.enum";
2
5
 
3
6
  //#region src/decorators/index.ts
4
- const UploadedFiles = createParamDecorator((_data, ctx) => {
5
- const req = ctx.switchToHttp().getRequest();
6
- const allFiles = [];
7
- Object.values(req.storedFiles).forEach((files) => {
8
- allFiles.push(...files);
9
- });
10
- return allFiles;
11
- });
12
- const UploadedFile = createParamDecorator((fieldName, ctx) => {
13
- const req = ctx.switchToHttp().getRequest();
14
- if (typeof fieldName === "string") {
15
- const files = req.storedFiles[fieldName];
16
- return files && files.length > 0 ? files[0] : null;
7
+ const API_PARAMETERS = "swagger/apiParameters";
8
+ const IGNORED_TYPES = new Set([
9
+ "Object",
10
+ "String",
11
+ "Number",
12
+ "Boolean",
13
+ "Array",
14
+ "Promise",
15
+ "FastifyRequest",
16
+ "IncomingMessage",
17
+ "FastifyReply",
18
+ "ServerResponse",
19
+ "LoggerService",
20
+ "MultiPartFile"
21
+ ]);
22
+ function getBodyType(target, key, idx) {
23
+ const routeArgs = Reflect.getMetadata(ROUTE_ARGS_METADATA, target.constructor, key) || {};
24
+ const types = Reflect.getMetadata("design:paramtypes", target, key) || [];
25
+ const bodyKey = Object.keys(routeArgs).find((k) => k.includes(`:${RouteParamtypes.BODY}`) || routeArgs[k].index === RouteParamtypes.BODY);
26
+ if (bodyKey) return types[routeArgs[bodyKey].index];
27
+ return types.find((t, i) => i !== idx && t && !IGNORED_TYPES.has(t.name));
28
+ }
29
+ function updateSwagger(target, key, idx, field, isArray) {
30
+ const method = target[key];
31
+ if (!method) return;
32
+ const params = Reflect.getMetadata(API_PARAMETERS, method) || [];
33
+ let bodyParam = params.find((p) => p.in === "body");
34
+ const fileProp = {
35
+ type: isArray ? "array" : "string",
36
+ format: "binary",
37
+ ...isArray && { items: {
38
+ type: "string",
39
+ format: "binary"
40
+ } }
41
+ };
42
+ if (bodyParam?.schema) {
43
+ bodyParam.schema.properties = {
44
+ ...bodyParam.schema.properties,
45
+ [field]: fileProp
46
+ };
47
+ Reflect.defineMetadata(API_PARAMETERS, params, method);
48
+ return;
17
49
  }
18
- return req.storedFiles;
50
+ let BodyClass = bodyParam?.type || getBodyType(target, key, idx);
51
+ if (!BodyClass?.name?.endsWith("_CombinedDto")) {
52
+ const Parent = BodyClass || class {};
53
+ BodyClass = class extends Parent {};
54
+ Object.defineProperty(BodyClass, "name", { value: `${target.constructor.name}_${key}_CombinedDto` });
55
+ const newParam = {
56
+ in: "body",
57
+ type: BodyClass,
58
+ required: true
59
+ };
60
+ const pIdx = params.findIndex((p) => p.in === "body");
61
+ if (pIdx > -1) params[pIdx] = newParam;
62
+ else params.push(newParam);
63
+ Reflect.defineMetadata(API_PARAMETERS, params, method);
64
+ }
65
+ ApiProperty(fileProp)(BodyClass.prototype, field);
66
+ }
67
+ const createUploader = (isArray, extract) => (field = isArray ? "files" : "file") => (target, key, idx) => {
68
+ updateSwagger(target, key, idx, field, isArray);
69
+ createParamDecorator((data, ctx) => extract(ctx.switchToHttp().getRequest(), data))(field)(target, key, idx);
70
+ };
71
+ const UploadedFiles = createUploader(true, (req) => {
72
+ const files = [];
73
+ if (req.storedFiles) Object.values(req.storedFiles).forEach((f) => files.push(...f));
74
+ return files;
19
75
  });
76
+ const UploadedFile = createUploader(false, (req, field) => req.storedFiles?.[field]?.[0] || null);
20
77
 
21
78
  //#endregion
22
79
  //#region src/utils/index.ts
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["allFiles: Storage.MultipartFile[]","validators: FileValidator[]","files: Record<string, any[]>","body: Record<string, any>","maxFileSize?: number","fileType?: string | RegExp"],"sources":["../src/decorators/index.ts","../src/utils/index.ts","../src/interceptors/index.ts","../src/model/index.ts"],"sourcesContent":["import { createParamDecorator, ExecutionContext } from \"@nestjs/common\";\nimport * as fastify from \"fastify\";\n\nexport const UploadedFiles = createParamDecorator(\n (_data: unknown, ctx: ExecutionContext) => {\n const req = ctx.switchToHttp().getRequest() as fastify.FastifyRequest;\n const allFiles: Storage.MultipartFile[] = [];\n Object.values(req.storedFiles).forEach((files) => {\n allFiles.push(...files);\n });\n return allFiles;\n }\n);\n\nexport const UploadedFile = createParamDecorator(\n (fieldName: string | unknown, ctx: ExecutionContext) => {\n const req = ctx.switchToHttp().getRequest() as fastify.FastifyRequest;\n\n if (typeof fieldName === \"string\") {\n const files = req.storedFiles[fieldName];\n return files && files.length > 0 ? files[0] : null;\n }\n\n return req.storedFiles;\n }\n);\n","import { MultipartFile } from \"@fastify/multipart\";\nimport {\n FileTypeValidator,\n FileValidator,\n MaxFileSizeValidator,\n} from \"@nestjs/common\";\nimport { MultipartOptions } from \"../model\";\n\nexport const getFileFromPart = async (\n part: MultipartFile,\n): Promise<Storage.MultipartFile> => {\n const buffer = await part.toBuffer();\n return {\n buffer,\n size: buffer.byteLength,\n filename: part.filename,\n mimetype: part.mimetype,\n fieldname: part.fieldname,\n };\n};\n\nexport const validateFile = (\n file: Storage.MultipartFile,\n options: MultipartOptions,\n): string | void => {\n const validators: FileValidator[] = [];\n\n if (options.maxFileSize)\n validators.push(new MaxFileSizeValidator({ maxSize: options.maxFileSize }));\n if (options.fileType)\n validators.push(new FileTypeValidator({ fileType: options.fileType }));\n\n for (const validator of validators) {\n if (validator.isValid(file)) continue;\n\n return validator.buildErrorMessage(file);\n }\n};\n","import { Observable } from \"rxjs\";\nimport {\n CallHandler,\n ExecutionContext,\n HttpException,\n HttpStatus,\n mixin,\n NestInterceptor,\n Type,\n} from \"@nestjs/common\";\nimport * as fastify from \"fastify\";\nimport { MultipartValue } from \"@fastify/multipart\";\nimport { MultipartOptions } from \"../model\";\nimport { getFileFromPart, validateFile } from \"../utils\";\n\nexport function MultipartInterceptor(\n options: MultipartOptions = {},\n): Type<NestInterceptor> {\n class MixinInterceptor implements NestInterceptor {\n async intercept(\n context: ExecutionContext,\n next: CallHandler,\n ): Promise<Observable<any>> {\n const req = context.switchToHttp().getRequest() as fastify.FastifyRequest;\n\n if (!req.isMultipart())\n throw new HttpException(\n \"The request should be a form-data\",\n HttpStatus.BAD_REQUEST,\n );\n\n const files: Record<string, any[]> = {};\n const body: Record<string, any> = {};\n for await (const part of req.parts()) {\n if (part.type !== \"file\") {\n body[part.fieldname] = (part as MultipartValue).value;\n continue;\n }\n const file = await getFileFromPart(part);\n const validationResult = validateFile(file, options);\n\n if (validationResult)\n throw new HttpException(\n validationResult,\n HttpStatus.UNPROCESSABLE_ENTITY,\n );\n\n files[part.fieldname] = files[part.fieldname] || [];\n files[part.fieldname].push(file);\n }\n req.storedFiles = files;\n req.body = body;\n\n return next.handle();\n }\n }\n\n return mixin(MixinInterceptor);\n}\n","export class MultipartOptions {\n constructor(\n public maxFileSize?: number,\n public fileType?: string | RegExp,\n ) {}\n}\n"],"mappings":";;;AAGA,MAAa,gBAAgB,sBAC1B,OAAgB,QAA0B;CACzC,MAAM,MAAM,IAAI,eAAe;CAC/B,MAAMA,WAAoC;AAC1C,QAAO,OAAO,IAAI,aAAa,SAAS,UAAU;AAChD,WAAS,KAAK,GAAG;;AAEnB,QAAO;;AAIX,MAAa,eAAe,sBACzB,WAA6B,QAA0B;CACtD,MAAM,MAAM,IAAI,eAAe;AAE/B,KAAI,OAAO,cAAc,UAAU;EACjC,MAAM,QAAQ,IAAI,YAAY;AAC9B,SAAO,SAAS,MAAM,SAAS,IAAI,MAAM,KAAK;;AAGhD,QAAO,IAAI;;;;;ACff,MAAa,kBAAkB,OAC7B,SACmC;CACnC,MAAM,SAAS,MAAM,KAAK;AAC1B,QAAO;EACL;EACA,MAAM,OAAO;EACb,UAAU,KAAK;EACf,UAAU,KAAK;EACf,WAAW,KAAK;;;AAIpB,MAAa,gBACX,MACA,YACkB;CAClB,MAAMC,aAA8B;AAEpC,KAAI,QAAQ,YACV,YAAW,KAAK,IAAI,qBAAqB,EAAE,SAAS,QAAQ;AAC9D,KAAI,QAAQ,SACV,YAAW,KAAK,IAAI,kBAAkB,EAAE,UAAU,QAAQ;AAE5D,MAAK,MAAM,aAAa,YAAY;AAClC,MAAI,UAAU,QAAQ,MAAO;AAE7B,SAAO,UAAU,kBAAkB;;;;;;ACpBvC,SAAgB,qBACd,UAA4B,IACL;CACvB,MAAM,iBAA4C;EAChD,MAAM,UACJ,SACA,MAC0B;GAC1B,MAAM,MAAM,QAAQ,eAAe;AAEnC,OAAI,CAAC,IAAI,cACP,OAAM,IAAI,cACR,qCACA,WAAW;GAGf,MAAMC,QAA+B;GACrC,MAAMC,OAA4B;AAClC,cAAW,MAAM,QAAQ,IAAI,SAAS;AACpC,QAAI,KAAK,SAAS,QAAQ;AACxB,UAAK,KAAK,aAAc,KAAwB;AAChD;;IAEF,MAAM,OAAO,MAAM,gBAAgB;IACnC,MAAM,mBAAmB,aAAa,MAAM;AAE5C,QAAI,iBACF,OAAM,IAAI,cACR,kBACA,WAAW;AAGf,UAAM,KAAK,aAAa,MAAM,KAAK,cAAc;AACjD,UAAM,KAAK,WAAW,KAAK;;AAE7B,OAAI,cAAc;AAClB,OAAI,OAAO;AAEX,UAAO,KAAK;;;AAIhB,QAAO,MAAM;;;;;ACzDf,IAAa,mBAAb,MAA8B;CAC5B,YACE,AAAOC,aACP,AAAOC,UACP;EAFO;EACA"}
1
+ {"version":3,"file":"index.mjs","names":["fileProp: ApiPropertyOptions","files: any[]","validators: FileValidator[]","files: Record<string, any[]>","body: Record<string, any>","maxFileSize?: number","fileType?: string | RegExp"],"sources":["../src/decorators/index.ts","../src/utils/index.ts","../src/interceptors/index.ts","../src/model/index.ts"],"sourcesContent":["import { createParamDecorator, ExecutionContext } from \"@nestjs/common\";\nimport * as fastify from \"fastify\";\nimport { ApiProperty, ApiPropertyOptions } from \"@nestjs/swagger\";\nimport { ROUTE_ARGS_METADATA } from \"@nestjs/common/constants\";\nimport { RouteParamtypes } from \"@nestjs/common/enums/route-paramtypes.enum\";\n\nconst API_PARAMETERS = \"swagger/apiParameters\";\nconst IGNORED_TYPES = new Set([\n \"Object\", \"String\", \"Number\", \"Boolean\", \"Array\", \"Promise\",\n \"FastifyRequest\", \"IncomingMessage\", \"FastifyReply\", \"ServerResponse\", \"LoggerService\", \"MultiPartFile\"\n]);\n\nfunction getBodyType(target: any, key: string, idx: number): any {\n const routeArgs = Reflect.getMetadata(ROUTE_ARGS_METADATA, target.constructor, key) || {};\n const types = Reflect.getMetadata(\"design:paramtypes\", target, key) || [];\n\n const bodyKey = Object.keys(routeArgs).find(k =>\n k.includes(`:${RouteParamtypes.BODY}`) || routeArgs[k].index === RouteParamtypes.BODY\n );\n if (bodyKey) return types[routeArgs[bodyKey].index];\n\n return types.find((t: any, i: number) => i !== idx && t && !IGNORED_TYPES.has(t.name));\n}\n\nfunction updateSwagger(target: any, key: string, idx: number, field: string, isArray: boolean) {\n const method = target[key];\n if (!method) return;\n\n const params = Reflect.getMetadata(API_PARAMETERS, method) || [];\n let bodyParam = params.find((p: any) => p.in === \"body\");\n const fileProp: ApiPropertyOptions = {\n type: isArray ? \"array\" : \"string\",\n format: \"binary\",\n ...(isArray && { items: { type: \"string\", format: \"binary\" } })\n };\n\n if (bodyParam?.schema) {\n bodyParam.schema.properties = { ...bodyParam.schema.properties, [field]: fileProp };\n Reflect.defineMetadata(API_PARAMETERS, params, method);\n return;\n }\n\n let BodyClass = bodyParam?.type || getBodyType(target, key, idx);\n if (!BodyClass?.name?.endsWith(\"_CombinedDto\")) {\n const Parent = BodyClass || class { };\n BodyClass = class extends Parent { };\n Object.defineProperty(BodyClass, \"name\", { value: `${target.constructor.name}_${key}_CombinedDto` });\n\n const newParam = { in: \"body\", type: BodyClass, required: true };\n const pIdx = params.findIndex((p: any) => p.in === \"body\");\n if (pIdx > -1) params[pIdx] = newParam; else params.push(newParam);\n Reflect.defineMetadata(API_PARAMETERS, params, method);\n }\n\n ApiProperty(fileProp)(BodyClass.prototype, field);\n}\n\nconst createUploader = (isArray: boolean, extract: (req: fastify.FastifyRequest, f: string) => any) =>\n (field: string = isArray ? \"files\" : \"file\") =>\n (target: any, key: string, idx: number) => {\n updateSwagger(target, key, idx, field, isArray);\n createParamDecorator((data: string, ctx: ExecutionContext) =>\n extract(ctx.switchToHttp().getRequest(), data)\n )(field)(target, key, idx);\n };\n\nexport const UploadedFiles = createUploader(true, (req) => {\n const files: any[] = [];\n if (req.storedFiles) Object.values(req.storedFiles).forEach(f => files.push(...f));\n return files;\n});\n\nexport const UploadedFile = createUploader(false, (req, field) => req.storedFiles?.[field]?.[0] || null);\n","import { MultipartFile } from \"@fastify/multipart\";\nimport {\n FileTypeValidator,\n FileValidator,\n MaxFileSizeValidator,\n} from \"@nestjs/common\";\nimport { MultipartOptions } from \"../model\";\n\nexport const getFileFromPart = async (\n part: MultipartFile,\n): Promise<Storage.MultipartFile> => {\n const buffer = await part.toBuffer();\n return {\n buffer,\n size: buffer.byteLength,\n filename: part.filename,\n mimetype: part.mimetype,\n fieldname: part.fieldname,\n };\n};\n\nexport const validateFile = (\n file: Storage.MultipartFile,\n options: MultipartOptions,\n): string | void => {\n const validators: FileValidator[] = [];\n\n if (options.maxFileSize)\n validators.push(new MaxFileSizeValidator({ maxSize: options.maxFileSize }));\n if (options.fileType)\n validators.push(new FileTypeValidator({ fileType: options.fileType }));\n\n for (const validator of validators) {\n if (validator.isValid(file)) continue;\n\n return validator.buildErrorMessage(file);\n }\n};\n","import { Observable } from \"rxjs\";\nimport {\n CallHandler,\n ExecutionContext,\n HttpException,\n HttpStatus,\n mixin,\n NestInterceptor,\n Type,\n} from \"@nestjs/common\";\nimport * as fastify from \"fastify\";\nimport { MultipartValue } from \"@fastify/multipart\";\nimport { MultipartOptions } from \"../model\";\nimport { getFileFromPart, validateFile } from \"../utils\";\n\nexport function MultipartInterceptor(\n options: MultipartOptions = {},\n): Type<NestInterceptor> {\n class MixinInterceptor implements NestInterceptor {\n async intercept(\n context: ExecutionContext,\n next: CallHandler,\n ): Promise<Observable<any>> {\n const req = context.switchToHttp().getRequest() as fastify.FastifyRequest;\n\n if (!req.isMultipart())\n throw new HttpException(\n \"The request should be a form-data\",\n HttpStatus.BAD_REQUEST,\n );\n\n const files: Record<string, any[]> = {};\n const body: Record<string, any> = {};\n for await (const part of req.parts()) {\n if (part.type !== \"file\") {\n body[part.fieldname] = (part as MultipartValue).value;\n continue;\n }\n const file = await getFileFromPart(part);\n const validationResult = validateFile(file, options);\n\n if (validationResult)\n throw new HttpException(\n validationResult,\n HttpStatus.UNPROCESSABLE_ENTITY,\n );\n\n files[part.fieldname] = files[part.fieldname] || [];\n files[part.fieldname].push(file);\n }\n req.storedFiles = files;\n req.body = body;\n\n return next.handle();\n }\n }\n\n return mixin(MixinInterceptor);\n}\n","export class MultipartOptions {\n constructor(\n public maxFileSize?: number,\n public fileType?: string | RegExp,\n ) {}\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,28 +1,35 @@
1
1
  {
2
2
  "name": "@globalart/nestjs-fastify",
3
- "version": "1.0.13",
3
+ "version": "2.0.1",
4
4
  "description": "A fastify for NestJS",
5
5
  "author": {
6
6
  "name": "GlobalArt, Inc"
7
7
  },
8
8
  "license": "MIT",
9
- "main": "./dist/index.js",
10
- "types": "./dist/index.d.ts",
9
+ "type": "module",
11
10
  "files": [
12
11
  "dist",
13
12
  "README.md",
14
13
  "LICENSE"
15
14
  ],
15
+ "main": "./dist/index.cjs",
16
+ "module": "./dist/index.mjs",
17
+ "types": "./dist/index.d.cts",
16
18
  "exports": {
17
19
  ".": {
18
- "require": "./dist/index.js",
19
- "import": "./dist/index.js",
20
- "types": "./dist/index.d.ts"
20
+ "import": {
21
+ "types": "./dist/index.d.mts",
22
+ "default": "./dist/index.mjs"
23
+ },
24
+ "require": {
25
+ "types": "./dist/index.d.cts",
26
+ "default": "./dist/index.cjs"
27
+ }
21
28
  }
22
29
  },
23
30
  "repository": {
24
31
  "type": "git",
25
- "url": "git+https://github.com/GlobalArtInc/nestjs-toolkit.git#main"
32
+ "url": "git+https://github.com/GlobalArtInc/ecosystem.git#main"
26
33
  },
27
34
  "keywords": [
28
35
  "nestjs",
@@ -37,35 +44,40 @@
37
44
  "build:watch": "tsdown --watch",
38
45
  "prepublishOnly": "npm run build",
39
46
  "publish:dev": "npm publish --access public --tag dev",
40
- "publish:npm": "release-it"
47
+ "publish:npm": "release-it --config ../../.release-it.json"
41
48
  },
42
49
  "dependencies": {
43
- "@fastify/cookie": "11.0.2",
44
- "@fastify/multipart": "9.2.1",
45
- "@nestjs/common": "^11.1.6",
46
- "@nestjs/core": "^11.1.6",
47
- "@nestjs/microservices": "11.1.6",
48
- "@nestjs/platform-fastify": "11.1.6",
49
- "@nestjs/swagger": "^11.2.0",
50
- "@nestjs/testing": "^11.1.6",
51
- "@nestjs/typeorm": "^11.0.0",
52
- "fastify": "^5.5.0"
50
+ "@fastify/cookie": "catalog:prod",
51
+ "@fastify/multipart": "catalog:prod",
52
+ "@nestjs/common": "catalog:prod",
53
+ "@nestjs/core": "catalog:prod",
54
+ "@nestjs/microservices": "catalog:prod",
55
+ "@nestjs/platform-fastify": "catalog:prod",
56
+ "@nestjs/swagger": "catalog:prod",
57
+ "@nestjs/testing": "catalog:prod",
58
+ "@nestjs/typeorm": "catalog:prod",
59
+ "fastify": "catalog:prod"
53
60
  },
54
61
  "devDependencies": {
55
- "@types/jest": "^30.0.0",
56
- "@types/node": "^24.3.0",
57
- "coveralls": "^3.1.1",
58
- "jest": "^30.0.3",
59
- "prettier": "^3.6.2",
60
- "reflect-metadata": "^0.2.2",
61
- "release-it": "19.0.4",
62
- "rxjs": "^7.8.2",
63
- "ts-jest": "^29.4.0",
64
- "ts-node": "^10.9.2",
65
- "tsdown": "0.14.2",
66
- "typescript": "^5.9.2"
62
+ "@types/jest": "catalog:dev",
63
+ "@types/node": "catalog:dev",
64
+ "class-validator": "catalog:dev",
65
+ "coveralls": "catalog:dev",
66
+ "jest": "catalog:dev",
67
+ "prettier": "catalog:dev",
68
+ "reflect-metadata": "catalog:dev",
69
+ "release-it": "catalog:dev",
70
+ "rxjs": "catalog:dev",
71
+ "ts-jest": "catalog:dev",
72
+ "ts-node": "catalog:dev",
73
+ "tsdown": "catalog:dev",
74
+ "typescript": "catalog:dev"
67
75
  },
68
76
  "publishConfig": {
69
77
  "access": "public"
70
- }
78
+ },
79
+ "peerDependencies": {
80
+ "class-validator": "^0.14.0"
81
+ },
82
+ "optionalDependencies": {}
71
83
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/decorators/index.ts","../src/types/index.ts","../src/model/index.ts","../src/interceptors/index.ts","../src/utils/index.ts"],"sourcesContent":[],"mappings":";;;;cAGa,8CAAa;cAWb,6CAAY;;;KCdb,aAAA,GAAgB,OAAA,CAAQ;KACxB,cAAA,GAAiB,OAAA,CAAQ;;;cCDxB,gBAAA;;uBAGkB;qEAAA;AFA/B;;;iBGYgB,oBAAA,WACL,mBACR,KAAK;;;cCTK,wBACL,kBACL,QAAQ,OAAA,CAAQ;cAWN,qBACL,OAAA,CAAQ,wBACL"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/decorators/index.ts","../src/types/index.ts","../src/model/index.ts","../src/interceptors/index.ts","../src/utils/index.ts"],"sourcesContent":[],"mappings":";;;;cAGa,8CAAa;cAWb,6CAAY;;;KCdb,aAAA,GAAgB,OAAA,CAAQ;KACxB,cAAA,GAAiB,OAAA,CAAQ;;;cCDxB,gBAAA;;uBAGkB;qEAAA;AFA/B;;;iBGYgB,oBAAA,WACL,mBACR,KAAK;;;cCTK,wBACL,kBACL,QAAQ,OAAA,CAAQ;cAWN,qBACL,OAAA,CAAQ,wBACL"}
package/dist/index.js DELETED
@@ -1,111 +0,0 @@
1
- //#region rolldown:runtime
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
- key = keys[i];
11
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
- get: ((k) => from[k]).bind(null, key),
13
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
- });
15
- }
16
- return to;
17
- };
18
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
- value: mod,
20
- enumerable: true
21
- }) : target, mod));
22
-
23
- //#endregion
24
- let __nestjs_common = require("@nestjs/common");
25
- __nestjs_common = __toESM(__nestjs_common);
26
-
27
- //#region src/decorators/index.ts
28
- const UploadedFiles = (0, __nestjs_common.createParamDecorator)((_data, ctx) => {
29
- const req = ctx.switchToHttp().getRequest();
30
- const allFiles = [];
31
- Object.values(req.storedFiles).forEach((files) => {
32
- allFiles.push(...files);
33
- });
34
- return allFiles;
35
- });
36
- const UploadedFile = (0, __nestjs_common.createParamDecorator)((fieldName, ctx) => {
37
- const req = ctx.switchToHttp().getRequest();
38
- if (typeof fieldName === "string") {
39
- const files = req.storedFiles[fieldName];
40
- return files && files.length > 0 ? files[0] : null;
41
- }
42
- return req.storedFiles;
43
- });
44
-
45
- //#endregion
46
- //#region src/utils/index.ts
47
- const getFileFromPart = async (part) => {
48
- const buffer = await part.toBuffer();
49
- return {
50
- buffer,
51
- size: buffer.byteLength,
52
- filename: part.filename,
53
- mimetype: part.mimetype,
54
- fieldname: part.fieldname
55
- };
56
- };
57
- const validateFile = (file, options) => {
58
- const validators = [];
59
- if (options.maxFileSize) validators.push(new __nestjs_common.MaxFileSizeValidator({ maxSize: options.maxFileSize }));
60
- if (options.fileType) validators.push(new __nestjs_common.FileTypeValidator({ fileType: options.fileType }));
61
- for (const validator of validators) {
62
- if (validator.isValid(file)) continue;
63
- return validator.buildErrorMessage(file);
64
- }
65
- };
66
-
67
- //#endregion
68
- //#region src/interceptors/index.ts
69
- function MultipartInterceptor(options = {}) {
70
- class MixinInterceptor {
71
- async intercept(context, next) {
72
- const req = context.switchToHttp().getRequest();
73
- if (!req.isMultipart()) throw new __nestjs_common.HttpException("The request should be a form-data", __nestjs_common.HttpStatus.BAD_REQUEST);
74
- const files = {};
75
- const body = {};
76
- for await (const part of req.parts()) {
77
- if (part.type !== "file") {
78
- body[part.fieldname] = part.value;
79
- continue;
80
- }
81
- const file = await getFileFromPart(part);
82
- const validationResult = validateFile(file, options);
83
- if (validationResult) throw new __nestjs_common.HttpException(validationResult, __nestjs_common.HttpStatus.UNPROCESSABLE_ENTITY);
84
- files[part.fieldname] = files[part.fieldname] || [];
85
- files[part.fieldname].push(file);
86
- }
87
- req.storedFiles = files;
88
- req.body = body;
89
- return next.handle();
90
- }
91
- }
92
- return (0, __nestjs_common.mixin)(MixinInterceptor);
93
- }
94
-
95
- //#endregion
96
- //#region src/model/index.ts
97
- var MultipartOptions = class {
98
- constructor(maxFileSize, fileType) {
99
- this.maxFileSize = maxFileSize;
100
- this.fileType = fileType;
101
- }
102
- };
103
-
104
- //#endregion
105
- exports.MultipartInterceptor = MultipartInterceptor;
106
- exports.MultipartOptions = MultipartOptions;
107
- exports.UploadedFile = UploadedFile;
108
- exports.UploadedFiles = UploadedFiles;
109
- exports.getFileFromPart = getFileFromPart;
110
- exports.validateFile = validateFile;
111
- //# sourceMappingURL=index.js.map
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":["allFiles: Storage.MultipartFile[]","validators: FileValidator[]","MaxFileSizeValidator","FileTypeValidator","HttpException","HttpStatus","files: Record<string, any[]>","body: Record<string, any>","maxFileSize?: number","fileType?: string | RegExp"],"sources":["../src/decorators/index.ts","../src/utils/index.ts","../src/interceptors/index.ts","../src/model/index.ts"],"sourcesContent":["import { createParamDecorator, ExecutionContext } from \"@nestjs/common\";\nimport * as fastify from \"fastify\";\n\nexport const UploadedFiles = createParamDecorator(\n (_data: unknown, ctx: ExecutionContext) => {\n const req = ctx.switchToHttp().getRequest() as fastify.FastifyRequest;\n const allFiles: Storage.MultipartFile[] = [];\n Object.values(req.storedFiles).forEach((files) => {\n allFiles.push(...files);\n });\n return allFiles;\n }\n);\n\nexport const UploadedFile = createParamDecorator(\n (fieldName: string | unknown, ctx: ExecutionContext) => {\n const req = ctx.switchToHttp().getRequest() as fastify.FastifyRequest;\n\n if (typeof fieldName === \"string\") {\n const files = req.storedFiles[fieldName];\n return files && files.length > 0 ? files[0] : null;\n }\n\n return req.storedFiles;\n }\n);\n","import { MultipartFile } from \"@fastify/multipart\";\nimport {\n FileTypeValidator,\n FileValidator,\n MaxFileSizeValidator,\n} from \"@nestjs/common\";\nimport { MultipartOptions } from \"../model\";\n\nexport const getFileFromPart = async (\n part: MultipartFile,\n): Promise<Storage.MultipartFile> => {\n const buffer = await part.toBuffer();\n return {\n buffer,\n size: buffer.byteLength,\n filename: part.filename,\n mimetype: part.mimetype,\n fieldname: part.fieldname,\n };\n};\n\nexport const validateFile = (\n file: Storage.MultipartFile,\n options: MultipartOptions,\n): string | void => {\n const validators: FileValidator[] = [];\n\n if (options.maxFileSize)\n validators.push(new MaxFileSizeValidator({ maxSize: options.maxFileSize }));\n if (options.fileType)\n validators.push(new FileTypeValidator({ fileType: options.fileType }));\n\n for (const validator of validators) {\n if (validator.isValid(file)) continue;\n\n return validator.buildErrorMessage(file);\n }\n};\n","import { Observable } from \"rxjs\";\nimport {\n CallHandler,\n ExecutionContext,\n HttpException,\n HttpStatus,\n mixin,\n NestInterceptor,\n Type,\n} from \"@nestjs/common\";\nimport * as fastify from \"fastify\";\nimport { MultipartValue } from \"@fastify/multipart\";\nimport { MultipartOptions } from \"../model\";\nimport { getFileFromPart, validateFile } from \"../utils\";\n\nexport function MultipartInterceptor(\n options: MultipartOptions = {},\n): Type<NestInterceptor> {\n class MixinInterceptor implements NestInterceptor {\n async intercept(\n context: ExecutionContext,\n next: CallHandler,\n ): Promise<Observable<any>> {\n const req = context.switchToHttp().getRequest() as fastify.FastifyRequest;\n\n if (!req.isMultipart())\n throw new HttpException(\n \"The request should be a form-data\",\n HttpStatus.BAD_REQUEST,\n );\n\n const files: Record<string, any[]> = {};\n const body: Record<string, any> = {};\n for await (const part of req.parts()) {\n if (part.type !== \"file\") {\n body[part.fieldname] = (part as MultipartValue).value;\n continue;\n }\n const file = await getFileFromPart(part);\n const validationResult = validateFile(file, options);\n\n if (validationResult)\n throw new HttpException(\n validationResult,\n HttpStatus.UNPROCESSABLE_ENTITY,\n );\n\n files[part.fieldname] = files[part.fieldname] || [];\n files[part.fieldname].push(file);\n }\n req.storedFiles = files;\n req.body = body;\n\n return next.handle();\n }\n }\n\n return mixin(MixinInterceptor);\n}\n","export class MultipartOptions {\n constructor(\n public maxFileSize?: number,\n public fileType?: string | RegExp,\n ) {}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAGA,MAAa,2DACV,OAAgB,QAA0B;CACzC,MAAM,MAAM,IAAI,eAAe;CAC/B,MAAMA,WAAoC;AAC1C,QAAO,OAAO,IAAI,aAAa,SAAS,UAAU;AAChD,WAAS,KAAK,GAAG;;AAEnB,QAAO;;AAIX,MAAa,0DACV,WAA6B,QAA0B;CACtD,MAAM,MAAM,IAAI,eAAe;AAE/B,KAAI,OAAO,cAAc,UAAU;EACjC,MAAM,QAAQ,IAAI,YAAY;AAC9B,SAAO,SAAS,MAAM,SAAS,IAAI,MAAM,KAAK;;AAGhD,QAAO,IAAI;;;;;ACff,MAAa,kBAAkB,OAC7B,SACmC;CACnC,MAAM,SAAS,MAAM,KAAK;AAC1B,QAAO;EACL;EACA,MAAM,OAAO;EACb,UAAU,KAAK;EACf,UAAU,KAAK;EACf,WAAW,KAAK;;;AAIpB,MAAa,gBACX,MACA,YACkB;CAClB,MAAMC,aAA8B;AAEpC,KAAI,QAAQ,YACV,YAAW,KAAK,IAAIC,qCAAqB,EAAE,SAAS,QAAQ;AAC9D,KAAI,QAAQ,SACV,YAAW,KAAK,IAAIC,kCAAkB,EAAE,UAAU,QAAQ;AAE5D,MAAK,MAAM,aAAa,YAAY;AAClC,MAAI,UAAU,QAAQ,MAAO;AAE7B,SAAO,UAAU,kBAAkB;;;;;;ACpBvC,SAAgB,qBACd,UAA4B,IACL;CACvB,MAAM,iBAA4C;EAChD,MAAM,UACJ,SACA,MAC0B;GAC1B,MAAM,MAAM,QAAQ,eAAe;AAEnC,OAAI,CAAC,IAAI,cACP,OAAM,IAAIC,8BACR,qCACAC,2BAAW;GAGf,MAAMC,QAA+B;GACrC,MAAMC,OAA4B;AAClC,cAAW,MAAM,QAAQ,IAAI,SAAS;AACpC,QAAI,KAAK,SAAS,QAAQ;AACxB,UAAK,KAAK,aAAc,KAAwB;AAChD;;IAEF,MAAM,OAAO,MAAM,gBAAgB;IACnC,MAAM,mBAAmB,aAAa,MAAM;AAE5C,QAAI,iBACF,OAAM,IAAIH,8BACR,kBACAC,2BAAW;AAGf,UAAM,KAAK,aAAa,MAAM,KAAK,cAAc;AACjD,UAAM,KAAK,WAAW,KAAK;;AAE7B,OAAI,cAAc;AAClB,OAAI,OAAO;AAEX,UAAO,KAAK;;;AAIhB,mCAAa;;;;;ACzDf,IAAa,mBAAb,MAA8B;CAC5B,YACE,AAAOG,aACP,AAAOC,UACP;EAFO;EACA"}