@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.
171 lines (167 loc) • 6.15 kB
JavaScript
/*
* The MIT License
*
* Copyright (c) 2026 Catbee Technologies. https://catbee.in/license
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { uuid } from '@catbee/utils/id';
import { HttpStatusCodes } from '@catbee/utils/http-status-codes';
import { ErrorResponse, createFinalErrorResponse } from '@catbee/utils/response';
import { getLogger } from '@catbee/utils/logger';
import { ContextStore, StoreKeys } from '@catbee/utils/context-store';
import { RequestTimeoutException } from '@catbee/utils/exception';
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
function requestId(options) {
const headerName = options?.headerName || "X-Request-ID";
const exposeHeader = options?.exposeHeader !== false;
const generateId = options?.generator || uuid;
return (req, res, next) => {
const existingId = req.headers[headerName.toLowerCase()];
const id = existingId || generateId();
req.id = id;
if (exposeHeader) {
res.setHeader(headerName, id);
}
next();
};
}
__name(requestId, "requestId");
function responseTime(options) {
const addHeader = options?.addHeader !== false;
const logOnComplete = options?.logOnComplete === true;
return (req, res, next) => {
const start = process.hrtime();
const getDuration = /* @__PURE__ */ __name(() => {
const diff = process.hrtime(start);
return (diff[0] * 1e3 + diff[1] * 1e-6).toFixed(2);
}, "getDuration");
if (addHeader) {
const originalWriteHead = res.writeHead;
res.writeHead = function(...args) {
res.setHeader("X-Response-Time", `${getDuration()}ms`);
return originalWriteHead.apply(this, args);
};
}
if (logOnComplete) {
res.on("finish", () => {
getLogger().info(`${req.method} ${req.url} - ${getDuration()}ms`);
});
}
next();
};
}
__name(responseTime, "responseTime");
function timeout(timeoutMs = 3e4) {
return (_req, res, next) => {
const timer = setTimeout(() => {
const response = new RequestTimeoutException("Request timed out");
res.status(HttpStatusCodes.REQUEST_TIMEOUT).json(response);
}, timeoutMs);
res.on("finish", () => {
clearTimeout(timer);
});
next();
};
}
__name(timeout, "timeout");
function setupRequestContext(options = {}) {
const { headerName = "x-request-id", autoLog = true } = options;
return (req, _res, next) => {
const requestId2 = req.headers[headerName] || req?.["id"] || uuid();
ContextStore.run({
[StoreKeys.REQUEST_ID]: requestId2
}, () => {
const childLogger = getLogger().child({
requestId: requestId2,
method: req.method,
url: req.originalUrl || req.url
});
req.logger = childLogger;
ContextStore.set(StoreKeys.LOGGER, childLogger);
if (autoLog) {
req.logger.info("Request context initialized");
}
next();
});
};
}
__name(setupRequestContext, "setupRequestContext");
function errorHandler(options) {
const logErrors = options?.logErrors !== false;
const includeDetails = options?.includeDetails === true;
return (err, req, res, _next) => {
const status = err?.status || err?.statusCode || HttpStatusCodes.INTERNAL_SERVER_ERROR;
if (logErrors) {
getLogger().error({
err
}, `${err.message || "Unknown error"}`);
}
if (err instanceof ErrorResponse) {
const result = {
error: true,
message: err.message,
timestamp: err.timestamp,
requestId: err?.requestId || req?.id || uuid(),
status,
path: req.originalUrl || req.url
};
if (includeDetails && err?.stack) {
result.stack = err.stack.split("\n").map((line) => line.trim());
}
return res.status(status).json(result);
}
return res.status(status).json(createFinalErrorResponse(req, status, err?.message || "Internal Server Error", err, {
includeDetails
}));
};
}
__name(errorHandler, "errorHandler");
function healthCheck(options) {
const path = options?.path || "/healthz";
const checks = options?.checks || [];
const detailed = options?.detailed !== false;
return async (req, res, next) => {
if (req.path !== path) return next();
const results = {};
let allHealthy = true;
for (const check of checks) {
try {
const result = await Promise.resolve(check.check());
results[check.name] = !!result;
if (!result) allHealthy = false;
} catch {
results[check.name] = false;
allHealthy = false;
}
}
const status = allHealthy ? HttpStatusCodes.OK : HttpStatusCodes.SERVICE_UNAVAILABLE;
const response = detailed ? {
status: allHealthy ? "ok" : "unhealthy",
checks: results,
timestamp: (/* @__PURE__ */ new Date()).toISOString()
} : {
status: allHealthy ? "ok" : "unhealthy"
};
res.status(status).json(response);
};
}
__name(healthCheck, "healthCheck");
export { errorHandler, healthCheck, requestId, responseTime, setupRequestContext, timeout };