multermate 1.1.1 → 2.1.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/cjs/index.js +360 -119
- package/dist/esm/index.js +358 -118
- package/index.d.ts +5 -0
- package/index.js +8 -1
- package/index.mjs +3 -12
- package/package.json +25 -11
- package/readme.md +274 -113
- package/types/index.d.ts +9 -0
package/dist/esm/index.js
CHANGED
|
@@ -2,22 +2,148 @@ import fs from 'fs/promises';
|
|
|
2
2
|
import multer from 'multer';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
|
-
//
|
|
5
|
+
// Custom error class for MulterMate
|
|
6
|
+
export class MultermateError extends Error {
|
|
7
|
+
constructor(message, code, field) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'MultermateError';
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.field = field;
|
|
12
|
+
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
13
|
+
if (Error.captureStackTrace) {
|
|
14
|
+
Error.captureStackTrace(this, MultermateError);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
// Define allowed MIME types - comprehensive list including all common file types
|
|
6
19
|
const ALLOWED_MIME_TYPES = {
|
|
7
|
-
|
|
8
|
-
|
|
20
|
+
// Images (all common image formats)
|
|
21
|
+
images: [
|
|
22
|
+
"image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp",
|
|
23
|
+
"image/svg+xml", "image/bmp", "image/tiff", "image/ico", "image/avif",
|
|
24
|
+
"image/heic", "image/heif", "image/x-icon", "image/vnd.microsoft.icon"
|
|
25
|
+
],
|
|
26
|
+
// Videos (all common video formats)
|
|
27
|
+
videos: [
|
|
28
|
+
"video/mp4", "video/mpeg", "video/ogg", "video/webm", "video/avi",
|
|
29
|
+
"video/mov", "video/wmv", "video/flv", "video/mkv", "video/m4v", "video/3gp",
|
|
30
|
+
"video/quicktime", "video/x-msvideo", "video/x-ms-wmv", "video/x-flv"
|
|
31
|
+
],
|
|
32
|
+
// Audio (all common audio formats)
|
|
33
|
+
audio: [
|
|
34
|
+
"audio/mpeg", "audio/wav", "audio/ogg", "audio/aac", "audio/flac",
|
|
35
|
+
"audio/m4a", "audio/wma", "audio/mp3", "audio/webm", "audio/x-wav",
|
|
36
|
+
"audio/x-m4a", "audio/x-aac", "audio/opus", "audio/amr"
|
|
37
|
+
],
|
|
38
|
+
// Documents (all common document formats)
|
|
39
|
+
documents: [
|
|
40
|
+
"application/pdf", "application/msword",
|
|
41
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
42
|
+
"application/vnd.ms-excel",
|
|
43
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
44
|
+
"application/vnd.ms-powerpoint",
|
|
45
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
46
|
+
"application/rtf", "application/vnd.oasis.opendocument.text",
|
|
47
|
+
"application/vnd.oasis.opendocument.spreadsheet", "application/vnd.oasis.opendocument.presentation",
|
|
48
|
+
"application/vnd.apple.pages", "application/vnd.apple.numbers", "application/vnd.apple.keynote"
|
|
49
|
+
],
|
|
50
|
+
// Text files (all common text formats)
|
|
51
|
+
text: [
|
|
52
|
+
"text/plain", "text/csv", "text/html", "text/css", "text/javascript",
|
|
53
|
+
"text/xml", "text/markdown", "text/x-python", "text/x-java-source",
|
|
54
|
+
"text/x-c", "text/x-c++", "text/x-php", "text/x-ruby", "text/x-go",
|
|
55
|
+
"text/x-rust", "text/x-typescript", "text/x-swift", "text/x-kotlin",
|
|
56
|
+
"text/x-scala", "text/x-perl", "text/x-shell", "text/x-sh", "text/x-bash",
|
|
57
|
+
"text/x-yaml", "text/yaml", "text/x-toml", "text/x-ini", "text/x-log"
|
|
58
|
+
],
|
|
59
|
+
// Archives (all common archive formats)
|
|
60
|
+
archives: [
|
|
61
|
+
"application/zip", "application/x-rar-compressed", "application/x-tar",
|
|
62
|
+
"application/gzip", "application/x-7z-compressed", "application/x-bzip2",
|
|
63
|
+
"application/x-xz", "application/x-compress", "application/x-lz4",
|
|
64
|
+
"application/x-lzma", "application/vnd.rar"
|
|
65
|
+
],
|
|
66
|
+
// Code files (programming language files)
|
|
67
|
+
code: [
|
|
68
|
+
"application/json", "application/xml", "application/javascript",
|
|
69
|
+
"application/typescript", "text/x-python", "text/x-java-source",
|
|
70
|
+
"text/x-c", "text/x-c++", "text/x-php", "text/x-ruby", "text/x-go",
|
|
71
|
+
"text/x-rust", "text/x-swift", "text/x-kotlin", "text/x-scala",
|
|
72
|
+
"text/x-csharp", "text/x-vb", "text/x-sql", "application/sql"
|
|
73
|
+
],
|
|
74
|
+
// Spreadsheets (separate category)
|
|
75
|
+
spreadsheets: [
|
|
76
|
+
"application/vnd.ms-excel",
|
|
77
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
78
|
+
"text/csv", "application/csv"
|
|
79
|
+
],
|
|
80
|
+
// Presentations (separate category)
|
|
81
|
+
presentations: [
|
|
82
|
+
"application/vnd.ms-powerpoint",
|
|
83
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
84
|
+
"application/vnd.oasis.opendocument.presentation"
|
|
85
|
+
],
|
|
86
|
+
// Fonts (font files)
|
|
87
|
+
fonts: [
|
|
88
|
+
"font/woff", "font/woff2", "font/ttf", "font/otf", "font/eot",
|
|
89
|
+
"application/font-woff", "application/font-woff2", "application/x-font-ttf",
|
|
90
|
+
"application/x-font-otf", "application/vnd.ms-fontobject"
|
|
91
|
+
],
|
|
92
|
+
// CAD files
|
|
93
|
+
cad: [
|
|
94
|
+
"application/dwg", "application/dxf", "model/vnd.dwf",
|
|
95
|
+
"application/acad", "image/vnd.dwg"
|
|
96
|
+
],
|
|
97
|
+
// 3D models
|
|
98
|
+
models: [
|
|
99
|
+
"model/obj", "model/gltf+json", "model/gltf-binary", "model/x3d+xml",
|
|
100
|
+
"model/stl", "model/ply", "application/x-blender"
|
|
101
|
+
],
|
|
102
|
+
// PDFs (separate category for backward compatibility)
|
|
9
103
|
pdfs: ["application/pdf"],
|
|
104
|
+
// All allowed types - comprehensive list (this is for backward compatibility)
|
|
10
105
|
all: [
|
|
11
|
-
|
|
12
|
-
"image/jpg",
|
|
13
|
-
"image/
|
|
14
|
-
"image/
|
|
15
|
-
|
|
16
|
-
"video/mpeg",
|
|
17
|
-
"video/
|
|
18
|
-
"video/
|
|
19
|
-
|
|
20
|
-
"
|
|
106
|
+
// Images
|
|
107
|
+
"image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp",
|
|
108
|
+
"image/svg+xml", "image/bmp", "image/tiff", "image/ico", "image/avif",
|
|
109
|
+
"image/heic", "image/heif", "image/x-icon", "image/vnd.microsoft.icon",
|
|
110
|
+
// Videos
|
|
111
|
+
"video/mp4", "video/mpeg", "video/ogg", "video/webm", "video/avi",
|
|
112
|
+
"video/mov", "video/wmv", "video/flv", "video/mkv", "video/m4v", "video/3gp",
|
|
113
|
+
"video/quicktime", "video/x-msvideo", "video/x-ms-wmv", "video/x-flv",
|
|
114
|
+
// Audio
|
|
115
|
+
"audio/mpeg", "audio/wav", "audio/ogg", "audio/aac", "audio/flac",
|
|
116
|
+
"audio/m4a", "audio/wma", "audio/mp3", "audio/webm", "audio/x-wav",
|
|
117
|
+
"audio/x-m4a", "audio/x-aac", "audio/opus", "audio/amr",
|
|
118
|
+
// Documents
|
|
119
|
+
"application/pdf", "application/msword",
|
|
120
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
121
|
+
"application/vnd.ms-excel",
|
|
122
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
123
|
+
"application/vnd.ms-powerpoint",
|
|
124
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
125
|
+
"application/rtf", "application/vnd.oasis.opendocument.text",
|
|
126
|
+
"application/vnd.oasis.opendocument.spreadsheet", "application/vnd.oasis.opendocument.presentation",
|
|
127
|
+
"application/vnd.apple.pages", "application/vnd.apple.numbers", "application/vnd.apple.keynote",
|
|
128
|
+
// Text files
|
|
129
|
+
"text/plain", "text/csv", "text/html", "text/css", "text/javascript",
|
|
130
|
+
"text/xml", "text/markdown", "text/x-python", "text/x-java-source",
|
|
131
|
+
"text/x-c", "text/x-c++", "text/x-php", "text/x-ruby", "text/x-go",
|
|
132
|
+
"text/x-rust", "text/x-typescript", "text/x-swift", "text/x-kotlin",
|
|
133
|
+
"text/x-scala", "text/x-perl", "text/x-shell", "text/x-sh", "text/x-bash",
|
|
134
|
+
"text/x-yaml", "text/yaml", "text/x-toml", "text/x-ini", "text/x-log",
|
|
135
|
+
// Archives
|
|
136
|
+
"application/zip", "application/x-rar-compressed", "application/x-tar",
|
|
137
|
+
"application/gzip", "application/x-7z-compressed", "application/x-bzip2",
|
|
138
|
+
"application/x-xz", "application/x-compress", "application/x-lz4",
|
|
139
|
+
"application/x-lzma", "application/vnd.rar",
|
|
140
|
+
// Code/Data
|
|
141
|
+
"application/json", "application/xml", "application/javascript",
|
|
142
|
+
"application/typescript", "text/x-csharp", "text/x-vb", "text/x-sql", "application/sql",
|
|
143
|
+
// Fonts
|
|
144
|
+
"font/woff", "font/woff2", "font/ttf", "font/otf", "font/eot",
|
|
145
|
+
"application/font-woff", "application/font-woff2", "application/x-font-ttf",
|
|
146
|
+
"application/x-font-otf", "application/vnd.ms-fontobject"
|
|
21
147
|
],
|
|
22
148
|
};
|
|
23
149
|
/**
|
|
@@ -28,36 +154,65 @@ const ALLOWED_MIME_TYPES = {
|
|
|
28
154
|
*/
|
|
29
155
|
const configureStorage = (destination) => {
|
|
30
156
|
return multer.diskStorage({
|
|
31
|
-
destination: (
|
|
32
|
-
|
|
157
|
+
destination: (req, file, cb) => {
|
|
158
|
+
const dir = destination || "uploads";
|
|
159
|
+
// Create directory synchronously to avoid callback issues
|
|
160
|
+
try {
|
|
161
|
+
require('fs').mkdirSync(dir, { recursive: true });
|
|
162
|
+
cb(null, dir);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
// Directory might already exist, that's okay
|
|
166
|
+
if (error.code === 'EEXIST') {
|
|
167
|
+
cb(null, dir);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
cb(new MultermateError(`Failed to create destination directory: ${dir}`, 'DESTINATION_ERROR'), '');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
33
173
|
},
|
|
34
174
|
filename: (_req, file, cb) => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
175
|
+
try {
|
|
176
|
+
const sanitizedFilename = file.originalname.replace(/\\/g, "/");
|
|
177
|
+
const extension = path.extname(sanitizedFilename);
|
|
178
|
+
const fieldName = file.fieldname || "file";
|
|
179
|
+
const uniqueName = uuidv4();
|
|
180
|
+
let fileName = `${uniqueName}-${fieldName}${extension}`;
|
|
181
|
+
// Replace backslashes with forward slashes in the final filename
|
|
182
|
+
fileName = fileName.replace(/\\/g, "/");
|
|
183
|
+
cb(null, fileName);
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
cb(new MultermateError('Failed to generate filename', 'FILENAME_ERROR'), '');
|
|
187
|
+
}
|
|
43
188
|
},
|
|
44
189
|
});
|
|
45
190
|
};
|
|
46
191
|
/**
|
|
47
192
|
* Function to configure file filter for Multer.
|
|
48
193
|
*
|
|
49
|
-
* @param allowedMimeTypes - Array of allowed MIME types.
|
|
194
|
+
* @param allowedMimeTypes - Array of allowed MIME types. Empty array means allow all file types.
|
|
50
195
|
* @returns File filter function for Multer.
|
|
51
196
|
*/
|
|
52
197
|
const configureFileFilter = (allowedMimeTypes) => {
|
|
53
198
|
return (_req, file, cb) => {
|
|
54
|
-
|
|
55
|
-
|
|
199
|
+
try {
|
|
200
|
+
// If no specific file types are restricted, allow ALL file types
|
|
201
|
+
if (allowedMimeTypes.length === 0) {
|
|
202
|
+
cb(null, true);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// Check if the file's MIME type is in the allowed list
|
|
206
|
+
if (allowedMimeTypes.includes(file.mimetype)) {
|
|
207
|
+
cb(null, true);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
const error = new MultermateError(`Invalid file type: ${file.mimetype}. Allowed types: ${allowedMimeTypes.join(', ')}`, 'INVALID_FILE_TYPE', file.fieldname);
|
|
211
|
+
cb(error);
|
|
212
|
+
}
|
|
56
213
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
error.code = 'INVALID_FILE_TYPE';
|
|
60
|
-
cb(error); // Reject the file if its MIME type is not allowed.
|
|
214
|
+
catch (error) {
|
|
215
|
+
cb(new MultermateError('File filter error', 'FILTER_ERROR'));
|
|
61
216
|
}
|
|
62
217
|
};
|
|
63
218
|
};
|
|
@@ -68,32 +223,37 @@ const configureFileFilter = (allowedMimeTypes) => {
|
|
|
68
223
|
* @returns Multer instance configured with the provided options.
|
|
69
224
|
*/
|
|
70
225
|
const configureMulter = ({ destination, filename, fileTypes = [], customMimeTypes = [], fileSizeLimit, preservePath = false, }) => {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (allowedMimeTypes.length === 0) {
|
|
87
|
-
allowedMimeTypes = ALLOWED_MIME_TYPES.all;
|
|
226
|
+
try {
|
|
227
|
+
const storage = configureStorage(destination);
|
|
228
|
+
// Combine allowed MIME types based on fileTypes array
|
|
229
|
+
let allowedMimeTypes = [];
|
|
230
|
+
if (customMimeTypes.length > 0) {
|
|
231
|
+
// Use custom MIME types if provided
|
|
232
|
+
allowedMimeTypes = customMimeTypes;
|
|
233
|
+
}
|
|
234
|
+
else if (fileTypes.length > 0) {
|
|
235
|
+
// Use default MIME types for specified fileTypes
|
|
236
|
+
fileTypes.forEach((type) => {
|
|
237
|
+
if (ALLOWED_MIME_TYPES[type]) {
|
|
238
|
+
allowedMimeTypes = allowedMimeTypes.concat(ALLOWED_MIME_TYPES[type]);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
88
241
|
}
|
|
242
|
+
// If neither customMimeTypes nor fileTypes are provided, allowedMimeTypes remains empty
|
|
243
|
+
// This means ALL file types are allowed (no restrictions)
|
|
244
|
+
// Remove duplicates
|
|
245
|
+
allowedMimeTypes = [...new Set(allowedMimeTypes)];
|
|
246
|
+
const fileFilter = configureFileFilter(allowedMimeTypes);
|
|
247
|
+
return multer({
|
|
248
|
+
storage,
|
|
249
|
+
fileFilter,
|
|
250
|
+
limits: { fileSize: fileSizeLimit || 1024 * 1024 * 50 }, // Default 50MB file size limit
|
|
251
|
+
preservePath,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
throw new MultermateError('Failed to configure multer', 'CONFIGURATION_ERROR');
|
|
89
256
|
}
|
|
90
|
-
const fileFilter = configureFileFilter(allowedMimeTypes);
|
|
91
|
-
return multer({
|
|
92
|
-
storage,
|
|
93
|
-
fileFilter,
|
|
94
|
-
limits: { fileSize: fileSizeLimit || 1024 * 1024 * 50 }, // Default 50MB file size limit
|
|
95
|
-
preservePath,
|
|
96
|
-
});
|
|
97
257
|
};
|
|
98
258
|
/**
|
|
99
259
|
* Function to handle a single file upload.
|
|
@@ -102,28 +262,53 @@ const configureMulter = ({ destination, filename, fileTypes = [], customMimeType
|
|
|
102
262
|
* @returns Multer middleware configured for single file upload.
|
|
103
263
|
*/
|
|
104
264
|
export function uploadSingle(options = {}) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (err) {
|
|
114
|
-
if (err.code === 'LIMIT_FILE_SIZE') {
|
|
115
|
-
req.fileValidationError = 'File size limit exceeded';
|
|
116
|
-
}
|
|
117
|
-
else if (err.code === 'INVALID_FILE_TYPE') {
|
|
118
|
-
req.fileValidationError = 'Invalid file type';
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
req.fileValidationError = err.message;
|
|
122
|
-
}
|
|
265
|
+
try {
|
|
266
|
+
const destination = options.destination || 'uploads';
|
|
267
|
+
const multerInstance = configureMulter(options);
|
|
268
|
+
const middleware = multerInstance.single(options.filename || "file");
|
|
269
|
+
return (req, res, next) => {
|
|
270
|
+
// Make sure the destination directory exists
|
|
271
|
+
try {
|
|
272
|
+
require('fs').mkdirSync(destination, { recursive: true });
|
|
123
273
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
274
|
+
catch (error) {
|
|
275
|
+
// Directory might already exist, ignore error
|
|
276
|
+
}
|
|
277
|
+
middleware(req, res, (err) => {
|
|
278
|
+
if (err) {
|
|
279
|
+
let errorMessage = 'Unknown upload error';
|
|
280
|
+
let errorCode = 'UPLOAD_ERROR';
|
|
281
|
+
if (err instanceof MultermateError) {
|
|
282
|
+
// Our custom error
|
|
283
|
+
req.fileValidationError = err.message;
|
|
284
|
+
return next(err);
|
|
285
|
+
}
|
|
286
|
+
else if (err.code === 'LIMIT_FILE_SIZE') {
|
|
287
|
+
errorMessage = `File size limit exceeded. Maximum allowed size: ${options.fileSizeLimit || '50MB'}`;
|
|
288
|
+
errorCode = 'FILE_SIZE_LIMIT_EXCEEDED';
|
|
289
|
+
}
|
|
290
|
+
else if (err.code === 'INVALID_FILE_TYPE') {
|
|
291
|
+
errorMessage = 'Invalid file type. Please check allowed file types.';
|
|
292
|
+
errorCode = 'INVALID_FILE_TYPE';
|
|
293
|
+
}
|
|
294
|
+
else if (err.code === 'LIMIT_UNEXPECTED_FILE') {
|
|
295
|
+
errorMessage = 'Unexpected field';
|
|
296
|
+
errorCode = 'UNEXPECTED_FIELD';
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
errorMessage = err.message || 'Upload failed';
|
|
300
|
+
}
|
|
301
|
+
const multermateError = new MultermateError(errorMessage, errorCode);
|
|
302
|
+
req.fileValidationError = errorMessage;
|
|
303
|
+
return next(multermateError);
|
|
304
|
+
}
|
|
305
|
+
next();
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
throw new MultermateError('Failed to create upload middleware', 'MIDDLEWARE_CREATION_ERROR');
|
|
311
|
+
}
|
|
127
312
|
}
|
|
128
313
|
/**
|
|
129
314
|
* Function to handle multiple file uploads across multiple fields.
|
|
@@ -132,48 +317,86 @@ export function uploadSingle(options = {}) {
|
|
|
132
317
|
* @returns Multer middleware configured for multiple file uploads.
|
|
133
318
|
*/
|
|
134
319
|
export function uploadMultiple(options) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if
|
|
146
|
-
|
|
320
|
+
try {
|
|
321
|
+
const destination = options.destination || 'uploads';
|
|
322
|
+
// Map fields configuration to multer format
|
|
323
|
+
const fieldConfigs = options.fields.map(field => ({
|
|
324
|
+
name: field.name,
|
|
325
|
+
maxCount: field.maxCount || 10, // Default maxCount is 10 if not specified.
|
|
326
|
+
}));
|
|
327
|
+
// Collect all allowed file types from fields
|
|
328
|
+
let allowedFileTypes = [];
|
|
329
|
+
if (options.customMimeTypes && options.customMimeTypes.length > 0) {
|
|
330
|
+
// Use custom MIME types if provided at the global level
|
|
331
|
+
allowedFileTypes = options.customMimeTypes;
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
// Collect file types from individual fields
|
|
335
|
+
options.fields.forEach((field) => {
|
|
336
|
+
const types = field.fileTypes || [];
|
|
337
|
+
types.forEach((type) => {
|
|
338
|
+
if (ALLOWED_MIME_TYPES[type]) {
|
|
339
|
+
allowedFileTypes = allowedFileTypes.concat(ALLOWED_MIME_TYPES[type]);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
const multerConfig = {
|
|
345
|
+
destination,
|
|
346
|
+
fileTypes: [],
|
|
347
|
+
customMimeTypes: allowedFileTypes.length > 0 ? allowedFileTypes : [],
|
|
348
|
+
fileSizeLimit: options.fileSizeLimit,
|
|
349
|
+
preservePath: options.preservePath
|
|
350
|
+
};
|
|
351
|
+
const multerInstance = configureMulter(multerConfig);
|
|
352
|
+
const middleware = multerInstance.fields(fieldConfigs);
|
|
353
|
+
return (req, res, next) => {
|
|
354
|
+
// Make sure the destination directory exists
|
|
355
|
+
try {
|
|
356
|
+
require('fs').mkdirSync(destination, { recursive: true });
|
|
147
357
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const multerConfig = {
|
|
151
|
-
destination,
|
|
152
|
-
fileTypes: [],
|
|
153
|
-
customMimeTypes: options.customMimeTypes || [],
|
|
154
|
-
fileSizeLimit: options.fileSizeLimit,
|
|
155
|
-
preservePath: options.preservePath
|
|
156
|
-
};
|
|
157
|
-
const multerInstance = configureMulter(multerConfig);
|
|
158
|
-
const middleware = multerInstance.fields(fieldConfigs);
|
|
159
|
-
return (req, res, next) => {
|
|
160
|
-
// Make sure the destination directory exists
|
|
161
|
-
require('fs').mkdirSync(destination, { recursive: true });
|
|
162
|
-
middleware(req, res, (err) => {
|
|
163
|
-
if (err) {
|
|
164
|
-
if (err.code === 'LIMIT_FILE_SIZE') {
|
|
165
|
-
req.fileValidationError = 'File size limit exceeded';
|
|
166
|
-
}
|
|
167
|
-
else if (err.code === 'INVALID_FILE_TYPE') {
|
|
168
|
-
req.fileValidationError = 'Invalid file type';
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
req.fileValidationError = err.message;
|
|
172
|
-
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
// Directory might already exist, ignore error
|
|
173
360
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
361
|
+
middleware(req, res, (err) => {
|
|
362
|
+
if (err) {
|
|
363
|
+
let errorMessage = 'Unknown upload error';
|
|
364
|
+
let errorCode = 'UPLOAD_ERROR';
|
|
365
|
+
if (err instanceof MultermateError) {
|
|
366
|
+
// Our custom error
|
|
367
|
+
req.fileValidationError = err.message;
|
|
368
|
+
return next(err);
|
|
369
|
+
}
|
|
370
|
+
else if (err.code === 'LIMIT_FILE_SIZE') {
|
|
371
|
+
errorMessage = `File size limit exceeded. Maximum allowed size: ${options.fileSizeLimit || '50MB'}`;
|
|
372
|
+
errorCode = 'FILE_SIZE_LIMIT_EXCEEDED';
|
|
373
|
+
}
|
|
374
|
+
else if (err.code === 'INVALID_FILE_TYPE') {
|
|
375
|
+
errorMessage = 'Invalid file type. Please check allowed file types.';
|
|
376
|
+
errorCode = 'INVALID_FILE_TYPE';
|
|
377
|
+
}
|
|
378
|
+
else if (err.code === 'LIMIT_UNEXPECTED_FILE') {
|
|
379
|
+
errorMessage = 'Unexpected field';
|
|
380
|
+
errorCode = 'UNEXPECTED_FIELD';
|
|
381
|
+
}
|
|
382
|
+
else if (err.code === 'LIMIT_FILE_COUNT') {
|
|
383
|
+
errorMessage = 'Too many files';
|
|
384
|
+
errorCode = 'FILE_COUNT_LIMIT_EXCEEDED';
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
errorMessage = err.message || 'Upload failed';
|
|
388
|
+
}
|
|
389
|
+
const multermateError = new MultermateError(errorMessage, errorCode);
|
|
390
|
+
req.fileValidationError = errorMessage;
|
|
391
|
+
return next(multermateError);
|
|
392
|
+
}
|
|
393
|
+
next();
|
|
394
|
+
});
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
throw new MultermateError('Failed to create multiple upload middleware', 'MIDDLEWARE_CREATION_ERROR');
|
|
399
|
+
}
|
|
177
400
|
}
|
|
178
401
|
/**
|
|
179
402
|
* Utility function to delete a file from the filesystem
|
|
@@ -183,20 +406,37 @@ export function uploadMultiple(options) {
|
|
|
183
406
|
*/
|
|
184
407
|
export async function deleteFile(filePath) {
|
|
185
408
|
try {
|
|
409
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
410
|
+
throw new MultermateError('Invalid file path provided', 'INVALID_PATH');
|
|
411
|
+
}
|
|
186
412
|
await fs.unlink(filePath);
|
|
187
413
|
return true;
|
|
188
414
|
}
|
|
189
415
|
catch (error) {
|
|
190
|
-
|
|
191
|
-
|
|
416
|
+
if (error instanceof MultermateError) {
|
|
417
|
+
throw error;
|
|
418
|
+
}
|
|
419
|
+
if (error.code === 'ENOENT') {
|
|
420
|
+
throw new MultermateError(`File not found: ${filePath}`, 'FILE_NOT_FOUND');
|
|
421
|
+
}
|
|
422
|
+
else if (error.code === 'EACCES') {
|
|
423
|
+
throw new MultermateError(`Permission denied: ${filePath}`, 'PERMISSION_DENIED');
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
throw new MultermateError(`Failed to delete file: ${error.message}`, 'DELETE_ERROR');
|
|
427
|
+
}
|
|
192
428
|
}
|
|
193
429
|
}
|
|
194
430
|
// Export the allowed file types for reference
|
|
195
431
|
export const ALLOWED_FILE_TYPES = Object.keys(ALLOWED_MIME_TYPES);
|
|
432
|
+
// Export MIME types for external use
|
|
433
|
+
export const MIME_TYPES = ALLOWED_MIME_TYPES;
|
|
196
434
|
// Export your functions
|
|
197
435
|
export default {
|
|
198
436
|
uploadSingle,
|
|
199
437
|
uploadMultiple,
|
|
200
438
|
deleteFile,
|
|
201
|
-
|
|
439
|
+
MultermateError,
|
|
440
|
+
ALLOWED_FILE_TYPES,
|
|
441
|
+
MIME_TYPES
|
|
202
442
|
};
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
// CommonJS entry point
|
|
2
|
-
|
|
2
|
+
try {
|
|
3
|
+
module.exports = require("./dist/cjs/index.js");
|
|
4
|
+
} catch (error) {
|
|
5
|
+
// Fallback if dist is not built yet
|
|
6
|
+
throw new Error(
|
|
7
|
+
'MulterMate: Please run "npm run build" to build the package before using it.'
|
|
8
|
+
);
|
|
9
|
+
}
|
package/index.mjs
CHANGED
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
// ES Module entry point
|
|
2
2
|
export * from "./dist/esm/index.js";
|
|
3
3
|
|
|
4
|
-
//
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
const multermate = require("./dist/cjs/index.js");
|
|
8
|
-
|
|
9
|
-
// Re-export everything from the CJS module
|
|
10
|
-
export const uploadSingle = multermate.uploadSingle;
|
|
11
|
-
export const uploadMultiple = multermate.uploadMultiple;
|
|
12
|
-
export const deleteFile = multermate.deleteFile;
|
|
13
|
-
|
|
14
|
-
// Default export
|
|
15
|
-
export default multermate;
|
|
4
|
+
// Default export for better compatibility
|
|
5
|
+
import multermateDefault from "./dist/esm/index.js";
|
|
6
|
+
export default multermateDefault;
|