CORE CONCEPTS
Module Scoping
Rasengan Server's DI container uses one registry and one instance per token — singletons are shared by every module that can see them. Scoping is a visibility filter on top of that single registry, never a second cache. A provider's own constructor dependencies always resolve in the scope of the module that owns the provider — not the module that triggered its construction — so a shared singleton behaves identically no matter who resolves it first.
What a Module Can See
A module's visible token set is exactly:
- Its own
providers. - The
exportsof every module in itsimportslist. - The
exportsof every module markedglobal: true, anywhere in the app.
// database.module.ts export default defineModule({ providers: [DatabaseService], exports: [DatabaseService], }); // user.module.ts export default defineModule({ imports: [databaseModule], // now UserService can inject DatabaseService providers: [UserService], controllers: [UserController], });
// config.module.ts export default defineModule({ providers: [ConfigService], exports: [ConfigService], global: true, });
Directed Errors, Not Silent Failures
Trying to inject something outside your module's visible set doesn't silently return undefined — it throws with an actionable message pointing at exactly what's missing:
[rasengan-server] module "UserModule" cannot resolve "DatabaseService". DatabaseService exists in module "DatabaseModule" but is not visible here — add it to that module's `exports` and add that module to this module's `imports` (or mark the owning module `global: true`).
A Provider Belongs to Exactly One Module
Registering the same class as a provider in two different modules is always a mistake and throws immediately:
[rasengan-server] DatabaseService is already registered by module "DatabaseModule". A provider can only belong to one module — add it to that module's `exports` and import it from here instead of declaring it twice.
Share a provider by exporting it from its owning module and importing that module elsewhere — never by declaring the same class in two providers arrays.
Name Collisions Across Imports
If two different visible modules each export a same-named provider, resolving by constructor parameter name is ambiguous — the container refuses to guess:
[rasengan-server] "logger" is ambiguous in module "UserModule" — visible from multiple modules (module "AuthModule", module "BillingModule"). Disambiguate with an explicit `deps: [...]` entry naming the exact class.
Fix it with an explicit deps array on a ProviderDefinition, naming the exact class you mean (see Providers & Tokens).
