@catbee/utils 2.0.0-next.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +55 -38
- package/array/index.cjs +37 -5
- package/array/index.d.ts +59 -8
- package/array/index.mjs +32 -4
- package/async/index.cjs +24 -3
- package/async/index.d.ts +18 -2
- package/async/index.mjs +24 -4
- package/cache/index.cjs +1 -1
- package/cache/index.d.ts +1 -1
- package/cache/index.mjs +1 -1
- package/config/index.cjs +8 -8
- package/config/index.d.ts +1 -1
- package/config/index.mjs +4 -4
- package/context-store/index.cjs +2 -3
- package/context-store/index.d.ts +1 -1
- package/context-store/index.mjs +2 -3
- package/crypto/index.cjs +55 -5
- package/crypto/index.d.ts +63 -2
- package/crypto/index.mjs +52 -7
- package/date/index.cjs +630 -1
- package/date/index.d.ts +487 -2
- package/date/index.mjs +621 -2
- package/{decorators → decorator}/index.cjs +425 -304
- package/{decorators → decorator}/index.d.ts +3 -2
- package/{decorators → decorator}/index.mjs +425 -304
- package/{dir → directory}/index.cjs +1 -1
- package/{dir → directory}/index.d.ts +1 -1
- package/{dir → directory}/index.mjs +1 -1
- package/env/index.cjs +90 -42
- package/env/index.d.ts +12 -1
- package/env/index.mjs +90 -42
- package/exception/index.cjs +1 -1
- package/exception/index.d.ts +1 -1
- package/exception/index.mjs +1 -1
- package/fs/index.cjs +1 -1
- package/fs/index.d.ts +1 -1
- package/fs/index.mjs +1 -1
- package/http-status-codes/index.cjs +1 -1
- package/http-status-codes/index.d.ts +1 -1
- package/http-status-codes/index.mjs +1 -1
- package/id/index.cjs +1 -1
- package/id/index.d.ts +1 -1
- package/id/index.mjs +1 -1
- package/index.cjs +10 -10
- package/index.d.ts +4 -4
- package/index.mjs +4 -4
- package/logger/index.cjs +2 -4
- package/logger/index.d.ts +2 -2
- package/logger/index.mjs +2 -4
- package/middleware/index.cjs +1 -1
- package/middleware/index.d.ts +1 -1
- package/middleware/index.mjs +1 -1
- package/{obj → object}/index.cjs +182 -108
- package/{obj → object}/index.d.ts +38 -2
- package/{obj → object}/index.mjs +180 -109
- package/package.json +31 -13
- package/performance/index.cjs +2 -2
- package/performance/index.d.ts +1 -1
- package/performance/index.mjs +2 -2
- package/request/index.cjs +36 -24
- package/request/index.d.ts +1 -1
- package/request/index.mjs +36 -24
- package/response/index.cjs +1 -1
- package/response/index.d.ts +1 -1
- package/response/index.mjs +1 -1
- package/server/index.cjs +233 -131
- package/server/index.d.ts +81 -1
- package/server/index.mjs +229 -127
- package/stream/index.cjs +1 -1
- package/stream/index.d.ts +1 -1
- package/stream/index.mjs +1 -1
- package/string/index.cjs +34 -1
- package/string/index.d.ts +45 -2
- package/string/index.mjs +31 -2
- package/type/index.cjs +18 -1
- package/type/index.d.ts +38 -2
- package/type/index.mjs +16 -2
- package/types/index.cjs +1 -1
- package/types/index.d.ts +10 -3
- package/types/index.mjs +1 -1
- package/url/index.cjs +61 -1
- package/url/index.d.ts +59 -2
- package/url/index.mjs +57 -2
- package/validation/index.cjs +3 -3
- package/validation/index.d.ts +1 -1
- package/validation/index.mjs +3 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* The MIT License
|
|
3
3
|
*
|
|
4
|
-
* Copyright (c)
|
|
4
|
+
* Copyright (c) 2026 Catbee Technologies. https://catbee.in/license
|
|
5
5
|
*
|
|
6
6
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
7
|
* of this software and associated documentation files (the "Software"), to deal
|
|
@@ -1150,7 +1150,7 @@ var require_Reflect = __commonJS({
|
|
|
1150
1150
|
}
|
|
1151
1151
|
});
|
|
1152
1152
|
|
|
1153
|
-
// src/
|
|
1153
|
+
// src/decorator/decorator.utils.ts
|
|
1154
1154
|
__toESM(require_Reflect());
|
|
1155
1155
|
var ROUTES_KEY = Symbol("routes");
|
|
1156
1156
|
var MIDDLEWARE_KEY = Symbol("middlewares");
|
|
@@ -1274,7 +1274,7 @@ var DIContainer = class {
|
|
|
1274
1274
|
};
|
|
1275
1275
|
var diContainer = new DIContainer();
|
|
1276
1276
|
function normalizeHeaderValue(value) {
|
|
1277
|
-
if (
|
|
1277
|
+
if (value === void 0) return void 0;
|
|
1278
1278
|
if (typeof value === "string") return value;
|
|
1279
1279
|
if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
|
|
1280
1280
|
return value;
|
|
@@ -1613,223 +1613,309 @@ function Log(options) {
|
|
|
1613
1613
|
__name(Log, "Log");
|
|
1614
1614
|
function registerControllers(router, controllers) {
|
|
1615
1615
|
controllers.forEach((ControllerClass) => {
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1616
|
+
registerSingleController(router, ControllerClass);
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1619
|
+
__name(registerControllers, "registerControllers");
|
|
1620
|
+
function registerSingleController(router, ControllerClass) {
|
|
1621
|
+
if (!Reflect.getMetadata("injectable", ControllerClass)) {
|
|
1622
|
+
Reflect.defineMetadata("injectable", true, ControllerClass);
|
|
1623
|
+
diContainer.register(ControllerClass);
|
|
1624
|
+
}
|
|
1625
|
+
const instance = diContainer.get(ControllerClass);
|
|
1626
|
+
const controllerMetadata = getControllerMetadata(ControllerClass);
|
|
1627
|
+
const routes = Reflect.getMetadata(ROUTES_KEY, ControllerClass) || [];
|
|
1628
|
+
routes.forEach((routeDef) => {
|
|
1629
|
+
registerRoute(router, instance, controllerMetadata, routeDef);
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
__name(registerSingleController, "registerSingleController");
|
|
1633
|
+
function getControllerMetadata(ControllerClass) {
|
|
1634
|
+
let basePath = Reflect.getMetadata("basePath", ControllerClass) || "";
|
|
1635
|
+
const controllerVersion = Reflect.getMetadata(VERSION_KEY, ControllerClass);
|
|
1636
|
+
if (controllerVersion?.options?.addPrefix) {
|
|
1637
|
+
basePath = `/${controllerVersion.version}${basePath || ""}`;
|
|
1638
|
+
}
|
|
1639
|
+
return {
|
|
1640
|
+
basePath,
|
|
1641
|
+
rateLimit: Reflect.getMetadata(RATE_LIMIT_KEY, ControllerClass),
|
|
1642
|
+
cache: Reflect.getMetadata(CACHE_KEY, ControllerClass),
|
|
1643
|
+
timeout: Reflect.getMetadata(TIMEOUT_KEY, ControllerClass),
|
|
1644
|
+
version: controllerVersion,
|
|
1645
|
+
roles: Reflect.getMetadata(ROLES_KEY, ControllerClass),
|
|
1646
|
+
logConfig: Reflect.getMetadata(LOG_KEY, ControllerClass),
|
|
1647
|
+
headers: Reflect.getMetadata(HEADER_KEY, ControllerClass) || {},
|
|
1648
|
+
middlewares: Reflect.getMetadata(MIDDLEWARE_KEY, ControllerClass) || [],
|
|
1649
|
+
beforeHooks: Reflect.getMetadata(BEFORE_KEY, ControllerClass) || [],
|
|
1650
|
+
afterHooks: Reflect.getMetadata(AFTER_KEY, ControllerClass) || [],
|
|
1651
|
+
contentType: Reflect.getMetadata(CONTENT_TYPE_KEY, ControllerClass)
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
__name(getControllerMetadata, "getControllerMetadata");
|
|
1655
|
+
function registerRoute(router, instance, controllerMetadata, routeDef) {
|
|
1656
|
+
const { path, method, handlerName } = routeDef;
|
|
1657
|
+
const routeMetadata = getRouteMetadata(instance, handlerName, controllerMetadata);
|
|
1658
|
+
const handler = createRouteHandler(instance, handlerName, routeMetadata);
|
|
1659
|
+
const finalPath = calculateFinalPath(path, routeMetadata, controllerMetadata);
|
|
1660
|
+
router[method](controllerMetadata.basePath + finalPath, ...routeMetadata.middlewares, handler);
|
|
1661
|
+
}
|
|
1662
|
+
__name(registerRoute, "registerRoute");
|
|
1663
|
+
function getRouteMetadata(instance, handlerName, controllerMetadata) {
|
|
1664
|
+
const methodMiddlewares = Reflect.getMetadata(MIDDLEWARE_KEY, instance, handlerName) || [];
|
|
1665
|
+
const params = Reflect.getMetadata(PARAMS_KEY, instance, handlerName) || [];
|
|
1666
|
+
const httpCode = Reflect.getMetadata(HTTP_CODE_KEY, instance, handlerName);
|
|
1667
|
+
const methodHeaders = Reflect.getMetadata(HEADER_KEY, instance, handlerName) || {};
|
|
1668
|
+
const methodBeforeHooks = Reflect.getMetadata(BEFORE_KEY, instance, handlerName) || [];
|
|
1669
|
+
const methodAfterHooks = Reflect.getMetadata(AFTER_KEY, instance, handlerName) || [];
|
|
1670
|
+
const redirect = Reflect.getMetadata(REDIRECT_KEY, instance, handlerName);
|
|
1671
|
+
const methodContentType = Reflect.getMetadata(CONTENT_TYPE_KEY, instance, handlerName);
|
|
1672
|
+
const methodVersion = Reflect.getMetadata(VERSION_KEY, instance, handlerName);
|
|
1673
|
+
const headers = {
|
|
1674
|
+
...controllerMetadata.headers,
|
|
1675
|
+
...methodHeaders
|
|
1676
|
+
};
|
|
1677
|
+
const middlewares = [
|
|
1678
|
+
...controllerMetadata.middlewares,
|
|
1679
|
+
...methodMiddlewares
|
|
1680
|
+
];
|
|
1681
|
+
const beforeHooks = [
|
|
1682
|
+
...controllerMetadata.beforeHooks,
|
|
1683
|
+
...methodBeforeHooks
|
|
1684
|
+
];
|
|
1685
|
+
const afterHooks = [
|
|
1686
|
+
...methodAfterHooks,
|
|
1687
|
+
...controllerMetadata.afterHooks
|
|
1688
|
+
];
|
|
1689
|
+
const contentType = methodContentType || controllerMetadata.contentType;
|
|
1690
|
+
const roles = Reflect.getMetadata(ROLES_KEY, instance, handlerName) || controllerMetadata.roles || [];
|
|
1691
|
+
const cache = Reflect.getMetadata(CACHE_KEY, instance, handlerName) || controllerMetadata.cache;
|
|
1692
|
+
const rateLimitOptions = Reflect.getMetadata(RATE_LIMIT_KEY, instance, handlerName) || controllerMetadata.rateLimit;
|
|
1693
|
+
const version = methodVersion || controllerMetadata.version;
|
|
1694
|
+
const timeout = Reflect.getMetadata(TIMEOUT_KEY, instance, handlerName) || controllerMetadata.timeout;
|
|
1695
|
+
const logConfig = Reflect.getMetadata(LOG_KEY, instance, handlerName) || controllerMetadata.logConfig;
|
|
1696
|
+
let rateLimiter = null;
|
|
1697
|
+
if (rateLimitOptions) {
|
|
1698
|
+
try {
|
|
1699
|
+
rateLimiter = rateLimiterCache.get(rateLimitOptions);
|
|
1700
|
+
} catch (err) {
|
|
1701
|
+
getLogger().warn({
|
|
1702
|
+
err
|
|
1703
|
+
}, "express-rate-limit not available, skipping rate limiting for this route");
|
|
1636
1704
|
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
if (
|
|
1672
|
-
|
|
1673
|
-
rateLimiter = rateLimiterCache.get(rateLimitOptions);
|
|
1674
|
-
} catch (err) {
|
|
1675
|
-
getLogger().warn({
|
|
1676
|
-
err
|
|
1677
|
-
}, "express-rate-limit not available, skipping rate limiting for this route");
|
|
1678
|
-
}
|
|
1705
|
+
}
|
|
1706
|
+
return {
|
|
1707
|
+
params,
|
|
1708
|
+
httpCode,
|
|
1709
|
+
headers,
|
|
1710
|
+
middlewares,
|
|
1711
|
+
beforeHooks,
|
|
1712
|
+
afterHooks,
|
|
1713
|
+
redirect,
|
|
1714
|
+
contentType,
|
|
1715
|
+
roles,
|
|
1716
|
+
cache,
|
|
1717
|
+
rateLimiter,
|
|
1718
|
+
version,
|
|
1719
|
+
methodVersion,
|
|
1720
|
+
timeout,
|
|
1721
|
+
logConfig
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
__name(getRouteMetadata, "getRouteMetadata");
|
|
1725
|
+
function calculateFinalPath(path, routeMetadata, controllerMetadata) {
|
|
1726
|
+
let finalPath = path;
|
|
1727
|
+
if (routeMetadata.methodVersion?.options?.addPrefix && routeMetadata.methodVersion.version !== controllerMetadata.version?.version) {
|
|
1728
|
+
finalPath = `/${routeMetadata.methodVersion.version}${path}`;
|
|
1729
|
+
}
|
|
1730
|
+
return finalPath;
|
|
1731
|
+
}
|
|
1732
|
+
__name(calculateFinalPath, "calculateFinalPath");
|
|
1733
|
+
function createRouteHandler(instance, handlerName, routeMetadata) {
|
|
1734
|
+
return async (req, res, next) => {
|
|
1735
|
+
req["startTime"] = Date.now();
|
|
1736
|
+
const timeoutState = setupTimeout(req, res, routeMetadata.timeout);
|
|
1737
|
+
try {
|
|
1738
|
+
if (timeoutState.timedOut) return;
|
|
1739
|
+
if (routeMetadata.rateLimiter) {
|
|
1740
|
+
await applyRateLimiting(req, res, routeMetadata.rateLimiter);
|
|
1679
1741
|
}
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1742
|
+
applyResponseHeaders(res, routeMetadata);
|
|
1743
|
+
logRouteEntry(req, routeMetadata.logConfig);
|
|
1744
|
+
if (!checkRoles(req, res, routeMetadata.roles, timeoutState)) {
|
|
1745
|
+
return;
|
|
1683
1746
|
}
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
if (logConfig.logBody) logData.body = req.body;
|
|
1730
|
-
logger.info({
|
|
1731
|
-
entry: logData
|
|
1732
|
-
}, "Route Entry:");
|
|
1733
|
-
}
|
|
1734
|
-
if (roles.length && !req?.user?.roles?.some((role) => roles.includes(role))) {
|
|
1735
|
-
const errorResponse = createFinalErrorResponse(req, HttpStatusCodes.FORBIDDEN, "Forbidden Insufficient Roles");
|
|
1736
|
-
res.status(HttpStatusCodes.FORBIDDEN).json(errorResponse);
|
|
1737
|
-
if (timeoutId) {
|
|
1738
|
-
clearTimeout(timeoutId);
|
|
1739
|
-
}
|
|
1740
|
-
return;
|
|
1741
|
-
}
|
|
1742
|
-
if (redirect?.url) {
|
|
1743
|
-
if (timeoutId) {
|
|
1744
|
-
clearTimeout(timeoutId);
|
|
1745
|
-
}
|
|
1746
|
-
return res.redirect(redirect.statusCode, redirect.url);
|
|
1747
|
-
}
|
|
1748
|
-
for (const fn of beforeHooks) await fn(req, res);
|
|
1749
|
-
const args = [];
|
|
1750
|
-
if (params.length) {
|
|
1751
|
-
params.forEach(({ index, type, key, options }) => {
|
|
1752
|
-
let rawValue;
|
|
1753
|
-
switch (type) {
|
|
1754
|
-
case "query":
|
|
1755
|
-
rawValue = key ? req.query[key] : req.query;
|
|
1756
|
-
args[index] = options ? applyParamOptions(rawValue, options, key) : rawValue;
|
|
1757
|
-
break;
|
|
1758
|
-
case "param":
|
|
1759
|
-
rawValue = key ? req.params[key] : req.params;
|
|
1760
|
-
args[index] = options ? applyParamOptions(rawValue, options, key) : rawValue;
|
|
1761
|
-
break;
|
|
1762
|
-
case "body":
|
|
1763
|
-
args[index] = key ? req.body?.[key] : req.body;
|
|
1764
|
-
break;
|
|
1765
|
-
case "req":
|
|
1766
|
-
args[index] = req;
|
|
1767
|
-
break;
|
|
1768
|
-
case "res":
|
|
1769
|
-
args[index] = res;
|
|
1770
|
-
break;
|
|
1771
|
-
case "logger":
|
|
1772
|
-
args[index] = getLogger();
|
|
1773
|
-
break;
|
|
1774
|
-
case "reqHeader":
|
|
1775
|
-
args[index] = key ? req.headers[key.toLowerCase()] : req.headers;
|
|
1776
|
-
break;
|
|
1777
|
-
case "reqId":
|
|
1778
|
-
args[index] = req.headers["x-request-id"] || req?.id || void 0;
|
|
1779
|
-
break;
|
|
1780
|
-
case "cookie":
|
|
1781
|
-
args[index] = key ? req.cookies?.[key] : req.cookies;
|
|
1782
|
-
break;
|
|
1783
|
-
}
|
|
1784
|
-
});
|
|
1785
|
-
}
|
|
1786
|
-
const result = instance[handlerName](...args);
|
|
1787
|
-
const awaited = result instanceof Promise ? await result : result;
|
|
1788
|
-
if (timeoutId) {
|
|
1789
|
-
clearTimeout(timeoutId);
|
|
1790
|
-
}
|
|
1791
|
-
if (timedOut) return;
|
|
1792
|
-
if (redirect && awaited && typeof awaited === "object" && "url" in awaited) {
|
|
1793
|
-
const redirectUrl = awaited.url;
|
|
1794
|
-
const redirectStatus = awaited.statusCode || redirect.statusCode;
|
|
1795
|
-
return res.redirect(redirectStatus, redirectUrl);
|
|
1796
|
-
}
|
|
1797
|
-
if (!res.headersSent && typeof awaited !== "undefined") {
|
|
1798
|
-
if (httpCode) res.status(httpCode);
|
|
1799
|
-
for (const [k, v] of Object.entries(headers)) {
|
|
1800
|
-
const normalized = normalizeHeaderValue(v);
|
|
1801
|
-
if (typeof normalized !== "undefined") {
|
|
1802
|
-
res.set(k, normalized);
|
|
1803
|
-
}
|
|
1804
|
-
}
|
|
1805
|
-
res.json(awaited);
|
|
1806
|
-
}
|
|
1807
|
-
if (logConfig?.logExit) {
|
|
1808
|
-
const logger = getLogger();
|
|
1809
|
-
const logData = {
|
|
1810
|
-
method: req.method,
|
|
1811
|
-
url: req.originalUrl || req.url,
|
|
1812
|
-
statusCode: res.statusCode,
|
|
1813
|
-
duration: `${Date.now() - req.startTime}ms`
|
|
1814
|
-
};
|
|
1815
|
-
if (logConfig.logResponse) logData.response = awaited;
|
|
1816
|
-
logger.info({
|
|
1817
|
-
exit: logData
|
|
1818
|
-
}, "Route Exit:");
|
|
1819
|
-
}
|
|
1820
|
-
for (const fn of afterHooks) await fn(req, res, awaited);
|
|
1821
|
-
} catch (err) {
|
|
1822
|
-
if (timeoutId) {
|
|
1823
|
-
clearTimeout(timeoutId);
|
|
1824
|
-
}
|
|
1825
|
-
next(err);
|
|
1826
|
-
}
|
|
1827
|
-
}, "handler");
|
|
1828
|
-
router[method](basePath + finalPath, ...middlewares, handler);
|
|
1747
|
+
if (routeMetadata.redirect?.url) {
|
|
1748
|
+
clearTimeout(timeoutState.timeoutId);
|
|
1749
|
+
return res.redirect(routeMetadata.redirect.statusCode, routeMetadata.redirect.url);
|
|
1750
|
+
}
|
|
1751
|
+
for (const fn of routeMetadata.beforeHooks) await fn(req, res);
|
|
1752
|
+
const args = extractHandlerArgs(req, res, routeMetadata.params);
|
|
1753
|
+
const result = instance[handlerName](...args);
|
|
1754
|
+
const awaited = result instanceof Promise ? await result : result;
|
|
1755
|
+
clearTimeout(timeoutState.timeoutId);
|
|
1756
|
+
if (timeoutState.timedOut) return;
|
|
1757
|
+
if (handleDynamicRedirect(res, routeMetadata.redirect, awaited)) {
|
|
1758
|
+
return;
|
|
1759
|
+
}
|
|
1760
|
+
sendResponse(res, awaited, routeMetadata.httpCode, routeMetadata.headers);
|
|
1761
|
+
logRouteExit(req, res, awaited, routeMetadata.logConfig);
|
|
1762
|
+
for (const fn of routeMetadata.afterHooks) await fn(req, res, awaited);
|
|
1763
|
+
} catch (err) {
|
|
1764
|
+
clearTimeout(timeoutState.timeoutId);
|
|
1765
|
+
next(err);
|
|
1766
|
+
}
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
__name(createRouteHandler, "createRouteHandler");
|
|
1770
|
+
function setupTimeout(req, res, timeout) {
|
|
1771
|
+
const state = {
|
|
1772
|
+
timeoutId: void 0,
|
|
1773
|
+
timedOut: false
|
|
1774
|
+
};
|
|
1775
|
+
if (timeout) {
|
|
1776
|
+
state.timeoutId = setTimeout(() => {
|
|
1777
|
+
if (!res.headersSent && !state.timedOut) {
|
|
1778
|
+
state.timedOut = true;
|
|
1779
|
+
const errorResponse = createFinalErrorResponse(req, HttpStatusCodes.REQUEST_TIMEOUT, "Request timed out");
|
|
1780
|
+
res.status(HttpStatusCodes.REQUEST_TIMEOUT).json(errorResponse);
|
|
1781
|
+
}
|
|
1782
|
+
}, timeout.ms);
|
|
1783
|
+
}
|
|
1784
|
+
return state;
|
|
1785
|
+
}
|
|
1786
|
+
__name(setupTimeout, "setupTimeout");
|
|
1787
|
+
async function applyRateLimiting(req, res, rateLimiter) {
|
|
1788
|
+
await new Promise((resolve, reject) => {
|
|
1789
|
+
rateLimiter(req, res, (err) => {
|
|
1790
|
+
if (err) reject(err);
|
|
1791
|
+
else resolve();
|
|
1829
1792
|
});
|
|
1830
1793
|
});
|
|
1831
1794
|
}
|
|
1832
|
-
__name(
|
|
1795
|
+
__name(applyRateLimiting, "applyRateLimiting");
|
|
1796
|
+
function applyResponseHeaders(res, routeMetadata) {
|
|
1797
|
+
if (!res.headersSent && routeMetadata.contentType) {
|
|
1798
|
+
res.setHeader("Content-Type", routeMetadata.contentType.type);
|
|
1799
|
+
}
|
|
1800
|
+
if (routeMetadata.version?.options?.addHeader && routeMetadata.version?.options?.headerName && routeMetadata.version?.version) {
|
|
1801
|
+
if (!res.headersSent) {
|
|
1802
|
+
res.setHeader(routeMetadata.version.options.headerName, routeMetadata.version.version);
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
if (routeMetadata.cache && !res.headersSent) {
|
|
1806
|
+
res.setHeader("Cache-Control", `public, max-age=${routeMetadata.cache.ttlSeconds}`);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
__name(applyResponseHeaders, "applyResponseHeaders");
|
|
1810
|
+
function logRouteEntry(req, logConfig) {
|
|
1811
|
+
if (logConfig?.logEntry) {
|
|
1812
|
+
const logger = getLogger();
|
|
1813
|
+
const logData = {
|
|
1814
|
+
method: req.method,
|
|
1815
|
+
url: req.originalUrl || req.url,
|
|
1816
|
+
userAgent: req.get("User-Agent")
|
|
1817
|
+
};
|
|
1818
|
+
if (logConfig.logParams) {
|
|
1819
|
+
logData.params = req.params;
|
|
1820
|
+
logData.query = req.query;
|
|
1821
|
+
}
|
|
1822
|
+
if (logConfig.logBody) logData.body = req.body;
|
|
1823
|
+
logger.info({
|
|
1824
|
+
entry: logData
|
|
1825
|
+
}, "Route Entry:");
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
__name(logRouteEntry, "logRouteEntry");
|
|
1829
|
+
function logRouteExit(req, res, result, logConfig) {
|
|
1830
|
+
if (logConfig?.logExit) {
|
|
1831
|
+
const logger = getLogger();
|
|
1832
|
+
const logData = {
|
|
1833
|
+
method: req.method,
|
|
1834
|
+
url: req.originalUrl || req.url,
|
|
1835
|
+
statusCode: res.statusCode,
|
|
1836
|
+
duration: `${Date.now() - req.startTime}ms`
|
|
1837
|
+
};
|
|
1838
|
+
if (logConfig.logResponse) logData.response = result;
|
|
1839
|
+
logger.info({
|
|
1840
|
+
exit: logData
|
|
1841
|
+
}, "Route Exit:");
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
__name(logRouteExit, "logRouteExit");
|
|
1845
|
+
function checkRoles(req, res, roles, timeoutState) {
|
|
1846
|
+
if (roles.length && !req?.user?.roles?.some((role) => roles.includes(role))) {
|
|
1847
|
+
const errorResponse = createFinalErrorResponse(req, HttpStatusCodes.FORBIDDEN, "Forbidden Insufficient Roles");
|
|
1848
|
+
res.status(HttpStatusCodes.FORBIDDEN).json(errorResponse);
|
|
1849
|
+
clearTimeout(timeoutState.timeoutId);
|
|
1850
|
+
return false;
|
|
1851
|
+
}
|
|
1852
|
+
return true;
|
|
1853
|
+
}
|
|
1854
|
+
__name(checkRoles, "checkRoles");
|
|
1855
|
+
function extractHandlerArgs(req, res, params) {
|
|
1856
|
+
const args = [];
|
|
1857
|
+
if (params.length) {
|
|
1858
|
+
params.forEach(({ index, type, key, options }) => {
|
|
1859
|
+
let rawValue;
|
|
1860
|
+
switch (type) {
|
|
1861
|
+
case "query":
|
|
1862
|
+
rawValue = key ? req.query[key] : req.query;
|
|
1863
|
+
args[index] = options ? applyParamOptions(rawValue, options, key) : rawValue;
|
|
1864
|
+
break;
|
|
1865
|
+
case "param":
|
|
1866
|
+
rawValue = key ? req.params[key] : req.params;
|
|
1867
|
+
args[index] = options ? applyParamOptions(rawValue, options, key) : rawValue;
|
|
1868
|
+
break;
|
|
1869
|
+
case "body":
|
|
1870
|
+
args[index] = key ? req.body?.[key] : req.body;
|
|
1871
|
+
break;
|
|
1872
|
+
case "req":
|
|
1873
|
+
args[index] = req;
|
|
1874
|
+
break;
|
|
1875
|
+
case "res":
|
|
1876
|
+
args[index] = res;
|
|
1877
|
+
break;
|
|
1878
|
+
case "logger":
|
|
1879
|
+
args[index] = getLogger();
|
|
1880
|
+
break;
|
|
1881
|
+
case "reqHeader":
|
|
1882
|
+
args[index] = key ? req.headers[key.toLowerCase()] : req.headers;
|
|
1883
|
+
break;
|
|
1884
|
+
case "reqId":
|
|
1885
|
+
args[index] = req.headers["x-request-id"] || req?.id || void 0;
|
|
1886
|
+
break;
|
|
1887
|
+
case "cookie":
|
|
1888
|
+
args[index] = key ? req.cookies?.[key] : req.cookies;
|
|
1889
|
+
break;
|
|
1890
|
+
}
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
return args;
|
|
1894
|
+
}
|
|
1895
|
+
__name(extractHandlerArgs, "extractHandlerArgs");
|
|
1896
|
+
function handleDynamicRedirect(res, redirect, result) {
|
|
1897
|
+
if (redirect && result && typeof result === "object" && "url" in result) {
|
|
1898
|
+
const redirectUrl = result.url;
|
|
1899
|
+
const redirectStatus = result.statusCode || redirect.statusCode;
|
|
1900
|
+
res.redirect(redirectStatus, redirectUrl);
|
|
1901
|
+
return true;
|
|
1902
|
+
}
|
|
1903
|
+
return false;
|
|
1904
|
+
}
|
|
1905
|
+
__name(handleDynamicRedirect, "handleDynamicRedirect");
|
|
1906
|
+
function sendResponse(res, result, httpCode, headers) {
|
|
1907
|
+
if (!res.headersSent && result !== void 0) {
|
|
1908
|
+
if (httpCode) res.status(httpCode);
|
|
1909
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
1910
|
+
const normalized = normalizeHeaderValue(v);
|
|
1911
|
+
if (normalized !== void 0) {
|
|
1912
|
+
res.set(k, normalized);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
res.json(result);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
__name(sendResponse, "sendResponse");
|
|
1833
1919
|
function applyParamOptions(rawValue, options, key) {
|
|
1834
1920
|
if (!options) return rawValue;
|
|
1835
1921
|
let value = rawValue;
|
|
@@ -1844,98 +1930,11 @@ function applyParamOptions(rawValue, options, key) {
|
|
|
1844
1930
|
}
|
|
1845
1931
|
if (value !== void 0 && value !== null) {
|
|
1846
1932
|
if (options.dataType === "array") {
|
|
1847
|
-
|
|
1848
|
-
if (typeof value === "string") {
|
|
1849
|
-
value = value.split(delimiter).map((v) => v.trim());
|
|
1850
|
-
} else if (!Array.isArray(value)) {
|
|
1851
|
-
value = [
|
|
1852
|
-
value
|
|
1853
|
-
];
|
|
1854
|
-
}
|
|
1855
|
-
if (options.type === "string" && options.pattern) {
|
|
1856
|
-
const hasInvalidPattern = value.some((v) => !options.pattern.test(v));
|
|
1857
|
-
if (hasInvalidPattern) {
|
|
1858
|
-
throw new BadRequestException(`Parameter '${key}' array contains values that do not match pattern: ${options.patternName || options.pattern}`);
|
|
1859
|
-
}
|
|
1860
|
-
}
|
|
1861
|
-
if (options.type === "number" && options.pattern) {
|
|
1862
|
-
const hasInvalidPattern = value.some((v) => !options.pattern.test(String(v)));
|
|
1863
|
-
if (hasInvalidPattern) {
|
|
1864
|
-
throw new BadRequestException(`Parameter '${key}' array contains values that do not match pattern: ${options.patternName || options.pattern}`);
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1867
|
-
if (options.type === "number" && options.throwError !== false) {
|
|
1868
|
-
const hasInvalidNumber = value.some((v) => Number.isNaN(Number(v)));
|
|
1869
|
-
if (hasInvalidNumber) {
|
|
1870
|
-
throw new BadRequestException(`Type error: expected number array${paramName}`);
|
|
1871
|
-
}
|
|
1872
|
-
if (options.min !== void 0) {
|
|
1873
|
-
const hasBelowMin = value.some((v) => Number(v) < options.min);
|
|
1874
|
-
if (hasBelowMin) {
|
|
1875
|
-
throw new BadRequestException(`Validation error: number array values must be >= ${options.min}${paramName}`);
|
|
1876
|
-
}
|
|
1877
|
-
}
|
|
1878
|
-
if (options.max !== void 0) {
|
|
1879
|
-
const hasAboveMax = value.some((v) => Number(v) > options.max);
|
|
1880
|
-
if (hasAboveMax) {
|
|
1881
|
-
throw new BadRequestException(`Validation error: number array values must be <= ${options.max}${paramName}`);
|
|
1882
|
-
}
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
if (options.type === "boolean" && options.throwError !== false) {
|
|
1886
|
-
const hasInvalidBoolean = value.some((v) => !isValidBooleanInput(v));
|
|
1887
|
-
if (hasInvalidBoolean) {
|
|
1888
|
-
throw new BadRequestException(`Type error: expected boolean array${paramName}`);
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
if (options.type) {
|
|
1892
|
-
value = value.map((v) => convertType(v, options.type || "string"));
|
|
1893
|
-
}
|
|
1933
|
+
value = handleArrayParam(value, options, key);
|
|
1894
1934
|
} else if (options.dataType === "object") {
|
|
1895
|
-
|
|
1896
|
-
try {
|
|
1897
|
-
value = JSON.parse(value);
|
|
1898
|
-
} catch (_e) {
|
|
1899
|
-
if (options.throwError !== false) {
|
|
1900
|
-
throw new BadRequestException(`Invalid JSON object${paramName}`);
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
1935
|
+
value = handleObjectParam(value, options, key);
|
|
1904
1936
|
} else if (options.type) {
|
|
1905
|
-
|
|
1906
|
-
value = convertType(value, options.type);
|
|
1907
|
-
if (options.throwError === false) {
|
|
1908
|
-
if (options.type === "number" && typeof value === "number") {
|
|
1909
|
-
if (options.min !== void 0 && value < options.min) {
|
|
1910
|
-
value = void 0;
|
|
1911
|
-
}
|
|
1912
|
-
if (options.max !== void 0 && value > options.max) {
|
|
1913
|
-
value = void 0;
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
|
-
const paramNotMatchMsg = `Parameter '${key}' does not match pattern: ${options.patternName || options.pattern}`;
|
|
1918
|
-
if (options.type === "string" && options.pattern && typeof value === "string" && !options.pattern.test(value)) {
|
|
1919
|
-
throw new BadRequestException(paramNotMatchMsg);
|
|
1920
|
-
} else if (options.type === "number" && options.pattern && typeof value === "number" && !options.pattern.test(String(value))) {
|
|
1921
|
-
throw new BadRequestException(paramNotMatchMsg);
|
|
1922
|
-
}
|
|
1923
|
-
if (options.throwError !== false) {
|
|
1924
|
-
if (options.type === "number" && typeof value === "number" && Number.isNaN(value)) {
|
|
1925
|
-
throw new BadRequestException(`Type error: expected number${paramName}`);
|
|
1926
|
-
}
|
|
1927
|
-
if (options.type === "number" && typeof value === "number") {
|
|
1928
|
-
if (options.min !== void 0 && value < options.min) {
|
|
1929
|
-
throw new BadRequestException(`Validation error: number must be >= ${options.min}${paramName}`);
|
|
1930
|
-
}
|
|
1931
|
-
if (options.max !== void 0 && value > options.max) {
|
|
1932
|
-
throw new BadRequestException(`Validation error: number must be <= ${options.max}${paramName}`);
|
|
1933
|
-
}
|
|
1934
|
-
}
|
|
1935
|
-
if (options.type === "boolean" && !isValidBooleanInput(originalValue)) {
|
|
1936
|
-
throw new BadRequestException(`Type error: expected boolean${paramName}`);
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1937
|
+
value = handleSingleParam(value, options, key);
|
|
1939
1938
|
}
|
|
1940
1939
|
}
|
|
1941
1940
|
if (options.transform && typeof options.transform === "function") {
|
|
@@ -1952,6 +1951,128 @@ function applyParamOptions(rawValue, options, key) {
|
|
|
1952
1951
|
return value;
|
|
1953
1952
|
}
|
|
1954
1953
|
__name(applyParamOptions, "applyParamOptions");
|
|
1954
|
+
function handleArrayParam(value, options, key) {
|
|
1955
|
+
const delimiter = options.delimiter || ",";
|
|
1956
|
+
if (typeof value === "string") {
|
|
1957
|
+
value = value.split(delimiter).map((v) => v.trim());
|
|
1958
|
+
} else if (!Array.isArray(value)) {
|
|
1959
|
+
value = [
|
|
1960
|
+
value
|
|
1961
|
+
];
|
|
1962
|
+
}
|
|
1963
|
+
validateArrayPattern(value, options, key);
|
|
1964
|
+
validateArrayElements(value, options, key);
|
|
1965
|
+
if (options.type) {
|
|
1966
|
+
value = value.map((v) => convertType(v, options.type || "string"));
|
|
1967
|
+
}
|
|
1968
|
+
return value;
|
|
1969
|
+
}
|
|
1970
|
+
__name(handleArrayParam, "handleArrayParam");
|
|
1971
|
+
function validateArrayPattern(value, options, key) {
|
|
1972
|
+
if (options.type === "string" && options.pattern) {
|
|
1973
|
+
const hasInvalidPattern = value.some((v) => !options.pattern.test(v));
|
|
1974
|
+
if (hasInvalidPattern) {
|
|
1975
|
+
throw new BadRequestException(`Parameter '${key}' array contains values that do not match pattern: ${options.patternName || options.pattern}`);
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
if (options.type === "number" && options.pattern) {
|
|
1979
|
+
const hasInvalidPattern = value.some((v) => !options.pattern.test(String(v)));
|
|
1980
|
+
if (hasInvalidPattern) {
|
|
1981
|
+
throw new BadRequestException(`Parameter '${key}' array contains values that do not match pattern: ${options.patternName || options.pattern}`);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
__name(validateArrayPattern, "validateArrayPattern");
|
|
1986
|
+
function validateArrayElements(value, options, key) {
|
|
1987
|
+
const paramName = key ? `: ${key}` : "";
|
|
1988
|
+
if (options.type === "number" && options.throwError !== false) {
|
|
1989
|
+
const hasInvalidNumber = value.some((v) => Number.isNaN(Number(v)));
|
|
1990
|
+
if (hasInvalidNumber) {
|
|
1991
|
+
throw new BadRequestException(`Type error: expected number array${paramName}`);
|
|
1992
|
+
}
|
|
1993
|
+
if (options.min !== void 0) {
|
|
1994
|
+
const hasBelowMin = value.some((v) => Number(v) < options.min);
|
|
1995
|
+
if (hasBelowMin) {
|
|
1996
|
+
throw new BadRequestException(`Validation error: number array values must be >= ${options.min}${paramName}`);
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
if (options.max !== void 0) {
|
|
2000
|
+
const hasAboveMax = value.some((v) => Number(v) > options.max);
|
|
2001
|
+
if (hasAboveMax) {
|
|
2002
|
+
throw new BadRequestException(`Validation error: number array values must be <= ${options.max}${paramName}`);
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
if (options.type === "boolean" && options.throwError !== false) {
|
|
2007
|
+
const hasInvalidBoolean = value.some((v) => !isValidBooleanInput(v));
|
|
2008
|
+
if (hasInvalidBoolean) {
|
|
2009
|
+
throw new BadRequestException(`Type error: expected boolean array${paramName}`);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
__name(validateArrayElements, "validateArrayElements");
|
|
2014
|
+
function handleObjectParam(value, options, key) {
|
|
2015
|
+
const paramName = key ? `: ${key}` : "";
|
|
2016
|
+
if (typeof value === "string") {
|
|
2017
|
+
try {
|
|
2018
|
+
value = JSON.parse(value);
|
|
2019
|
+
} catch (_e) {
|
|
2020
|
+
if (options.throwError !== false) {
|
|
2021
|
+
throw new BadRequestException(`Invalid JSON object${paramName}`);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
return value;
|
|
2026
|
+
}
|
|
2027
|
+
__name(handleObjectParam, "handleObjectParam");
|
|
2028
|
+
function handleSingleParam(value, options, key) {
|
|
2029
|
+
const paramName = key ? `: ${key}` : "";
|
|
2030
|
+
const originalValue = value;
|
|
2031
|
+
const targetType = options.type ?? "string";
|
|
2032
|
+
value = convertType(value, targetType);
|
|
2033
|
+
if (options.throwError === false) {
|
|
2034
|
+
if (options.type === "number" && typeof value === "number") {
|
|
2035
|
+
if (options.min !== void 0 && value < options.min) {
|
|
2036
|
+
value = void 0;
|
|
2037
|
+
}
|
|
2038
|
+
if (options.max !== void 0 && value > options.max) {
|
|
2039
|
+
value = void 0;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
validateSinglePattern(value, options, key);
|
|
2044
|
+
validateSingleType(value, originalValue, options, paramName);
|
|
2045
|
+
return value;
|
|
2046
|
+
}
|
|
2047
|
+
__name(handleSingleParam, "handleSingleParam");
|
|
2048
|
+
function validateSinglePattern(value, options, key) {
|
|
2049
|
+
const paramNotMatchMsg = `Parameter '${key}' does not match pattern: ${options.patternName || options.pattern}`;
|
|
2050
|
+
if (options.type === "string" && options.pattern && typeof value === "string" && !options.pattern.test(value)) {
|
|
2051
|
+
throw new BadRequestException(paramNotMatchMsg);
|
|
2052
|
+
} else if (options.type === "number" && options.pattern && typeof value === "number" && !options.pattern.test(String(value))) {
|
|
2053
|
+
throw new BadRequestException(paramNotMatchMsg);
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
__name(validateSinglePattern, "validateSinglePattern");
|
|
2057
|
+
function validateSingleType(value, originalValue, options, paramName) {
|
|
2058
|
+
if (options.throwError !== false) {
|
|
2059
|
+
if (options.type === "number" && typeof value === "number" && Number.isNaN(value)) {
|
|
2060
|
+
throw new BadRequestException(`Type error: expected number${paramName}`);
|
|
2061
|
+
}
|
|
2062
|
+
if (options.type === "number" && typeof value === "number") {
|
|
2063
|
+
if (options.min !== void 0 && value < options.min) {
|
|
2064
|
+
throw new BadRequestException(`Validation error: number must be >= ${options.min}${paramName}`);
|
|
2065
|
+
}
|
|
2066
|
+
if (options.max !== void 0 && value > options.max) {
|
|
2067
|
+
throw new BadRequestException(`Validation error: number must be <= ${options.max}${paramName}`);
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
if (options.type === "boolean" && !isValidBooleanInput(originalValue)) {
|
|
2071
|
+
throw new BadRequestException(`Type error: expected boolean${paramName}`);
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
__name(validateSingleType, "validateSingleType");
|
|
1955
2076
|
function isValidBooleanInput(value) {
|
|
1956
2077
|
if (typeof value === "boolean") return true;
|
|
1957
2078
|
if (typeof value === "number") return value === 0 || value === 1;
|