@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.
158 lines • 6.96 kB
JavaScript
import { randomUUID } from "crypto";
import { Config } from "../config";
import { uuid } from "./id.utils";
import { HttpStatusCodes } from "./http-status-codes";
import { ErrorResponse } from "./response.utils";
import { Env } from "./env.utils";
import { getLogger } from "./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
*/
export function requestId(options) {
var headerName = (options === null || options === void 0 ? void 0 : options.headerName) || "X-Request-ID";
var exposeHeader = (options === null || options === void 0 ? void 0 : options.exposeHeader) !== false;
return function (req, res, next) {
// Use existing request ID from header or generate a new one
var existingId = req.headers[headerName.toLowerCase()];
var id = existingId || 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
*/
export function responseTime(options) {
var addHeader = (options === null || options === void 0 ? void 0 : options.addHeader) !== false;
var logOnComplete = (options === null || options === void 0 ? void 0 : options.logOnComplete) === true;
return function (req, res, next) {
var start = process.hrtime();
// Function to calculate elapsed time
var calculateDuration = function () {
var diff = process.hrtime(start);
return diff[0] * 1e3 + diff[1] * 1e-6; // Convert to ms
};
// Handle response completion
res.on("finish", function () {
var duration = calculateDuration();
if (addHeader) {
res.setHeader("X-Response-Time", "".concat(duration.toFixed(2), "ms"));
}
if (logOnComplete) {
var method = req.method, url = req.url;
getLogger().info("".concat(method, " ").concat(url, " - ").concat(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
*/
export function timeout(timeoutMs) {
if (timeoutMs === void 0) { timeoutMs = Config.Http.timeout; }
return function (req, res, next) {
// Set timeout for the request
var timer = setTimeout(function () {
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", function () {
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
*/
var createErrorResponse = function (message, req, error, options) {
var isDev = Env.isDev();
var includeDetails = (options === null || options === void 0 ? void 0 : options.includeDetails) && isDev;
var response = {
error: true,
message: message,
timestamp: new Date().toISOString(),
requestId: (error === null || error === void 0 ? void 0 : error.requestId) || req.id || 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(function (line) { return 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
*/
export function errorHandler(options) {
var logErrors = (options === null || options === void 0 ? void 0 : options.logErrors) !== false;
var includeDetails = (options === null || options === void 0 ? void 0 : options.includeDetails) === true;
var logger = (options === null || options === void 0 ? void 0 : options.logger) || getLogger().error;
return function (err, req, res, _next) {
// Determine status code
var status = (err === null || err === void 0 ? void 0 : err.status) || (err === null || err === void 0 ? void 0 : err.statusCode) || HttpStatusCodes.INTERNAL_SERVER_ERROR;
// Log error if enabled
if (logErrors) {
var logMessage = "[ERROR] ".concat(req.method, " ").concat(req.originalUrl || req.url, ": ").concat(err.message || "Unknown error");
logger(logMessage, err);
}
// Handle ErrorResponse instances (our custom error class)
if (err instanceof ErrorResponse) {
return res.status(status).json({
error: err.error,
message: err.message,
timestamp: err.timestamp,
requestId: err.requestId || req.id || 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: includeDetails,
}));
};
}
//# sourceMappingURL=middleware.utils.js.map