UNPKG

@mini2/core

Version:

Mini Express Framework - Lightweight and modular Express.js framework with TypeScript support

365 lines 17.7 kB
"use strict"; 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