smart-multer-utils 0.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.
@@ -0,0 +1 @@
1
+ export const asyncHandler = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
@@ -0,0 +1,11 @@
1
+ import fs from "fs";
2
+ export function cleanupFile(file) {
3
+ if ((file === null || file === void 0 ? void 0 : file.path) && fs.existsSync(file.path)) {
4
+ fs.unlinkSync(file.path);
5
+ }
6
+ }
7
+ export function cleanupFiles(files) {
8
+ if (!files)
9
+ return;
10
+ files.forEach(cleanupFile);
11
+ }
@@ -0,0 +1,11 @@
1
+ import sharp from "sharp";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ export async function compressImage(file, quality = 80) {
5
+ const ext = path.extname(file.filename);
6
+ const compressed = file.path.replace(ext, `-compressed${ext}`);
7
+ await sharp(file.path).jpeg({ quality }).png({ quality }).toFile(compressed);
8
+ fs.unlinkSync(file.path);
9
+ file.path = compressed;
10
+ file.filename = path.basename(compressed);
11
+ }
@@ -0,0 +1,33 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { getExtension } from "./validators.js";
4
+ export function ensureDir(dir) {
5
+ if (!fs.existsSync(dir)) {
6
+ fs.mkdirSync(dir, { recursive: true });
7
+ }
8
+ }
9
+ export function resolveUploadPath(file, folder) {
10
+ var _a, _b;
11
+ const ext = getExtension(file);
12
+ const base = (_a = folder === null || folder === void 0 ? void 0 : folder.basePath) !== null && _a !== void 0 ? _a : "uploads";
13
+ let finalPath = base;
14
+ if ((folder === null || folder === void 0 ? void 0 : folder.byCategory) && folder.extensionMap) {
15
+ finalPath = path.join(base, (_b = folder.extensionMap[ext]) !== null && _b !== void 0 ? _b : "others");
16
+ }
17
+ if (folder === null || folder === void 0 ? void 0 : folder.byExtension) {
18
+ finalPath = path.join(finalPath, ext);
19
+ }
20
+ if ((folder === null || folder === void 0 ? void 0 : folder.autoCreate) !== false) {
21
+ ensureDir(finalPath);
22
+ }
23
+ return finalPath;
24
+ }
25
+ export function resolveFileSizeLimit(file, sizeConfig) {
26
+ var _a, _b, _c, _d;
27
+ if (!(sizeConfig === null || sizeConfig === void 0 ? void 0 : sizeConfig.enabled)) {
28
+ return ((_a = sizeConfig === null || sizeConfig === void 0 ? void 0 : sizeConfig.defaultMB) !== null && _a !== void 0 ? _a : 5) * 1024 * 1024;
29
+ }
30
+ const ext = getExtension(file);
31
+ const limit = (_d = (_c = (_b = sizeConfig.perExtensionMB) === null || _b === void 0 ? void 0 : _b[ext]) !== null && _c !== void 0 ? _c : sizeConfig.defaultMB) !== null && _d !== void 0 ? _d : 5;
32
+ return limit * 1024 * 1024;
33
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./uploader.js";
2
+ export * from "./asyncHandler.js";
3
+ export * from "./cleanup.js";
4
+ export * from "./types.js";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,65 @@
1
+ import multer from "multer";
2
+ import { validateExtension } from "./validators.js";
3
+ import { resolveUploadPath, resolveFileSizeLimit } from "./helpers.js";
4
+ import { cleanupFile, cleanupFiles } from "./cleanup.js";
5
+ import { compressImage } from "./compress.js";
6
+ export function createUploader(config) {
7
+ const { fieldName, allowedExtensions, sizeConfig, filename, multiple = false, maxFiles = 5, folderConfig, cleanupOnError = true, compressImage: shouldCompress = false, imageQuality = 80, } = config;
8
+ const storage = multer.diskStorage({
9
+ destination: (req, file, cb) => {
10
+ cb(null, resolveUploadPath(file, folderConfig));
11
+ },
12
+ filename: (req, file, cb) => {
13
+ var _a;
14
+ const name = (_a = filename === null || filename === void 0 ? void 0 : filename(req, file)) !== null && _a !== void 0 ? _a : `${Date.now()}-${file.originalname}`;
15
+ cb(null, name);
16
+ },
17
+ });
18
+ const upload = multer({
19
+ storage,
20
+ fileFilter: (req, file, cb) => {
21
+ if (!validateExtension(file, allowedExtensions)) {
22
+ return cb(new Error("File extension not allowed"));
23
+ }
24
+ cb(null, true);
25
+ },
26
+ limits: {
27
+ fileSize: (sizeConfig === null || sizeConfig === void 0 ? void 0 : sizeConfig.defaultMB)
28
+ ? sizeConfig.defaultMB * 1024 * 1024
29
+ : undefined,
30
+ },
31
+ });
32
+ const wrap = (middleware) => async (req, res, next) => {
33
+ middleware(req, res, async (err) => {
34
+ if (err) {
35
+ if (cleanupOnError) {
36
+ cleanupFile(req.file);
37
+ cleanupFiles(req.files);
38
+ }
39
+ return next(err);
40
+ }
41
+ try {
42
+ const files = req.file ? [req.file] : req.files || [];
43
+ for (const file of files) {
44
+ const maxSize = resolveFileSizeLimit(file, sizeConfig);
45
+ if (file.size > maxSize) {
46
+ throw new Error(`File ${file.originalname} exceeds size limit`);
47
+ }
48
+ if (shouldCompress) {
49
+ await compressImage(file, imageQuality);
50
+ }
51
+ }
52
+ next();
53
+ }
54
+ catch (e) {
55
+ cleanupFile(req.file);
56
+ cleanupFiles(req.files);
57
+ next(e);
58
+ }
59
+ });
60
+ };
61
+ return {
62
+ single: () => wrap(upload.single(fieldName)),
63
+ multiple: () => wrap(upload.array(fieldName, maxFiles)),
64
+ };
65
+ }
@@ -0,0 +1,9 @@
1
+ import path from "path";
2
+ export function getExtension(file) {
3
+ return path.extname(file.originalname).replace(".", "").toLowerCase();
4
+ }
5
+ export function validateExtension(file, allowed) {
6
+ if (!allowed || allowed.length === 0)
7
+ return true;
8
+ return allowed.includes(getExtension(file));
9
+ }
@@ -0,0 +1 @@
1
+ export const asyncHandler = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
@@ -0,0 +1,11 @@
1
+ import fs from "fs";
2
+ export function cleanupFile(file) {
3
+ if ((file === null || file === void 0 ? void 0 : file.path) && fs.existsSync(file.path)) {
4
+ fs.unlinkSync(file.path);
5
+ }
6
+ }
7
+ export function cleanupFiles(files) {
8
+ if (!files)
9
+ return;
10
+ files.forEach(cleanupFile);
11
+ }
@@ -0,0 +1,11 @@
1
+ import sharp from "sharp";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ export async function compressImage(file, quality = 80) {
5
+ const ext = path.extname(file.filename);
6
+ const compressed = file.path.replace(ext, `-compressed${ext}`);
7
+ await sharp(file.path).jpeg({ quality }).png({ quality }).toFile(compressed);
8
+ fs.unlinkSync(file.path);
9
+ file.path = compressed;
10
+ file.filename = path.basename(compressed);
11
+ }
@@ -0,0 +1,33 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { getExtension } from "./validators.js";
4
+ export function ensureDir(dir) {
5
+ if (!fs.existsSync(dir)) {
6
+ fs.mkdirSync(dir, { recursive: true });
7
+ }
8
+ }
9
+ export function resolveUploadPath(file, folder) {
10
+ var _a, _b;
11
+ const ext = getExtension(file);
12
+ const base = (_a = folder === null || folder === void 0 ? void 0 : folder.basePath) !== null && _a !== void 0 ? _a : "uploads";
13
+ let finalPath = base;
14
+ if ((folder === null || folder === void 0 ? void 0 : folder.byCategory) && folder.extensionMap) {
15
+ finalPath = path.join(base, (_b = folder.extensionMap[ext]) !== null && _b !== void 0 ? _b : "others");
16
+ }
17
+ if (folder === null || folder === void 0 ? void 0 : folder.byExtension) {
18
+ finalPath = path.join(finalPath, ext);
19
+ }
20
+ if ((folder === null || folder === void 0 ? void 0 : folder.autoCreate) !== false) {
21
+ ensureDir(finalPath);
22
+ }
23
+ return finalPath;
24
+ }
25
+ export function resolveFileSizeLimit(file, sizeConfig) {
26
+ var _a, _b, _c, _d;
27
+ if (!(sizeConfig === null || sizeConfig === void 0 ? void 0 : sizeConfig.enabled)) {
28
+ return ((_a = sizeConfig === null || sizeConfig === void 0 ? void 0 : sizeConfig.defaultMB) !== null && _a !== void 0 ? _a : 5) * 1024 * 1024;
29
+ }
30
+ const ext = getExtension(file);
31
+ const limit = (_d = (_c = (_b = sizeConfig.perExtensionMB) === null || _b === void 0 ? void 0 : _b[ext]) !== null && _c !== void 0 ? _c : sizeConfig.defaultMB) !== null && _d !== void 0 ? _d : 5;
32
+ return limit * 1024 * 1024;
33
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./uploader.js";
2
+ export * from "./asyncHandler.js";
3
+ export * from "./cleanup.js";
4
+ export * from "./types.js";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,65 @@
1
+ import multer from "multer";
2
+ import { validateExtension } from "./validators.js";
3
+ import { resolveUploadPath, resolveFileSizeLimit } from "./helpers.js";
4
+ import { cleanupFile, cleanupFiles } from "./cleanup.js";
5
+ import { compressImage } from "./compress.js";
6
+ export function createUploader(config) {
7
+ const { fieldName, allowedExtensions, sizeConfig, filename, multiple = false, maxFiles = 5, folderConfig, cleanupOnError = true, compressImage: shouldCompress = false, imageQuality = 80, } = config;
8
+ const storage = multer.diskStorage({
9
+ destination: (req, file, cb) => {
10
+ cb(null, resolveUploadPath(file, folderConfig));
11
+ },
12
+ filename: (req, file, cb) => {
13
+ var _a;
14
+ const name = (_a = filename === null || filename === void 0 ? void 0 : filename(req, file)) !== null && _a !== void 0 ? _a : `${Date.now()}-${file.originalname}`;
15
+ cb(null, name);
16
+ },
17
+ });
18
+ const upload = multer({
19
+ storage,
20
+ fileFilter: (req, file, cb) => {
21
+ if (!validateExtension(file, allowedExtensions)) {
22
+ return cb(new Error("File extension not allowed"));
23
+ }
24
+ cb(null, true);
25
+ },
26
+ limits: {
27
+ fileSize: (sizeConfig === null || sizeConfig === void 0 ? void 0 : sizeConfig.defaultMB)
28
+ ? sizeConfig.defaultMB * 1024 * 1024
29
+ : undefined,
30
+ },
31
+ });
32
+ const wrap = (middleware) => async (req, res, next) => {
33
+ middleware(req, res, async (err) => {
34
+ if (err) {
35
+ if (cleanupOnError) {
36
+ cleanupFile(req.file);
37
+ cleanupFiles(req.files);
38
+ }
39
+ return next(err);
40
+ }
41
+ try {
42
+ const files = req.file ? [req.file] : req.files || [];
43
+ for (const file of files) {
44
+ const maxSize = resolveFileSizeLimit(file, sizeConfig);
45
+ if (file.size > maxSize) {
46
+ throw new Error(`File ${file.originalname} exceeds size limit`);
47
+ }
48
+ if (shouldCompress) {
49
+ await compressImage(file, imageQuality);
50
+ }
51
+ }
52
+ next();
53
+ }
54
+ catch (e) {
55
+ cleanupFile(req.file);
56
+ cleanupFiles(req.files);
57
+ next(e);
58
+ }
59
+ });
60
+ };
61
+ return {
62
+ single: () => wrap(upload.single(fieldName)),
63
+ multiple: () => wrap(upload.array(fieldName, maxFiles)),
64
+ };
65
+ }
@@ -0,0 +1,9 @@
1
+ import path from "path";
2
+ export function getExtension(file) {
3
+ return path.extname(file.originalname).replace(".", "").toLowerCase();
4
+ }
5
+ export function validateExtension(file, allowed) {
6
+ if (!allowed || allowed.length === 0)
7
+ return true;
8
+ return allowed.includes(getExtension(file));
9
+ }
@@ -0,0 +1,2 @@
1
+ import { Request, Response, NextFunction } from "express";
2
+ export declare const asyncHandler: (fn: Function) => (req: Request, res: Response, next: NextFunction) => Promise<any>;
@@ -0,0 +1,2 @@
1
+ export declare function cleanupFile(file?: Express.Multer.File): void;
2
+ export declare function cleanupFiles(files?: Express.Multer.File[]): void;
@@ -0,0 +1 @@
1
+ export declare function compressImage(file: Express.Multer.File, quality?: number): Promise<void>;
@@ -0,0 +1,4 @@
1
+ import { FolderConfig, SizeConfig } from "./types.js";
2
+ export declare function ensureDir(dir: string): void;
3
+ export declare function resolveUploadPath(file: Express.Multer.File, folder?: FolderConfig): string;
4
+ export declare function resolveFileSizeLimit(file: Express.Multer.File, sizeConfig?: SizeConfig): number;
@@ -0,0 +1,4 @@
1
+ export * from "./uploader.js";
2
+ export * from "./asyncHandler.js";
3
+ export * from "./cleanup.js";
4
+ export * from "./types.js";
@@ -0,0 +1,25 @@
1
+ import { Request } from "express";
2
+ export interface FolderConfig {
3
+ basePath?: string;
4
+ autoCreate?: boolean;
5
+ byExtension?: boolean;
6
+ byCategory?: boolean;
7
+ extensionMap?: Record<string, string>;
8
+ }
9
+ export interface SizeConfig {
10
+ enabled?: boolean;
11
+ defaultMB?: number;
12
+ perExtensionMB?: Record<string, number>;
13
+ }
14
+ export interface UploadConfig {
15
+ fieldName: string;
16
+ allowedExtensions?: string[];
17
+ sizeConfig?: SizeConfig;
18
+ filename?: (req: Request, file: Express.Multer.File) => string;
19
+ multiple?: boolean;
20
+ maxFiles?: number;
21
+ folderConfig?: FolderConfig;
22
+ cleanupOnError?: boolean;
23
+ compressImage?: boolean;
24
+ imageQuality?: number;
25
+ }
@@ -0,0 +1,5 @@
1
+ import { UploadConfig } from "./types.js";
2
+ export declare function createUploader(config: UploadConfig): {
3
+ single: () => (req: any, res: any, next: any) => Promise<void>;
4
+ multiple: () => (req: any, res: any, next: any) => Promise<void>;
5
+ };
@@ -0,0 +1,2 @@
1
+ export declare function getExtension(file: Express.Multer.File): string;
2
+ export declare function validateExtension(file: Express.Multer.File, allowed?: string[]): boolean;
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "smart-multer-utils",
3
+ "version": "0.0.1",
4
+ "description": "Config-driven Multer utility for easy file uploads with extension-based validation, auto folder management, size limits, cleanup, and optional image compression",
5
+ "type": "module",
6
+ "main": "dist/cjs/index.js",
7
+ "module": "dist/esm/index.js",
8
+ "types": "dist/types/index.d.ts",
9
+ "license": "MIT",
10
+ "author": {
11
+ "name": "Manan Patel",
12
+ "email": "mananpatel1603@gmail.com",
13
+ "url": "https://github.com/mananzealousweb"
14
+ },
15
+ "maintainers": [
16
+ {
17
+ "name": "Manan Patel",
18
+ "email": "mananpatel1603@gmail.com"
19
+ }
20
+ ],
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/mananzealousweb/RealTMS-Backend.git"
24
+ },
25
+ "scripts": {
26
+ "build:esm": "tsc -p tsconfig.esm.json",
27
+ "build:cjs": "tsc -p tsconfig.cjs.json",
28
+ "build": "npm run build:esm && npm run build:cjs"
29
+ },
30
+ "dependencies": {
31
+ "express": "^5.2.1",
32
+ "multer": "^2.0.2",
33
+ "sharp": "^0.34.5"
34
+ },
35
+ "keywords": [
36
+ "multer",
37
+ "file-upload",
38
+ "express",
39
+ "upload-middleware",
40
+ "file-validation",
41
+ "image-upload",
42
+ "compression",
43
+ "nodejs",
44
+ "internal",
45
+ "utility"
46
+ ],
47
+ "devDependencies": {
48
+ "@types/express": "^5.0.6",
49
+ "@types/multer": "^2.0.0",
50
+ "@types/node": "^25.0.3"
51
+ }
52
+ }