import type { ModuleParams } from "@erp/core/api"; import { logger } from "../logger"; /** Tipos mínimos para evitar acoplarse a una versión concreta de Sequelize */ type SequelizeLike = { sync: (options?: any) => Promise; models: Record; }; type ModelStatic = { name: string; // Algunas implementaciones añaden associate(sequelize) para definir relaciones associate?: (sequelize: SequelizeLike) => void; }; export type ModelInitializer = (sequelize: SequelizeLike) => ModelStatic; /** Estructuras internas (no exportadas) con trazabilidad de módulo. */ type InitializerEntry = { init: ModelInitializer; moduleName: string }; const allModelInitializers: InitializerEntry[] = []; const registeredModels: Map = new Map(); let modelsInitialized = false; /** 🔹 Registra inicializadores de modelos de un módulo models: lista de funciones que definen modelos (sequelize) => ModelStatic ctx.moduleName: para trazabilidad y detección de colisiones */ export const registerModels = ( models: ModelInitializer[], _params?: ModuleParams, ctx?: { moduleName: string } ) => { const moduleName = ctx?.moduleName ?? "unknown"; if (!Array.isArray(models) || models.length === 0) { logger.warn(`No models provided by module "${moduleName}"`, { label: "registerModels" }); return; } for (const init of models) { // Guardamos con el módulo que los aporta (para logs/errores) allModelInitializers.push({ init, moduleName }); } logger.info(`📦 ${models.length} model initializer(s) enqueued from "${moduleName}"`, { label: "registerModels", }); }; /** 🔹 Inicializa todos los modelos registrados y configura asociaciones. Detecta colisiones por nombre de modelo entre módulos. Controla el modo de sincronización por ENV. */ export const initModels = async (params: ModuleParams) => { if (modelsInitialized) { logger.warn("Models already initialized. Skipping initModels()", { label: "initModels" }); return; } logger.info("Init models...", { label: "initModels" }); const { database } = params as { database: SequelizeLike }; if (!database) { const error = new Error("❌ Database not found."); logger.error(error.message, { label: "initModels" }); throw error; } // 1) Definir modelos (y detectar colisiones) for (const { init, moduleName } of allModelInitializers) { try { const model = init(database); if (!model || typeof model.name !== "string" || !model.name) { throw new Error(`Invalid model initializer: missing or empty "name"`); } if (registeredModels.has(model.name)) { const existing = registeredModels.get(model.name)!; throw new Error( `Model name collision: "${model.name}" from module "${moduleName}" conflicts with existing model from "${existing.moduleName}"` ); } registeredModels.set(model.name, { model, moduleName }); logger.info(`🔸 Model "${model.name}" registered (sequelize)`, { label: "registerModel", module: moduleName, }); } catch (err: any) { // Agregamos contexto del módulo que aportó el initializer logger.error(`⛔️ Failed to define model (module="${moduleName}") : ${err?.message ?? err}`, { label: "initModels", stack: err?.stack, }); throw err; } } // 2) Configurar asociaciones for (const { model, moduleName } of registeredModels.values()) { if (typeof model.associate === "function") { try { model.associate(database); } catch (err: any) { logger.error( `⛔️ Failed to associate model "${model.name}" (module="${moduleName}") : ${err?.message ?? err}`, { label: "initModels", stack: err?.stack } ); throw err; } } } // 3) Sincronizar base de datos (según modo) const syncModeEnv = params.config.database.syncMode; // "none" | "alter" | "force" logger.info(`ℹ️ Database sync mode ${syncModeEnv}`, { label: "initModels", }); try { if (syncModeEnv === "none") { logger.info("✔️ Database sync skipped (mode=none)", { label: "initModels" }); } else if (syncModeEnv === "alter") { await database.sync({ force: false, alter: true }); logger.info("✔️ Database synchronized successfully (mode=alter).", { label: "initModels" }); } else if (syncModeEnv === "force") { await database.sync({ force: true, alter: false }); logger.warn("⚠️ Database synchronized with FORCE (mode=force).", { label: "initModels" }); } else { logger.warn(`⚠️ Unknown DB sync mode "${String(syncModeEnv)}" → skipping`, { label: "initModels", }); } } catch (err) { const error = err as Error; logger.error("❌ Error synchronizing database:", { error, label: "initModels" }); throw error; } finally { modelsInitialized = true; } }; /** 🔹 Utilidades opcionales (DX/tests) */ export const getRegisteredModels = () => Array.from(registeredModels.entries()).map(([name, { moduleName }]) => ({ name, moduleName, })); export const resetModelsRegistry = () => { allModelInitializers.length = 0; registeredModels.clear(); modelsInitialized = false; logger.info("Model registry has been reset.", { label: "initModels" }); };