multermate 2.2.1 → 2.3.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/dist/esm/index.js CHANGED
@@ -1,13 +1,13 @@
1
- import fs from 'fs/promises';
2
- import { mkdirSync } from 'fs';
3
- import multer from 'multer';
4
- import path from 'path';
5
- import { v4 as uuidv4 } from 'uuid';
1
+ import { mkdirSync } from "fs";
2
+ import fs from "fs/promises";
3
+ import multer from "multer";
4
+ import path from "path";
5
+ import { v4 as uuidv4 } from "uuid";
6
6
  // Custom error class for MulterMate
7
7
  export class MultermateError extends Error {
8
8
  constructor(message, code, field) {
9
9
  super(message);
10
- this.name = 'MultermateError';
10
+ this.name = "MultermateError";
11
11
  this.code = code;
12
12
  this.field = field;
13
13
  // Maintains proper stack trace for where our error was thrown (only available on V8)
@@ -20,131 +20,310 @@ export class MultermateError extends Error {
20
20
  const ALLOWED_MIME_TYPES = {
21
21
  // Images (all common image formats)
22
22
  images: [
23
- "image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp",
24
- "image/svg+xml", "image/bmp", "image/tiff", "image/ico", "image/avif",
25
- "image/heic", "image/heif", "image/x-icon", "image/vnd.microsoft.icon"
23
+ "image/jpeg",
24
+ "image/jpg",
25
+ "image/png",
26
+ "image/gif",
27
+ "image/webp",
28
+ "image/svg+xml",
29
+ "image/bmp",
30
+ "image/tiff",
31
+ "image/ico",
32
+ "image/avif",
33
+ "image/heic",
34
+ "image/heif",
35
+ "image/x-icon",
36
+ "image/vnd.microsoft.icon",
26
37
  ],
27
38
  // Videos (all common video formats)
28
39
  videos: [
29
- "video/mp4", "video/mpeg", "video/ogg", "video/webm", "video/avi",
30
- "video/mov", "video/wmv", "video/flv", "video/mkv", "video/m4v", "video/3gp",
31
- "video/quicktime", "video/x-msvideo", "video/x-ms-wmv", "video/x-flv"
40
+ "video/mp4",
41
+ "video/mpeg",
42
+ "video/ogg",
43
+ "video/webm",
44
+ "video/avi",
45
+ "video/mov",
46
+ "video/wmv",
47
+ "video/flv",
48
+ "video/mkv",
49
+ "video/m4v",
50
+ "video/3gp",
51
+ "video/quicktime",
52
+ "video/x-msvideo",
53
+ "video/x-ms-wmv",
54
+ "video/x-flv",
32
55
  ],
33
56
  // Audio (all common audio formats)
34
57
  audio: [
35
- "audio/mpeg", "audio/wav", "audio/ogg", "audio/aac", "audio/flac",
36
- "audio/m4a", "audio/wma", "audio/mp3", "audio/webm", "audio/x-wav",
37
- "audio/x-m4a", "audio/x-aac", "audio/opus", "audio/amr"
58
+ "audio/mpeg",
59
+ "audio/wav",
60
+ "audio/ogg",
61
+ "audio/aac",
62
+ "audio/flac",
63
+ "audio/m4a",
64
+ "audio/wma",
65
+ "audio/mp3",
66
+ "audio/webm",
67
+ "audio/x-wav",
68
+ "audio/x-m4a",
69
+ "audio/x-aac",
70
+ "audio/opus",
71
+ "audio/amr",
38
72
  ],
39
73
  // Documents (all common document formats)
40
74
  documents: [
41
- "application/pdf", "application/msword",
75
+ "application/pdf",
76
+ "application/msword",
42
77
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
43
78
  "application/vnd.ms-excel",
44
79
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
45
80
  "application/vnd.ms-powerpoint",
46
81
  "application/vnd.openxmlformats-officedocument.presentationml.presentation",
47
- "application/rtf", "application/vnd.oasis.opendocument.text",
48
- "application/vnd.oasis.opendocument.spreadsheet", "application/vnd.oasis.opendocument.presentation",
49
- "application/vnd.apple.pages", "application/vnd.apple.numbers", "application/vnd.apple.keynote"
82
+ "application/rtf",
83
+ "application/vnd.oasis.opendocument.text",
84
+ "application/vnd.oasis.opendocument.spreadsheet",
85
+ "application/vnd.oasis.opendocument.presentation",
86
+ "application/vnd.apple.pages",
87
+ "application/vnd.apple.numbers",
88
+ "application/vnd.apple.keynote",
50
89
  ],
51
90
  // Text files (all common text formats)
52
91
  text: [
53
- "text/plain", "text/csv", "text/html", "text/css", "text/javascript",
54
- "text/xml", "text/markdown", "text/x-python", "text/x-java-source",
55
- "text/x-c", "text/x-c++", "text/x-php", "text/x-ruby", "text/x-go",
56
- "text/x-rust", "text/x-typescript", "text/x-swift", "text/x-kotlin",
57
- "text/x-scala", "text/x-perl", "text/x-shell", "text/x-sh", "text/x-bash",
58
- "text/x-yaml", "text/yaml", "text/x-toml", "text/x-ini", "text/x-log"
92
+ "text/plain",
93
+ "text/csv",
94
+ "text/html",
95
+ "text/css",
96
+ "text/javascript",
97
+ "text/xml",
98
+ "text/markdown",
99
+ "text/x-python",
100
+ "text/x-java-source",
101
+ "text/x-c",
102
+ "text/x-c++",
103
+ "text/x-php",
104
+ "text/x-ruby",
105
+ "text/x-go",
106
+ "text/x-rust",
107
+ "text/x-typescript",
108
+ "text/x-swift",
109
+ "text/x-kotlin",
110
+ "text/x-scala",
111
+ "text/x-perl",
112
+ "text/x-shell",
113
+ "text/x-sh",
114
+ "text/x-bash",
115
+ "text/x-yaml",
116
+ "text/yaml",
117
+ "text/x-toml",
118
+ "text/x-ini",
119
+ "text/x-log",
59
120
  ],
60
121
  // Archives (all common archive formats)
61
122
  archives: [
62
- "application/zip", "application/x-rar-compressed", "application/x-tar",
63
- "application/gzip", "application/x-7z-compressed", "application/x-bzip2",
64
- "application/x-xz", "application/x-compress", "application/x-lz4",
65
- "application/x-lzma", "application/vnd.rar"
123
+ "application/zip",
124
+ "application/x-rar-compressed",
125
+ "application/x-tar",
126
+ "application/gzip",
127
+ "application/x-7z-compressed",
128
+ "application/x-bzip2",
129
+ "application/x-xz",
130
+ "application/x-compress",
131
+ "application/x-lz4",
132
+ "application/x-lzma",
133
+ "application/vnd.rar",
66
134
  ],
67
135
  // Code files (programming language files)
68
136
  code: [
69
- "application/json", "application/xml", "application/javascript",
70
- "application/typescript", "text/x-python", "text/x-java-source",
71
- "text/x-c", "text/x-c++", "text/x-php", "text/x-ruby", "text/x-go",
72
- "text/x-rust", "text/x-swift", "text/x-kotlin", "text/x-scala",
73
- "text/x-csharp", "text/x-vb", "text/x-sql", "application/sql"
137
+ "application/json",
138
+ "application/xml",
139
+ "application/javascript",
140
+ "application/typescript",
141
+ "text/x-python",
142
+ "text/x-java-source",
143
+ "text/x-c",
144
+ "text/x-c++",
145
+ "text/x-php",
146
+ "text/x-ruby",
147
+ "text/x-go",
148
+ "text/x-rust",
149
+ "text/x-swift",
150
+ "text/x-kotlin",
151
+ "text/x-scala",
152
+ "text/x-csharp",
153
+ "text/x-vb",
154
+ "text/x-sql",
155
+ "application/sql",
74
156
  ],
75
157
  // Spreadsheets (separate category)
76
158
  spreadsheets: [
77
159
  "application/vnd.ms-excel",
78
160
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
79
- "text/csv", "application/csv"
161
+ "text/csv",
162
+ "application/csv",
80
163
  ],
81
164
  // Presentations (separate category)
82
165
  presentations: [
83
166
  "application/vnd.ms-powerpoint",
84
167
  "application/vnd.openxmlformats-officedocument.presentationml.presentation",
85
- "application/vnd.oasis.opendocument.presentation"
168
+ "application/vnd.oasis.opendocument.presentation",
86
169
  ],
87
170
  // Fonts (font files)
88
171
  fonts: [
89
- "font/woff", "font/woff2", "font/ttf", "font/otf", "font/eot",
90
- "application/font-woff", "application/font-woff2", "application/x-font-ttf",
91
- "application/x-font-otf", "application/vnd.ms-fontobject"
172
+ "font/woff",
173
+ "font/woff2",
174
+ "font/ttf",
175
+ "font/otf",
176
+ "font/eot",
177
+ "application/font-woff",
178
+ "application/font-woff2",
179
+ "application/x-font-ttf",
180
+ "application/x-font-otf",
181
+ "application/vnd.ms-fontobject",
92
182
  ],
93
183
  // CAD files
94
184
  cad: [
95
- "application/dwg", "application/dxf", "model/vnd.dwf",
96
- "application/acad", "image/vnd.dwg"
185
+ "application/dwg",
186
+ "application/dxf",
187
+ "model/vnd.dwf",
188
+ "application/acad",
189
+ "image/vnd.dwg",
97
190
  ],
98
191
  // 3D models
99
192
  models: [
100
- "model/obj", "model/gltf+json", "model/gltf-binary", "model/x3d+xml",
101
- "model/stl", "model/ply", "application/x-blender"
193
+ "model/obj",
194
+ "model/gltf+json",
195
+ "model/gltf-binary",
196
+ "model/x3d+xml",
197
+ "model/stl",
198
+ "model/ply",
199
+ "application/x-blender",
102
200
  ],
103
201
  // PDFs (separate category for backward compatibility)
104
202
  pdfs: ["application/pdf"],
105
203
  // All allowed types - comprehensive list (this is for backward compatibility)
106
204
  all: [
107
205
  // Images
108
- "image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp",
109
- "image/svg+xml", "image/bmp", "image/tiff", "image/ico", "image/avif",
110
- "image/heic", "image/heif", "image/x-icon", "image/vnd.microsoft.icon",
206
+ "image/jpeg",
207
+ "image/jpg",
208
+ "image/png",
209
+ "image/gif",
210
+ "image/webp",
211
+ "image/svg+xml",
212
+ "image/bmp",
213
+ "image/tiff",
214
+ "image/ico",
215
+ "image/avif",
216
+ "image/heic",
217
+ "image/heif",
218
+ "image/x-icon",
219
+ "image/vnd.microsoft.icon",
111
220
  // Videos
112
- "video/mp4", "video/mpeg", "video/ogg", "video/webm", "video/avi",
113
- "video/mov", "video/wmv", "video/flv", "video/mkv", "video/m4v", "video/3gp",
114
- "video/quicktime", "video/x-msvideo", "video/x-ms-wmv", "video/x-flv",
221
+ "video/mp4",
222
+ "video/mpeg",
223
+ "video/ogg",
224
+ "video/webm",
225
+ "video/avi",
226
+ "video/mov",
227
+ "video/wmv",
228
+ "video/flv",
229
+ "video/mkv",
230
+ "video/m4v",
231
+ "video/3gp",
232
+ "video/quicktime",
233
+ "video/x-msvideo",
234
+ "video/x-ms-wmv",
235
+ "video/x-flv",
115
236
  // Audio
116
- "audio/mpeg", "audio/wav", "audio/ogg", "audio/aac", "audio/flac",
117
- "audio/m4a", "audio/wma", "audio/mp3", "audio/webm", "audio/x-wav",
118
- "audio/x-m4a", "audio/x-aac", "audio/opus", "audio/amr",
237
+ "audio/mpeg",
238
+ "audio/wav",
239
+ "audio/ogg",
240
+ "audio/aac",
241
+ "audio/flac",
242
+ "audio/m4a",
243
+ "audio/wma",
244
+ "audio/mp3",
245
+ "audio/webm",
246
+ "audio/x-wav",
247
+ "audio/x-m4a",
248
+ "audio/x-aac",
249
+ "audio/opus",
250
+ "audio/amr",
119
251
  // Documents
120
- "application/pdf", "application/msword",
252
+ "application/pdf",
253
+ "application/msword",
121
254
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
122
255
  "application/vnd.ms-excel",
123
256
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
124
257
  "application/vnd.ms-powerpoint",
125
258
  "application/vnd.openxmlformats-officedocument.presentationml.presentation",
126
- "application/rtf", "application/vnd.oasis.opendocument.text",
127
- "application/vnd.oasis.opendocument.spreadsheet", "application/vnd.oasis.opendocument.presentation",
128
- "application/vnd.apple.pages", "application/vnd.apple.numbers", "application/vnd.apple.keynote",
259
+ "application/rtf",
260
+ "application/vnd.oasis.opendocument.text",
261
+ "application/vnd.oasis.opendocument.spreadsheet",
262
+ "application/vnd.oasis.opendocument.presentation",
263
+ "application/vnd.apple.pages",
264
+ "application/vnd.apple.numbers",
265
+ "application/vnd.apple.keynote",
129
266
  // Text files
130
- "text/plain", "text/csv", "text/html", "text/css", "text/javascript",
131
- "text/xml", "text/markdown", "text/x-python", "text/x-java-source",
132
- "text/x-c", "text/x-c++", "text/x-php", "text/x-ruby", "text/x-go",
133
- "text/x-rust", "text/x-typescript", "text/x-swift", "text/x-kotlin",
134
- "text/x-scala", "text/x-perl", "text/x-shell", "text/x-sh", "text/x-bash",
135
- "text/x-yaml", "text/yaml", "text/x-toml", "text/x-ini", "text/x-log",
267
+ "text/plain",
268
+ "text/csv",
269
+ "text/html",
270
+ "text/css",
271
+ "text/javascript",
272
+ "text/xml",
273
+ "text/markdown",
274
+ "text/x-python",
275
+ "text/x-java-source",
276
+ "text/x-c",
277
+ "text/x-c++",
278
+ "text/x-php",
279
+ "text/x-ruby",
280
+ "text/x-go",
281
+ "text/x-rust",
282
+ "text/x-typescript",
283
+ "text/x-swift",
284
+ "text/x-kotlin",
285
+ "text/x-scala",
286
+ "text/x-perl",
287
+ "text/x-shell",
288
+ "text/x-sh",
289
+ "text/x-bash",
290
+ "text/x-yaml",
291
+ "text/yaml",
292
+ "text/x-toml",
293
+ "text/x-ini",
294
+ "text/x-log",
136
295
  // Archives
137
- "application/zip", "application/x-rar-compressed", "application/x-tar",
138
- "application/gzip", "application/x-7z-compressed", "application/x-bzip2",
139
- "application/x-xz", "application/x-compress", "application/x-lz4",
140
- "application/x-lzma", "application/vnd.rar",
296
+ "application/zip",
297
+ "application/x-rar-compressed",
298
+ "application/x-tar",
299
+ "application/gzip",
300
+ "application/x-7z-compressed",
301
+ "application/x-bzip2",
302
+ "application/x-xz",
303
+ "application/x-compress",
304
+ "application/x-lz4",
305
+ "application/x-lzma",
306
+ "application/vnd.rar",
141
307
  // Code/Data
142
- "application/json", "application/xml", "application/javascript",
143
- "application/typescript", "text/x-csharp", "text/x-vb", "text/x-sql", "application/sql",
308
+ "application/json",
309
+ "application/xml",
310
+ "application/javascript",
311
+ "application/typescript",
312
+ "text/x-csharp",
313
+ "text/x-vb",
314
+ "text/x-sql",
315
+ "application/sql",
144
316
  // Fonts
145
- "font/woff", "font/woff2", "font/ttf", "font/otf", "font/eot",
146
- "application/font-woff", "application/font-woff2", "application/x-font-ttf",
147
- "application/x-font-otf", "application/vnd.ms-fontobject"
317
+ "font/woff",
318
+ "font/woff2",
319
+ "font/ttf",
320
+ "font/otf",
321
+ "font/eot",
322
+ "application/font-woff",
323
+ "application/font-woff2",
324
+ "application/x-font-ttf",
325
+ "application/x-font-otf",
326
+ "application/vnd.ms-fontobject",
148
327
  ],
149
328
  };
150
329
  /**
@@ -164,11 +343,11 @@ const configureStorage = (destination) => {
164
343
  }
165
344
  catch (error) {
166
345
  // Directory might already exist, that's okay
167
- if (error.code === 'EEXIST') {
346
+ if (error.code === "EEXIST") {
168
347
  cb(null, dir);
169
348
  }
170
349
  else {
171
- cb(new MultermateError(`Failed to create destination directory: ${dir}. Error: ${error.message}`, 'DESTINATION_ERROR'), '');
350
+ cb(new MultermateError(`Failed to create destination directory: ${dir}. Error: ${error.message}`, "DESTINATION_ERROR"), "");
172
351
  }
173
352
  }
174
353
  },
@@ -184,26 +363,93 @@ const configureStorage = (destination) => {
184
363
  cb(null, fileName);
185
364
  }
186
365
  catch (error) {
187
- cb(new MultermateError('Failed to generate filename', 'FILENAME_ERROR'), '');
366
+ cb(new MultermateError("Failed to generate filename", "FILENAME_ERROR"), "");
188
367
  }
189
368
  },
190
369
  });
191
370
  };
192
- const normalizePathForDb = (value) => value.replace(/\\/g, '/');
371
+ const normalizePathForDb = (value) => value.replace(/\\/g, "/");
372
+ const FILE_KIND_TO_CATEGORIES = {
373
+ image: ["images"],
374
+ video: ["videos"],
375
+ audio: ["audio"],
376
+ document: ["documents"],
377
+ text: ["text"],
378
+ archive: ["archives"],
379
+ code: ["code"],
380
+ spreadsheet: ["spreadsheets"],
381
+ presentation: ["presentations"],
382
+ font: ["fonts"],
383
+ cad: ["cad"],
384
+ model: ["models"],
385
+ mix: ["images", "videos", "audio", "documents", "text", "archives"],
386
+ any: ["all"],
387
+ };
388
+ const FILE_TYPE_ALIASES = {
389
+ image: ["images"],
390
+ images: ["images"],
391
+ video: ["videos"],
392
+ videos: ["videos"],
393
+ document: ["documents"],
394
+ documents: ["documents"],
395
+ archive: ["archives"],
396
+ archives: ["archives"],
397
+ spreadsheet: ["spreadsheets"],
398
+ spreadsheets: ["spreadsheets"],
399
+ presentation: ["presentations"],
400
+ presentations: ["presentations"],
401
+ font: ["fonts"],
402
+ fonts: ["fonts"],
403
+ model: ["models"],
404
+ models: ["models"],
405
+ mixed: ["images", "videos", "audio", "documents", "text", "archives"],
406
+ mix: ["images", "videos", "audio", "documents", "text", "archives"],
407
+ any: ["all"],
408
+ };
409
+ const resolveAllowedMimeTypes = (fileTypes = [], fileKinds = []) => {
410
+ const selectedCategories = new Set();
411
+ const invalidSelectors = [];
412
+ fileKinds.forEach((kind) => {
413
+ const categories = FILE_KIND_TO_CATEGORIES[kind];
414
+ categories.forEach((category) => selectedCategories.add(category));
415
+ });
416
+ fileTypes.forEach((rawType) => {
417
+ const normalized = String(rawType).toLowerCase();
418
+ const aliasCategories = FILE_TYPE_ALIASES[normalized];
419
+ if (aliasCategories) {
420
+ aliasCategories.forEach((category) => selectedCategories.add(category));
421
+ return;
422
+ }
423
+ if (ALLOWED_MIME_TYPES[normalized]) {
424
+ selectedCategories.add(normalized);
425
+ return;
426
+ }
427
+ invalidSelectors.push(rawType);
428
+ });
429
+ let allowedMimeTypes = [];
430
+ selectedCategories.forEach((category) => {
431
+ allowedMimeTypes = allowedMimeTypes.concat(ALLOWED_MIME_TYPES[category] || []);
432
+ });
433
+ return {
434
+ allowedMimeTypes: [...new Set(allowedMimeTypes)],
435
+ invalidSelectors,
436
+ };
437
+ };
193
438
  const getPhysicalDestination = (destination, absoluteDestination) => {
194
439
  if (!absoluteDestination) {
195
- return destination || 'uploads';
440
+ return destination || "uploads";
196
441
  }
197
442
  if (!path.isAbsolute(absoluteDestination)) {
198
- throw new MultermateError(`absoluteDestination must be an absolute path. Received: ${absoluteDestination}`, 'INVALID_ABSOLUTE_DESTINATION');
443
+ throw new MultermateError(`absoluteDestination must be an absolute path. Received: ${absoluteDestination}`, "INVALID_ABSOLUTE_DESTINATION");
199
444
  }
200
- return absoluteDestination;
445
+ const destinationSegment = (destination || "uploads").replace(/^[\\/]+/, "");
446
+ return path.join(absoluteDestination, destinationSegment);
201
447
  };
202
448
  const sanitizeStoredPathsForDb = (req, destination, absoluteDestination) => {
203
449
  if (!absoluteDestination) {
204
450
  return;
205
451
  }
206
- const dbDestination = normalizePathForDb(destination || 'uploads');
452
+ const dbDestination = normalizePathForDb(destination || "uploads");
207
453
  if (req.file) {
208
454
  req.file.destination = dbDestination;
209
455
  req.file.path = normalizePathForDb(path.join(dbDestination, req.file.filename));
@@ -236,12 +482,12 @@ const configureFileFilter = (allowedMimeTypes) => {
236
482
  cb(null, true);
237
483
  }
238
484
  else {
239
- const error = new MultermateError(`Invalid file type: ${file.mimetype}. Allowed types: ${allowedMimeTypes.join(', ')}`, 'INVALID_FILE_TYPE', file.fieldname);
485
+ const error = new MultermateError(`Invalid file type: ${file.mimetype}. Allowed types: ${allowedMimeTypes.join(", ")}`, "INVALID_FILE_TYPE", file.fieldname);
240
486
  cb(error);
241
487
  }
242
488
  }
243
489
  catch (error) {
244
- cb(new MultermateError('File filter error', 'FILTER_ERROR'));
490
+ cb(new MultermateError("File filter error", "FILTER_ERROR"));
245
491
  }
246
492
  };
247
493
  };
@@ -251,7 +497,7 @@ const configureFileFilter = (allowedMimeTypes) => {
251
497
  * @param options - Configuration options for Multer.
252
498
  * @returns Multer instance configured with the provided options.
253
499
  */
254
- const configureMulter = ({ destination, absoluteDestination, filename, fileTypes = [], customMimeTypes = [], fileSizeLimit, preservePath = false, }) => {
500
+ const configureMulter = ({ destination, absoluteDestination, filename, fileKinds = [], fileTypes = [], customMimeTypes = [], fileSizeLimit, preservePath = false, }) => {
255
501
  try {
256
502
  const storage = configureStorage(getPhysicalDestination(destination, absoluteDestination));
257
503
  // Combine allowed MIME types based on fileTypes array
@@ -260,13 +506,13 @@ const configureMulter = ({ destination, absoluteDestination, filename, fileTypes
260
506
  // Use custom MIME types if provided
261
507
  allowedMimeTypes = customMimeTypes;
262
508
  }
263
- else if (fileTypes.length > 0) {
264
- // Use default MIME types for specified fileTypes
265
- fileTypes.forEach((type) => {
266
- if (ALLOWED_MIME_TYPES[type]) {
267
- allowedMimeTypes = allowedMimeTypes.concat(ALLOWED_MIME_TYPES[type]);
268
- }
269
- });
509
+ else if (fileTypes.length > 0 || fileKinds.length > 0) {
510
+ const { allowedMimeTypes: resolvedMimeTypes, invalidSelectors } = resolveAllowedMimeTypes(fileTypes, fileKinds);
511
+ if (invalidSelectors.length > 0) {
512
+ throw new MultermateError(`Unknown file type selectors: ${invalidSelectors.join(", ")}. ` +
513
+ `Use supported categories from ALLOWED_FILE_TYPES or fileKinds (image, document, video, mix, etc.).`, "INVALID_FILE_TYPE_SELECTOR");
514
+ }
515
+ allowedMimeTypes = resolvedMimeTypes;
270
516
  }
271
517
  // If neither customMimeTypes nor fileTypes are provided, allowedMimeTypes remains empty
272
518
  // This means ALL file types are allowed (no restrictions)
@@ -281,7 +527,7 @@ const configureMulter = ({ destination, absoluteDestination, filename, fileTypes
281
527
  });
282
528
  }
283
529
  catch (error) {
284
- throw new MultermateError('Failed to configure multer', 'CONFIGURATION_ERROR');
530
+ throw new MultermateError("Failed to configure multer", "CONFIGURATION_ERROR");
285
531
  }
286
532
  };
287
533
  /**
@@ -305,27 +551,28 @@ export function uploadSingle(options = {}) {
305
551
  }
306
552
  middleware(req, res, (err) => {
307
553
  if (err) {
308
- let errorMessage = 'Unknown upload error';
309
- let errorCode = 'UPLOAD_ERROR';
554
+ let errorMessage = "Unknown upload error";
555
+ let errorCode = "UPLOAD_ERROR";
310
556
  if (err instanceof MultermateError) {
311
557
  // Our custom error
312
558
  req.fileValidationError = err.message;
313
559
  return next(err);
314
560
  }
315
- else if (err.code === 'LIMIT_FILE_SIZE') {
316
- errorMessage = `File size limit exceeded. Maximum allowed size: ${options.fileSizeLimit || '50MB'}`;
317
- errorCode = 'FILE_SIZE_LIMIT_EXCEEDED';
561
+ else if (err.code === "LIMIT_FILE_SIZE") {
562
+ errorMessage = `File size limit exceeded. Maximum allowed size: ${options.fileSizeLimit || "50MB"}`;
563
+ errorCode = "FILE_SIZE_LIMIT_EXCEEDED";
318
564
  }
319
- else if (err.code === 'INVALID_FILE_TYPE') {
320
- errorMessage = 'Invalid file type. Please check allowed file types.';
321
- errorCode = 'INVALID_FILE_TYPE';
565
+ else if (err.code === "INVALID_FILE_TYPE") {
566
+ errorMessage =
567
+ "Invalid file type. Please check allowed file types.";
568
+ errorCode = "INVALID_FILE_TYPE";
322
569
  }
323
- else if (err.code === 'LIMIT_UNEXPECTED_FILE') {
324
- errorMessage = 'Unexpected field';
325
- errorCode = 'UNEXPECTED_FIELD';
570
+ else if (err.code === "LIMIT_UNEXPECTED_FILE") {
571
+ errorMessage = "Unexpected field";
572
+ errorCode = "UNEXPECTED_FIELD";
326
573
  }
327
574
  else {
328
- errorMessage = err.message || 'Upload failed';
575
+ errorMessage = err.message || "Upload failed";
329
576
  }
330
577
  const multermateError = new MultermateError(errorMessage, errorCode);
331
578
  req.fileValidationError = errorMessage;
@@ -337,7 +584,7 @@ export function uploadSingle(options = {}) {
337
584
  };
338
585
  }
339
586
  catch (error) {
340
- throw new MultermateError('Failed to create upload middleware', 'MIDDLEWARE_CREATION_ERROR');
587
+ throw new MultermateError("Failed to create upload middleware", "MIDDLEWARE_CREATION_ERROR");
341
588
  }
342
589
  }
343
590
  /**
@@ -350,7 +597,7 @@ export function uploadMultiple(options) {
350
597
  try {
351
598
  const storageDestination = getPhysicalDestination(options.destination, options.absoluteDestination);
352
599
  // Map fields configuration to multer format
353
- const fieldConfigs = options.fields.map(field => ({
600
+ const fieldConfigs = options.fields.map((field) => ({
354
601
  name: field.name,
355
602
  maxCount: field.maxCount || 10, // Default maxCount is 10 if not specified.
356
603
  }));
@@ -363,12 +610,11 @@ export function uploadMultiple(options) {
363
610
  else {
364
611
  // Collect file types from individual fields
365
612
  options.fields.forEach((field) => {
366
- const types = field.fileTypes || [];
367
- types.forEach((type) => {
368
- if (ALLOWED_MIME_TYPES[type]) {
369
- allowedFileTypes = allowedFileTypes.concat(ALLOWED_MIME_TYPES[type]);
370
- }
371
- });
613
+ const { allowedMimeTypes, invalidSelectors } = resolveAllowedMimeTypes(field.fileTypes || [], field.fileKinds || []);
614
+ if (invalidSelectors.length > 0) {
615
+ throw new MultermateError(`Unknown file type selectors in field "${field.name}": ${invalidSelectors.join(", ")}`, "INVALID_FILE_TYPE_SELECTOR", field.name);
616
+ }
617
+ allowedFileTypes = allowedFileTypes.concat(allowedMimeTypes);
372
618
  });
373
619
  }
374
620
  const multerConfig = {
@@ -377,7 +623,7 @@ export function uploadMultiple(options) {
377
623
  fileTypes: [],
378
624
  customMimeTypes: allowedFileTypes.length > 0 ? allowedFileTypes : [],
379
625
  fileSizeLimit: options.fileSizeLimit,
380
- preservePath: options.preservePath
626
+ preservePath: options.preservePath,
381
627
  };
382
628
  const multerInstance = configureMulter(multerConfig);
383
629
  const middleware = multerInstance.fields(fieldConfigs);
@@ -391,31 +637,32 @@ export function uploadMultiple(options) {
391
637
  }
392
638
  middleware(req, res, (err) => {
393
639
  if (err) {
394
- let errorMessage = 'Unknown upload error';
395
- let errorCode = 'UPLOAD_ERROR';
640
+ let errorMessage = "Unknown upload error";
641
+ let errorCode = "UPLOAD_ERROR";
396
642
  if (err instanceof MultermateError) {
397
643
  // Our custom error
398
644
  req.fileValidationError = err.message;
399
645
  return next(err);
400
646
  }
401
- else if (err.code === 'LIMIT_FILE_SIZE') {
402
- errorMessage = `File size limit exceeded. Maximum allowed size: ${options.fileSizeLimit || '50MB'}`;
403
- errorCode = 'FILE_SIZE_LIMIT_EXCEEDED';
647
+ else if (err.code === "LIMIT_FILE_SIZE") {
648
+ errorMessage = `File size limit exceeded. Maximum allowed size: ${options.fileSizeLimit || "50MB"}`;
649
+ errorCode = "FILE_SIZE_LIMIT_EXCEEDED";
404
650
  }
405
- else if (err.code === 'INVALID_FILE_TYPE') {
406
- errorMessage = 'Invalid file type. Please check allowed file types.';
407
- errorCode = 'INVALID_FILE_TYPE';
651
+ else if (err.code === "INVALID_FILE_TYPE") {
652
+ errorMessage =
653
+ "Invalid file type. Please check allowed file types.";
654
+ errorCode = "INVALID_FILE_TYPE";
408
655
  }
409
- else if (err.code === 'LIMIT_UNEXPECTED_FILE') {
410
- errorMessage = 'Unexpected field';
411
- errorCode = 'UNEXPECTED_FIELD';
656
+ else if (err.code === "LIMIT_UNEXPECTED_FILE") {
657
+ errorMessage = "Unexpected field";
658
+ errorCode = "UNEXPECTED_FIELD";
412
659
  }
413
- else if (err.code === 'LIMIT_FILE_COUNT') {
414
- errorMessage = 'Too many files';
415
- errorCode = 'FILE_COUNT_LIMIT_EXCEEDED';
660
+ else if (err.code === "LIMIT_FILE_COUNT") {
661
+ errorMessage = "Too many files";
662
+ errorCode = "FILE_COUNT_LIMIT_EXCEEDED";
416
663
  }
417
664
  else {
418
- errorMessage = err.message || 'Upload failed';
665
+ errorMessage = err.message || "Upload failed";
419
666
  }
420
667
  const multermateError = new MultermateError(errorMessage, errorCode);
421
668
  req.fileValidationError = errorMessage;
@@ -427,7 +674,7 @@ export function uploadMultiple(options) {
427
674
  };
428
675
  }
429
676
  catch (error) {
430
- throw new MultermateError('Failed to create multiple upload middleware', 'MIDDLEWARE_CREATION_ERROR');
677
+ throw new MultermateError("Failed to create multiple upload middleware", "MIDDLEWARE_CREATION_ERROR");
431
678
  }
432
679
  }
433
680
  /**
@@ -438,8 +685,8 @@ export function uploadMultiple(options) {
438
685
  */
439
686
  export async function deleteFile(filePath) {
440
687
  try {
441
- if (!filePath || typeof filePath !== 'string') {
442
- throw new MultermateError('Invalid file path provided', 'INVALID_PATH');
688
+ if (!filePath || typeof filePath !== "string") {
689
+ throw new MultermateError("Invalid file path provided", "INVALID_PATH");
443
690
  }
444
691
  await fs.unlink(filePath);
445
692
  return true;
@@ -448,14 +695,14 @@ export async function deleteFile(filePath) {
448
695
  if (error instanceof MultermateError) {
449
696
  throw error;
450
697
  }
451
- if (error.code === 'ENOENT') {
452
- throw new MultermateError(`File not found: ${filePath}`, 'FILE_NOT_FOUND');
698
+ if (error.code === "ENOENT") {
699
+ throw new MultermateError(`File not found: ${filePath}`, "FILE_NOT_FOUND");
453
700
  }
454
- else if (error.code === 'EACCES') {
455
- throw new MultermateError(`Permission denied: ${filePath}`, 'PERMISSION_DENIED');
701
+ else if (error.code === "EACCES") {
702
+ throw new MultermateError(`Permission denied: ${filePath}`, "PERMISSION_DENIED");
456
703
  }
457
704
  else {
458
- throw new MultermateError(`Failed to delete file: ${error.message}`, 'DELETE_ERROR');
705
+ throw new MultermateError(`Failed to delete file: ${error.message}`, "DELETE_ERROR");
459
706
  }
460
707
  }
461
708
  }
@@ -470,5 +717,5 @@ export default {
470
717
  deleteFile,
471
718
  MultermateError,
472
719
  ALLOWED_FILE_TYPES,
473
- MIME_TYPES
720
+ MIME_TYPES,
474
721
  };