Inversion (DI)
@venizia/ignis-inversion is the standalone IoC container - decorator-based injection, a fluent binding API, and singleton/transient scoping - that every other IGNIS package builds on.
In one example
The smallest real use: bind a class with constructor injection, then resolve it through the container.
import { Container, inject, BindingScopes } from '@venizia/ignis-inversion';
class UserService {
constructor(@inject({ key: 'config.appName' }) private appName: string) {}
}
const container = new Container({ scope: 'MyApp' });
container.bind<string>({ key: 'config.appName' }).toValue('MyApp');
container.bind<UserService>({ key: 'services.UserService' })
.toClass(UserService)
.setScope(BindingScopes.SINGLETON);
const userService = container.get<UserService>({ key: 'services.UserService' });A class needs no decorator to be bindable - the binding is always created explicitly with container.bind(), and scope is set on the binding via setScope(). The framework layer (@venizia/ignis) creates these bindings for you for controllers, services, and repositories via app.controller() / app.service() / @repository.
How it works
- Three ways to resolve a binding.
toClass(container instantiates with DI),toValue(returned as-is),toProvider(factory function orIProviderclass). AllBindingsetters returnthis, so calls chain. - Instantiation is two-phase. Constructor injection runs first, reading
@injectmetadata by parameter index and passing resolved values as constructor args; property injection runs second, assigning each@inject-decorated property on the built instance.container.resolve(cls)andcontainer.instantiate(cls)are the same method -resolveis an alias. - Every constructor parameter must carry
@inject. The metadata array is index-keyed - an undecorated parameter leaves a hole the container has no way to fill.instantiate()refuses the class by name and parameter index rather than passingundefined. - Namespaces auto-tag bindings. A key like
services.UserServicetags the bindingservicesautomatically;setTags()adds more.findByTag()queries by tag, with anexcludelist. - Keys can be a
string, asymbol, or{ namespace, key }(built into a dotted string viaBindingKeys.build).
Scopes
| Scope | Constant | Behavior |
|---|---|---|
| Transient | BindingScopes.TRANSIENT | New instance on every resolution (default) |
| Singleton | BindingScopes.SINGLETON | Cached on the Binding after first resolution |
IMPORTANT
Singleton caching lives on the Binding object, not the container. Rebinding a key creates a fresh Binding with its own cache; a Binding reference you hold onto keeps its own cache independent of container.clear()/reset() on a different Binding for the same key.
Property-injected classes only get their @inject properties populated when built through the container (container.resolve()/instantiate()) - new MyClass() leaves them undefined. The Full reference covers MetadataRegistry, gets(), key formats, IProvider, and every error message in detail.
Common tasks
Bind a class with constructor injection
class OrderService {
constructor(
@inject({ key: 'repositories.OrderRepository' }) private orderRepository: OrderRepository,
@inject({ key: 'services.Logger', isOptional: true }) private logger?: Logger,
) {}
}Bind a value or a provider
container.bind<string>({ key: 'APP_NAME' }).toValue('MyApp');
container.bind<DatabaseConnection>({ key: 'db.connection' })
.toProvider((container) => {
const config = container.get<Config>({ key: 'config.database' });
return new DatabaseConnection(config);
});Set the scope
container.bind({ key: 'services.CacheService' })
.toClass(CacheService)
.setScope(BindingScopes.SINGLETON); // default is TRANSIENT if omittedInject into a property instead of the constructor
class UserService {
@inject({ key: 'repositories.UserRepository' })
private userRepository: UserRepository;
}Resolve an optional dependency
isOptional: true returns undefined instead of throwing when the key is unbound - on the constructor and via container.get(). gets() resolves several keys at once, always treating each as optional.
const maybeService = container.get<MyService>({ key: 'services.Optional', isOptional: true });Instantiate a class without registering it
const instance = container.resolve<MyClass>(MyClass); // full DI, not bound to a keySee also
- Full reference - every method,
MetadataRegistry, error message, and edge case - Dependency Injection Guide - DI fundamentals in the framework layer
- Application -
ApplicationextendsContainer - Dependency Injection API - the framework-layer DI reference
- Helpers Overview - all available helpers
- Architectural Patterns - DI patterns
Files:
packages/inversion/src/modules/container/container.ts-Container, two-phaseinstantiate()packages/inversion/src/modules/container/base.ts-BaseContainer, binding storagepackages/inversion/src/modules/binding/binding.ts-Bindingpackages/inversion/src/modules/metadata/injectors.ts-@injectpackages/inversion/src/modules/registry/registry.ts-MetadataRegistry