UNPKG

@cmmv/core

Version:

CMMV core module for contract and application management

555 lines (549 loc) 25.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Application = void 0; const fs = require("node:fs"); const path = require("node:path"); const node_process_1 = require("node:process"); const fg = require("fast-glob"); const Terser = require("terser"); const _1 = require("."); const decorators_1 = require("./decorators"); const transpilers_1 = require("./transpilers"); process.on('uncaughtException', (err) => { console.error(err); }); class Application { constructor(settings, compile = false) { this.logger = new _1.Logger('Application'); this.wSConnections = new Map(); this.controllers = []; this.submodules = []; this.contracts = []; this.configs = []; this.entities = []; this.models = []; this.resolvers = []; this.providersMap = new Map(); this.logger.log('Initialize application'); this.settings = settings; this.compile = compile; this.preInitialize(); Application.instance = this; } async preInitialize() { await _1.Config.loadConfig(); this.httpOptions = (this.settings && this.settings.httpOptions) || {}; this.httpAdapter = this.settings && this.settings.httpAdapter ? new this.settings.httpAdapter() : null; if (this.httpAdapter && !this.compile) { if (this.settings.wsAdapter) this.wsAdapter = new this.settings.wsAdapter(this.httpAdapter); this.host = _1.Config.get('server.host', '0.0.0.0'); this.port = _1.Config.get('server.port', 3000); Application.contractsCls = this.settings.contracts || []; this.transpilers = this.settings.transpilers || []; this.modules = this.settings.modules || []; this.resolvers = this.settings.resolvers || []; this.contracts = this.settings.contracts?.map((contractClass) => new contractClass()) || []; this.initialize(this.settings, this.compile); } else if (this.settings && this.settings.contracts?.length > 0) { Application.contractsCls = this.settings.contracts || []; this.transpilers = (this.settings && this.settings.transpilers) || []; this.modules = (this.settings && this.settings.modules) || []; this.resolvers = this.settings.resolvers || []; this.contracts = (this.settings && this.settings.contracts?.map((contractClass) => new contractClass())) || []; this.initialize(this.settings, this.compile); } } async initialize(settings, compile = false) { try { this.settings = settings; await _1.Hooks.execute(_1.HooksType.onPreInitialize); const env = _1.Config.get('env', process.env.NODE_ENV); this.loadModules(this.modules); await _1.Config.validateConfigs(this.configs); await this.getPublicContracts(); this.processContracts(); this.transpilers.push(transpilers_1.ApplicationTranspile); this.transpilers.push(transpilers_1.ContractsTranspile); if (env === 'dev' || env === 'development' || env === 'build') { if (this.transpilers.length > 0) { const transpile = new _1.Transpile(this.transpilers); await transpile.transpile(); this.logger.log('All transpilers executed successfully.'); } else { this.logger.log('No transpilers provided.'); } if (this.httpAdapter) { const appModel = await Application.generateModule(); if (appModel) this.loadModules([...this.modules, appModel]); this.createScriptBundle(); this.createCSSBundle(); } } else { const tsconfig = new Function(`return(${fs.readFileSync(path.resolve('./tsconfig.json'), 'utf-8')})`)(); const outputPath = path.resolve(tsconfig.compilerOptions.outDir, `app.module.js`); if (fs.existsSync(outputPath)) { const { ApplicationModule } = await Promise.resolve(`${outputPath}`).then(s => require(s)); this.loadModules([...this.modules, ApplicationModule]); } else { this.loadModules([...this.modules]); } } const providersLoad = []; const processProvider = async (provider) => { if (provider && typeof provider.loadConfig === 'function') providersLoad.push(provider?.loadConfig(this)); if (_1.Scope.has(`_await_service_${provider.name}`)) { providersLoad.push(new Promise(async (resolve) => { const actions = _1.Scope.getArray(`_await_service_${provider.name}`); if (actions.length > 0) { actions.map(({ cb, context }) => { cb.bind(context).call(context); }); } resolve(true); })); } }; settings.services?.forEach(processProvider); settings.providers?.forEach(processProvider); await Promise.all(providersLoad); await _1.Hooks.execute(_1.HooksType.onInitialize); if (this.httpAdapter && !compile) { await this.httpAdapter.init(this, this.httpOptions); settings?.httpMiddlewares?.forEach((middleware) => { this.httpAdapter?.use(middleware); }); await _1.Hooks.execute(_1.HooksType.onHTTPServerInit); if (this.wsAdapter) this.wsServer = this.wsAdapter.create(this.httpAdapter, this); await this.httpAdapter .listen(`${this.host}:${this.port}`) .then(() => { _1.Hooks.execute(_1.HooksType.onListen); this.logger.log(`Server HTTP successfully started on ${this.host}:${this.port}`); }) .catch((error) => { this.logger.error(`Failed to start HTTP server on ${this.httpBind}: ${error.message || error}`); }); } } catch (error) { await _1.Hooks.execute(_1.HooksType.onError, { error }); console.log(error); this.logger.error(`Failed to initialize application: ${error.message || error}`); } if (compile) this.logger.log(`Compilation process complete!`); } async restart() { if (this.httpAdapter) this.httpAdapter.close(); if (this.wsAdapter) this.wsAdapter.close(); this.preInitialize(); return true; } async execProcess(settings) { try { Application.contractsCls = settings.contracts || []; this.modules = (settings && settings.modules) || []; this.transpilers = settings.transpilers || []; this.resolvers = settings.resolvers || []; this.contracts = settings.contracts?.map((contractClass) => new contractClass()) || []; await _1.Hooks.execute(_1.HooksType.onPreInitialize); this.loadModules(this.modules); await _1.Config.validateConfigs(this.configs); await this.getPublicContracts(); this.processContracts(); this.transpilers.push(transpilers_1.ApplicationTranspile); this.transpilers.push(transpilers_1.ContractsTranspile); if (this.transpilers.length > 0) { const transpile = new _1.Transpile(this.transpilers); await transpile.transpile(); this.logger.log('All transpilers executed successfully.'); } else { this.logger.log('No transpilers provided.'); } const providersLoad = []; settings.services?.forEach(async (service) => { if (service && typeof service.loadConfig === 'function') providersLoad.push(service?.loadConfig(this)); }); settings.providers?.forEach(async (provider) => { if (provider && typeof provider.loadConfig === 'function') providersLoad.push(provider?.loadConfig(this)); }); await Promise.all(providersLoad); await _1.Hooks.execute(_1.HooksType.onInitialize); } catch (error) { await _1.Hooks.execute(_1.HooksType.onError, { error }); console.log(error); this.logger.error(error.message || error); } } async createScriptBundle() { const dirBuild = path.resolve('./public/assets'); const finalbundle = path.resolve('./public/assets/bundle.min.js'); const files = await fg(path.resolve('./public/core/*.min.js'), { ignore: ['node_modules/**'], }); const lines = []; files.forEach((file) => { lines.push(fs.readFileSync(file, 'utf-8')); lines.push(''); }); if (lines.length > 0) { const bundleContent = lines.join('\n'); const result = await Terser.minify(bundleContent, { compress: { dead_code: true, conditionals: true, unused: true, drop_debugger: true, drop_console: process.env.NODE_ENV !== 'dev', }, mangle: { toplevel: true }, output: { beautify: false, }, sourceMap: { url: 'inline', }, }); if (finalbundle.length > 0) { if (!fs.existsSync(dirBuild)) fs.mkdirSync(dirBuild, { recursive: true }); fs.writeFileSync(finalbundle, result.code, { encoding: 'utf-8', }); } } } async createCSSBundle() { const dirBuild = path.resolve('./public/assets'); const finalbundle = path.resolve('./public/assets/bundle.min.css'); const files = await fg(path.resolve('./public/core/*.min.css'), { ignore: ['node_modules/**'], }); const lines = []; files.forEach((file) => { lines.push(fs.readFileSync(file, 'utf-8')); lines.push(''); }); if (lines.length > 0) { const bundleContent = lines.join('\n'); if (finalbundle.length > 0) { if (!fs.existsSync(dirBuild)) fs.mkdirSync(dirBuild, { recursive: true }); fs.writeFileSync(finalbundle, bundleContent, { encoding: 'utf-8', }); } } } loadModules(modules) { if (modules && modules.length > 0) { modules.forEach((module) => { if (module) { if (!module.devMode || (module.devMode === true && process.env.NODE_ENV === 'dev')) { Application.contractsCls.push(...module.getContractsCls()); this.transpilers.push(...module.getTranspilers()); this.controllers.push(...module.getControllers()); this.submodules.push(...module.getSubmodules()); this.contracts.push(...module.getContracts()); this.entities.push(...module.getEntities()); this.models.push(...module.getModels()); this.configs.push(...module.getConfigsSchemas()); this.resolvers.push(...module.getResolvers()); module.getProviders().forEach((provider) => { if (provider) { const paramTypes = Reflect.getMetadata('design:paramtypes', provider) || []; const instances = paramTypes.map((paramType) => this.providersMap.get(paramType.name) || new paramType()); if (provider && typeof provider.loadConfig === 'function') provider?.loadConfig(this); const providerInstance = new provider(...instances); this.providersMap.set(provider.name, providerInstance); } }); if (module.getSubmodules().length > 0) this.loadModules(module.getSubmodules()); } } }); } } processContracts() { if (Array.isArray(this.contracts) && this.contracts.length > 0) { this.contracts.forEach((contract) => { if (contract) { const target = contract.constructor || contract; const prototype = target.prototype || contract.prototype; const namespace = Reflect.getMetadata(decorators_1.NAMESPACE_METADATA, contract.constructor); const isPublic = Reflect.getMetadata(decorators_1.PUBLIC_METADATA, contract.constructor); const controllerName = Reflect.getMetadata(decorators_1.CONTROLLER_NAME_METADATA, contract.constructor); const subPath = Reflect.getMetadata(decorators_1.SUB_PATH_METADATA, contract.constructor); const protoPath = Reflect.getMetadata(decorators_1.PROTO_PATH_METADATA, contract.constructor); const protoPackage = Reflect.getMetadata(decorators_1.PROTO_PACKAGE_METADATA, contract.constructor); const fields = Reflect.getMetadata(decorators_1.FIELD_METADATA, prototype) || []; const messages = Reflect.getMetadata(decorators_1.MESSAGE_METADATA, prototype) || []; const services = Reflect.getMetadata(decorators_1.SERVICE_METADATA, prototype) || []; const directMessage = Reflect.getMetadata(decorators_1.DIRECTMESSAGE_METADATA, contract.constructor); const generateController = Reflect.getMetadata(decorators_1.GENERATE_CONTROLLER_METADATA, contract.constructor); const generateEntities = Reflect.getMetadata(decorators_1.GENERATE_ENTITIES_METADATA, contract.constructor); const generateBoilerplates = Reflect.getMetadata(decorators_1.GENERATE_BOILERPLATES_METADATA, contract.constructor); const auth = Reflect.getMetadata(decorators_1.AUTH_METADATA, contract.constructor); const rootOnly = Reflect.getMetadata(decorators_1.ROOTONLY_METADATA, contract.constructor); const controllerCustomPath = Reflect.getMetadata(decorators_1.CONTROLLER_CUSTOM_PATH_METADATA, contract.constructor); const imports = Reflect.getMetadata(decorators_1.CONTROLLER_IMPORTS, contract.constructor); const indexs = Reflect.getMetadata(decorators_1.CONTROLLER_INDEXS, contract.constructor); const cache = Reflect.getMetadata(decorators_1.CONTROLLER_CACHE, contract.constructor); let options = Reflect.getMetadata(decorators_1.CONTROLLER_OPTIONS, contract.constructor); const viewForm = Reflect.getMetadata(decorators_1.CONTROLLER_VIEWFORM, contract.constructor); const viewPage = Reflect.getMetadata(decorators_1.CONTROLLER_VIEWPAGE, contract.constructor); if (!options || typeof options !== 'object') options = {}; const contractStructure = { namespace, isPublic, contractName: contract.constructor.name, controllerName, subPath, protoPath, protoPackage, fields, messages, services, directMessage, generateController, generateEntities, generateBoilerplates, auth, rootOnly, controllerCustomPath, imports, indexs, cache, customProto: contract.customProto, customTypes: contract.customTypes, options, viewForm, viewPage, }; if (contractStructure.controllerName) { _1.Scope.addToArray('__contracts', contractStructure); } else { this.logger.error(`Contract ${contractStructure.contractName} has no controller name`); } } }); const contracts = _1.Scope.getArray('__contracts'); contracts?.forEach((contract) => this.loadModels(contract)); } } async loadModels(contract) { const modelName = `${contract.controllerName}`; const modelFileName = `${modelName.toLowerCase()}.model.ts`; const isModuleContract = contract.options?.moduleContract === true; const outputDir = isModuleContract ? this.getGeneratedPath(contract, 'models') : this.getRootPath(contract, 'models'); const outputFilePath = path.join(outputDir, modelFileName); const modelImport = await Promise.resolve(`${path.resolve(outputFilePath)}`).then(s => require(s)); Application.models.set(modelName, modelImport[modelName]); } getRootPath(contract, context) { const rootDir = _1.Config.get('app.sourceDir', 'src'); let outputDir = contract.subPath ? path.join(rootDir, context, contract.subPath) : path.join(rootDir, context); if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true }); return outputDir; } getGeneratedPath(contract, context) { let outputDir = contract.subPath ? path.join('.generated', context, contract.subPath) : path.join('.generated', context); if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true }); return outputDir; } async getPublicContracts() { const contracts = await fg([ path.join((0, node_process_1.cwd)(), 'src', 'contracts', '**', '*.ts'), path.join((0, node_process_1.cwd)(), 'src', 'contracts', '*.ts'), ]); for (const contract of contracts) { const contractPath = path.relative(__dirname, contract); const contractContent = await Promise.resolve(`${contractPath}`).then(s => require(s)); for (const key in contractContent) { if (key.includes('Contract')) { Application.contractsCls.push(contractContent[key]); const instance = new contractContent[key](); const isPublic = Reflect.getMetadata(decorators_1.PUBLIC_METADATA, instance.constructor); if (isPublic === true) this.contracts.push(instance); } } } } static awaitModule(moduleName, cb, context) { if (_1.Module.hasModule(moduleName)) { cb.bind(context).call(context); } else { _1.Scope.addToArray(`_await_module_${moduleName}`, { cb, context, }); } } static awaitService(serviceName, cb, context) { _1.Scope.addToArray(`_await_service_${serviceName}`, { cb, context, }); } static getModel(modelName) { if (Application.models.has(modelName)) return Application.models.get(modelName); throw new Error(`Could not load model '${modelName}'`); } static async generateModule() { try { const outputPath = path.resolve('.generated', `app.module.ts`); const moduleTemplate = `/** ********************************************** This script was generated automatically by CMMV. It is recommended not to modify this file manually, as it may be overwritten by the application. ********************************************** **/ import "reflect-metadata"; import { Module, ApplicationTranspile, ApplicationConfig, ContractsTranspile, SchedulingService, EventsService } from "@cmmv/core"; //Controllers ${Application.appModule.controllers.map((controller) => `import { ${controller.name} } from "${controller.path}";`).join('\n')} //Providers ${Application.appModule.providers.map((provider) => `import { ${provider.name} } from "${provider.path}";`).join('\n')} export let ApplicationModule = new Module("app", { configs: [ApplicationConfig], controllers: [ ${Application.appModule.controllers.map((controller) => controller.name).join(', \n\t\t')} ], providers: [ ${Application.appModule.providers.map((provider) => provider.name).join(', \n\t\t')}, SchedulingService, EventsService ], transpilers: [ ApplicationTranspile, ContractsTranspile ] });`; if (!fs.existsSync(path.dirname(outputPath))) fs.mkdirSync(path.dirname(outputPath), { recursive: true }); await fs.writeFileSync(outputPath, moduleTemplate, 'utf8'); const { ApplicationModule } = await Promise.resolve(`${outputPath}`).then(s => require(s)); return ApplicationModule; } catch (e) { console.error(e); new _1.Logger('Application').error(e.message, 'generateModule'); return null; } } getHttpAdapter() { return this.httpAdapter; } getUnderlyingHttpServer() { this.httpAdapter.getHttpServer(); } getWSServer() { return this.wsServer; } static create(settings) { return new Application(settings); } static compile(settings) { return new Application(settings, true); } static exec(settings) { return new Application(settings, true).execProcess(settings); } static async execAsyncFn(settings, providerClass, functionName) { return new Promise(async (resolve, reject) => { try { const app = new Application(settings, true); await app.execProcess(settings); const provider = new providerClass(); if (typeof provider[functionName] !== 'function') throw new Error(`The function '${functionName}' was not found in the provider`); const result = await provider[functionName](); resolve(result); } catch (e) { reject(e); } }); } static setHTTPMiddleware(cb) { Application.appModule.httpMiddlewares.push(cb); } static setHTTPInterceptor(cb) { Application.appModule.httpInterceptors.push(cb); } static setHTTPAfterRender(cb) { Application.appModule.httpAfterRender.push(cb); } static resolveProvider(providerCls) { const paramTypes = Reflect.getMetadata('design:paramtypes', providerCls) || []; const instances = paramTypes.map((paramType) => Application.instance.providersMap.get(paramType.name)); const instance = new providerCls(...instances); return instance; } static getContract(contractName) { return Application.contractsCls.find((cls) => cls.name === contractName); } static getResolvers() { return Array.from(new Set(Application.instance.resolvers)); } static getResolver(resolverName) { return Application.instance.resolvers.filter((cls) => cls.name === resolverName); } } exports.Application = Application; Application.appModule = { controllers: [], providers: [], httpMiddlewares: [], httpInterceptors: [], httpAfterRender: [], }; Application.contractsCls = []; Application.models = new Map();