// lib/core/component-registry.ts // Copyright (C) 2025 DTP Technologies, LLC // All Rights Reserved import env from "../../config/env.js"; import assert from "node:assert"; import { promises as fs } from "node:fs"; import path from "node:path"; import mongoose from "mongoose"; import { DtpBase } from "./base.js"; import { DtpPlatform } from "./platform.js"; import { DtpService } from "./service.js"; import { WebServer } from "../web/server.js"; import { WebController } from "../web/controller.js"; type ModelRegistry = Record; type ServiceRegistry = Record; type ControllerRegistry = Record; export class DtpComponentRegistry extends DtpBase { models: ModelRegistry = {}; services: ServiceRegistry = {}; controllers: ControllerRegistry = {}; static get name() { return "DtpComponentRegistry"; } static get slug() { return "DtpComponentRegistry"; } constructor(platform: DtpPlatform) { super(platform, DtpComponentRegistry); } async loadModels() { const basePath = path.join(env.src, "app", "models"); this.log.info("loading models", { basePath }); const entries = await fs.readdir(basePath, { withFileTypes: true }); for (const entry of entries) { if (!entry.isFile()) { continue; } if (!entry.name.endsWith("js") && !entry.name.endsWith("ts")) { continue; } const filename = path.join(basePath, entry.name); const model = (await import(filename)).default; if (this.models[model.modelName]) { this.log.error("model name collision", { name: model.modelName }); throw new Error( "You are registering more than one model with the same name" ); } this.models[model.modelName] = model; this.log.info("model loaded", { name: model.modelName }); } } async loadServices(): Promise { const basePath = path.join(env.src, "app", "services"); this.log.debug("loading services", { basePath }); const entries = await fs.readdir(basePath, { withFileTypes: true }); const inits = []; for await (const entry of entries) { if (!entry.isFile()) { continue; } if (!entry.name.endsWith("js") && !entry.name.endsWith("ts")) { continue; } try { const ServiceClass = (await import(path.join(basePath, entry.name))) .default; this.log.info("loading service", { script: entry.name, name: ServiceClass.name, slug: ServiceClass.slug, }); const service = new ServiceClass(this.platform); this.services[ServiceClass.slug] = service; inits.push(service); } catch (error) { this.log.error("failed to load service", { script: entry, error }); throw new Error("failed to load service", { cause: error }); } } for await (const service of inits) { await service.start(); } } getService(slug: string): T { const service = this.services[slug]; if (!service) { throw new Error(`Service ${slug} is not loaded`); } return service as T; } async loadControllers(server: WebServer): Promise { assert(server.app, "ExpressJS instance required"); const basePath = path.join(env.src, "app", "controllers"); this.log.debug("loading controllers", { basePath }); const entries = await fs.readdir(basePath, { withFileTypes: true }); const inits = []; for await (const entry of entries) { if (!entry.isFile()) { continue; } if (!entry.name.endsWith("js") && !entry.name.endsWith("ts")) { continue; } try { const ControllerClass = (await import(path.join(basePath, entry.name))) .default; if (!ControllerClass) { this.log.error("failed to receive a default export from controller", { script: entry.name, }); throw new Error("controller failed to provide a default export"); } this.log.info("loading controller", { script: entry.name, name: ControllerClass.name, slug: ControllerClass.slug, }); const controller = new ControllerClass(server, ControllerClass); this.controllers[ControllerClass.slug] = controller; inits.push({ ControllerClass, controller }); } catch (error) { this.log.error("failed to load controller", { error }); throw new Error("failed to load controller", { cause: error }); } } for await (const init of inits) { await init.controller.start(); } for await (const init of inits) { this.log.info("mounting controller", { controller: init.ControllerClass.name, route: init.controller.route, }); server.app.use(init.controller.route, init.controller.router); } } getController(slug: string): T { const controller = this.controllers[slug]; if (!controller) { throw new Error(`Controller ${slug} is not loaded`); } return controller as T; } }