2026-01-13 18:41:14 +00:00
|
|
|
|
import type { ModuleParams } from "@erp/core/api";
|
|
|
|
|
|
|
2025-05-09 10:45:32 +00:00
|
|
|
|
import { logger } from "../logger";
|
2025-05-04 20:06:57 +00:00
|
|
|
|
|
2025-08-12 15:09:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
Tipos mínimos para evitar acoplarse a una versión concreta de Sequelize
|
|
|
|
|
|
*/
|
|
|
|
|
|
type SequelizeLike = {
|
|
|
|
|
|
sync: (options?: any) => Promise<void>;
|
2025-09-12 16:23:36 +00:00
|
|
|
|
models: Record<string, unknown>;
|
2025-08-12 15:09:04 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
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<string, { model: ModelStatic; moduleName: string }> = new Map();
|
|
|
|
|
|
|
|
|
|
|
|
let modelsInitialized = false;
|
2025-05-04 20:06:57 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-12 15:09:04 +00:00
|
|
|
|
🔹 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",
|
|
|
|
|
|
});
|
2025-05-04 20:06:57 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-08-12 15:09:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
🔹 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.
|
|
|
|
|
|
*/
|
2025-05-04 20:06:57 +00:00
|
|
|
|
export const initModels = async (params: ModuleParams) => {
|
2025-08-12 15:09:04 +00:00
|
|
|
|
if (modelsInitialized) {
|
|
|
|
|
|
logger.warn("Models already initialized. Skipping initModels()", { label: "initModels" });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-09 10:45:32 +00:00
|
|
|
|
logger.info("Init models...", { label: "initModels" });
|
2025-05-04 20:06:57 +00:00
|
|
|
|
|
2025-08-12 15:09:04 +00:00
|
|
|
|
const { database } = params as { database: SequelizeLike };
|
2025-05-04 20:06:57 +00:00
|
|
|
|
if (!database) {
|
|
|
|
|
|
const error = new Error("❌ Database not found.");
|
2025-08-12 15:09:04 +00:00
|
|
|
|
logger.error(error.message, { label: "initModels" });
|
2025-05-04 20:06:57 +00:00
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 15:09:04 +00:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
2025-05-04 20:06:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 15:09:04 +00:00
|
|
|
|
// 2) Configurar asociaciones
|
|
|
|
|
|
for (const { model, moduleName } of registeredModels.values()) {
|
2025-05-04 20:06:57 +00:00
|
|
|
|
if (typeof model.associate === "function") {
|
2025-08-12 15:09:04 +00:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2025-05-04 20:06:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-12 15:09:04 +00:00
|
|
|
|
// 3) Sincronizar base de datos (según modo)
|
2026-02-13 09:09:19 +00:00
|
|
|
|
const syncModeEnv = params.config.database.syncMode; // "none" | "alter" | "force"
|
2026-02-12 14:46:47 +00:00
|
|
|
|
|
2026-02-13 09:09:19 +00:00
|
|
|
|
logger.info(`ℹ️ Database sync mode ${syncModeEnv}`, {
|
|
|
|
|
|
label: "initModels",
|
|
|
|
|
|
});
|
2025-08-12 15:09:04 +00:00
|
|
|
|
|
2025-05-04 20:06:57 +00:00
|
|
|
|
try {
|
2026-02-12 14:46:47 +00:00
|
|
|
|
if (syncModeEnv === "none") {
|
2025-08-12 15:09:04 +00:00
|
|
|
|
logger.info("✔️ Database sync skipped (mode=none)", { label: "initModels" });
|
2026-02-12 14:46:47 +00:00
|
|
|
|
} else if (syncModeEnv === "alter") {
|
2025-05-04 20:06:57 +00:00
|
|
|
|
await database.sync({ force: false, alter: true });
|
2025-08-12 15:09:04 +00:00
|
|
|
|
logger.info("✔️ Database synchronized successfully (mode=alter).", { label: "initModels" });
|
2026-02-12 14:46:47 +00:00
|
|
|
|
} else if (syncModeEnv === "force") {
|
2025-08-12 15:09:04 +00:00
|
|
|
|
await database.sync({ force: true, alter: false });
|
|
|
|
|
|
logger.warn("⚠️ Database synchronized with FORCE (mode=force).", { label: "initModels" });
|
2025-05-04 20:06:57 +00:00
|
|
|
|
} else {
|
2026-02-12 14:46:47 +00:00
|
|
|
|
logger.warn(`⚠️ Unknown DB sync mode "${String(syncModeEnv)}" → skipping`, {
|
|
|
|
|
|
label: "initModels",
|
|
|
|
|
|
});
|
2025-05-04 20:06:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
const error = err as Error;
|
2025-05-09 10:45:32 +00:00
|
|
|
|
logger.error("❌ Error synchronizing database:", { error, label: "initModels" });
|
2025-05-04 20:06:57 +00:00
|
|
|
|
throw error;
|
2025-08-12 15:09:04 +00:00
|
|
|
|
} finally {
|
|
|
|
|
|
modelsInitialized = true;
|
2025-05-04 20:06:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-08-12 15:09:04 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
🔹 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" });
|
|
|
|
|
|
};
|