diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index d5ff32cb..c7af1bbd 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -1,3 +1,5 @@ +import { sequelize } from "config/database"; +import { initPackages } from "core/package-loader"; import dotenv from "dotenv"; import express, { Application } from "express"; import helmet from "helmet"; @@ -5,7 +7,6 @@ import responseTime from "response-time"; import { authProvider } from "./contexts/auth/infraestructure"; import { logger } from "./core/common/infrastructure/logger"; import { globalErrorHandler } from "./core/common/presentation"; -import { v1Routes } from "./routes"; dotenv.config(); @@ -47,7 +48,8 @@ export function createApp(): Application { }); // Registrar rutas de la API - app.use("/api/v1", v1Routes()); + // app.use("/api/v1", v1Routes()); + initPackages(app, sequelize); // Gestión global de errores. // Siempre al final de la cadena de middlewares diff --git a/apps/server/src/config/database.ts b/apps/server/src/config/database.ts index c409ea9b..bd2160ce 100644 --- a/apps/server/src/config/database.ts +++ b/apps/server/src/config/database.ts @@ -1,7 +1,6 @@ import { logger } from "core/common/infrastructure/logger"; import dotenv from "dotenv"; import { Sequelize } from "sequelize"; -import { registerModels } from "./register-models"; dotenv.config(); @@ -40,7 +39,7 @@ export const sequelize = new Sequelize( export async function connectToDatabase(): Promise { try { await sequelize.authenticate(); - await registerModels(); + //await registerModels(); logger.info(`✔️${" "}Database connection established successfully.`); } catch (error) { logger.error("❌ Unable to connect to the database:", error); diff --git a/apps/server/src/core/common/infrastructure/sequelize/sequelize-transaction-manager.ts b/apps/server/src/core/common/infrastructure/sequelize/sequelize-transaction-manager.ts index afd4b3d3..ed7ea392 100644 --- a/apps/server/src/core/common/infrastructure/sequelize/sequelize-transaction-manager.ts +++ b/apps/server/src/core/common/infrastructure/sequelize/sequelize-transaction-manager.ts @@ -1,5 +1,5 @@ -import { sequelize } from "@config/database"; import { Transaction } from "sequelize"; +import { sequelize } from "../../../../config/database"; import { TransactionManager } from "../database"; export class SequelizeTransactionManager extends TransactionManager { diff --git a/apps/server/src/core/package-loader.ts b/apps/server/src/core/package-loader.ts new file mode 100644 index 00000000..a4fe4050 --- /dev/null +++ b/apps/server/src/core/package-loader.ts @@ -0,0 +1,55 @@ +import { IPackageServer } from "@libs/package"; +import { Express } from "express"; +import { Sequelize } from "sequelize"; +import { registerService } from "./service-registry"; + +const registeredPackages: Map = new Map(); +const initializedPackages = new Set(); + +export function registerPackage(pkg: IPackageServer) { + if (registeredPackages.has(pkg.metadata.name)) { + throw new Error(`❌ Paquete "${pkg.metadata.name}" ya registrado.`); + } + registeredPackages.set(pkg.metadata.name, pkg); +} + +export function initPackages(app: Express, sequelize: Sequelize) { + for (const [name] of registeredPackages) { + loadPackage(name, app, sequelize); + } +} + +function loadPackage(name: string, app: Express, sequelize: Sequelize) { + if (initializedPackages.has(name)) return; + + const pkg = registeredPackages.get(name); + if (!pkg) throw new Error(`❌ Paquete "${name}" no encontrado.`); + + // Resolver dependencias primero + const deps = pkg.metadata.dependencies || []; + deps.forEach((dep) => loadPackage(dep, app, sequelize)); + + // Inicializar el package + pkg.init(app); + + const pkgApi = pkg.registerDependencies?.(); + + // Registrar modelos de Sequelize, si los expone + + if (pkgApi?.models) { + for (const initModelFn of pkgApi.models) { + initModelFn(sequelize); + } + } + + // Registrar sus servicios, si los expone + if (pkgApi?.services) { + const services = pkgApi.services; + if (services && typeof services === "object") { + registerService(pkg.metadata.name, services); + } + } + + initializedPackages.add(name); + console.log(`✅ Paquete "${name}" inicializado.`); +} diff --git a/apps/server/src/core/plugin-registry/plugin-registry.ts b/apps/server/src/core/plugin-registry/plugin-registry.ts deleted file mode 100644 index 6e022fb6..00000000 --- a/apps/server/src/core/plugin-registry/plugin-registry.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Application } from "express"; -import { IPlugin } from "./plugin.interface"; - -const plugins: IPlugin[] = []; - -export function registerPlugin(plugin: IPlugin) { - plugins.push(plugin); -} - -export function initPlugins(app: Application) { - plugins.forEach((plugin) => plugin.init(app)); -} diff --git a/apps/server/src/core/plugin-registry/plugin.interface.ts b/apps/server/src/core/plugin-registry/plugin.interface.ts deleted file mode 100644 index 7c674611..00000000 --- a/apps/server/src/core/plugin-registry/plugin.interface.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Application } from "express"; - -export interface PluginMetadata { - name: string; - version: string; - description?: string; - - /** - * Lista de nombres de plugins requeridos por este plugin. - */ - dependencies?: string[]; -} - -export interface IPlugin { - metadata: PluginMetadata; - init(app: Application): void; - registerDependencies?(): void; -} diff --git a/apps/server/src/core/service-registry.ts b/apps/server/src/core/service-registry.ts new file mode 100644 index 00000000..133b92db --- /dev/null +++ b/apps/server/src/core/service-registry.ts @@ -0,0 +1,36 @@ +const services: Record = {}; + +/** + * Registra un objeto de servicio (API) bajo un nombre. + */ +export function registerService(name: string, api: any) { + if (services[name]) { + throw new Error(`❌ Servicio "${name}" ya fue registrado.`); + } + services[name] = api; +} + +/** + * Recupera un servicio registrado, con tipado opcional. + */ +export function getService(name: string): T { + const service = services[name]; + if (!service) { + throw new Error(`❌ Servicio "${name}" no encontrado.`); + } + return service; +} + +/** + * Permite saber si un servicio fue registrado. + */ +export function hasService(name: string): boolean { + return !!services[name]; +} + +/** + * Devuelve todos los servicios (para depuración o tests). + */ +export function listServices(): string[] { + return Object.keys(services); +} diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index bc1a462a..9206a4ec 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -1,5 +1,6 @@ import http from "http"; import { DateTime } from "luxon"; +import { registerPackages } from "packages"; import { createApp } from "./app"; import { ENV } from "./config"; import { connectToDatabase } from "./config/database"; @@ -66,6 +67,9 @@ const serverConnection = (conn: any) => { //const sequelizeConn = createSequelizeAdapter(); //const firebirdConn = createFirebirdAdapter(); +// Cargar paquetes de la aplicación +registerPackages(); + // Crea el servidor HTTP const server = http .createServer(createApp()) diff --git a/apps/server/src/packages.ts b/apps/server/src/packages.ts new file mode 100644 index 00000000..4c50395d --- /dev/null +++ b/apps/server/src/packages.ts @@ -0,0 +1,8 @@ +// apps/server/src/plugins.ts +//import { ContactsPackage } from '@packages/contacts/server'; +//import { InvoicesPackage } from '@packages/invoices/server'; + +export const registerPackages = () => { + //registerPackage(ContactsPackage); + //registerPackage(InvoicesPackage); +}; diff --git a/apps/server/tsconfig.json b/apps/server/tsconfig.json index 90f192a5..4c269135 100644 --- a/apps/server/tsconfig.json +++ b/apps/server/tsconfig.json @@ -8,10 +8,11 @@ "allowSyntheticDefaultImports": true, "baseUrl": "src", "paths": { - "@shared/*": ["../../packages/shared/*"], - "@common/*": ["common/*"], "@contexts/*": ["contexts/*"], - "@config/*": ["config/*"] + "@libs/*": ["../../../libs/*"], + "@packages/*": ["../../../packages/*"] + /*"@common/*": ["common/*"],*/ + /*"@config/*": ["config/*"]*/ } }, diff --git a/libs/package/package-server.interface.ts b/libs/package/package-server.interface.ts index ca32d79c..5fb98dc7 100644 --- a/libs/package/package-server.interface.ts +++ b/libs/package/package-server.interface.ts @@ -1,10 +1,10 @@ //Contrato para los packages backend (Node.js) import { Express } from "express"; -import { PackageMetadata } from "./types"; +import { PackageDependencies, PackageMetadata } from "./types"; export interface IPackageServer { metadata: PackageMetadata; init(app: Express): void; - registerDependencies?(): void; + registerDependencies?(): PackageDependencies; } diff --git a/libs/package/package.json b/libs/package/package.json index 5e9919f0..a1d24799 100644 --- a/libs/package/package.json +++ b/libs/package/package.json @@ -8,6 +8,7 @@ "@types/react": "^19.1.2", "express": "^4.18.2", "react": "^18.3.1", + "sequelize": "^6.37.5", "typescript": "*" } } diff --git a/libs/package/types.ts b/libs/package/types.ts index 4276ce32..c28e2056 100644 --- a/libs/package/types.ts +++ b/libs/package/types.ts @@ -1,8 +1,17 @@ // Contiene tipos comunes entre cliente y servidor +import { Model, Sequelize } from "sequelize"; + +type ModelInitializer = (sequelize: Sequelize) => typeof Model; + export interface PackageMetadata { name: string; version: string; description?: string; dependencies?: string[]; } + +export interface PackageDependencies { + models?: ModelInitializer[]; + services?: { [key: string]: any }; +} diff --git a/packages/invoices/server/index.ts b/packages/invoices/server/index.ts new file mode 100644 index 00000000..9feba167 --- /dev/null +++ b/packages/invoices/server/index.ts @@ -0,0 +1,30 @@ +/* import { getService } from "@apps/server/src/core/service-registry"; */ +import { IPackageServer } from "@libs/package"; +//import initInvoiceModel from './models/contact.model'; + +export const InvoicesPackage: IPackageServer = { + metadata: { + name: "invoices", + version: "1.0.0", + dependencies: ["contacts"], + }, + init(app) { + // const contacts = getService("contacts"); + + app.get("/invoices", (req, res) => { + // const customer = await contacts.getContactById('123'); + res.send("Aquí van las facturas"); + }); + }, + registerDependencies() { + return { + models: [ + /*initInvoiceModel*/ + ], + services: { + getInvoice: () => {}, + /*...*/ + }, + }; + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 82e65f72..1bf2c865 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -283,6 +283,9 @@ importers: react: specifier: ^18.3.1 version: 18.3.1 + sequelize: + specifier: ^6.37.5 + version: 6.37.7(mysql2@3.14.0) typescript: specifier: '*' version: 5.7.3