@catbee/utils
Version:
A modular, production-grade utility toolkit for Node.js and TypeScript, designed for robust, scalable applications (including Express-based services). All utilities are tree-shakable and can be imported independently.
163 lines • 7.14 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.requestId = requestId;
exports.responseTime = responseTime;
exports.timeout = timeout;
exports.errorHandler = errorHandler;
const crypto_1 = require("crypto");
const config_1 = require("../config");
const id_utils_1 = require("./id.utils");
const http_status_codes_1 = require("./http-status-codes");
const response_utils_1 = require("./response.utils");
const env_utils_1 = require("./env.utils");
const logger_utils_1 = require("./logger.utils");
/**
* Attaches a unique request ID to each request.
* Useful for request tracing and correlation between logs.
*
* @param {object} [options] - Configuration options
* @param {string} [options.headerName='X-Request-ID'] - Header name for request ID
* @param {boolean} [options.exposeHeader=true] - Whether to expose the header in response
* @returns {Middleware} Express-compatible middleware
*/
function requestId(options) {
const headerName = (options === null || options === void 0 ? void 0 : options.headerName) || "X-Request-ID";
const exposeHeader = (options === null || options === void 0 ? void 0 : options.exposeHeader) !== false;
return (req, res, next) => {
// Use existing request ID from header or generate a new one
const existingId = req.headers[headerName.toLowerCase()];
const id = existingId || (0, crypto_1.randomUUID)();
// Attach ID to request object
req.id = id;
// Add ID to response headers
if (exposeHeader) {
res.setHeader(headerName, id);
}
next();
};
}
/**
* Measures request processing time and logs or adds it to response headers.
*
* @param {object} [options] - Configuration options
* @param {boolean} [options.addHeader=true] - Whether to add X-Response-Time header
* @param {boolean} [options.logOnComplete=false] - Whether to log timing info
* @returns {Middleware} Express-compatible middleware
*/
function responseTime(options) {
const addHeader = (options === null || options === void 0 ? void 0 : options.addHeader) !== false;
const logOnComplete = (options === null || options === void 0 ? void 0 : options.logOnComplete) === true;
return (req, res, next) => {
const start = process.hrtime();
// Function to calculate elapsed time
const calculateDuration = () => {
const diff = process.hrtime(start);
return diff[0] * 1e3 + diff[1] * 1e-6; // Convert to ms
};
// Handle response completion
res.on("finish", () => {
const duration = calculateDuration();
if (addHeader) {
res.setHeader("X-Response-Time", `${duration.toFixed(2)}ms`);
}
if (logOnComplete) {
const { method, url } = req;
(0, logger_utils_1.getLogger)().info(`${method} ${url} - ${duration.toFixed(2)}ms`);
}
});
next();
};
}
/**
* Request timeout middleware.
* Aborts requests that take too long to process.
*
* @param {number} [timeoutMs=30000] - Timeout in milliseconds
* @returns {Middleware} Express-compatible middleware
*/
function timeout(timeoutMs = config_1.Config.Http.timeout) {
return (req, res, next) => {
// Set timeout for the request
const timer = setTimeout(() => {
res.status(408).json({
error: true,
message: "Request timeout",
status: 408,
timestamp: new Date().toISOString(),
});
}, timeoutMs);
// Clear timeout when response is sent
res.on("finish", () => {
clearTimeout(timer);
});
next();
};
}
/**
* Creates a standardized error response object for API errors.
*
* @param {string} message - Error message
* @param {Request} req - Express request object
* @param {any} error - Original error object
* @param {object} [options] - Additional options
* @param {boolean} [options.includeDetails=false] - Whether to include error details in non-production
* @returns {object} Formatted error response
*/
const createErrorResponse = (message, req, error, options) => {
const isDev = env_utils_1.Env.isDev();
const includeDetails = (options === null || options === void 0 ? void 0 : options.includeDetails) && isDev;
const response = {
error: true,
message,
timestamp: new Date().toISOString(),
requestId: (error === null || error === void 0 ? void 0 : error.requestId) || req.id || (0, id_utils_1.uuid)(),
path: req.originalUrl || req.url,
};
// Include error code if present
if (error === null || error === void 0 ? void 0 : error.code) {
response.code = error.code;
}
// Include stack trace in development mode if requested
if (includeDetails && (error === null || error === void 0 ? void 0 : error.stack)) {
response.stack = error.stack.split("\n").map((line) => line.trim());
}
return response;
};
/**
* Global error handling middleware with enhanced features.
*
* @param {object} [options] - Error handler options
* @param {boolean} [options.logErrors=true] - Whether to log errors
* @param {boolean} [options.includeDetails=false] - Whether to include error details in non-production
* @param {Function} [options.logger=getLogger().error] - Custom logging function
* @returns {(err: any, req: Request, res: Response, next: NextFunction) => void} Error middleware
*/
function errorHandler(options) {
const logErrors = (options === null || options === void 0 ? void 0 : options.logErrors) !== false;
const includeDetails = (options === null || options === void 0 ? void 0 : options.includeDetails) === true;
const logger = (options === null || options === void 0 ? void 0 : options.logger) || (0, logger_utils_1.getLogger)().error;
return (err, req, res, _next) => {
// Determine status code
const status = (err === null || err === void 0 ? void 0 : err.status) || (err === null || err === void 0 ? void 0 : err.statusCode) || http_status_codes_1.HttpStatusCodes.INTERNAL_SERVER_ERROR;
// Log error if enabled
if (logErrors) {
const logMessage = `[ERROR] ${req.method} ${req.originalUrl || req.url}: ${err.message || "Unknown error"}`;
logger(logMessage, err);
}
// Handle ErrorResponse instances (our custom error class)
if (err instanceof response_utils_1.ErrorResponse) {
return res.status(status).json({
error: err.error,
message: err.message,
timestamp: err.timestamp,
requestId: err.requestId || req.id || (0, id_utils_1.uuid)(),
path: req.originalUrl || req.url,
});
}
// Handle any other errors
return res.status(status).json(createErrorResponse((err === null || err === void 0 ? void 0 : err.message) || "Internal Server Error", req, err, {
includeDetails,
}));
};
}
//# sourceMappingURL=middleware.utils.js.map