tranxpress
Version:
A smart async wrapper for Express with MongoDB transaction support.
73 lines (63 loc) • 2.01 kB
text/typescript
import { Request, Response, NextFunction } from "express";
import mongoose, { ClientSession } from "mongoose";
import { AsyncHandlerOptions, AsyncFn } from "../types/sharedTypes";
import { MongoHandledError } from "../utils/mongoErrorHandler";
export const asyncHandler = (
fn: AsyncFn,
options: AsyncHandlerOptions = {}
): AsyncFn => {
const { useTransaction = false, customErrorHandler } = options;
return async (
req: Request,
res: Response,
next: NextFunction
): Promise<void> => {
let session: ClientSession | null = null;
try {
if (useTransaction) {
session = await mongoose.startSession();
session.startTransaction();
req.session = session;
}
await fn(req, res, next);
if (session) {
await session.commitTransaction();
session.endSession();
}
} catch (error: any) {
console.error("❌ Error caught in asyncHandler:", error);
if (session) {
try {
await session.abortTransaction();
session.endSession();
} catch (abortErr) {
console.error("⚠️ Failed to abort transaction:", abortErr);
}
}
// Wrap known MongoDB errors
if (
error.name === "MongoServerError" ||
error.name === "MongoError" ||
error.name === "CastError" ||
error.name === "ValidationError"
) {
error = new MongoHandledError(error);
}
// 🔁 Support nested asyncHandlers
if (typeof customErrorHandler === "function") {
try {
const result = customErrorHandler(error, req, res, next);
if (result instanceof Promise) {
await result;
}
} catch (customHandlerError) {
console.error("⚠️ Error inside customErrorHandler:", customHandlerError);
}
}
return next({
message: error?.message || "Internal Server Error",
statusCode: error?.statusCode || 500,
});
}
};
};