multer-utility
Version:
A comprehensive TypeScript wrapper for Multer with enhanced features including memory/disk storage, flexible configuration, and file management utilities
305 lines • 10.6 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MulterError = exports.createAudioUploader = exports.createImageUploader = exports.createMemoryUploader = exports.createDiskUploader = exports.UploadService = void 0;
const multer_1 = __importStar(require("multer"));
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
class UploadService {
constructor(config = {}) {
/**
* File filter for validation
*/
this.fileFilter = (req, file, cb) => {
const validation = this.validateFile(file);
if (validation.isValid) {
cb(null, true);
}
else {
cb(new multer_1.MulterError("LIMIT_UNEXPECTED_FILE", validation.error));
}
};
this.config = { ...UploadService.DEFAULT_CONFIG, ...config };
if (this.config.storage === 'disk' && this.config.createDirIfNotExists) {
this.ensureUploadDirExists();
}
this.uploader = (0, multer_1.default)({
storage: this.getStorageEngine(),
limits: { fileSize: this.config.maxFileSize },
fileFilter: this.fileFilter,
});
}
/**
* Ensure upload directory exists
*/
ensureUploadDirExists() {
if (!fs_1.default.existsSync(this.config.uploadDir)) {
fs_1.default.mkdirSync(this.config.uploadDir, { recursive: true });
}
}
/**
* Get storage engine based on configuration
*/
getStorageEngine() {
if (this.config.storage === 'memory') {
return multer_1.default.memoryStorage();
}
return multer_1.default.diskStorage({
destination: (_, __, cb) => cb(null, this.config.uploadDir),
filename: (_, file, cb) => {
const filename = this.generateFilename(file);
cb(null, filename);
},
});
}
/**
* Generate filename based on configuration
*/
generateFilename(file) {
// Use custom naming function if provided
if (this.config.customNaming && this.config.customNaming(file)) {
return this.config.customNaming(file);
}
let filename = file.originalname;
// Preserve original name or generate new one
if (!this.config.preserveOriginalName) {
const ext = path_1.default.extname(file.originalname);
const baseName = path_1.default.basename(file.originalname, ext);
// Sanitize filename
if (this.config.sanitizeFilenames) {
filename = baseName.replace(/[^a-zA-Z0-9.-]/g, "_") + ext;
}
// Add timestamp
if (this.config.useTimestamp) {
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
filename = `${timestamp}-${filename}`;
}
}
return filename;
}
/**
* Validate file against configured rules
*/
validateFile(file) {
const ext = path_1.default.extname(file.originalname).toLowerCase();
// Check MIME type
if (!this.config.allowedMimeTypes.includes(file.mimetype)) {
return {
isValid: false,
error: `Invalid MIME type: ${file.mimetype}. Allowed: ${this.config.allowedMimeTypes.join(', ')}`
};
}
// Check file extension
if (!this.config.allowedExtensions.includes(ext)) {
return {
isValid: false,
error: `Invalid file extension: ${ext}. Allowed: ${this.config.allowedExtensions.join(', ')}`
};
}
return { isValid: true };
}
/**
* Upload single file
*/
single(fieldName) {
return this.uploader.single(fieldName);
}
/**
* Upload multiple files (array)
*/
array(fieldName, maxCount) {
return this.uploader.array(fieldName, maxCount);
}
/**
* Upload multiple files (different fields)
*/
fields(fields) {
return this.uploader.fields(fields);
}
/**
* Handle any files
*/
any() {
return this.uploader.any();
}
/**
* No files expected
*/
none() {
return this.uploader.none();
}
/**
* Get upload configuration
*/
getConfig() {
return { ...this.config };
}
/**
* Get file info for uploaded file
*/
getFileInfo(file) {
return {
originalName: file.originalname,
filename: file.filename || 'memory-storage',
mimetype: file.mimetype,
size: file.size,
encoding: file.encoding,
fieldname: file.fieldname,
destination: file.destination,
path: file.path,
buffer: file.buffer, // Available in memory storage
};
}
/**
* Delete uploaded file (disk storage only)
*/
async deleteFile(filename) {
if (this.config.storage === 'memory') {
throw new Error('Cannot delete file from memory storage');
}
try {
const filePath = path_1.default.join(this.config.uploadDir, filename);
await fs_1.default.promises.unlink(filePath);
return true;
}
catch (error) {
return false;
}
}
/**
* Check if file exists (disk storage only)
*/
fileExists(filename) {
if (this.config.storage === 'memory') {
return false;
}
const filePath = path_1.default.join(this.config.uploadDir, filename);
return fs_1.default.existsSync(filePath);
}
/**
* Get file path (disk storage only)
*/
getFilePath(filename) {
if (this.config.storage === 'memory') {
throw new Error('Memory storage does not have file paths');
}
return path_1.default.join(this.config.uploadDir, filename);
}
/**
* Get upload directory stats
*/
async getUploadStats() {
if (this.config.storage === 'memory') {
return { message: 'Memory storage - no persistent files' };
}
try {
const files = await fs_1.default.promises.readdir(this.config.uploadDir);
const stats = await Promise.all(files.map(async (file) => {
const filePath = path_1.default.join(this.config.uploadDir, file);
const stat = await fs_1.default.promises.stat(filePath);
return {
name: file,
size: stat.size,
created: stat.birthtime,
modified: stat.mtime,
};
}));
return {
totalFiles: files.length,
totalSize: stats.reduce((acc, file) => acc + file.size, 0),
files: stats,
};
}
catch (error) {
throw new Error(`Failed to get upload stats: ${error}`);
}
}
}
exports.UploadService = UploadService;
UploadService.DEFAULT_CONFIG = {
storage: 'disk',
uploadDir: path_1.default.join(process.cwd(), "uploads"),
allowedMimeTypes: ["image/jpeg", "image/png", "image/gif", "audio/mpeg", "application/pdf"],
allowedExtensions: [".jpg", ".jpeg", ".png", ".gif", ".mp3", ".pdf"],
maxFileSize: 1024 * 1024 * 15,
useTimestamp: true,
sanitizeFilenames: true,
customNaming: () => '',
createDirIfNotExists: true,
preserveOriginalName: false
};
// Convenience functions for common use cases
const createDiskUploader = (uploadDir, config) => {
return new UploadService({
storage: 'disk',
uploadDir,
...config
});
};
exports.createDiskUploader = createDiskUploader;
const createMemoryUploader = (config) => {
return new UploadService({
storage: 'memory',
...config
});
};
exports.createMemoryUploader = createMemoryUploader;
const createImageUploader = (uploadDir) => {
return new UploadService({
storage: 'disk',
uploadDir: uploadDir || path_1.default.join(process.cwd(), 'uploads', 'images'),
allowedMimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
allowedExtensions: ['.jpg', '.jpeg', '.png', '.gif', '.webp'],
maxFileSize: 1024 * 1024 * 5, // 5MB
});
};
exports.createImageUploader = createImageUploader;
const createAudioUploader = (uploadDir) => {
return new UploadService({
storage: 'disk',
uploadDir: uploadDir || path_1.default.join(process.cwd(), 'uploads', 'audio'),
allowedMimeTypes: ['audio/mpeg', 'audio/wav', 'audio/ogg'],
allowedExtensions: ['.mp3', '.wav', '.ogg'],
maxFileSize: 1024 * 1024 * 50, // 50MB
});
};
exports.createAudioUploader = createAudioUploader;
// Export types
var multer_2 = require("multer");
Object.defineProperty(exports, "MulterError", { enumerable: true, get: function () { return multer_2.MulterError; } });
//# sourceMappingURL=index.js.map