@nivinjoseph/n-ject
v3.0.4
Published
IOC container
Downloads
343
Maintainers
Readme
@nivinjoseph/n-ject
A powerful and flexible Inversion of Control (IoC) container for TypeScript/JavaScript applications.
Features
- Dependency Injection (DI) container with TypeScript support
- Configurable component Lifestyle (scoping)
- Easy-to-use decorator-based injection
- Hierarchical scoping system
- Type-safe dependency resolution
- Flexible component registration
- Circular dependency detection
- Service Locator pattern support for dynamic dependency resolution
- Multiple injection strategies (constructor and service locator)
Installation
# Using npm
npm install @nivinjoseph/n-ject
# Using yarn
yarn add @nivinjoseph/n-jectRequirements
- Node.js >= 20.10
- TypeScript >= 5.3 (for TypeScript projects)
Basic Usage
import { Container, inject } from "@nivinjoseph/n-ject";
// Define your service
class UserService
{
public getUser(id: string): Promise<any>
{
return Promise.resolve({ id, name: "John Doe" });
}
}
// Define a component that depends on UserService
@inject("userService")
class UserController
{
private readonly _userService: UserService;
constructor(userService: UserService)
{
given(userService, "userService").ensureHasValue().ensureIsObject();
this._userService = userService;
}
public async getUser(id: string): Promise<any>
{
return await this._userService.getUser(id);
}
}
// Create and configure the container
const container = new Container();
// Register components
container
.register("userService", UserService)
.register("userController", UserController);
// Create a scope and resolve dependencies
const scope = container.createScope();
const userController = scope.resolve<UserController>("userController");Component Registration
Components can be registered with different lifestyles:
container
.registerTransient("transientService", TransientService)
.registerScoped("scopedService", ScopedService)
.registerSingleton("singletonService", SingletonService)
.registerInstance("instanceService", new InstanceService());You can also register aliases for the same component:
container
.registerSingleton("userService", UserService, "userRepository", "userManager")
.registerScoped("orderService", OrderService, "orderProcessor", "orderHandler");The available lifestyles are:
Transient: Creates a new instance each time the component is resolvedScoped: Creates one instance per scopeSingleton: Creates one instance for the entire containerInstance: Uses a pre-created instance
Component Installer
Component installers provide a way to organize and group related component registrations. They are useful for:
- Grouping related components together
- Conditional registration based on configuration
- Modularizing your application's component registration
Basic Installer Example
// Define your installer
class UserComponentsInstaller implements ComponentInstaller
{
public install(registry: Registry): void
{
registry
.registerSingleton("userService", UserService)
.registerScoped("userRepository", UserRepository)
.registerTransient("userValidator", UserValidator);
}
}
// Use the installer
const container = new Container();
container.install(new UserComponentsInstaller());
container.bootstrap();
// Components are now available for resolution
const userService = container.resolve<UserService>("userService");Dependency Injection
Use the @inject decorator to specify dependencies:
@inject("dependency1", "dependency2")
class MyComponent
{
private readonly _dep1: Dependency1;
private readonly _dep2: Dependency2;
constructor(dep1: Dependency1, dep2: Dependency2)
{
given(dep1, "dep1").ensureHasValue().ensureIsObject();
this._dep1 = dep1;
given(dep2, "dep2").ensureHasValue().ensureIsObject();
this._dep2 = dep2;
}
}Scoping
The framework supports scoping for component resolution:
const scope = container.createScope();
// Components resolved from the scope respect their configured Lifestyle
const service = scope.resolve<MyService>("myService");
// Don't forget to dispose the scope when done
await scope.dispose();Service Locator Pattern
In addition to constructor-based injection, the framework supports Service Locator pattern for more flexible dependency resolution.
Service Locator Injection
import { ServiceLocator, inject } from "@nivinjoseph/n-ject";
@inject("ServiceLocator")
class MyService
{
private readonly _serviceLocator: ServiceLocator;
constructor(serviceLocator: ServiceLocator)
{
given(serviceLocator, "serviceLocator").ensureHasValue().ensureIsObject();
this._serviceLocator = serviceLocator;
}
public async doSomething(): Promise<void>
{
// Resolve dependencies on demand
const logger = this._serviceLocator.resolve<Logger>("logger");
const config = this._serviceLocator.resolve<Config>("config");
// Use the resolved dependencies
await logger.log("Doing something");
const value = config.get("someKey");
}
}
// Register the service
container.registerSingleton("myService", MyService);Creating Scopes from Service Locator
class RequestHandler
{
private readonly _serviceLocator: ServiceLocator;
constructor(serviceLocator: ServiceLocator)
{
given(serviceLocator, "serviceLocator").ensureHasValue().ensureIsObject();
this._serviceLocator = serviceLocator;
}
public async handleRequest(): Promise<void>
{
// Create a new scope for this request
const scope = this._serviceLocator.createScope();
try
{
// Resolve scoped services
const userService = scope.resolve<UserService>("userService");
const transactionService = scope.resolve<TransactionService>("transactionService");
// Use the services
await userService.process();
await transactionService.commit();
}
finally
{
// Always dispose the scope when done
await scope.dispose();
}
}
}
// Register the handler
container.registerSingleton("requestHandler", RequestHandler);Benefits of Service Locator Pattern
- Lazy Resolution: Dependencies are resolved only when needed
- Dynamic Resolution: Can resolve different implementations based on runtime conditions
- Flexible Scoping: Can create and manage scopes programmatically
- Runtime Configuration: Can change resolution behavior at runtime
When to Use Service Locator
- When dependencies are optional or conditional
- When you need to create scopes dynamically
- When you need runtime flexibility in dependency resolution
API Reference
Container
The main IoC container class that manages component registration and scope creation.
Methods:
registerTransient(key: string, component: Function, ...aliases: Array<string>): Registers a component with transient LifestyleregisterScoped(key: string, component: Function, ...aliases: Array<string>): Registers a component with scoped LifestyleregisterSingleton(key: string, component: Function, ...aliases: Array<string>): Registers a component with singleton LifestyleregisterInstance(key: string, instance: object, ...aliases: Array<string>): Registers a pre-created instancecreateScope(): Creates a new scopehasRegistration(key: string): Checks if a component is registeredderegister(key: string): Removes a component registrationinstall(componentInstaller: ComponentInstaller): Installs components using an installerbootstrap(): Bootstraps the containerdispose(): Disposes the container and its resources
Scope
Represents a dependency resolution scope.
Methods:
resolve<T>(key: string): Resolves a componentdispose(): Disposes the scope and its resources
Decorators
@inject(...dependencies): Specifies component dependencies
Lifestyle
Singleton: One instance per containerScoped: One instance per scopeTransient: New instance per resolutionInstance: Uses a pre-created instance
Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Dependencies
Core dependencies:
- @nivinjoseph/n-defensive
- @nivinjoseph/n-exception
- @nivinjoseph/n-ext
- @nivinjoseph/n-util
Support
For issues and feature requests, please use the GitHub issue tracker.
