@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.
155 lines • 7.06 kB
JavaScript
/* eslint-disable @typescript-eslint/no-unused-vars */
import "reflect-metadata";
// --- End sample Express interfaces ---
const ROUTES_KEY = Symbol("routes");
const MIDDLEWARE_KEY = Symbol("middlewares");
const PARAMS_KEY = Symbol("params");
// ---- Route Decorators ----
function createRouteDecorator(method) {
return (path) => {
return (target, propertyKey, descriptor) => {
const routes = Reflect.getMetadata(ROUTES_KEY, target.constructor) || [];
routes.push({
path,
method,
handlerName: propertyKey,
});
Reflect.defineMetadata(ROUTES_KEY, routes, target.constructor);
};
};
}
export const Get = createRouteDecorator("get");
export const Post = createRouteDecorator("post");
export const Put = createRouteDecorator("put");
export const Patch = createRouteDecorator("patch");
export const Delete = createRouteDecorator("delete");
export const Options = createRouteDecorator("options");
export const Head = createRouteDecorator("head");
export const Trace = createRouteDecorator("trace");
export const Connect = createRouteDecorator("connect");
// ---- Controller Decorator ----
export function Controller(basePath) {
return (target) => {
Reflect.defineMetadata("basePath", basePath, target);
};
}
// ---- Middleware Decorator ----
export function Use(...middlewares) {
return (target, propertyKey, descriptor) => {
const existing = Reflect.getMetadata(MIDDLEWARE_KEY, target, propertyKey) || [];
Reflect.defineMetadata(MIDDLEWARE_KEY, [...existing, ...middlewares], target, propertyKey);
};
}
// ---- Parameter Decorators ----
function createParamDecorator(type, key) {
return (paramKey) => {
return (target, propertyKey, parameterIndex) => {
const params = Reflect.getMetadata(PARAMS_KEY, target, propertyKey) || [];
// Ensure parameters are ordered by index
params.push({ index: parameterIndex, type, key: paramKey || key });
params.sort((a, b) => a.index - b.index);
Reflect.defineMetadata(PARAMS_KEY, params, target, propertyKey);
};
};
}
export const Query = createParamDecorator("query");
export const Param = createParamDecorator("param");
export const Body = createParamDecorator("body");
export const Req = createParamDecorator("req");
export const Res = createParamDecorator("res");
// Custom HTTP status code decorator
const HTTP_CODE_KEY = Symbol("httpCode");
export function HttpCode(status) {
return (target, propertyKey, descriptor) => {
Reflect.defineMetadata(HTTP_CODE_KEY, status, target, propertyKey);
};
}
// Custom header decorator
const HEADER_KEY = Symbol("headers");
export function Header(name, value) {
return (target, propertyKey, descriptor) => {
const headers = Reflect.getMetadata(HEADER_KEY, target, propertyKey) || {};
headers[name] = value;
Reflect.defineMetadata(HEADER_KEY, headers, target, propertyKey);
};
}
// Before/After hooks (not Express middleware, but can be used for logging, etc.)
const BEFORE_KEY = Symbol("before");
const AFTER_KEY = Symbol("after");
export function Before(fn) {
return (target, propertyKey, descriptor) => {
const hooks = Reflect.getMetadata(BEFORE_KEY, target, propertyKey) || [];
hooks.push(fn);
Reflect.defineMetadata(BEFORE_KEY, hooks, target, propertyKey);
};
}
export function After(fn) {
return (target, propertyKey, descriptor) => {
const hooks = Reflect.getMetadata(AFTER_KEY, target, propertyKey) || [];
hooks.push(fn);
Reflect.defineMetadata(AFTER_KEY, hooks, target, propertyKey);
};
}
// ---- Register Controllers ----
export function registerControllers(router, controllers) {
controllers.forEach((ControllerClass) => {
const instance = new ControllerClass();
const basePath = Reflect.getMetadata("basePath", ControllerClass) || "";
const routes = Reflect.getMetadata(ROUTES_KEY, ControllerClass) || [];
routes.forEach(({ path, method, handlerName }) => {
const middlewares = Reflect.getMetadata(MIDDLEWARE_KEY, instance, handlerName) || [];
const params = Reflect.getMetadata(PARAMS_KEY, instance, handlerName) || [];
const httpCode = Reflect.getMetadata(HTTP_CODE_KEY, instance, handlerName);
const headers = Reflect.getMetadata(HEADER_KEY, instance, handlerName) || {};
const beforeHooks = Reflect.getMetadata(BEFORE_KEY, instance, handlerName) || [];
const afterHooks = Reflect.getMetadata(AFTER_KEY, instance, handlerName) || [];
const handler = async (req, res, next) => {
var _a, _b;
try {
for (const fn of beforeHooks)
await fn(req, res);
const args = [];
if (params.length) {
params.forEach(({ index, type, key }) => {
var _a;
switch (type) {
case "query":
args[index] = key ? req.query[key] : req.query;
break;
case "param":
args[index] = key ? req.params[key] : req.params;
break;
case "body":
args[index] = key ? (_a = req.body) === null || _a === void 0 ? void 0 : _a[key] : req.body;
break;
case "req":
args[index] = req;
break;
case "res":
args[index] = res;
break;
}
});
}
const result = instance[handlerName](...args);
// Support both sync and async handlers
const awaited = result instanceof Promise ? await result : result;
if (!res.headersSent && typeof awaited !== "undefined") {
if (httpCode)
(_a = res.status) === null || _a === void 0 ? void 0 : _a.call(res, httpCode);
for (const [k, v] of Object.entries(headers))
(_b = res.set) === null || _b === void 0 ? void 0 : _b.call(res, k, v);
res.json(awaited);
}
for (const fn of afterHooks)
await fn(req, res, awaited);
}
catch (err) {
next(err);
}
};
router[method](basePath + path, ...middlewares, handler);
});
});
}
//# sourceMappingURL=decorators.utils.js.map