meocord
Version:
Decorator-based Discord bot framework built on discord.js. Brings NestJS-style controllers, dependency injection, guards, and testing utilities to bot development — with a full CLI and TypeScript-first design.
64 lines (61 loc) • 2.81 kB
JavaScript
import 'reflect-metadata';
import { Container, injectable } from 'inversify';
import { Client } from 'discord.js';
import { Logger } from '../common/logger.js';
import '../common/theme.js';
import { MeoCordApp } from './meocord.app.js';
import { loadMeoCordConfig } from '../util/meocord-config-loader.util.js';
import { MetadataKey } from '../enum/metadata-key.enum.js';
/**
* Recursively binds a class and all its constructor dependencies to the container in singleton scope.
*/ function bindDependencies(container, cls) {
if (container.isBound(cls)) return;
if (!Reflect.hasMetadata(MetadataKey.Injectable, cls)) {
injectable()(cls);
}
container.bind(cls).toSelf().inSingletonScope();
const deps = Reflect.getMetadata(MetadataKey.ParamTypes, cls) || [];
for (const dep of deps){
if (dep === Client) continue;
bindDependencies(container, dep);
}
}
class MeoCordFactory {
static create(target) {
const options = Reflect.getMetadata(MetadataKey.AppOptions, target);
if (!options) {
if (typeof target === 'function') {
this.logger.error(`No @MeoCord() options found for class: ${target.name}`);
} else {
this.logger.error('No @MeoCord() options found for the provided target.');
}
throw new Error('Target class is not decorated with @MeoCord().');
}
const meocordConfig = loadMeoCordConfig();
if (!meocordConfig) {
throw new Error('MeoCord config not found. Ensure meocord.config.ts exists.');
}
const container = new Container();
// Bind the Discord client as a constant value
const discordClient = new Client(options.clientOptions);
container.bind(Client).toConstantValue(discordClient);
// Bind all controllers and their transitive dependencies
for (const ctrl of options.controllers){
bindDependencies(container, ctrl);
}
// Bind and eagerly instantiate standalone services so their constructors run.
// This is critical for event-driven services that register Discord event
// listeners (or connect to external systems) inside their constructor.
for (const svc of options.services ?? []){
bindDependencies(container, svc);
container.get(svc);
}
// Stamp each controller class with the container so @UseGuard can resolve guards
for (const ctrl of options.controllers){
Reflect.defineMetadata(MetadataKey.Container, container, ctrl);
}
return new MeoCordApp(options.controllers, container, discordClient, meocordConfig.discordToken, options.activities);
}
}
MeoCordFactory.logger = new Logger();
export { MeoCordFactory };