CORE CONCEPTS

Module Plugins

ModulePlugin is the generic extension point that lets ecosystem packages — @rasenganjs/ws, @rasenganjs/queue — add their own field to defineModule() (gateways, queues) without rasengan-server core knowing what either concept is.

Registering plugins before the modules that use them
bootstrap((app) => { app.registerPlugin(createWsPlugin()); // claims the "gateways" key app.registerPlugin(createQueuePlugin()); // claims the "queues" key app.registerModule(appModule); // may declare gateways/queues on any sub-module });

Order between registerPlugin() and registerModule() calls doesn't matter — both just need to happen before compile() runs (which bootstrap() triggers internally).

Why This Exists

Core only knows about name, prefix, middlewares, imports, controllers, and providers on a ModuleConfig. Any other key is opaque to core and must be claimed by a registered ModulePlugin, or compile() throws:

[rasengan-server] Module declares unknown key "gateways" with no matching
plugin. If this is meant to be handled by an ecosystem package (e.g.
`@rasenganjs/ws` for "gateways"), call `app.registerPlugin(...)` into
`bootstrap()`.

This is deliberate — a forgotten registerPlugin() call fails loudly instead of silently doing nothing.

ModulePlugin Interface

interface ModulePlugin { key: string; // the ModuleConfig field this plugin claims, e.g. 'gateways' register( app: ServerApp, container: ContainerView, mod: ModuleConfig, value: unknown ): void; asProviders?(value: unknown): ProviderLike[]; }
  • register() — called once per flattened module that declares a non-undefined value at key, after providers are registered. Use app's public methods (e.g. app.websocket()) to wire in whatever the key represents.
  • asProviders() (optional) — extracts real DI-provider tokens hiding inside value. When present, compile() merges those tokens into the module's providers before registration, visibility computation, and eager resolution — so a plugin-declared class (a Gateway, a Queue) becomes a real, exportable, eagerly-resolved singleton exactly like a hand-declared provider.

Writing Your Own Plugin

A minimal example plugin
import type { ModulePlugin } from '@rasenganjs/server'; function createSchedulerPlugin(): ModulePlugin { return { key: 'schedulers', asProviders: (value) => value as ProviderLike[], // schedulers extend Provider register(app, container, mod, value) { const schedulerClasses = value as SchedulerClass[]; for (const SchedulerClass of schedulerClasses) { const scheduler = container.resolve(SchedulerClass); scheduler.start(); } }, }; }
Using it
bootstrap((app) => { app.registerPlugin(createSchedulerPlugin()); app.registerModule( defineModule({ schedulers: [ReportScheduler], // an unrecognized key without the plugin above }) ); });

Only one plugin may claim a given key — registering a second plugin for the same key throws immediately.

WebSockets
Config & Build