UNPKG

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.

113 lines (110 loc) 3.71 kB
import 'reflect-metadata'; import { Container, injectable } from 'inversify'; import { MetadataKey } from '../enum/metadata-key.enum.js'; function isValueProvider(p) { return 'useValue' in p; } /** * Resolved test module. Retrieve instances via `.get()`. */ class TestingModule { get(token) { return this.container.get(token); } constructor(container){ this.container = container; } } /** * Builder returned by `MeoCordTestingModule.create()`. * Call `.compile()` to get the resolved `TestingModule`. */ class TestingModuleBuilder { overrideProvider(token) { return { useValue: (value)=>{ this.overrides.set(token, { provide: token, useValue: value }); return this; } }; } overrideGuard(guard) { return { useValue: (stub)=>{ this.guardOverrides.set(guard, stub); return this; } }; } compile() { const container = new Container(); // Merge explicit providers with overrides (overrides win) const providers = new Map(); for (const p of this.options.providers ?? []){ providers.set(p.provide, p); } for (const [token, override] of this.overrides){ providers.set(token, override); } // Bind explicit providers for (const provider of providers.values()){ if (isValueProvider(provider)) { container.bind(provider.provide).toConstantValue(provider.useValue); } else { const cls = provider.useClass; if (!Reflect.hasMetadata(MetadataKey.Injectable, cls)) { injectable()(cls); } container.bind(provider.provide).to(cls).inSingletonScope(); } } // Bind guard overrides — prevents inversify from auto-wiring guard dependencies for (const [guardClass, stub] of this.guardOverrides){ container.bind(guardClass).toConstantValue(stub); } // Recursively bind controllers and their dependencies, skipping already-bound tokens const bindClass = (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){ bindClass(dep); } }; for (const ctrl of this.options.controllers ?? []){ bindClass(ctrl); // Stamp container on controller class so @UseGuard works in tests too Reflect.defineMetadata(MetadataKey.Container, container, ctrl); } return new TestingModule(container); } constructor(options){ this.options = options; this.overrides = new Map(); this.guardOverrides = new Map(); } } /** * Entry point for building isolated test modules. * * @example * ```typescript * const module = MeoCordTestingModule.create({ * controllers: [PingController], * providers: [ * { provide: PingService, useValue: { handlePing: jest.fn().mockResolvedValue('pong') } }, * ], * }).compile() * * const controller = module.get(PingController) * ``` */ class MeoCordTestingModule { static create(options) { return new TestingModuleBuilder(options); } } export { MeoCordTestingModule, TestingModule, TestingModuleBuilder };