UNPKG

@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
/* * 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 };