@mini2/core
Version:
Mini Express Framework - Lightweight and modular Express.js framework with TypeScript support
365 lines • 17.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.keyOfParams = exports.keyOfQuery = exports.keyOfBody = exports.keyOfNext = exports.keyOfRes = exports.keyOfReq = exports.keyOfRouteOptions = exports.keyOfModuleName = exports.keyOfName = exports.keyOfPath = exports.RouteRegistry = exports.Controller = void 0;
exports.controller = controller;
exports.httpMethod = httpMethod;
exports.get = get;
exports.post = post;
exports.put = put;
exports.del = del;
exports.patch = patch;
exports.validate = validate;
exports.authenticated = authenticated;
exports.authorized = authorized;
exports.middleware = middleware;
exports.custom = custom;
exports.req = req;
exports.res = res;
exports.next = next;
exports.body = body;
exports.query = query;
exports.params = params;
exports.buildRouterFromController = buildRouterFromController;
exports.buildApp = buildApp;
require("reflect-metadata");
const express_1 = __importDefault(require("express"));
const array_unify_1 = require("./utils/array-unify");
const validation_middleware_1 = __importDefault(require("./middlewares/validation.middleware"));
const authenticated_middleware_1 = require("./middlewares/authenticated.middleware");
const authorized_middleware_1 = require("./middlewares/authorized.middleware");
class Controller {
constructor() {
const ctor = this.constructor;
const metaPath = Reflect.getMetadata(exports.keyOfPath, ctor);
const metaName = Reflect.getMetadata(exports.keyOfName, ctor);
const metaModuleName = Reflect.getMetadata(exports.keyOfModuleName, ctor);
this.path = metaPath;
this.name = metaName || metaPath;
this.moduleName = metaModuleName || metaName;
this.routeDefinitions = RouteRegistry.getRouteDefinitions(ctor);
}
getRouteDefinition(methodName) {
const find = this.routeDefinitions.routes.find(item => item.methodName === methodName);
if (!find)
throw new Error("Route definition of method not found");
return find;
}
getRouteDefinitions() {
return this.routeDefinitions;
}
}
exports.Controller = Controller;
/* ------------------------------------------------------------------ */
/* RouteRegistry */
/* ------------------------------------------------------------------ */
class RouteRegistry {
static getCtor(target) {
return typeof target === 'function' ? target : target.constructor;
}
static getRouteDefinitions(target) {
const ctor = this.getCtor(target);
if (!ctor.__routeDefinitions) {
ctor.__routeDefinitions = { basePath: '', routes: [] };
}
return ctor.__routeDefinitions;
}
static setBasePath(constructor, basePath, controllerName, moduleName) {
const defs = this.getRouteDefinitions(constructor);
defs.basePath = basePath;
if (controllerName !== undefined)
defs.controllerName = controllerName;
if (moduleName !== undefined)
defs.moduleName = moduleName;
}
static getOrCreateRoute(target, methodName) {
const defs = this.getRouteDefinitions(target);
let route = defs.routes.find(r => r.methodName === methodName);
if (!route) {
route = {
methodName,
validations: [],
permissions: [],
otherHttpMiddlewares: [],
parameterIndices: {},
};
defs.routes.push(route);
}
return route;
}
static updateRoute(target, methodName, updates) {
const route = this.getOrCreateRoute(target, methodName);
if (updates.validations && updates.validations.length) {
route.validations = [...route.validations, ...updates.validations];
}
if (updates.permissions && updates.permissions.length) {
route.permissions = Array.from(new Set([...(route.permissions ?? []), ...updates.permissions]));
}
if (updates.otherHttpMiddlewares && updates.otherHttpMiddlewares.length) {
route.otherHttpMiddlewares = Array.from(new Set([...(route.otherHttpMiddlewares ?? []), ...updates.otherHttpMiddlewares]));
}
if (updates.method !== undefined)
route.method = updates.method;
if (updates.path !== undefined)
route.path = updates.path;
if (updates.name !== undefined)
route.name = updates.name;
if (updates.authenticated !== undefined)
route.authenticated = updates.authenticated;
if (updates.extraData !== undefined)
route.extraData = updates.extraData;
if (updates.parameterIndices) {
route.parameterIndices = { ...(route.parameterIndices ?? {}), ...updates.parameterIndices };
}
}
static setParameterIndex(target, methodName, slot, index) {
const route = this.getOrCreateRoute(target, methodName);
route.parameterIndices = { ...(route.parameterIndices ?? {}), [slot]: index };
}
static getRoutes(target) {
return this.getRouteDefinitions(target).routes;
}
static getBasePathOf(target) {
return this.getRouteDefinitions(target).basePath;
}
}
exports.RouteRegistry = RouteRegistry;
/* ------------------------------------------------------------------ */
/* Metadata Keys */
/* ------------------------------------------------------------------ */
exports.keyOfPath = Symbol('path');
exports.keyOfName = Symbol('name');
exports.keyOfModuleName = Symbol('moduleName');
exports.keyOfRouteOptions = Symbol('routeOptions');
exports.keyOfReq = Symbol('req');
exports.keyOfRes = Symbol('res');
exports.keyOfNext = Symbol('next');
exports.keyOfBody = Symbol('body');
exports.keyOfQuery = Symbol('query');
exports.keyOfParams = Symbol('params');
/* ------------------------------------------------------------------ */
/* Decorators */
/* ------------------------------------------------------------------ */
function controller(path, name, moduleName) {
return function (constructor) {
const resolvedName = name ?? path;
const resolvedModuleName = moduleName ?? resolvedName;
// metadata'yı DOĞRUDAN orijinal constructor'a yaz
Reflect.defineMetadata(exports.keyOfPath, path, constructor);
Reflect.defineMetadata(exports.keyOfName, resolvedName, constructor);
Reflect.defineMetadata(exports.keyOfModuleName, resolvedModuleName, constructor);
// registry'yi orijinal constructor için güncelle
RouteRegistry.setBasePath(constructor, path, resolvedName, resolvedModuleName);
// ÖNEMLİ: sınıfı sarmalama! aynı constructor'ı döndür
return constructor;
};
}
function httpMethod(newOptions) {
return function (target, propertyKey, _descriptor) {
const existingOptions = Reflect.getMetadata(exports.keyOfRouteOptions, target, propertyKey) || {};
const method = newOptions.method ?? existingOptions.method;
const path = newOptions.path ?? existingOptions.path;
const validations = (0, array_unify_1.arrayUnify)((newOptions.validations ?? []).concat(existingOptions.validations ?? []));
const permissions = (0, array_unify_1.arrayUnify)((newOptions.permissions ?? []).concat(existingOptions.permissions ?? []));
const authenticated = newOptions.authenticated !== undefined ? newOptions.authenticated : existingOptions.authenticated;
const otherHttpMiddlewares = (0, array_unify_1.arrayUnify)((newOptions.otherHttpMiddlewares ?? []).concat(existingOptions.otherHttpMiddlewares ?? []));
const name = newOptions.name ?? existingOptions.name;
const extraData = existingOptions.extraData ?? new Map();
if (newOptions.extraData) {
const newOptionsExtraData = newOptions.extraData;
for (const k of Array.from(newOptionsExtraData.keys())) {
const currentValue = extraData.get(k);
if (currentValue === undefined) {
extraData.set(k, newOptionsExtraData.get(k));
continue;
}
const newValue = newOptionsExtraData.get(k);
let finalValue = currentValue;
if (Array.isArray(currentValue)) {
finalValue = (0, array_unify_1.arrayUnify)([...currentValue, ...newValue]);
}
else if (currentValue !== null &&
typeof currentValue === 'object' &&
Object.getPrototypeOf(currentValue) === Object.prototype &&
newValue !== null &&
typeof newValue === 'object' &&
Object.getPrototypeOf(newValue) === Object.prototype) {
finalValue = { ...currentValue, ...newValue };
}
else {
finalValue = newValue;
}
extraData.set(k, finalValue);
}
}
const mergedOptions = {};
if (method !== undefined)
mergedOptions.method = method;
if (path !== undefined)
mergedOptions.path = path;
if (validations.length)
mergedOptions.validations = validations;
if (permissions.length)
mergedOptions.permissions = permissions;
if (authenticated !== undefined)
mergedOptions.authenticated = authenticated;
if (otherHttpMiddlewares.length)
mergedOptions.otherHttpMiddlewares = otherHttpMiddlewares;
if (name !== undefined)
mergedOptions.name = name;
if (extraData && extraData.size > 0)
mergedOptions.extraData = extraData;
// NOT: method/param dekoratör metadataları **prototype** üzerinde tutuluyor
Reflect.defineMetadata(exports.keyOfRouteOptions, mergedOptions, target, propertyKey);
const updates = {
...(validations.length ? { validations } : {}),
...(permissions.length ? { permissions } : {}),
...(otherHttpMiddlewares.length ? { otherHttpMiddlewares } : {}),
};
if (method !== undefined)
updates.method = method;
if (path !== undefined)
updates.path = path;
if (name !== undefined)
updates.name = name;
if (authenticated !== undefined)
updates.authenticated = authenticated;
if (extraData && extraData.size > 0)
updates.extraData = extraData;
RouteRegistry.updateRoute(target, propertyKey, updates);
};
}
/* HTTP method sugar */
function get(path, name) { return httpMethod({ path, method: 'get', name: name ?? path }); }
function post(path, name) { return httpMethod({ path, method: 'post', name: name ?? path }); }
function put(path, name) { return httpMethod({ path, method: 'put', name: name ?? path }); }
function del(path, name) { return httpMethod({ path, method: 'delete', name: name ?? path }); }
function patch(path, name) { return httpMethod({ path, method: 'patch', name: name ?? path }); }
/* Sugar decorators */
function validate(options) {
return httpMethod({ validations: Array.isArray(options) ? options : [options] });
}
function authenticated(value = true) {
return httpMethod({ authenticated: value });
}
function authorized(value) {
return httpMethod({ permissions: Array.isArray(value) ? value : [value] });
}
function middleware(mw) {
return httpMethod({ otherHttpMiddlewares: [mw] });
}
function custom(key, value) {
const extraData = new Map();
extraData.set(key, value);
return httpMethod({ extraData: extraData });
}
/* Param dekoratörleri (prototype'a yazar) */
function req() { return (t, k, i) => { Reflect.defineMetadata(exports.keyOfReq, i, t, k); RouteRegistry.setParameterIndex(t, k, 'req', i); }; }
function res() { return (t, k, i) => { Reflect.defineMetadata(exports.keyOfRes, i, t, k); RouteRegistry.setParameterIndex(t, k, 'res', i); }; }
function next() { return (t, k, i) => { Reflect.defineMetadata(exports.keyOfNext, i, t, k); RouteRegistry.setParameterIndex(t, k, 'next', i); }; }
function body() { return (t, k, i) => { Reflect.defineMetadata(exports.keyOfBody, i, t, k); RouteRegistry.setParameterIndex(t, k, 'body', i); }; }
function query() { return (t, k, i) => { Reflect.defineMetadata(exports.keyOfQuery, i, t, k); RouteRegistry.setParameterIndex(t, k, 'query', i); }; }
function params() { return (t, k, i) => { Reflect.defineMetadata(exports.keyOfParams, i, t, k); RouteRegistry.setParameterIndex(t, k, 'params', i); }; }
/* ------------------------------------------------------------------ */
/* Router Builder (metadata'yı prototype'tan okur) */
/* ------------------------------------------------------------------ */
function buildRouterFromController(controllerInstance) {
const ctor = controllerInstance.constructor;
const proto = Object.getPrototypeOf(controllerInstance);
const path = Reflect.getMetadata(exports.keyOfPath, ctor);
if (!path)
throw new Error('Controller class must have a path property');
const allProperties = Object.getOwnPropertyNames(proto);
const router = express_1.default.Router();
for (const property of allProperties) {
const routeOptions = Reflect.getMetadata(exports.keyOfRouteOptions, proto, property);
if (!routeOptions)
continue;
if (!routeOptions.path)
throw new Error(`Route path is required for ${ctor.name}.${property}`);
if (!routeOptions.method)
throw new Error(`Route method is required for ${ctor.name}.${property}`);
const { validations, permissions, authenticated, otherHttpMiddlewares } = routeOptions;
const handler = proto[property].bind(controllerInstance);
const validationMiddlewares = [];
const pushOnce = (arr, mw) => { if (!arr.includes(mw))
arr.push(mw); };
const order = ['params', 'query', 'body'];
for (const v of validations ?? []) {
for (const t of order) {
const klass = v[t];
if (!klass)
continue;
pushOnce(validationMiddlewares, (0, validation_middleware_1.default)(klass, t));
}
}
const middlewares = [];
if (authenticated)
middlewares.push(authenticated_middleware_1.authenticatedMiddleware);
if (permissions && permissions.length > 0)
middlewares.push((0, authorized_middleware_1.authorizedMiddleware)(permissions));
if (otherHttpMiddlewares)
middlewares.push(...otherHttpMiddlewares);
if (validationMiddlewares.length)
middlewares.push(...validationMiddlewares);
const method = routeOptions.method;
const routePath = routeOptions.path;
const reqIndex = Reflect.getMetadata(exports.keyOfReq, proto, property);
const resIndex = Reflect.getMetadata(exports.keyOfRes, proto, property);
const nextIndex = Reflect.getMetadata(exports.keyOfNext, proto, property);
const bodyIndex = Reflect.getMetadata(exports.keyOfBody, proto, property);
const queryIndex = Reflect.getMetadata(exports.keyOfQuery, proto, property);
const paramsIndex = Reflect.getMetadata(exports.keyOfParams, proto, property);
const handlerMiddleware = async (req, res, next) => {
try {
const argMap = new Map();
if (typeof reqIndex === 'number')
argMap.set(reqIndex, req);
if (typeof resIndex === 'number')
argMap.set(resIndex, res);
if (typeof nextIndex === 'number')
argMap.set(nextIndex, next);
if (typeof bodyIndex === 'number')
argMap.set(bodyIndex, req.validatedBody ?? req.body);
if (typeof queryIndex === 'number')
argMap.set(queryIndex, req.validatedQuery ?? req.query);
if (typeof paramsIndex === 'number')
argMap.set(paramsIndex, req.validatedParams ?? req.params);
let realArgs;
if (argMap.size > 0) {
const maxIndex = Math.max(...Array.from(argMap.keys()));
realArgs = Array.from({ length: maxIndex + 1 }, (_, i) => argMap.get(i));
}
else {
realArgs = [req, res, next];
}
const result = await handler(...realArgs);
if (result && typeof result.build === 'function') {
result.build(res);
}
else if (!res.headersSent) {
res.json(result);
}
}
catch (error) {
next(error);
}
};
router[method](routePath, ...middlewares, handlerMiddleware);
}
return router;
}
function buildApp(app, controllers) {
for (const instance of controllers) {
const router = buildRouterFromController(instance);
const controllerPath = Reflect.getMetadata(exports.keyOfPath, instance.constructor);
if (controllerPath)
app.use(controllerPath, router);
else
app.use(router);
}
return app;
}
//# sourceMappingURL=rest.js.map